Agent 编排
Agent 不是「更聪明的 ChatBot」,而是能自己决定下一步做什么的程序。这篇的重点不是「怎么让它更自主」,而是怎么给它装上刹车。
1. 从 ChatBot 到 Agent
| 形态 | 一次请求里发生什么 |
|---|---|
| ChatBot | 一问一答,固定流程 |
| 带工具的 ChatBot | 一问 → 最多调几个工具 → 答(由我们写好顺序) |
| Agent | 一问 → 模型自己决定「查什么/调什么/要不要再查」→ 多轮 → 答 |
分界线在决策权归谁:流程写死在代码里 = 工作流;流程由模型动态决定 = Agent。
代价:不可预测(同样的输入可能走出不同路径)、成本不可控(可能调 1 次也可能调 20 次)、调试困难。所以真实项目里最常见的是**「工作流 + 局部 Agent」**。
2. ReAct:最常用的循环
Question: 用户问「我们上季度华东区销售额同比增长多少?」
Thought: 需要先查上季度华东区销售额,再查去年同期
Action: query_sales({"region":"华东","quarter":"2026Q1"})
Observation: 1280 万
Thought: 还需要去年同期的数据
Action: query_sales({"region":"华东","quarter":"2025Q1"})
Observation: 1000 万
Thought: 同比增长 28%,可以回答了
Final Answer: 同比增长 28%(1280 万 vs 1000 万)Question: 用户问「我们上季度华东区销售额同比增长多少?」
Thought: 需要先查上季度华东区销售额,再查去年同期
Action: query_sales({"region":"华东","quarter":"2026Q1"})
Observation: 1280 万
Thought: 还需要去年同期的数据
Action: query_sales({"region":"华东","quarter":"2025Q1"})
Observation: 1000 万
Thought: 同比增长 28%,可以回答了
Final Answer: 同比增长 28%(1280 万 vs 1000 万)三个必须有的东西:
- 停止条件:拿到足够信息,或达到最大步数;
- 观察压缩:工具结果要截断/摘要,否则几轮就撑爆上下文;
- 失败可恢复:工具报错后,让模型看到错误并决定换路,而不是直接崩。
常见失败:
| 症状 | 原因 | 处理 |
|---|---|---|
| 反复调同一个工具 | 结果不满足它,或者没看懂返回值 | 返回值加明确字段 + 提示「已查询过,不要重复」 |
| 死循环 | 缺停止条件 | 最大步数 + 同一工具连续失败 2 次就中止 |
| 越绕越远 | 任务太开放 | 收紧系统提示词,给出明确的子目标 |
| 忘记已有信息 | 上下文被工具结果挤满 | 中间结果摘要,只保留关键数字 |
3. Plan-and-Execute:先列计划再执行
Plan:
1. 查订单 → 2. 判断是否超时 → 3. 超时则查物流 → 4. 生成答复
Execute: 逐步执行,每步可观察
Replan: 某步失败/结果异常 → 重新规划剩余步骤Plan:
1. 查订单 → 2. 判断是否超时 → 3. 超时则查物流 → 4. 生成答复
Execute: 逐步执行,每步可观察
Replan: 某步失败/结果异常 → 重新规划剩余步骤| 对比 | ReAct | Plan-and-Execute |
|---|---|---|
| 决策粒度 | 每步都问模型 | 一次规划,多次执行 |
| 成本 | 步数多、调用多 | 调用少、省 token |
| 灵活性 | 高(随时改主意) | 低(计划错了要 replan) |
| 适合 | 探索型任务(找资料、排障) | 结构清晰的复杂任务(多步业务流程) |
4. Reflexion:让 Agent 自我批评
1. 生成初稿 → 2. 自我检查(对照要求/事实/格式)→ 3. 发现问题就改 → 4. 最多 2 轮1. 生成初稿 → 2. 自我检查(对照要求/事实/格式)→ 3. 发现问题就改 → 4. 最多 2 轮要点:
- 检查项要具体(「是否引用了资料」「数字是否来自工具」),否则模型只会说「看起来不错」;
- 最多 1~2 轮,多了收益锐减、成本上升;
- 适合「可验证」的任务(代码能跑、JSON 能解析、断言能过),不适合开放式写作。
5. 工作流 + 局部 Agent(生产推荐)
用户输入
↓
① 意图分类(规则或小模型,确定性强)
├─ 简单 FAQ → 直接 RAG 回答(不动用 Agent)
├─ 查询类 → 固定 2 步工具调用(工作流)
├─ 需要规划 → 进入 Agent 循环(最多 5 步)
└─ 高风险 → 转人工
↓
② 结果校验(格式/事实/敏感词)
↓
③ 输出用户输入
↓
① 意图分类(规则或小模型,确定性强)
├─ 简单 FAQ → 直接 RAG 回答(不动用 Agent)
├─ 查询类 → 固定 2 步工具调用(工作流)
├─ 需要规划 → 进入 Agent 循环(最多 5 步)
└─ 高风险 → 转人工
↓
② 结果校验(格式/事实/敏感词)
↓
③ 输出为什么要先分类:80% 的请求其实走确定流程就够,没必要付 Agent 的成本和不确定性。能用规则的地方不要用模型。
6. 编排:把它写成状态机
不要用「while + if」硬写循环,用状态机/图表达:
java
// 伪代码:节点 + 边 + 终止条件
var graph = Graph.builder()
.node("classify", this::classify) // 意图分类
.node("retrieve", this::retrieve) // RAG
.node("plan", this::plan) // 拆任务
.node("act", this::act) // 调工具
.node("reflect", this::reflect) // 自检
.node("answer", this::answer)
.edge("classify", ctx -> switch (ctx.intent()) {
case FAQ -> "retrieve";
case QUERY -> "act";
case PLAN -> "plan";
})
.edge("plan", "act")
.edge("act", ctx -> ctx.needReflect() ? "reflect" : "answer")
.edge("reflect", ctx -> ctx.passed() ? "answer" : "act") // 有上限!
.build();// 伪代码:节点 + 边 + 终止条件
var graph = Graph.builder()
.node("classify", this::classify) // 意图分类
.node("retrieve", this::retrieve) // RAG
.node("plan", this::plan) // 拆任务
.node("act", this::act) // 调工具
.node("reflect", this::reflect) // 自检
.node("answer", this::answer)
.edge("classify", ctx -> switch (ctx.intent()) {
case FAQ -> "retrieve";
case QUERY -> "act";
case PLAN -> "plan";
})
.edge("plan", "act")
.edge("act", ctx -> ctx.needReflect() ? "reflect" : "answer")
.edge("reflect", ctx -> ctx.passed() ? "answer" : "act") // 有上限!
.build();好处:每个节点的输入输出可见、可单测、可观测,出问题能定位到具体节点。LangGraph 这类框架的本质就是这个。
7. 边界控制:给 Agent 装刹车
| 刹车 | 设多少 | 为什么 |
|---|---|---|
| 最大步数 | 5~10 | 防死循环 |
| 单次 token 预算 | 按成本倒推 | 防烧钱 |
| 单步超时 + 总超时 | 工具 3~10s,总 60~120s | 防拖死请求 |
| 工具白名单 | 显式声明 | 防越权 |
| 同一工具连续失败 | 2 次即中止 | 防无效重试 |
| 危险操作审批 | 下单/退款/删除类要人确认 | 人在回路(Human-in-the-loop) |
| 幂等键 | 每个任务一个 | 重试不重复执行 |
人在回路的两种形态:
- 阻断式:危险操作前暂停,等用户点「确认」(体验差但安全);
- 旁路式:先执行,异步复核,异常再回滚/告警(体验好,要求业务可补偿)。
8. 失败处理
| 失败 | 表现 | 处理 |
|---|---|---|
| 工具超时 | 某步卡住 | 超时转重试(1 次)→ 换备选工具 → 降级话术 |
| 工具返回空 | 模型反复重试 | 明确告诉它「没有结果,请换方式或告知用户」 |
| 幻觉调用不存在的工具 | 报 unknown tool | 把可用工具清单再强调一次,或走兜底 |
| 输出不合格式 | 解析失败 | 带着错误信息重试 1 次 |
| 模型服务故障 | 全部失败 | 切备用模型 → 固定话术(见 07 工程化) |
9. 怎么评估一个 Agent
| 指标 | 含义 |
|---|---|
| 任务成功率 | 端到端是否达成目标(最重要) |
| 平均步数 | 越少越好(成本和延迟) |
| 单任务成本 | token × 价格 + 工具调用费 |
| P95 耗时 | 用户能不能忍 |
| 人工接管率 | 兜底触发频率 |
| 失败归因分布 | 选错工具 / 参数错 / 理解错 / 上游错 |
评估集要包含多步任务,不能只用单轮问答 —— 单轮看不出 Agent 的价值,也测不出它的坑。
10. 速查表
| 问题 | 一句话 |
|---|---|
| Agent 和工作流的区别 | 流程写死 = 工作流;模型自己决定下一步 = Agent |
| ReAct 三步 | Thought → Action → Observation,直到 Final Answer |
| Agent 必备的刹车 | 最大步数、预算、超时、白名单、人工审批 |
| 为什么生产用「工作流 + 局部 Agent」 | 80% 请求走确定流程就够,省钱省心 |
| Plan-and-Execute 与 ReAct 选谁 | 结构清晰用 Plan,探索型用 ReAct |
| Reflexion 适合什么 | 可验证的任务(代码/JSON/断言),最多 1~2 轮 |
| 死循环怎么防 | 步数上限 + 同工具连续失败 2 次中止 |
| 怎么编排 | 写成状态机/图,节点可单测可观测 |
| 怎么衡量 | 任务成功率 + 平均步数 + 单任务成本 + P95 |
Cao Note