19-大模型智能体开发:LangChain&LangGraph实战+完整案例

系列文章导航:AI系列文章导航目录-持续更新中
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,麻烦大家帮点贡献下点击量支持一下,您的支持是我更新的动力。

第19课:Agent开发框架------LangChain & LangGraph 完全指南(四)

五、LangGraph实战模式

5.1 模式一:多Agent协作

为什么需要多Agent?

单个Agent面对复杂任务时,容易"力不从心":一个LLM同时扮演研究者、写手和审核员,往往每个角色都做不好。

复制代码
单Agent困境:
  用户: "写一篇关于AI Agent的技术博客"
  
  单Agent的思考:
    我需要搜索资料 → 搜索结果出来了 → 我边搜边写...
    问题: 写的时候忘了搜到的关键信息,又要重新搜
    结果: 质量平庸,像维基百科拼凑

多Agent解法:
  Supervisor: "这个任务需要研究+写作+审核,分配一下"
  → Researcher: "我搜集到这些关键数据和案例..."
  → Writer: "我基于研究结果写出这篇文章..."
  → Reviewer: "这里数据有误,这里逻辑不通,修改一下..."
  → Writer: "好的,修改完成"
  → Supervisor: "质量达标,结束"
  
  每个Agent只专注一件事,整体质量远高于单Agent

多Agent vs 单Agent 的选择标准

场景 推荐方案 原因
简单问答 单Agent 多Agent增加延迟且无必要
需多个专业视角 多Agent 不同专业Agent互相补充
任务可自然分解 多Agent 如:调研→写作→审核
需要过程可审计 多Agent 每步有明确的责任Agent
核心架构:Supervisor模式

Supervisor(主管)是多Agent最经典的编排模式:

复制代码
执行流程:
  START → Supervisor → 决定分配给谁
              ↓
       ┌──────┼──────┐
       ↓      ↓      ↓
   Researcher Writer Reviewer
       ↓      ↓      ↓
       └──────┼──────┘
              ↓
       Supervisor → FINISH? → END

每个Agent完成后回到Supervisor,Supervisor是唯一的调度中枢。Agent之间不需要知道彼此的存在。

基础实现
python 复制代码
"""
多Agent模式: 多个专业Agent协作完成复杂任务

架构:
  Supervisor(主管)→ 分配任务给不同的Agent
  Agent们各自完成任务后,结果汇总给Supervisor
  Supervisor决定是否需要继续或结束

类比:
  Supervisor = 项目经理
  Agents = 研究员、写手、审核员等
"""

from typing import TypedDict, Annotated, Literal, List
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

# ===== State =====

class MultiAgentState(TypedDict):
    messages: Annotated[list, add_messages]
    next_agent: str
    task_complete: bool

# ===== 各专业Agent =====

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

def supervisor(state: MultiAgentState) -> dict:
    """主管: 分析任务,决定分配给谁
    
    关键设计: Supervisor通过分析对话内容决定下一步
    - 这是多Agent协调的核心
    - 提示词需要明确每个Agent的能力边界
    """
    system = """你是一个项目主管。根据当前对话,决定下一步应该交给谁:
    - "researcher": 需要搜索和调研信息
    - "writer": 需要撰写内容
    - "reviewer": 需要审查和修改
    - "FINISH": 任务已完成
    
    只回复一个词: researcher/writer/reviewer/FINISH"""
    
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    next_agent = response.content.strip().lower()
    
    return {
        "next_agent": next_agent,
        "task_complete": next_agent == "finish"
    }

def researcher(state: MultiAgentState) -> dict:
    """研究员: 搜索和整理信息"""
    system = "你是一个研究员。根据任务需求,提供详细的研究信息和数据。"
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [AIMessage(content=f"[研究员] {response.content}", name="researcher")]}

def writer(state: MultiAgentState) -> dict:
    """写手: 撰写内容"""
    system = "你是一个专业写手。根据已有信息,撰写高质量的内容。"
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [AIMessage(content=f"[写手] {response.content}", name="writer")]}

def reviewer(state: MultiAgentState) -> dict:
    """审核员: 审查和提出修改意见"""
    system = "你是一个严格的审核员。检查内容质量,提出具体修改意见。"
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [AIMessage(content=f"[审核员] {response.content}", name="reviewer")]}

# ===== 路由 =====

def route_supervisor(state: MultiAgentState) -> Literal["researcher", "writer", "reviewer", "end"]:
    """路由逻辑: Supervisor决策后,把任务分发给对应Agent"""
    if state.get("task_complete"):
        return "end"
    next_agent = state.get("next_agent", "researcher")
    if next_agent in ["researcher", "writer", "reviewer"]:
        return next_agent
    return "end"

# ===== 构建图 =====

graph = StateGraph(MultiAgentState)

graph.add_node("supervisor", supervisor)
graph.add_node("researcher", researcher)
graph.add_node("writer", writer)
graph.add_node("reviewer", reviewer)

graph.add_edge(START, "supervisor")
graph.add_conditional_edges("supervisor", route_supervisor, {
    "researcher": "researcher",
    "writer": "writer",
    "reviewer": "reviewer",
    "end": END,
})
# 每个Agent完成后回到supervisor(核心设计!)
graph.add_edge("researcher", "supervisor")
graph.add_edge("writer", "supervisor")
graph.add_edge("reviewer", "supervisor")

app = graph.compile()

# 运行
result = app.invoke({
    "messages": [HumanMessage(content="写一篇关于AI Agent的技术博客,500字左右")],
    "next_agent": "",
    "task_complete": False,
}, config={"recursion_limit": 20})

for msg in result["messages"]:
    if msg.content:
        print(f"\n{msg.content[:200]}")
技术要点

1. Supervisor的判断准确性是关键瓶颈。提升方法:用更强的模型做Supervisor;用结构化输出替代自由文本解析;给Supervisor明确的Agent能力边界描述。

2. 防止无限循环 。除recursion_limit外,还需:Supervisor连续N次相同决策→强制结束;总步骤超限→强制汇总。

3. Agent身份标识name="researcher"在LangSmith中可按名称过滤追踪每个Agent的行为。

5.2 模式二:RAG Agent(检索增强生成)

为什么需要RAG Agent(而不只是普通RAG)?

普通RAG是"线性流程":用户提问→检索一次→生成回答。这在简单场景下够用,但面对复杂问题时,一次检索往往不够。

复制代码
普通RAG的局限:
  用户: "你们退款政策是什么?如果我在偏远地区,配送要多久?"
  
  普通RAG: 检索"退款政策 配送" → 可能只匹配到一条 → 漏掉另一条
  结果: 只回答了退款政策,配送问题被忽略

RAG Agent: Agent判断"这个问题有两个子问题"
  → 检索退款政策 → 获取到退款信息
  → 检索配送政策 → 获取到配送信息
  → 综合两个结果 → 完整回答
  → 检查是否还有遗漏 → 没有 → 输出

RAG Agent比普通RAG多了"决策能力":Agent可以判断什么时候需要检索、检索什么、检索结果是否足够、是否需要重新检索。

基础实现

与5.1一样,我们用LangGraph的图编排来构建RAG Agent------Agent节点负责思考+决策,Tools节点负责执行检索,两者通过条件边循环。

python 复制代码
"""
RAG Agent = 能主动搜索知识库的Agent

与普通RAG的区别:
  普通RAG: 用户提问 → 检索 → 生成回答(一次性)
  RAG Agent: 用户提问 → Agent判断是否需要检索 → 检索 → 判断信息是否足够 → 可能再次检索 → 生成回答

优势: Agent可以多次检索、调整检索策略、判断信息质量
"""

from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage

# ===== 1. 定义工具 =====

@tool
def search_knowledge_base(query: str) -> str:
    """搜索内部知识库。当需要查找公司内部信息、产品文档、技术规范时使用。
    
    Args:
        query: 搜索查询,尽量具体
    """
    # 实际项目中这里是向量检索
    knowledge = {
        "退款政策": "7天无理由退款,需要商品完好。超过7天需要申请特殊审批。",
        "配送时间": "普通配送3-5天,加急配送1-2天,偏远地区额外2-3天。",
        "会员等级": "银卡: 消费满1000元,金卡: 消费满5000元,钻石卡: 消费满20000元。",
    }
    results = []
    for key, value in knowledge.items():
        if any(word in query for word in key):
            results.append(f"{key}: {value}")
    return "\n".join(results) if results else "未找到相关信息"

@tool
def search_order_database(order_id: str) -> str:
    """查询订单数据库。当用户询问具体订单状态时使用。
    
    Args:
        order_id: 订单编号
    """
    orders = {
        "ORD001": "状态: 已签收, 金额: ¥299, 商品: 蓝牙耳机",
        "ORD002": "状态: 配送中, 金额: ¥1299, 商品: 机械键盘",
    }
    return orders.get(order_id, f"订单{order_id}不存在")

tools = [search_knowledge_base, search_order_database]

# ===== 2. 定义State =====

class RAGAgentState(TypedDict):
    messages: Annotated[list, add_messages]

# ===== 3. 绑定工具到LLM =====

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)

SYSTEM_PROMPT = """你是一个智能客服助手。
    
规则:
1. 回答问题前,先搜索知识库确认信息
2. 不要编造信息,如果知识库没有相关内容,如实告知
3. 对于订单查询,使用订单数据库工具
4. 回答要简洁、专业、友好"""

# ===== 4. 定义节点 =====

def agent_node(state: RAGAgentState) -> dict:
    """Agent节点: LLM决定是否需要调用工具、调用哪个工具
    
    关键: bind_tools让LLM可以决定调用工具,返回的AIMessage会带tool_calls
    """
    messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
    response = llm_with_tools.invoke(messages)
    return {"messages": [response]}

def should_continue(state: RAGAgentState) -> Literal["tools", "end"]:
    """路由: 如果LLM想调工具→走tools节点;否则→结束"""
    last_message = state["messages"][-1]
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    return "end"

# ===== 5. 构建图 =====

graph = StateGraph(RAGAgentState)

graph.add_node("agent", agent_node)
graph.add_node("tools", ToolNode(tools))  # ToolNode自动执行工具并将结果包装成ToolMessage

graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {
    "tools": "tools",
    "end": END,
})
graph.add_edge("tools", "agent")  # 工具结果返回agent,agent决定下一步

app = graph.compile()

# ===== 6. 运行 =====

result = app.invoke({
    "messages": [HumanMessage(content="我的订单ORD001什么时候到?还有你们的退款政策是什么?")]
})

for msg in result["messages"]:
    if msg.content:
        print(msg.content[:200])
关键技术点

1. 检索策略。生产环境中从关键词匹配升级为向量检索+BM25混合检索,提高召回率。Agent应能根据检索结果质量判断是否需要改写查询重新检索。

2. 工具描述的重要性。RAG Agent的检索质量很大程度上取决于工具描述(docstring)写得好不好。LLM通过描述判断"什么时候该用哪个检索工具"。

3. 与普通RAG的选型。简单FAQ用普通RAG(更快、更便宜);复杂多跳问题用RAG Agent;需要跨多个数据源查询的用RAG Agent。

5.3 模式三:Plan-and-Execute(计划执行)

为什么需要Plan-and-Execute?

ReAct Agent"走一步看一步",面对复杂多步骤任务时容易在中间迷失、遗漏关键步骤、出错后无法回溯。

复制代码
ReAct的问题:
  用户: "帮我做一个2024年AI发展趋势分析报告"
  → Agent思考、搜索 → 思考、搜索 → ...
  没有全局规划 → 可能在某个步骤重复搜索 → Token浪费 → 可能遗漏关键维度

Plan-and-Execute解法:
  规划 → [步骤1:搜集数据, 步骤2:技术分析, 步骤3:竞争格局, 步骤4:写报告]
  → 执行步骤1 → 执行步骤2 → ... → 汇总
  每步清晰可追踪,哪步出问题一目了然

选型标准:任务可预分解为3-10个独立步骤时用Plan-and-Execute;流程不确定、需边做边看时用ReAct。

基础实现
python 复制代码
"""
Plan-and-Execute模式:
  1. Planner: 将复杂任务分解为3-5个具体步骤
  2. Executor: 逐步执行每个步骤,把之前结果也传给LLM
  3. Summarizer: 汇总所有步骤,生成最终答案

适合: 复杂的多步骤任务

类比:
  普通Agent = 走一步看一步
  Plan-and-Execute = 先画地图,再按地图走
"""

from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

class PlanExecuteState(TypedDict):
    messages: Annotated[list, add_messages]
    plan: List[str]           # 计划步骤列表
    current_step: int         # 当前执行到第几步
    step_results: List[str]   # 每步的执行结果
    final_answer: str         # 最终答案

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

def planner(state: PlanExecuteState) -> dict:
    """制定计划:将复杂任务分解为步骤
    
    设计要点:
    - 提示词要求输出格式化的步骤列表(便于解析)
    - 步骤数量控制在3-5个
    """
    system = """你是一个任务规划专家。将用户的复杂任务分解为3-5个具体步骤。
    每个步骤一行,格式: "步骤N: 具体内容"
    只输出步骤列表,不要其他内容。"""
    
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    
    # 解析计划
    steps = [line.strip() for line in response.content.split("\n") if line.strip()]
    
    return {
        "plan": steps,
        "current_step": 0,
        "step_results": [],
        "messages": [AIMessage(content=f"计划制定完成:\n" + "\n".join(steps))]
    }

def executor(state: PlanExecuteState) -> dict:
    """执行当前步骤
    
    关键: 把之前步骤的结果传给LLM,避免重复劳动
    """
    current = state["current_step"]
    plan = state["plan"]
    
    if current >= len(plan):
        return {}
    
    step = plan[current]
    system = f"""执行以下步骤并给出结果:
    {step}
    
    之前步骤的结果:
    {chr(10).join(state['step_results']) if state['step_results'] else '无'}"""
    
    response = llm.invoke([SystemMessage(content=system)] + state["messages"])
    
    new_results = state["step_results"] + [f"步骤{current+1}结果: {response.content}"]
    
    return {
        "current_step": current + 1,
        "step_results": new_results,
        "messages": [AIMessage(content=f"[执行步骤{current+1}] {response.content}")]
    }

def should_continue_plan(state: PlanExecuteState) -> str:
    if state["current_step"] >= len(state["plan"]):
        return "summarize"
    return "execute"

def summarizer(state: PlanExecuteState) -> dict:
    """汇总所有步骤结果,生成最终答案"""
    system = """根据以下执行结果,生成一个完整的最终答案:
    
    """ + "\n".join(state["step_results"])
    
    response = llm.invoke([SystemMessage(content=system)] + state["messages"])
    return {
        "final_answer": response.content,
        "messages": [AIMessage(content=f"[最终答案] {response.content}")]
    }

# 构建图
graph = StateGraph(PlanExecuteState)
graph.add_node("planner", planner)
graph.add_node("executor", executor)
graph.add_node("summarizer", summarizer)

graph.add_edge(START, "planner")
graph.add_edge("planner", "executor")
graph.add_conditional_edges("executor", should_continue_plan, {
    "execute": "executor",
    "summarize": "summarizer",
})
graph.add_edge("summarizer", END)

app = graph.compile()

result = app.invoke({
    "messages": [HumanMessage(content="帮我做一个关于2024年AI发展趋势的分析报告")],
    "plan": [],
    "current_step": 0,
    "step_results": [],
    "final_answer": "",
})
技术要点

1. 计划解析。LLM输出格式不固定。提升方法:要求输出JSON格式(最稳定);用正则提取步骤行;对非标准格式做fallback。

2. 步骤间信息传递。Executor需知道之前步骤的结果。但步骤多了prompt会很长,需做截断或摘要。

3. 重新规划(Re-Planning) 。某步失败时:重试(最多3次);或跳过该步骤继续;或重新规划剩余步骤。需要在should_continue中加入失败检测逻辑。

5.4 模式四:Reflection(自我反思)

为什么需要Reflection?

LLM一次性生成的内容通常质量不够好。人类写文章都需要"初稿→修改→再修改",Agent也应该有同样的能力。

复制代码
无Reflection:
  用户: "写一篇关于Python装饰器的技术说明"
  → Agent: 输出一段文字 → 结束
  问题: 可能有错误、不够清晰、表达冗长

有Reflection:
  用户: "写一篇关于Python装饰器的技术说明"
  → 生成初稿 → 反思(发现问题) → 修改 → 反思(仍不满意) → 再修改 → 通过
  结果: 质量远高于一次性生成

适用场景:写作、代码生成、分析报告等对质量要求高的输出。不适合实时对话(延迟增加)。

基础实现
python 复制代码
"""
Reflection模式:
  Agent生成内容后,自己审查自己的输出,发现问题后修改

流程:
  生成 → 反思 → 修改 → 反思 → ... → 满意后输出

适合: 需要高质量输出的场景(写作、代码生成、分析报告)
"""

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

class ReflectionState(TypedDict):
    messages: Annotated[list, add_messages]
    draft: str
    critique: str
    revision_count: int
    is_satisfactory: bool

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)

def generate(state: ReflectionState) -> dict:
    """生成初稿或基于修改意见重新生成
    
    关键: 如果有修改意见,将意见拼入prompt指导修改
    """
    system = "你是一个专业写手。根据要求生成高质量内容。"
    if state.get("critique"):
        # 如果有修改意见,基于意见修改
        system += f"\n\n上一版的修改意见:\n{state['critique']}\n请据此改进。"
    
    messages = [SystemMessage(content=system)] + state["messages"]
    response = llm.invoke(messages)
    
    return {
        "draft": response.content,
        "messages": [AIMessage(content=response.content)],
        "revision_count": state.get("revision_count", 0) + 1,
    }

def reflect(state: ReflectionState) -> dict:
    """反思和评价当前草稿
    
    关键: 从多个维度系统评价,而非笼统的"好不好"
    """
    system = """你是一个严格的内容审查员。评价以下内容的质量:

内容:
{draft}

请从以下维度评价:
1. 准确性
2. 完整性
3. 可读性
4. 逻辑性

如果内容质量足够好(8分以上),回复"APPROVED"。
否则,给出具体的修改建议。""".format(draft=state["draft"])
    
    response = llm.invoke([SystemMessage(content=system)])
    
    is_good = "APPROVED" in response.content.upper()
    
    return {
        "critique": response.content,
        "is_satisfactory": is_good,
    }

def should_continue_reflection(state: ReflectionState) -> str:
    """判断是否需要继续修改
    
    两个终止条件:
    1. 审查通过
    2. 修改次数达到上限(防止无限循环)
    """
    if state.get("is_satisfactory") or state.get("revision_count", 0) >= 3:
        return "end"
    return "revise"

# 构建图
graph = StateGraph(ReflectionState)
graph.add_node("generate", generate)
graph.add_node("reflect", reflect)

graph.add_edge(START, "generate")
graph.add_edge("generate", "reflect")
graph.add_conditional_edges("reflect", should_continue_reflection, {
    "revise": "generate",  # 不满意 → 重新生成
    "end": END,            # 满意 → 结束
})

app = graph.compile()

result = app.invoke({
    "messages": [HumanMessage(content="写一段关于Python装饰器的技术说明,200字左右")],
    "draft": "",
    "critique": "",
    "revision_count": 0,
    "is_satisfactory": False,
})
print(f"经过 {result['revision_count']} 次修改")
print(f"最终内容: {result['draft']}")
技术要点

1. 反思质量是关键。如果审查员太宽松(什么都给APPROVED),Reflection就失效了。建议:反思提示词要明确评价标准;可以给不同场景配置不同的严格程度。

2. 修改次数上限。代码中设置最多3次以防止无限循环。实践中2-3次通常是最佳平衡点。

3. 反思和生成的模型可以不同。用强模型做反思(挑剔),用普通模型做生成;或者同一个模型但评估时降低temperature。


六、LangGraph生产部署

6.1 LangGraph Platform / LangSmith Deployment(云部署)

复制代码
LangGraph Platform = 官方提供的部署方案(现已整合为LangSmith Deployment)
被Klarna、Replit、Elastic等企业采用

架构:
  ┌─────────────────────────────────────────┐
  │           LangGraph Platform            │
  ├─────────────────────────────────────────┤
  │  API Server (FastAPI)                   │
  │    ├── /threads      (对话管理)         │
  │    ├── /runs         (执行管理)         │
  │    ├── /assistants   (Agent管理)        │
  │    └── /store        (持久化存储)       │
  ├─────────────────────────────────────────┤
  │  Task Queue (后台任务)                  │
  │    ├── 长时间运行的Agent任务            │
  │    └── 定时任务                         │
  ├─────────────────────────────────────────┤
  │  Checkpointer (PostgreSQL)              │
  │    ├── 状态持久化                       │
  │    └── 多轮对话历史                     │
  ├─────────────────────────────────────────┤
  │  Store (长期记忆)                       │
  │    ├── 用户偏好                         │
  │    └── 跨对话记忆                       │
  └─────────────────────────────────────────┘

部署方式:
  1. LangGraph Cloud (托管服务)
  2. Self-hosted (Docker自部署)
  3. langgraph-cli (本地开发)

6.2 本地部署示例

python 复制代码
# langgraph.json - 项目配置文件
"""
{
    "dependencies": ["."],
    "graphs": {
        "my_agent": "./agent.py:graph"
    },
    "env": ".env"
}
"""

# agent.py - Agent定义
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from typing import TypedDict, Annotated

class State(TypedDict):
    messages: Annotated[list, add_messages]

@tool
def get_info(query: str) -> str:
    """获取信息"""
    return f"关于{query}的信息..."

llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools([get_info])

def agent(state: State):
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: State):
    last = state["messages"][-1]
    if hasattr(last, "tool_calls") and last.tool_calls:
        return "tools"
    return "end"

from langgraph.prebuilt import ToolNode

graph_builder = StateGraph(State)
graph_builder.add_node("agent", agent)
graph_builder.add_node("tools", ToolNode([get_info]))
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": END})
graph_builder.add_edge("tools", "agent")

# 导出graph供LangGraph Platform使用
graph = graph_builder.compile()
bash 复制代码
# 本地启动LangGraph服务
pip install langgraph-cli
langgraph dev  # 启动开发服务器,默认端口8123

# 或者用Docker
# langgraph up  # 启动生产服务器
python 复制代码
# 客户端调用
from langgraph_sdk import get_client

client = get_client(url="http://localhost:8123")

# 创建对话线程
thread = await client.threads.create()

# 发送消息
result = await client.runs.create(
    thread["thread_id"],
    assistant_id="my_agent",
    input={"messages": [{"role": "user", "content": "你好"}]}
)

# 流式调用
async for event in client.runs.stream(
    thread["thread_id"],
    assistant_id="my_agent",
    input={"messages": [{"role": "user", "content": "北京天气"}]},
    stream_mode="messages"
):
    print(event)

6.3 LangSmith------可观测性

为什么 LangSmith 会出现?

2023 年初,开发者们开始把 LangChain Agent 部署到生产环境,然后遇到了一个共同的噩梦:

"Agent 给出了一个错误答案,但我完全不知道它在哪一步出了问题。是提示词的问题?是工具调用的问题?是模型的问题?我只能在代码里加 print,一步步调试......"

这就是 LangSmith 要解决的问题:让 Agent 的执行过程变得可见、可追踪、可调试。

类比:LangSmith 之于 Agent,就像 Datadog 之于微服务------你不能在生产环境里靠 print 调试。

python 复制代码
"""
LangSmith = LangChain生态的可观测性平台

功能:
  1. Tracing: 追踪Agent每一步的执行(输入、输出、耗时、token消耗)
  2. Evaluation: 评估Agent的质量(准确率、延迟等)
  3. Monitoring: 生产环境监控(错误率、延迟分布等)
  4. Playground: 在线调试Agent
  5. Datasets: 管理测试数据集

配置(只需要设置环境变量,代码无需修改):
"""

import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls-..."
os.environ["LANGCHAIN_PROJECT"] = "my-production-agent"

# 之后所有LangChain/LangGraph的调用都会自动上报到LangSmith
# 你可以在 https://smith.langchain.com 看到:
#   - 每次调用的完整链路
#   - 每个节点的输入输出
#   - Token消耗和延迟
#   - 错误信息

# ============ 自定义追踪 ============

from langsmith import traceable

@traceable(name="my_custom_function")
def process_data(data: str) -> str:
    """这个函数的执行也会被追踪"""
    result = some_processing(data)
    return result

# ============ 评估 ============

from langsmith import Client
from langsmith.evaluation import evaluate

client = Client()

# 创建测试数据集
dataset = client.create_dataset("agent_test_cases")
client.create_examples(
    inputs=[
        {"messages": [{"role": "user", "content": "北京天气"}]},
        {"messages": [{"role": "user", "content": "帮我算1+1"}]},
    ],
    outputs=[
        {"expected": "应该包含天气信息"},
        {"expected": "应该回答2"},
    ],
    dataset_id=dataset.id,
)

# 运行评估
def predict(inputs):
    result = app.invoke(inputs)
    return {"output": result["messages"][-1].content}

results = evaluate(
    predict,
    data="agent_test_cases",
    evaluators=[
        # 自定义评估函数
        lambda run, example: {"score": 1 if "天气" in run.outputs["output"] else 0}
    ],
)

6.4 生产环境最佳实践

python 复制代码
"""
生产环境部署Agent的关键考虑:
"""

# ============ 1. 配置管理 ============

from pydantic_settings import BaseSettings

class AgentConfig(BaseSettings):
    """Agent配置(从环境变量读取)"""
    openai_api_key: str
    model_name: str = "gpt-4o-mini"
    temperature: float = 0
    max_retries: int = 3
    timeout: int = 30
    recursion_limit: int = 25
    
    # 持久化
    postgres_url: str = "postgresql://localhost:5432/langgraph"
    
    # 可观测性
    langsmith_api_key: str = ""
    langsmith_project: str = "production"
    
    class Config:
        env_file = ".env"

config = AgentConfig()

# ============ 2. 结构化日志 ============

import structlog

logger = structlog.get_logger()

def agent_node(state: State) -> dict:
    logger.info("agent_node_start", 
                message_count=len(state["messages"]),
                thread_id=state.get("thread_id"))
    try:
        response = llm.invoke(state["messages"])
        logger.info("agent_node_success",
                    has_tool_calls=bool(response.tool_calls),
                    tokens=response.usage_metadata)
        return {"messages": [response]}
    except Exception as e:
        logger.error("agent_node_error", error=str(e))
        raise

# ============ 3. 速率限制 ============

from langchain_core.rate_limiters import InMemoryRateLimiter

rate_limiter = InMemoryRateLimiter(
    requests_per_second=10,    # 每秒最多10次请求
    check_every_n_seconds=0.1,
    max_bucket_size=20,
)

llm = ChatOpenAI(
    model="gpt-4o-mini",
    rate_limiter=rate_limiter,
)

# ============ 4. Fallback(降级) ============

from langchain_core.runnables import RunnableWithFallbacks

# 主模型失败时,自动切换到备用模型
primary_llm = ChatOpenAI(model="gpt-4o")
fallback_llm = ChatOpenAI(model="gpt-4o-mini")

llm_with_fallback = primary_llm.with_fallbacks([fallback_llm])

# ============ 5. 缓存 ============

from langchain_core.globals import set_llm_cache
from langchain_community.cache import SQLiteCache

# 相同输入直接返回缓存结果(节省token和时间)
set_llm_cache(SQLiteCache(database_path=".langchain_cache.db"))

# ============ 6. 安全防护 ============

def input_guard(state: State) -> dict:
    """输入防护节点: 过滤恶意输入"""
    last_msg = state["messages"][-1].content
    
    # 检查注入攻击
    dangerous_patterns = ["ignore previous instructions", "system prompt"]
    for pattern in dangerous_patterns:
        if pattern.lower() in last_msg.lower():
            return {"messages": [AIMessage(content="抱歉,我无法处理这个请求。")]}
    
    return {}  # 通过检查,不修改state

def output_guard(state: State) -> dict:
    """输出防护节点: 过滤敏感信息"""
    last_msg = state["messages"][-1].content
    
    # 检查是否泄露敏感信息
    import re
    # 脱敏手机号
    sanitized = re.sub(r'1[3-9]\d{9}', '1**********', last_msg)
    # 脱敏身份证
    sanitized = re.sub(r'\d{17}[\dXx]', '***', sanitized)
    
    if sanitized != last_msg:
        return {"messages": [AIMessage(content=sanitized)]}
    return {}

七、完整项目实战:智能客服Agent

python 复制代码
"""
完整的生产级客服Agent示例
功能:
  1. 查询订单状态
  2. 处理退款申请(需人工确认)
  3. 回答常见问题(RAG)
  4. 转人工客服
  5. 多轮对话记忆
  6. 流式输出
"""

from typing import TypedDict, Annotated, Literal, Optional
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage

# ===== 1. 定义工具 =====

@tool
def query_order(order_id: str) -> str:
    """查询订单状态。当用户询问订单相关信息时使用。
    
    Args:
        order_id: 订单编号,格式如ORD001
    """
    orders = {
        "ORD001": {"status": "已签收", "amount": 299, "item": "蓝牙耳机", "date": "2024-01-15"},
        "ORD002": {"status": "配送中", "amount": 1299, "item": "机械键盘", "date": "2024-01-18"},
        "ORD003": {"status": "已取消", "amount": 599, "item": "鼠标", "date": "2024-01-10"},
    }
    order = orders.get(order_id)
    if order:
        return f"订单{order_id}: 商品={order['item']}, 金额=¥{order['amount']}, 状态={order['status']}, 日期={order['date']}"
    return f"订单{order_id}不存在,请检查订单号"

@tool
def search_faq(question: str) -> str:
    """搜索常见问题知识库。当用户询问政策、规则等通用问题时使用。
    
    Args:
        question: 用户的问题关键词
    """
    faq = {
        "退款": "退款政策: 签收7天内可无理由退款,需商品完好。超过7天需联系客服申请特殊处理。退款到账时间: 1-3个工作日。",
        "配送": "配送说明: 普通配送3-5个工作日,加急配送1-2个工作日(额外¥10)。偏远地区额外2-3天。",
        "会员": "会员等级: 银卡(消费满¥1000)享95折, 金卡(满¥5000)享9折, 钻石卡(满¥20000)享85折。",
        "发票": "发票说明: 支持电子发票和纸质发票。下单时选择,或订单完成后7天内申请。",
    }
    results = []
    for key, value in faq.items():
        if key in question or any(char in question for char in key):
            results.append(value)
    return "\n".join(results) if results else "未找到相关FAQ,建议转人工客服"

@tool
def create_refund_request(order_id: str, reason: str) -> str:
    """创建退款申请。仅当用户明确要求退款时使用。
    
    Args:
        order_id: 要退款的订单编号
        reason: 退款原因
    """
    return f"退款申请已创建: 订单={order_id}, 原因={reason}。预计1-3个工作日处理完成。"

tools = [query_order, search_faq, create_refund_request]

# ===== 2. 定义State =====

class CustomerServiceState(TypedDict):
    messages: Annotated[list, add_messages]
    needs_human: bool
    customer_sentiment: str  # positive/neutral/negative

# ===== 3. 定义节点 =====

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)

SYSTEM_PROMPT = """你是"小智",一个专业友好的客服助手。

你的能力:
1. 查询订单状态 (query_order)
2. 搜索常见问题 (search_faq)  
3. 创建退款申请 (create_refund_request)

规则:
1. 始终礼貌、专业、简洁
2. 回答前先用工具确认信息,不要编造
3. 退款操作前要和用户确认
4. 如果用户情绪激动或问题超出能力范围,建议转人工
5. 每次回复不超过100字(除非需要详细解释)

转人工的条件:
- 用户明确要求转人工
- 用户连续表达不满
- 问题超出你的处理范围"""

def agent_node(state: CustomerServiceState) -> dict:
    """主Agent节点"""
    messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
    response = llm_with_tools.invoke(messages)
    
    # 检测是否需要转人工
    needs_human = False
    if response.content:
        needs_human = any(kw in response.content for kw in ["转人工", "转接人工", "人工客服"])
    
    return {
        "messages": [response],
        "needs_human": needs_human,
    }

def human_handoff_node(state: CustomerServiceState) -> dict:
    """转人工节点"""
    return {
        "messages": [AIMessage(content="好的,正在为您转接人工客服,请稍候。在等待期间,您可以继续描述问题,人工客服接入后会看到完整对话记录。")]
    }

tool_node = ToolNode(tools)

# ===== 4. 定义路由 =====

def route_agent(state: CustomerServiceState) -> Literal["tools", "human", "end"]:
    """Agent路由"""
    if state.get("needs_human"):
        return "human"
    
    last_message = state["messages"][-1]
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    
    return "end"

# ===== 5. 构建图 =====

graph = StateGraph(CustomerServiceState)

graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_node("human_handoff", human_handoff_node)

graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", route_agent, {
    "tools": "tools",
    "human": "human_handoff",
    "end": END,
})
graph.add_edge("tools", "agent")
graph.add_edge("human_handoff", END)

# 编译(带持久化,支持多轮对话)
memory = MemorySaver()
app = graph.compile(checkpointer=memory)

# ===== 6. 运行示例 =====

def chat(user_input: str, thread_id: str = "customer_001"):
    """模拟客服对话"""
    config = {"configurable": {"thread_id": thread_id}}
    
    result = app.invoke(
        {"messages": [HumanMessage(content=user_input)], "needs_human": False, "customer_sentiment": "neutral"},
        config=config
    )
    
    # 获取最后一条AI消息
    ai_messages = [m for m in result["messages"] if isinstance(m, AIMessage) and m.content]
    if ai_messages:
        return ai_messages[-1].content
    return "处理中..."

# 模拟多轮对话
print("客服: " + chat("你好,我想查一下我的订单"))
print("客服: " + chat("订单号是ORD001"))
print("客服: " + chat("我想退款,耳机有杂音"))
print("客服: " + chat("你们的退款政策是什么?"))

"""
预期输出:
客服: 你好!请问您的订单号是多少?我帮您查询。
客服: 您的订单ORD001: 蓝牙耳机,¥299,已签收(2024-01-15)。请问有什么需要帮助的?
客服: 抱歉给您带来不便。确认一下,您要为订单ORD001申请退款,原因是耳机有杂音,对吗?
客服: 我们的退款政策是: 签收7天内可无理由退款,需商品完好。超过7天需联系客服申请特殊处理。退款到账时间1-3个工作日。
"""

八、LangChain/LangGraph常见问题和技巧

8.1 调试技巧

Agent出问题时,靠print逐个调试效率极低。以下四招覆盖日常调试80%的场景。

python 复制代码
# ============ 1. 开启verbose模式 ============
# 用途: 快速看Agent每一步调了什么工具、返回了什么。适合日常开发。

# 方式1: 环境变量
import os
os.environ["LANGCHAIN_VERBOSE"] = "true"

# 方式2: 设置全局调试
from langchain.globals import set_debug, set_verbose
set_debug(True)    # 打印所有内部调用(非常详细,适合排疑难杂症)
set_verbose(True)  # 打印关键步骤(适中,推荐日常使用)

# ============ 2. 打印图结构 ============
# 用途: 确认你的图(节点+边)和预期一致。图结构出错是最隐蔽的bug。

# 查看图的节点和边
print(app.get_graph().nodes)
print(app.get_graph().edges)

# 打印Mermaid图(可视化了解流程)
print(app.get_graph().draw_mermaid())

# ============ 3. 逐步执行(查看每一步的State变化)============
# 用途: 当你不知道Agent哪一步出了问题,stream是最有效的排查方式。

# 使用stream查看每一步
for step in app.stream({"messages": [HumanMessage(content="test")]}, config):
    print(f"Step: {step}")
    print("---")

# ============ 4. 检查State(事后分析)============
# 用途: 执行完后检查state里的消息历史、中间变量等。

# 执行后查看完整State
state = app.get_state(config)
print(f"Messages: {len(state.values['messages'])}")
for msg in state.values['messages']:
    print(f"  {msg.__class__.__name__}: {msg.content[:50] if msg.content else 'N/A'}")

8.2 性能优化

Agent的性能瓶颈主要集中在两个地方:Token消耗(钱)和响应延迟(时间)。以下三个策略覆盖从"能用"到"便宜且快"的优化路径。

python 复制代码
# ============ 1. 减少Token消耗 ============
# 策略: 裁剪历史消息 + 用便宜的模型做简单任务

# 限制历史消息数量(长对话必做,否则token爆炸)
def trim_messages(state: State) -> dict:
    """只保留最近N条消息"""
    messages = state["messages"]
    if len(messages) > 20:
        # 保留system message + 最近18条
        system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
        recent_msgs = messages[-18:]
        return {"messages": system_msgs + recent_msgs}
    return {}

# 用更便宜的模型做简单任务(省钱的关键)
cheap_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)   # 路由、分类、简单问答
expensive_llm = ChatOpenAI(model="gpt-4o", temperature=0.7)  # 复杂推理、生成、创作

# ============ 2. 并发优化 ============
# 策略: 批量请求(当有多个独立请求时用batch而非逐个invoke)

# 批量处理(串行改并行,延迟从N*T降到约T)
results = llm.batch(
    ["问题1", "问题2", "问题3"],
    config={"max_concurrency": 3}  # 最多3个并发(避免触发API限流)
)

# ============ 3. 缓存 ============
# 策略: 相同输入不重复调API(开发调试时非常有用)

from langchain_core.globals import set_llm_cache
from langchain_community.cache import InMemoryCache

set_llm_cache(InMemoryCache())
# 相同输入会直接返回缓存结果,节省时间和Token
# 注意: 生产环境用SQLiteCache或RedisCache,重启服务缓存还在

8.3 常见陷阱

这些是LangGraph新手必踩的坑,提前了解能省很多时间。

python 复制代码
"""
陷阱1: State更新不是替换,是合并
"""
# ❌ 错误: 以为返回完整State------add_messages会追加,导致消息重复!
def bad_node(state: State) -> dict:
    return {
        "messages": state["messages"] + [new_msg],
    }

# ✅ 正确: 只返回增量,add_messages reducer自动合并
def good_node(state: State) -> dict:
    return {
        "messages": [new_msg],
    }

"""
陷阱2: 条件边的返回值必须和映射表匹配
"""
# ❌ 错误: 路由函数返回了映射中不存在的值------LangGraph直接报错
def bad_router(state) -> str:
    return "unknown_node"

# ✅ 正确: 用Literal类型约束返回值,既清晰又有IDE提示
def good_router(state) -> Literal["tools", "end"]:
    ...

"""
陷阱3: 工具调用后的边要回到Agent,不能直接END
"""
# ❌ 错误: tools节点执行完直接结束,Agent看不到工具结果
graph.add_edge("tools", END)

# ✅ 正确: tools执行完回到agent,agent看到结果后决定下一步
graph.add_edge("tools", "agent")

"""
陷阱4: Checkpointer的thread_id必须唯一
"""
# ❌ 错误: 所有用户共用一个thread_id------所有人的对话混在一起!
config = {"configurable": {"thread_id": "global"}}

# ✅ 正确: 每个用户/对话一个thread_id
config = {"configurable": {"thread_id": f"user_{user_id}_conv_{conv_id}"}}

"""
陷阱5: 流式输出要清楚stream_mode的含义
"""
# ❌ 错误: stream_mode="values"每步都输出完整历史------前端看到重复消息
for state in app.stream(input, config, stream_mode="values"):
    for msg in state["messages"]:
        print(msg.content)

# ✅ 正确: stream_mode="updates"只输出增量------前端每次只收到新内容
for event in app.stream(input, config, stream_mode="updates"):
    for node, output in event.items():
        if "messages" in output:
            for msg in output["messages"]:
                if msg.content:
                    print(msg.content)

九、学习路线图和资源

9.1 推荐学习顺序

复制代码
Week 1: 基础
  Day 1-2: ChatModel + Messages + Prompts
           → 能用LangChain调用LLM,理解消息系统
  Day 3-4: Tools + LCEL
           → 能定义工具,能用管道串联组件
  Day 5-7: LangGraph基础(State + Node + Edge)
           → 能构建简单的ReAct Agent

Week 2: 进阶
  Day 1-2: Human-in-the-loop + Checkpointer
           → 能实现人机协作和多轮对话
  Day 3-4: 流式输出 + 错误处理
           → 能构建用户体验良好的Agent
  Day 5-7: 多Agent模式 + 子图
           → 能构建复杂的多Agent系统

Week 3: 生产
  Day 1-2: LangSmith(可观测性)
           → 能追踪和调试Agent
  Day 3-4: 部署(LangGraph Platform)
           → 能部署Agent为API服务
  Day 5-7: 完整项目实战
           → 从零构建一个生产级Agent

9.2 官方资源

复制代码
文档:
  - LangChain: https://python.langchain.com/docs/
  - LangGraph: https://langchain-ai.github.io/langgraph/
  - LangSmith: https://docs.smith.langchain.com/

教程:
  - LangGraph官方教程: https://langchain-ai.github.io/langgraph/tutorials/
  - LangChain Academy: https://academy.langchain.com/ (免费课程)

代码:
  - GitHub: https://github.com/langchain-ai/langchain
  - LangGraph: https://github.com/langchain-ai/langgraph
  - 示例项目: https://github.com/langchain-ai/langgraph/tree/main/examples

社区:
  - Discord: https://discord.gg/langchain
  - Twitter: @LangChainAI

📝 作业

作业1:用LangGraph实现一个客服Agent

要求:

  1. 能查询订单状态
  2. 能创建退款申请(需确认)
  3. 能转人工
  4. 使用自定义状态图
  5. 支持多轮对话(使用Checkpointer)

提示:参考第七章的完整项目实战。

作业2:实现一个Plan-and-Execute Agent

要求:

  1. 用户输入一个复杂任务
  2. Agent先制定计划(3-5步)
  3. 逐步执行计划
  4. 如果某步失败,能重新规划
  5. 最终汇总结果

提示:参考5.3节的模式。

作业3:实现一个带Reflection的写作Agent

要求:

  1. 用户给出写作主题
  2. Agent生成初稿
  3. 自动审查并给出修改意见
  4. 根据意见修改(最多3轮)
  5. 输出最终版本

提示:参考5.4节的模式。


下一篇文章见:AI系列文章导航目录-持续更新中