AI Agent 这个词在过去一年里被反复提起,但真正动手搭过一套能跑起来的系统之后,你会发现大部分公开资料都停留在概念层面。七要素、决策点这些说法听起来很完整,可一旦落到代码里,问题就变成了:循环到底怎么写才不会失控?工具调用失败了谁来兜底?上下文塞满了怎么办?我前后搭过几套不同规模的 Agent,从最简单的单工具问答到带记忆、带多轮编排的复杂流程,踩过的坑基本都集中在这些"工程细节"上,而不是模型本身。这篇就把我从七要素到七个决策点的完整思考链路摊开讲,重点放在"为什么这么设计"和"实际写的时候会撞到什么",适合已经了解 LLM 基础、准备动手做 Agent 的开发者,也适合已经在做但总觉得系统不稳的人对照排查。
1. 先把七要素拆开看:它们不是清单,是依赖关系
很多人第一次接触 Agent 的七要素,会把它当成一个 checklist——模型、工具、记忆、规划、执行、反馈、循环,挨个实现一遍就以为完事了。我一开始也是这么想的,结果搭出来的东西能跑 demo,一上真实任务就崩。后来才明白,这七个要素之间存在强依赖,顺序错了或者某一环偷懒,整个系统就会在某个意想不到的地方断掉。
1.1 模型是地基,但它决定的不只是"聪不聪明"
选模型这件事,新手最容易只看 benchmark 分数。我早期也这样,挑了个榜单上很强的模型,结果发现它在工具调用格式上极其不稳定,同样的 prompt 有时候输出标准 JSON,有时候夹带一段解释文字,解析器直接报错。后来换成工具调用能力专门优化过的模型,虽然通用问答分数低一点,但整个系统的稳定性上了一个台阶。
这里的关键认知是:Agent 场景下,模型的"指令遵循能力"和"结构化输出稳定性"比纯推理能力更重要。因为 Agent 的每一步输出都要被程序解析、被下游消费,格式一乱,后面全乱。你可以做个简单测试,给候选模型同一个工具调用 prompt 跑 50 次,统计格式合规率,低于 95% 的基本可以直接排除。
另一个容易被忽略的点是上下文窗口和成本的关系。窗口大不代表要全用上,我见过有人把整个对话历史无脑塞进去,token 消耗飞快,而且模型在超长上下文里的注意力会稀释,反而容易漏掉关键指令。合理的做法是分层管理:系统提示词固定,近期对话保留原文,早期对话做摘要压缩。
1.2 工具是手脚,但"能调用"和"调得好"是两回事
工具这一环,最朴素的实现就是给模型一个函数列表,让它自己选。但实际跑起来你会发现几个问题:工具描述写得含糊,模型就选错;工具太多,模型就挑花眼;工具返回结果太长,直接把上下文撑爆。
我现在的做法是给每个工具写"三件套":一句话功能说明、明确的入参 schema、以及一个典型调用示例。别小看这个示例,它对模型选择正确率的提升非常明显。另外工具数量控制在 10 个以内比较稳妥,超过之后要么分组,要么做一层路由先筛选候选工具。
工具返回值的处理也有讲究。比如一个查询数据库的工具返回了 200 行数据,你不能原样丢回给模型。我的处理是:结构化数据先在代码层做聚合或截断,只把模型决策真正需要的字段传回去。这一步做得好,能省下大量 token,也能避免模型被无关信息干扰。
1.3 记忆不是"存聊天记录"那么简单
很多人把记忆理解成把历史对话存下来,下次拼回去。这只对了一半。真正的记忆要分三层:工作记忆(当前任务的临时状态)、短期记忆(本次会话的上下文)、长期记忆(跨会话的知识沉淀)。
工作记忆最容易被忽略,但它恰恰是 Agent 稳定性的关键。比如一个多步任务,Agent 执行到第三步时,需要知道第一步产出的中间结果。这个结果如果只存在于对话历史里,很容易被后续内容挤掉。我的做法是显式维护一个状态对象,把关键中间结果结构化存起来,每一步都从状态对象里读,而不是从对话历史里"回忆"。
长期记忆则要考虑检索问题。全量塞进去不现实,得做向量化检索。但检索的召回质量直接决定 Agent 会不会"记错事",所以嵌入模型的选择和分块策略要花心思,不能随便切。
1.4 规划、执行、反馈:三个容易被合并却必须分开的环节
规划是"想清楚要做几步",执行是"真的去做",反馈是"判断做得对不对"。新手常把这三个揉在一起,让模型一步到位输出最终答案。这在简单任务上没问题,但复杂任务必然翻车。
分开之后的好处是每一环都可以独立优化和调试。规划错了,你能定位到是拆解逻辑的问题;执行错了,是工具或参数的问题;反馈错了,是判断标准的问题。混在一起,你只能看到"结果不对",根本无从下手。
1.5 循环是骨架,但它的终止条件才是灵魂
七要素里循环排在最后,但它是把所有东西串起来的那根线。循环本身不难写,难的是"什么时候停"。终止条件设计不好,要么死循环烧钱,要么提前退出任务没做完。
我一般会设三重保险:最大步数限制、连续无进展检测、以及显式的完成信号。三者任一触发就停。最大步数防死循环,无进展检测防原地打转,完成信号让正常任务能及时收尾。这三重里,无进展检测最容易被漏掉,但它在实际运行中救场的次数最多。
2. 七个决策点:Agent 真正"做选择"的地方
七要素讲的是系统由什么组成,七个决策点讲的是系统在运行时要在哪些地方做判断。这两者不是一回事,前者是静态结构,后者是动态行为。我把这七个决策点按执行顺序梳理一遍,每个都说说实际实现时的取舍。
2.1 决策点一:这一轮要不要调用工具
这是每一轮循环的第一个判断。模型需要决定:是直接回答,还是调用工具获取更多信息。听起来简单,但这里有个经典陷阱——模型倾向于"过度调用"。明明自己知道答案,也要调个工具确认一下,白白增加延迟和成本。
我的应对是在系统提示词里明确写清楚调用工具的边界,比如"仅当需要实时数据或外部信息时才调用工具"。同时可以在反馈环节加一个判断,如果工具返回的信息和模型已知的重合,就在下一轮提示它直接回答。实测下来,这个调整能把无效工具调用降低三成左右。
2.2 决策点二:调用哪个工具
工具选择错误是 Agent 最常见的失败模式之一。原因通常有三个:工具描述重叠、任务表述模糊、候选工具太多。
解决思路是"描述差异化 + 路由前置"。描述差异化是指确保每个工具的功能说明互不重叠,边界清晰。路由前置是指当工具数量多时,先用一个轻量模型或规则做初筛,把候选缩小到 3-5 个再让主模型选。这样既降低了选择难度,也省了 token。
2.3 决策点三:参数怎么填
工具选对了,参数填错一样白搭。参数填充的难点在于:模型需要从上下文里抽取正确的值,还要符合 schema 的类型要求。
我的经验是给每个参数都写清楚"从哪来"和"什么格式"。比如日期参数,明确写"格式为 YYYY-MM-DD,从用户消息中提取"。另外在代码层做一层校验,类型不对或必填缺失时,不要直接报错终止,而是把错误信息回传给模型让它重填。这个"重试而非终止"的策略,能显著提升任务完成率。
2.4 决策点四:结果怎么解读
工具返回结果后,模型要判断这个结果意味着什么。这里最容易出问题的是"异常结果"的处理。比如查询返回空、接口报错、数据格式和预期不符。
很多实现直接把这些异常结果原样丢给模型,模型可能理解不了就瞎编。正确做法是在代码层先做一轮归一化,把异常转成模型能理解的描述,比如"查询返回空结果,可能原因是没有匹配数据"。同时明确告诉模型遇到这种情况该怎么办,是重试、换工具、还是告知用户。
2.5 决策点五:任务是否完成
这是循环终止的核心判断。模型需要根据当前状态判断任务是否已经达成目标。难点在于"部分完成"和"完全完成"的区分。
我的做法是让规划环节就定义好"完成标准",每一步执行后对照标准检查。标准要具体可验证,比如"已获取用户指定的三个字段"而不是"任务基本完成"。模糊的标准会让模型过早收尾。
2.6 决策点六:出错后怎么恢复
Agent 运行中出错是常态,关键是恢复策略。我把错误分成三类:可重试的(如网络抖动)、可替代的(如换个工具)、不可恢复的(如参数根本性错误)。
对应三种处理:可重试的自动重试有限次;可替代的让模型重新规划;不可恢复的明确告知用户并给出建议。这个分类逻辑要写进系统提示词,让模型知道遇到不同错误该怎么反应,而不是一律卡死。
2.7 决策点七:上下文怎么管理
这是贯穿全程的决策点。每一轮循环都会往上下文里加内容,加着加着就满了。什么时候压缩、压缩什么、保留什么,都需要策略。
我的策略是"近期保原文、中期做摘要、远期做检索"。具体阈值根据模型窗口定,一般用到 70% 就开始触发压缩。压缩时优先保留系统提示词、当前任务状态、最近几轮对话,早期的工具调用结果可以摘要成一句话。
3. 循环机制:写一个不会失控的 Agent 主循环
循环是 Agent 的心脏,但公开资料里很少讲清楚怎么写才稳。我把自己的主循环结构拆开讲,重点说几个关键控制点。
3.1 主循环的基本骨架
一个稳健的主循环大致是这样的结构:初始化状态对象,进入循环,每轮先检查终止条件,然后让模型决策,执行决策,更新状态,再回到循环开头。这个骨架看起来简单,但每一环都有细节。
初始化阶段要把系统提示词、工具定义、任务目标、初始状态都准备好。这里有个容易漏的点:任务目标要写得足够具体,最好包含明确的完成标准。我见过太多因为目标模糊导致 Agent 跑偏的案例。
循环开头的终止检查要放在模型调用之前,这样能避免不必要的模型调用。检查项包括步数上限、时间上限、无进展计数、以及显式的完成标志。
3.2 无进展检测怎么做才准
无进展检测是防死循环的关键,但做得太敏感会误杀正常任务,太迟钝又起不到作用。我的做法是维护一个"状态指纹",每轮结束后计算当前状态的哈希,如果连续 N 轮指纹相同,就判定为无进展。
指纹要包含哪些内容很关键。我一般包含:当前任务进度、最近一次工具调用及结果摘要、模型输出的关键决策。这样能捕捉到"看起来在动但实际原地打转"的情况。N 的取值我一般设 3,实测比较平衡。
3.3 状态对象的维护
状态对象是整个循环的"记忆中枢",它要记录:任务目标、已完成步骤、当前进度、关键中间结果、错误历史。每一轮循环都要读写这个对象。
维护状态对象的原则是"显式优于隐式"。不要依赖对话历史来隐式传递状态,而是每一步都显式更新状态对象。这样即使对话历史被压缩了,状态也不会丢。我踩过的坑就是早期依赖对话历史,结果一压缩就丢关键信息,任务直接跑偏。
3.4 异常中断与恢复
循环运行中可能遇到各种异常:模型调用超时、工具执行报错、解析失败等。这些异常不能直接让整个循环崩掉,要有捕获和恢复机制。
我的做法是在循环内做 try-catch,捕获后把异常转成结构化信息写入状态对象,然后让模型在下一轮基于这个信息决定怎么处理。同时设一个异常计数器,连续异常超过阈值就终止并上报。这样既给了恢复机会,又防止无限重试。
4. 工具调用:从定义到执行的完整链路
工具调用是 Agent 和外部世界交互的唯一通道,这条链路的每个环节都可能出问题。我把它拆成定义、选择、填充、执行、解读五段来讲。
4.1 工具定义的规范
一个好的工具定义应该包含:名称(简洁明确)、功能描述(一句话说清做什么)、参数 schema(类型、必填、说明)、返回值说明、以及调用示例。
名称要避免歧义,比如search和query这种就很容易混。功能描述要写"做什么"而不是"怎么做",比如"查询指定城市的当前天气"比"调用天气 API"更好。参数说明要具体到格式和来源。
调用示例这一项很多人省掉,但它对模型理解工具用途帮助极大。我一般给每个工具配一个真实的调用例子,包括输入和预期输出。
4.2 工具选择的降错策略
前面提过路由前置,这里展开说。当工具数量超过 10 个,我会加一层轻量路由:先用关键词匹配或小模型做初筛,把候选缩到 3-5 个,再让主模型在候选里选。
路由的实现可以很简单,给每个工具打几个标签,根据任务描述匹配标签。也可以用嵌入模型算相似度。关键是这一层要快、要便宜,不能成为瓶颈。
4.3 参数填充的校验与重试
参数填充后必须校验,校验不通过不要直接失败,而是把错误信息回传模型重填。重试次数设 2-3 次,超过就放弃这个工具,让模型换方案。
校验内容包括:必填项是否齐全、类型是否正确、格式是否符合、值是否在合理范围。这些校验在代码层做,比让模型自己检查可靠得多。
4.4 执行层的超时与隔离
工具执行要有超时控制,不能让它无限等待。超时时间根据工具性质定,查询类可以短一点,计算类可以长一点。超时后要返回明确的超时信息,而不是空结果。
隔离是指工具执行失败不能影响主循环。每个工具调用都包在独立的错误处理里,失败就返回错误信息,主循环继续。
4.5 结果解读的归一化
工具返回结果后,先做归一化再给模型。归一化包括:截断过长内容、提取关键字段、把异常转成描述性文本。
比如一个返回 JSON 的工具,如果返回了错误码,归一化时就转成"调用失败,原因:xxx"。如果返回正常但数据量大,就只保留模型决策需要的字段。这一步做得好,能大幅提升模型解读的准确率。
5. 记忆与上下文:让 Agent 记得住又不撑爆
记忆和上下文管理是 Agent 工程里最考验设计功力的部分。做得好,Agent 表现得像个有经验的助手;做得差,它就像个失忆症患者,每轮都在重新开始。
5.1 三层记忆的分工
工作记忆存当前任务的临时状态,生命周期就是一次任务。短期记忆存本次会话的对话历史,生命周期是一次会话。长期记忆存跨会话的知识,生命周期是永久。
三层的存储介质和检索方式都不同。工作记忆直接放内存对象,读写最快。短期记忆放会话存储,按时间顺序组织。长期记忆放向量库,按语义检索。
5.2 上下文压缩的触发与策略
压缩触发阈值一般设在窗口的 70% 左右。压缩时优先保留:系统提示词、当前任务状态、最近 3-5 轮对话。早期的工具调用结果可以摘要成一句话,比如"已查询天气,结果为晴"。
压缩本身也可以让模型来做,但要给明确的指令,比如"把以下对话压缩成不超过 200 字的摘要,保留关键决策和结果"。压缩后的内容要标注清楚这是摘要,避免模型误以为是原文。
5.3 长期记忆的写入与检索
长期记忆不是什么都存,要有选择。我一般只存:用户偏好、重要事实、成功的问题解决模式。写入前做一轮判断,值得存才存。
检索时用向量相似度召回 top-k,再让模型判断哪些相关。k 一般设 3-5,太多会引入噪声。检索结果要标注来源和时间,方便模型判断时效性。
5.4 记忆冲突的处理
当新信息和旧记忆冲突时怎么办?我的做法是让新信息优先,但保留旧记忆并标注冲突。比如用户之前说喜欢 A,现在说喜欢 B,就记录"用户偏好从 A 变为 B"。这样模型能理解变化,而不是困惑。
6. 容错与安全:Agent 跑得稳的底层保障
Agent 一旦接入真实系统,容错和安全就不是可选项而是必选项。这部分我踩过的坑最多,也最想提醒后来者。
6.1 错误分类与分级处理
错误要分类,不同类不同处理。我分四级:提示级(不影响继续)、警告级(需要调整但可继续)、错误级(当前步骤失败需重试或换方案)、致命级(必须终止)。
分级的标准是"是否影响任务目标达成"。提示级和警告级记录即可,错误级触发恢复逻辑,致命级终止并上报。
6.2 重试策略的设计
重试不是无脑重试,要有策略。我一般用指数退避,第一次等 1 秒,第二次 2 秒,第三次 4 秒。重试次数上限 3 次。超过就放弃当前方案,让模型重新规划。
重试时要区分"可重试错误"和"不可重试错误"。网络超时可重试,参数错误重试也没用,直接换方案。
6.3 权限与边界控制
Agent 能调用的工具要有权限控制。危险操作(如删除、支付)要么禁止,要么加二次确认。工具的参数也要做范围校验,防止模型填出离谱的值。
我一般会给工具打权限标签,高危工具在系统提示词里明确标注"需谨慎使用",并在执行前加一道确认逻辑。
6.4 输出内容的过滤
Agent 的输出要过滤,防止泄露敏感信息或产生不当内容。过滤在代码层做,不依赖模型自觉。过滤规则包括:敏感词、格式校验、长度限制。
6.5 可观测性建设
Agent 运行过程要可观测,否则出问题无从排查。我一般记录:每轮的输入输出、工具调用详情、状态变化、错误信息。这些日志按任务 ID 组织,方便回溯。
可观测性还包括指标监控:任务成功率、平均步数、工具调用分布、错误率。这些指标能帮你发现系统性问题。
7. 从零搭一个最小可用 Agent 的实操路径
讲了这么多原理,最后落到实操。我给一条从零到最小可用的路径,按这个顺序走,能少踩很多坑。
7.1 第一步:定义任务和完成标准
先别写代码,先把任务想清楚。任务是什么、输入是什么、输出是什么、怎么算完成。完成标准要具体可验证。
7.2 第二步:选模型和工具
模型选工具调用稳定的,工具先选 2-3 个最核心的。别一上来就堆工具,先把主流程跑通。
7.3 第三步:写主循环骨架
按前面讲的骨架写:初始化、循环、终止检查、模型决策、执行、状态更新。先跑通单轮,再跑多轮。
7.4 第四步:加状态管理和终止条件
状态对象和终止条件是稳定性的关键,这一步不能省。三重终止保险都要加上。
7.5 第五步:加错误处理和日志
错误处理和日志是排查问题的基础,越早加越好。别等到出问题才补。
7.6 第六步:压测和调优
用真实任务压测,看成功率、步数、成本。根据数据调优提示词、工具定义、终止阈值。
7.7 第七步:加记忆和上下文管理
最后再加记忆和上下文压缩,因为这部分复杂度高,前面跑稳了再加不容易乱。
8. 几个我踩过的坑和对应的解法
最后分享几个具体的坑,都是实际跑出来的,不是理论推演。
8.1 坑一:模型过度调用工具
表现是明明能直接回答也要调工具。解法是在提示词里明确边界,并在反馈环节加判断。
8.2 坑二:上下文压缩丢关键信息
表现是压缩后任务跑偏。解法是压缩时显式保留任务状态和完成标准,不依赖对话历史。
8.3 坑三:工具返回异常导致模型瞎编
表现是工具报错后模型编造结果。解法是归一化异常结果,明确告知模型这是错误。
8.4 坑四:死循环烧钱
表现是任务卡住反复调用。解法是三重终止保险,尤其是无进展检测。
8.5 坑五:状态丢失导致重复劳动
表现是 Agent 重复做已完成的步骤。解法是显式状态对象,每步读写。
这些坑的共同点是:都不是模型能力问题,而是工程设计问题。把工程做扎实,Agent 的稳定性会有质的提升。我现在的习惯是每上一个新 Agent,先把这五个坑的防护都加上,再考虑功能扩展。