08. 智能体工程
八、Agent 工程¶
这一章要先把 Agent 当成运行时系统来理解,不要先背热词。ReAct、planning、reflection、memory、handoff、多 Agent、A2A 这些词只有放回运行时,才知道它们为什么会一起出现。只要系统允许模型在多步之间持续做决定,它就不再是一次普通回答,而是一条可中断、可恢复、会消耗资源、也可能产生真实副作用的执行链。这样一来,state、checkpoint、interrupt/resume、approval、trace 和 eval 同时出现就完全合理了,因为它们本来就在共同回答一个问题:这条运行时链怎样被治理住。
总判断:Agent 是运行时,不是更会聊天的模型¶
Agent 的核心不在于”更聪明”,而在于”系统允许模型在一个受控循环里不断决定下一步”。普通模型调用是一问一答,系统拼好上下文,模型返回结果,流程结束。确定性工作流虽有很多步,但每步由程序预先写死。Agent 的下一步走向根据中间观察来决定。Agent 不是比工作流更高级,而是比工作流更难治理。任务可以用稳定流程解决时,硬上 Agent 会把系统从可测变成难测。
判断该不该做 Agent,看任务复杂度。路径固定、输入输出明确、失败语义清楚时,优先写确定性工作流。系统需要在多种工具、信息源和状态之间动态选择下一步,Agent 才开始有价值。这条边界立住后,单 Agent、多 Agent、planning 和 graph runtime 的讨论才不会飘。
运行时最小闭环:目标、状态、工具、观察和停止条件¶
Agent 至少同时有目标、状态、工具集合、观察结果和停止条件。目标决定要完成什么,状态决定当前能看见哪些事实,工具集合界定能做哪些动作,观察结果把执行后的新信息写回系统,停止条件决定何时结束。缺了任何一项,系统容易变形。没有状态,模型每轮像失忆。没有工具边界,”能做什么”和”该不该做”被混在一起。没有停止条件,低价值循环和重复调用成为典型后果。
用户问题含糊时,是否先澄清取决于缺失信息会不会改变执行风险和答案边界。缺的是对象身份、单号、时间范围或操作目标,猜错就可能误查、越权或误执行,应先澄清。低风险的只读检索,系统能低成本先缩小范围——拿到相关制度版本、候选工单或命中的知识库——再回头追问,能兼顾效率和体验。”问题还不清楚”和”可以继续执行”被混为一谈时,系统才真正危险。
复杂度升级谱系:先 workflow,后更重的 agent runtime¶
控制权要先分清。workflow 里主要路径由代码预设,模型只负责局部语义判断。agent runtime 里,系统根据中间观察让模型决定下一步。单次模型调用、检索增强和结构化输出还在轻层。任务变成多步但路径仍能提前写清时,用 workflow。中间观察经常改写走向时,才把控制权交给 runtime。
轻模式 workflow 有五类。Sequential 适合稳定步骤:先抽取、再归一化、再生成。中间结果经常改变后续路径时,链不宜继续拉长。Routing 适合入口复杂但分支稳定的系统。高风险入口先用规则兜住,剩余模糊请求交给模型分流。Parallel 只用于子任务真的独立时;合流必须看前一步结果时,强并行把一致性问题提前引爆。
更重一点的仍然是 workflow,不是 agent。Evaluator-optimizer 适合格式校验、事实核对和风格修正,在已有候选上做质量闭环,不负责执行授权。Orchestrator-workers 适合研究、报告生成、模块化代码检查这类可拆分任务,worker 仍是受控执行单元。worker 已经需要自己的工具循环、记忆和失败恢复时,系统越过 workflow 边界,进入 supervisor-worker 或 multi-agent runtime。
这些 pattern 翻回本讲义语境:Sequential 是轻量 chain,Routing 是 router,Parallel 是并行候选和分段处理,Evaluator-optimizer 是轻量 reflection,Orchestrator-workers 是 planner 或 supervisor-worker 的前置形态。主路径仍然握在代码手里。模型在局部做判断、筛选和修正,不从头到尾接管执行流。
workflow 不够时,第一档才是 ReAct。单 Agent 加工具循环:系统在同一份状态里反复做”判断下一步、调工具、观察结果、继续或停止”。适合目标明确但路径不完全固定、每一步都依赖上一跳观察的任务。路径已稳定到可以预先写死时,普通 workflow 更稳。实现 ReAct 时,目标、工具边界、状态回写、停止条件和异常收口这五个对象要钉住。ReAct 才能是运行时,不是论文术语。
ReAct 讲到能实现时,最小循环可以压成这个样子:
for step := 0; step < maxSteps; step++ {
decision := llm.Decide(goal, state, tools)
if decision.Stop {
return finalize(state)
}
result := executeTool(decision.Tool, decision.Args)
state = reduce(state, decision, result)
if duplicated(state) || noProgress(state) {
return failOrEscalate(state)
}
}
return stepLimitExceeded(state)
这个伪代码把 ReAct 的五个硬对象钉住了:goal 决定当前为谁服务,state 决定这轮判断基于什么现实,tools 决定模型可行动的边界,reduce 决定观察结果怎样回写状态,maxSteps 和 duplicated/noProgress 决定系统什么时候必须止损。有这些对象,ReAct 才能被治理。只让模型自由生成一串 Thought/Action 文本,仍只是演示,不是运行时。
ReAct、workflow、Planner/Executor 放在一起看,边界清楚。workflow 适合步骤固定、失败分支已知的链路。ReAct 适合局部不确定、总体步数不长的链路。Planner/Executor 适合目标明确、子任务很多、恢复点必须显式设计的长任务。固定流程不需要每轮重新决策,额外引入不确定性。过早上 Planner/Executor 时,计划正确性、状态同步和恢复点设计明显变难。
CoT 和 Planning 必须明确拆开。CoT 把当前这一步的推理过程显化出来,生成”我为什么这么判断”。Planning 生成”接下来准备怎样执行”的任务表或状态转移方案。CoT 更偏单步推理解释,Planning 更偏多步执行组织。CoT 对回滚、重试和恢复的帮助有限,因为它不会天然产出稳定的步骤对象。Planning 天然需要步骤编号、依赖关系、预期产物和失败后的恢复策略,否则无法真正驱动执行。让模型写出”先查三篇论文,再各自提取方法和结论,最后合成中文总结”,这叫 planning。让模型解释”为什么我先去检索论文而不是先写总结”,这才更接近 CoT。
直接比较”产物对象”:CoT 产出的更像一段自由推理文本,例如”因为用户提到 RAG+RL,所以我先去找综述,再找近年的代表论文”。Planning 产出的应该是结构化步骤对象:
[
{"step_id": 1, "task": "检索近三年 RAG+RL 论文", "success_criteria": "拿到至少 3 篇高相关论文"},
{"step_id": 2, "task": "抽取每篇论文的方法与结论", "depends_on": [1]},
{"step_id": 3, "task": "合成中文总结", "depends_on": [2]}
]
计划对象天然带 step_id、依赖、成功条件和恢复入口,系统才知道失败后该重试哪一步、回滚哪一步、从哪里恢复。CoT 即使写得再漂亮,也往往只是”我脑子里在想什么”,不是”系统接下来要执行什么”。层级关系:workflow 负责预设主路径,planning 负责把长任务显式拆成可执行对象,agent 在执行过程中根据观察结果动态选择下一步。三者不是互斥关系,是一条从轻到重的控制权升级链。
继续往上升级:Plan-Then-Act、Planner/Executor、Graph 或 State Machine、Specialist、Supervisor/Worker、多 Agent 和搜索式规划。计划稳定、每步产物好校验时,用 Plan-Then-Act。任务更长、计划和执行需要分开治理时,用 Planner/Executor。需要 checkpoint、人工确认和 durable execution 时,把节点和状态转移显式建成 Graph 或 State Machine。不同角色真的带来分工收益时,才往 Specialist、Supervisor/Worker 和 Multi-Agent 走。
多 Agent 不是”更高级的 ReAct”,解决的是任务、状态和结果怎样在多个执行者之间安全流转。worker 一旦拥有自己的工具循环、长期状态、失败恢复和权限边界,系统关注点不再是单个 loop,而是协作协议、幂等、版本和终止条件。规则稳定、路径短、审计要求极高的任务,不必强行引入多执行者。连状态、trace 和恢复点都没立住时,越重的范式把问题藏得更深。
ToT、GoT 和 LATS 不是默认升级方向。搜索式推理范式适合组合空间很大、必须显式探索多个候选思路的任务:复杂规划、博弈式推理和高难度方案搜索。大多数线上 Agent 不需要这一步,步数、延迟和成本明显抬高。workflow、ReAct 或 Planner/Executor 已经把问题收住时,停在那一层。单条思路走到底明显不够,多分支探索的收益真能覆盖额外成本,才切到搜索式方案。
搜索式规划的代价:
ToT、LATS 绝不能默认开。分支宽度稍上去,成本就呈指数级膨胀。每次扩展还要调用工具、rerank 或额外 grader 时,时延和费用一起失控。ToT 和 LATS 是”把搜索预算显式化”的运行时,不是”更会思考一点的 ReAct”。前者用分支和评估器换解空间探索,后者在一条局部闭环里动态前进。
AutoGPT 和 BabyAGI 这类早期循环式 Agent 的工程化定位:让大家第一次看见”模型可以自己拆任务、调工具、写记忆再继续往下走”的闭环,集中暴露了现代 Agent 运行时为什么必须补状态、预算、停止条件、审批和恢复机制。放在历史位置上看有价值,不该成为今天工业 Agent 的设计模板。
状态与记忆:从 LangGraph 的 state 到长期可检索记忆¶
先建 state,再聊 memory。Agent 的核心对象是运行时状态,不是对话框。至少要有当前目标、关键上下文、最近一次工具结果、审批状态、下一待执行节点和必要约束。LangGraph 把对象显式化:state 是共享运行时对象,node 是执行单元,edge 是转移规则,observation 是执行后的新事实,checkpoint 是可恢复快照,interrupt/resume 把自动执行切回人工或外部事件,durable execution 保证长任务不因进程重启丢状态。
记忆按稳定性分层。最近几轮和当前任务直接相关的消息与工具结果,留在短期工作记忆。确认过但不需要逐句保留的内容,压成摘要记忆。单据号、权限范围、审批节点、最近一次工具结果这类机器要直接消费的事实,抬成结构化状态。用户偏好、术语映射、历史案例和稳定知识,写进长期可检索记忆。完整历史要保存,方便审计和回放,”完整保存”不等于”本轮全都给模型看”。
只对当前任务有效、很快就会过期的内容,不急着沉到长期记忆。临时中间态留在 scratchpad。跨几个步骤仍有效的事实抬成结构化状态。跨很多轮仍有价值的知识再沉到长期记忆。已反复验证、应长期约束系统行为的经验,才进入规则层或模板层。线上常说的元记忆接近这最后一层,不是在运行时随意改模型权重。
长期记忆工程化时,不能只谈”查向量库”。向量数据库适合历史案例和语义近似检索。图数据库适合实体关系和显式依赖。结构化存储适合确定字段、偏好、黑白名单和强时效规则。稳的系统让不同类型的记忆落在不同介质上。写入前去重、合并、加 TTL 和遗忘策略。检索时同时看时间、新鲜度、来源可信度和角色优先级。只看语义相似度,”很像但已过期”的旧记忆迟早被排到前面。
长期记忆排序是多因子打分:
memory_score =
α * semantic_similarity +
β * freshness +
γ * source_trust +
δ * role_priority -
ε * staleness_penalty
长期记忆排序不是只看语义相似度。语义相关但过期的制度、来源不明的用户自述、低优先级角色写入的旧状态,不该和”高相似度 + 高可信度 + 高新鲜度”的记忆同权竞争。长期记忆污染的根因:系统只有语义通道,没有时间和来源治理。
retrieval 和 memory 不混。retrieval 解决”这一轮缺什么外部证据”,memory 解决”系统跨轮应该记住什么稳定状态”。检索命中的内容可以是临时证据、候选事实和外部资料,不自动写成长期记忆。长期记忆不做去重、合并和时效判断时,不直接回灌进检索库。这条边界立住后,系统不会把短期命中误沉淀成长久状态。
上下文窗口不够时,压缩目标不是单纯减少字数,而是保住执行所需的事实骨架。压缩顺序:先保留最近几轮和当前步骤直接相关的原始消息,再把更早轮次压成运行摘要,再把关键业务事实提炼成结构化状态,然后把稳定知识和可复用案例写回长期可检索记忆,最后做去重与语义合并。任务中途需要人工接手时,额外生成交接快照,明确当前目标、已完成步骤、卡点、待确认事项和恢复入口。
滑动窗口、摘要压缩、结构化状态、向量检索和交接快照解决不同问题。滑动窗口最省事,越来越依赖最近轮次。摘要压缩最省 token,也最容易把限制条件压扁。结构化状态最适合保住任务骨架。向量检索适合从长历史里按需捞回相关记忆,对写入质量和时间权重敏感。交接快照在长任务切换执行者或人工接管时最有价值。工业系统把它们混用,不押一种。
放到 coding agent 里看,状态设计更具体。模型做代码理解、重构建议或单测生成时,该看到的是文件边界、AST 结构、调用图、依赖层次、外部副作用和已有测试约束,不是全仓库原文。高度依赖数据库、RPC、时间、随机数、全局状态或外部进程的函数天然不适合直接生成单测。这些信息不先被提炼成结构化状态,模型很难判断应该生成单测、集成测试,还是跳过并要求人工确认。
checkpoint 或 snapshot 解决的是任务中断、失败或等待人工确认后系统如何从中间恢复,不只是”多存一份日志”。保存在 checkpoint 里的:当前 state、已完成步骤、关键工具结果、审批状态和下一个待执行节点。不该原封不动塞回模型上下文的:大段重复对话、原始长文档全文和已失效的中间草稿。恢复虽然看起来完整,实际把上下文噪声和成本一起带回来。长期记忆写入前需要去重和语义合并,避免同一事实被反复表达、反复写入,最终让检索结果被同义重复内容淹没。
Agent 的关键状态对象要先固定,再谈范式和框架¶
真正能落地的 Agent 不会只有一份”聊天历史”。最小状态集合至少包括:goal 或当前任务目标,request/session meta,user/tenant policy snapshot,working memory summary,structured slots,current plan or next step,tool registry snapshot,evidence bag,approval state,checkpoint refs 和 result ledger。关键是区分”哪些字段必须被程序理解,哪些字段只需要给模型看”。把所有状态都塞进自然语言历史里,后面无论是 ReAct、LangGraph 还是自研运行时,都会在恢复、去重和审批上变得很脆。
Session、User、Tenant 不能混成一个 id¶
面试常追问 Session 和 User ID 绑定,考察的是有没有把会话、身份和租户边界拆开。user_id 回答”谁在发起这次请求”,tenant_id 回答”这次请求落在哪个权限与资源边界内”,session_id 回答”当前这条会话或执行链该写回哪一份状态”。一个用户可以有多个 session,一个 tenant 里也可以有很多用户和很多 session。同一个 session 必须绑定一份确定的 tenant_id + user_id + policy snapshot,不是前端想切就切。会话在创建时由服务端把这三者绑死,后续流式事件、检索工具和长期记忆只回显这份绑定结果,不再相信前端重复提交的身份字段。
记忆、RAG 和工具调用要按同一条状态机来编排¶
很多系统把记忆、RAG 和工具调用分别做成三个模块,彼此之间状态不共享,模型每一步都像重新认识世界。编排方式:先由当前状态判断这一步缺的是长期偏好、外部事实还是业务动作。缺稳定偏好就读长期记忆,缺外部证据就走知识库查询工具,缺真实副作用就走业务工具。每一步观察结果先回写结构化状态,再决定哪些内容值得进入摘要记忆或长期记忆。把 RAG 当成一种只读证据工具,而不是另起一套平行上下文链,工具、检索和记忆的运行时边界就会清楚很多。
治理机制:reflection、approval、interrupt/resume、A2A 和循环防控¶
reflection 是运行时自检机制,在关键节点再做一次校验。最终回答前检查语气、合规和格式,工具执行后复盘结果是否已经足够,在长任务中判断当前计划是否偏航。适合放在高风险回答前、格式要求严格的输出前,以及重要工具调用后的收束阶段。不适合无差别地每一步都开,代价直接:多一次模型调用,多一段延迟,可能让系统过于保守。reflection 值不值得引入,取决于这一步自检是否真的能显著降低事故概率。
规划失败恢复建成独立机制,不靠 reflection 或重试兜底。复杂任务里,API 超时、返回格式错误、子任务部分失败、外部资源短时不可用都很常见。恢复链:先判断失败发生在计划层、执行层还是观察层。暂时性基础设施失败做局部重试。步骤结果格式异常做参数修复或重新执行当前步。依赖已失效或目标明显偏航时触发重规划。已产生副作用的步骤要求显式补偿或人工确认。系统没有把步骤边界、依赖关系和副作用点显式化时,重规划最后通常只是重新生成一段更长的自然语言。
恢复链压成最小状态机:
暂时性失败 -> 局部重试
格式失败 -> 修参数 / 重做当前步
依赖变化 -> 基于当前 state 重规划
副作用失败 -> 补偿 / 审批 / 人工接管
状态损坏 -> 从最近 checkpoint 恢复
每一条分支解决不同故障。真正危险的是系统把所有失败都降成同一句”再来一次”。动作已有副作用、外部世界已被改写时,盲目从头来过是在重复制造事故。
approval 和 interrupt/resume 是同一套治理体系的另一面。approval 解决高风险动作不能自动越界,interrupt/resume 解决长任务、人工确认和外部事件怎样插入运行时。没有 interrupt/resume,系统一旦需要人确认,只能从头来过。没有 approval,模型的一次误判就可能直接触发真实副作用。checkpoint、approval 和 interrupt/resume 经常同时出现,因为它们都在收束一条长生命周期任务的状态边界。
reflection、retry、approval、interrupt/resume 和 checkpoint 放在一起比较。retry 解决暂时性失败,不解决错误决策。reflection 解决关键节点自检,不解决真实副作用授权。approval 解决高风险动作必须有人类边界。interrupt/resume 解决执行流如何被暂停和继续。checkpoint 解决从哪里恢复,而不是该不该恢复。很多系统一出问题就只会”再试一次”或”让模型再想一遍”,本质上是没有先判断故障类型。
A2A 放在”多执行者协作协议”语境里理解。它关心任务、状态和结果怎样在不同 Agent 之间交接,不是两个模型能不能互相聊天。真正难的地方有五个:状态是否一致,权限边界会不会被上一跳带穿,重复任务能否幂等去重,状态版本是否会被旧结果覆盖,系统怎样知道该停而不是继续相互转发。防止两个 Agent 陷入递归对话,同时上几道硬边界:限制最大 hop 或最大 step,给每次任务分配唯一 ID 并做去重,让状态带版本号,限制对等方可调用的角色和工具,显式定义终止信号——“已拿到最终结论””需要人工确认””证据不足停止”。没有这些边界,多 Agent 协作从分工变成放大不确定性。
handoff、共享状态、共享记忆和显式消息通信分开看。handoff 像控制权转移,适合某一步后明确由另一个执行者接管。共享状态适合多个节点围绕同一任务对象协作,要求状态模型很稳。共享记忆适合沉淀长期知识,不适合承载当前任务的精细控制流。显式消息通信最灵活,适合跨系统或跨团队协作,对幂等、版本和终止信号要求最高。单任务内尽量共享结构化状态,跨执行者协作优先用显式 handoff。多个执行者长期共享同类知识时,逐步引入共享记忆。一上来”大家共用一份上下文”,最容易把边界和责任一起搞乱。
goal setting and monitoring、exception handling and recovery、human-in-the-loop 不应只放在资料区。goal setting 的价值:把”当前目标是什么、成功条件是什么、什么时候算偏航”写进运行时,不只写在一段自然语言 prompt 里。monitoring 的价值:看任务完成率、平均步数、重复调用率、递归 handoff 率、人工接管率和恢复成功率这些直接暴露运行时问题的指标。exception handling and recovery 要求系统在工具失败、审批挂起、外部事件延迟和进程重启时,知道如何暂停、如何恢复、如何回放,不是一出错就整条链从头重跑。human-in-the-loop 不是最后兜底的一句口号,要明确哪些节点允许人工确认、人工修改怎样写回 state、恢复后从哪一步继续,以及哪些高风险动作永远不能跳过人工边界。
learning and adaptation 有工程化定位。生产系统不让 Agent 在运行时随意自我改写规则,把失败样本、trace、人工修正和评测结果回流到离线改进链路,再更新 prompt、路由规则、工具策略、评测集和长期记忆写入策略。值得鼓励的 adaptation,发生在受控迭代里,不发生在一次在线会话的自我演化里。
开发型 Agent 也属于同一类运行时,面向的对象从业务工单变成了代码库、终端、测试和评审。Claude Code、Codex 这类系统把 rules / memory / tools / skills / review 一起显式化了。rules 或项目说明文件负责长期约束,memory 负责跨会话保留项目知识,tools 负责编辑、搜索、终端和外部系统接入,skills 负责把重复工作方法打包成可复用能力,review 握着最终把关权。开发型 Agent 和业务型 Agent 的差别主要在任务对象,不在运行时本质。
视角往上抬一层,治理对象指向 Agent 中台或平台能力。单个智能助手应用关心一条业务链能不能跑稳,Agent 中台关心多团队怎样复用能力、怎样在统一边界下接入新工具、怎样让不同 Agent 共享运行时和治理能力。平台层,能力注册表、统一适配层、状态与记忆服务、checkpoint 存储、审批与风险控制、trace/eval/audit、租户和权限边界被收成一套公共底座。Agent 中台考的是”同样的治理规则能不能稳定作用在不同 Agent 和不同业务线上”。
失败模式、排查顺序和适用边界¶
Agent 运行时最常见的失败方式:边界没立住。目标定义模糊导致任务漂移,状态分层不清导致旧结论持续污染后续动作,工具结果没有结构化让每一步都像重新理解世界,checkpoint 缺失让长任务一断就只能从头开始,多 Agent 没有 hop、step 和任务 ID 限制就递归互转。排查顺序固定:先看目标和停止条件,再看状态对象和记忆装配,再看工具结果是否足够结构化,再看 handoff 与 checkpoint,最后看模型为什么在已有材料上判断偏了。
单测生成是一个好的失败样本。覆盖率高但测试质量很差,说明系统把”生成了很多代码”误当成”生成了有效验证”。先筛掉不适合直接做单测的目标函数,再根据依赖边界决定哪些外部依赖必须 mock,哪些 mock 反而会掩盖真实行为,把评估指标从覆盖率扩到断言质量、边界条件、稳定性和可维护性。函数强依赖数据库和 RPC 时,需要先把依赖替身、测试夹具和可重复运行环境准备好。否则模型生成的测试即使语法正确,也很难稳定执行。
不是所有场景都值得做 Agent。规则稳定、步骤明确、审计要求极高的链路,普通工作流更稳。高风险写操作还没有 approval、checkpoint 和人工接管能力时,强上 Agent 只是放大风险。超低延迟的一次性问答、请求里只需一次检索或一次工具调用时,未必需要引入完整运行时。真正适合 Agent 的:目标明确但路径不完全固定、需要在多种信息源和工具之间动态决策、团队已经具备 trace、评测、审批和恢复基础设施的任务。
评估 Agent 是否真的变好,看任务完成率、平均步数、工具成功率、人工接管率、checkpoint 恢复成功率、重复调用率、递归 handoff 率、单位任务成本和失败样本结构变化。这些指标说不清时,系统多半还没有真正进入可经营的工程阶段。