系列文章导航: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
要求:
- 能查询订单状态
- 能创建退款申请(需确认)
- 能转人工
- 使用自定义状态图
- 支持多轮对话(使用Checkpointer)
提示:参考第七章的完整项目实战。
作业2:实现一个Plan-and-Execute Agent
要求:
- 用户输入一个复杂任务
- Agent先制定计划(3-5步)
- 逐步执行计划
- 如果某步失败,能重新规划
- 最终汇总结果
提示:参考5.3节的模式。
作业3:实现一个带Reflection的写作Agent
要求:
- 用户给出写作主题
- Agent生成初稿
- 自动审查并给出修改意见
- 根据意见修改(最多3轮)
- 输出最终版本
提示:参考5.4节的模式。
下一篇文章见:AI系列文章导航目录-持续更新中