本文目录29 个章节
第 4 章:Agent 架构与 LangGraph 状态编排
Agent 的价值不是“让模型自由发挥”,而是在不确定任务中允许模型选择下一步,同时把状态、权限、终止条件和错误恢复留在可控的工程结构里。一个可靠 Agent 通常是“确定性工作流 + 有限模型决策”的组合。
1. 先判断:到底需不需要 Agent
使用普通函数或工作流的条件:
- 步骤固定;
- 输入字段明确;
- 规则可以穷举;
- 错误代价高且没有人工审批;
- 对延迟和可复现性要求很高。
使用 Agent 的条件:
- 用户目标开放,路径无法预先完全确定;
- 可用工具较多,需要根据上下文选择;
- 中间结果会改变后续计划;
- 需要搜索、尝试、反思或追问;
- 能定义最大步数、权限和停止条件。
🔥 P0 高频必会:Agent 和 Workflow 有什么区别?
Workflow 的控制流主要由开发者预定义;Agent 让模型在运行时决定部分步骤或工具。生产系统通常不会二选一,而是在显式工作流中嵌入有限 Agent 决策,把高风险与关键规则保持确定性。
2. ReAct、Plan-and-Execute 与 Router
2.1 Router
一次模型判断把请求路由到固定处理链。它最简单、延迟低、可测试,适合“知识问答 / 订单查询 / 人工客服”分流。
2.2 ReAct 循环
模型根据当前状态选择动作,执行工具,观察结果,再决定下一步:
输入 -> 决定动作 -> 调用工具 -> 观察 -> 再决定 -> 完成优点是灵活;缺点是容易循环、重复调用和成本失控。必须设置最大步骤、工具权限和错误策略。
2.3 Plan-and-Execute
先生成计划,再逐步执行;执行结果可触发重规划。适合长任务,但计划可能过时,且多一次或多次模型调用。
2.4 选择建议
| 任务 | 优先结构 |
|---|---|
| 单次分类/路由 | Router |
| 2–5 步工具探索 | 有上限的 ReAct |
| 长期研究或复杂交付 | Plan-and-Execute + Checkpoint |
| 高风险写操作 | Workflow + 审批节点 |
| 批量稳定处理 | 确定性 Pipeline |
3. 状态是 Agent 的事实来源
不要让状态只存在于一大段聊天文本中。用明确 Schema 保存:
from typing import Literal, TypedDict
class AgentState(TypedDict, total=False):
run_id: str
user_id: str
goal: str
route: Literal["knowledge", "ticket", "human"]
retrieved_evidence: list[dict]
tool_calls: list[dict]
tool_results: list[dict]
step_count: int
max_steps: int
final_answer: str
error_code: str | None状态设计原则:
- 保存原始结构化数据,不要只保存格式化字符串;
- 区分用户输入、模型建议、已验证事实和工具结果;
- 标出敏感字段,不把密钥和完整凭据写进状态;
- 状态更新应尽量追加或显式覆盖;
- 为长任务考虑序列化、版本迁移和过期清理。
LangGraph 官方把节点描述为“接收当前状态并返回更新的 Python 函数”,并建议把错误作为流程的一部分处理。参见 Thinking in LangGraph。
🔥 P0 高频必会:为什么需要显式状态?
显式状态让控制流可追踪、可持久化、可恢复和可测试;可以区分已验证事实与模型文本,也能在人工审批和失败重试时精确恢复。只依赖聊天历史很难保证一致性。
4. 一个最小状态图
下面示例不绑定具体模型 SDK,重点看控制流。
from typing import Literal, TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
class SupportState(TypedDict, total=False):
query: str
route: Literal["knowledge", "ticket", "human"]
evidence: list[str]
ticket_id: str
answer: str
def classify(state: SupportState) -> dict:
query = state["query"]
if "退款" in query or "故障" in query:
return {"route": "ticket"}
if "人工" in query:
return {"route": "human"}
return {"route": "knowledge"}
def route_after_classify(state: SupportState) -> str:
return state["route"]
def retrieve(state: SupportState) -> dict:
# # 实际项目中调用 Retriever,并返回带来源的结构化 Evidence。
return {"evidence": ["[S1] 示例制度:普通问题先查知识库。"]}
def answer(state: SupportState) -> dict:
evidence = "\n".join(state.get("evidence", []))
return {"answer": f"根据以下资料回答:\n{evidence}"}
def create_ticket(state: SupportState) -> dict:
# # 真正实现必须有权限、幂等键和审计。
return {"ticket_id": "T-001", "answer": "已创建工单 T-001"}
def handoff(state: SupportState) -> dict:
return {"answer": "已转接人工客服"}
builder = StateGraph(SupportState)
builder.add_node("classify", classify)
builder.add_node("retrieve", retrieve)
builder.add_node("answer", answer)
builder.add_node("create_ticket", create_ticket)
builder.add_node("handoff", handoff)
builder.add_edge(START, "classify")
builder.add_conditional_edges(
"classify",
route_after_classify,
{
"knowledge": "retrieve",
"ticket": "create_ticket",
"human": "handoff",
},
)
builder.add_edge("retrieve", "answer")
builder.add_edge("answer", END)
builder.add_edge("create_ticket", END)
builder.add_edge("handoff", END)
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "demo-thread-1"}}
result = graph.invoke({"query": "年假有多少天?"}, config=config)
print(result["answer"])教学示例使用内存 Checkpointer;生产应使用持久化存储。LangGraph 的 Persistence 文档说明,Checkpoint 可支持人工介入、会话记忆、时间回放和故障恢复,参见 Persistence。
5. 模型决策节点应该返回什么
不要让路由节点返回自由文本。定义结构:
from typing import Literal
from pydantic import BaseModel, Field
class NextAction(BaseModel):
action: Literal["search", "create_ticket", "ask_user", "finish"]
tool_name: str | None = None
tool_arguments: dict = Field(default_factory=dict)
user_question: str | None = None
final_answer: str | None = None模型只建议下一动作,应用层仍要验证:
tool_name是否在允许列表;- 参数是否通过 Schema;
- 用户是否有权限;
- 是否超过最大步数和成本预算;
- 写操作是否需要审批;
- 当前状态是否允许该动作。
6. 终止条件与循环控制
每个 Agent Run 至少需要:
- 最大步骤;
- 最大模型调用数;
- 最大工具调用数;
- 最大 Token/成本;
- 全链路截止时间;
- 相同动作重复检测;
- 用户取消;
- 明确成功与失败状态。
def guard_limits(state: AgentState) -> Literal["continue", "stop"]:
if state["step_count"] >= state["max_steps"]:
return "stop"
calls = state.get("tool_calls", [])
if len(calls) >= 2 and calls[-1] == calls[-2]:
return "stop"
return "continue"相同工具名不一定代表重复,需比较规范化参数和业务意图;但简单检测能捕获大量死循环。
🔥 P0 高频必会:如何防止 Agent 无限循环?
限制步数、时间、Token 和成本;检测重复状态/动作;工具返回结构化错误;为无进展定义终止条件;高风险或不确定情况转人工;Trace 中记录每步原因。不能只在 Prompt 里写“不要循环”。
7. 错误是流程的一部分
LangGraph 官方示例把错误分为:瞬时错误、模型可恢复错误、用户可修复错误和未知错误。对应策略:
| 类型 | 谁处理 | 策略 |
|---|---|---|
| 429、网络抖动 | 系统 | Retry Policy |
| 工具参数非法 | 模型 | 把结构化错误写回状态,允许有限纠正 |
| 缺少订单号 | 用户 | Interrupt / Ask User |
| 越权 | 系统 | 拒绝并审计,不让模型绕过 |
| 未知异常 | 工程师 | 失败、报警、保留 Trace |
不要让模型对所有异常自行决定。权限、资金、删除、外发等风险必须由确定性代码控制。
8. Checkpoint 与恢复
Checkpoint 保存每步状态快照,使长任务可以:
- 进程重启后恢复;
- 人工审批后继续;
- 从失败步骤重试;
- 回放错误轨迹;
- 从历史状态 fork 新实验。
但是恢复会导致某些代码重新执行,所以副作用必须幂等。官方 Interrupt 文档特别提醒:恢复时节点可能从头执行,interrupt 之前的副作用需要幂等。参见 Interrupts。
9. Human-in-the-Loop
from langgraph.types import Command, interrupt
def approve_ticket(state: SupportState) -> dict:
decision = interrupt({
"question": "是否创建工单?",
"proposed_title": state["query"][:80],
})
return {"approved": bool(decision)}
## 首次运行遇到 interrupt 后暂停;恢复时必须使用同一 thread_id。
## graph.invoke(Command(resume=True), config=config)审批载荷必须让人能够判断风险:动作、对象、关键参数、预计影响,而不是只显示“是否继续”。
🔥 P0 高频必会:哪些动作需要 HITL?
不可逆、高金额、外部沟通、敏感数据访问、权限变更、代码执行和低置信度动作。阈值应由风险和业务政策决定,不是所有工具都弹窗,也不能把关键审批交给模型自己取消。
10. 确定性边界
把任务拆成两类:
适合模型判断
- 用户意图;
- 信息提取;
- 候选工具选择;
- 文本总结;
- 开放式计划建议。
必须由代码决定
- 身份与权限;
- 金额上限;
- 状态迁移是否合法;
- 参数类型与业务约束;
- 是否需要审批;
- 重试次数、超时和成本上限;
- 审计记录。
这条边界是生产 Agent 与 Demo 的关键差异。
11. 单 Agent 先于多 Agent
如果一个状态图加几个专用节点能解决,不要立即拆成多个“角色”。多 Agent 会带来:
- 更多模型调用和延迟;
- 上下文传递损失;
- 责任归属不清;
- 更复杂的死循环和评测;
- 权限与身份管理难度。
只有当任务可清晰分区、需要并行或不同工具/权限隔离时才考虑多 Agent。第 6 章会详细展开。
12. 本章练习
练习 A:三路客服图
实现知识问答、创建工单、人工转接三条路径。路由器必须结构化输出;写操作需要审批;每条路径有测试。
练习 B:故障恢复
让“创建工单”在第一次调用后模拟网络超时。证明恢复执行不会重复创建工单,并保存 Checkpoint。
练习 C:循环红队
构造工具持续返回“请重试”的情况。系统必须在最大步数内停止,并返回可诊断错误,而不是耗尽预算。
13. 面试高频问答
🔥 P0:你如何设计 Agent 状态?
区分输入、计划、已验证事实、工具请求、工具结果、审批、错误和最终输出;使用类型化 Schema;保存原始数据;支持序列化和版本迁移;敏感凭据不进入状态;更新规则明确。
🔥 P0:Checkpoint 和 Memory 有什么区别?
Checkpoint 保存某次运行/线程的执行状态,用于恢复和回放;长期 Memory 保存跨线程需要复用的信息。两者生命周期、作用域和写入策略不同,不能把所有历史 Checkpoint 都当用户记忆。
🔥 P0:如何评估 Agent 的轨迹?
不仅看最终答案,还看工具选择、参数、步骤数、重复调用、无效动作、权限违规和是否在适当时机停止/转人工。用标注轨迹、允许动作集合和成本指标共同评测。
⭐ P1:ReAct 和 Plan-and-Execute 如何选?
短、探索性任务用有上限 ReAct;长任务可先计划、分步执行并允许重规划。固定流程不应强行 Agent 化。选择依据是任务长度、路径不确定性、工具数量、错误代价和延迟预算。
⭐ P1:图结构升级会遇到什么问题?
旧 Checkpoint 可能指向已删除或改名节点,状态字段类型也可能不兼容。需要状态版本、迁移策略、向后兼容、灰度发布和对进行中线程的处理方案。
14. 本章完成标准
- 能说明何时不用 Agent;
- 能实现带条件路由的状态图;
- 有最大步数、错误分类、Checkpoint 和审批;
- 写工具恢复后不会重复产生副作用;
- 能从 Trace 解释每一步为什么发生;
- 能清楚划分模型判断和确定性代码边界。
REFERENCES
参考链接
所属系列
AI Agent 开发学习与面试指南