AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道概念"到"工程落地"之间隔着一整条鸿沟。我见过太多团队拿着一个 LLM API Key 就开始画架构图,结果卡在工具调用格式对不上、循环停不下来、上下文越滚越长这些具体问题上。这篇内容不打算再重复"Agent 等于 LLM 加记忆加工具"这种入门定义,而是从工程实现的视角,把 Agent 拆成七个必须想清楚的要素,再顺着一次完整执行链路,梳理出七个真正影响系统能不能上线的决策点。如果你正在做 Agent 开发、正在选型 Agent 框架,或者已经写了一版 Demo 但发现它一放到真实场景就崩,这篇应该能帮你把脑子里那团模糊的东西理清楚。
1. 先把"七要素"落到工程图纸上
很多人第一次接触 Agent 架构时,看到的都是那张经典的循环图:感知、规划、行动、观察,转一圈再转一圈。这张图没错,但它太抽象了,抽象到你照着它写不出代码。我更喜欢把 Agent 拆成七个可以分别设计、分别测试的工程要素,这样每一块出问题的时候你能定位到具体位置,而不是笼统地说"我的 Agent 不好使"。
1.1 模型层:不是选最强的,是选最合适的
模型层是整个 Agent 的推理内核。这里第一个决策就是选型。我的经验是,不要一上来就盯着参数最大的模型。Agent 场景和单轮问答场景对模型的要求完全不同。单轮问答看重的是知识广度和表达质量,而 Agent 看重的是指令遵循能力和结构化输出稳定性。
什么叫结构化输出稳定性?就是当你要求模型输出一个 JSON 格式的工具调用请求时,它能不能每次都老老实实按 schema 来,而不是自由发挥加一段解释文字。我实测下来,有些在对话上表现很好的模型,一到要求严格 JSON 输出就开始飘,要么多一个字段,要么把该是字符串的地方写成对象。这种不稳定在 Demo 阶段你可能手动改改就过去了,但一旦进入自动化循环,一次格式错误就可能导致整个任务链断裂。
所以模型选型时,我会优先做一件事:拿二十条真实的工具调用请求,让候选模型各跑一遍,统计格式合规率。这个数字比任何 benchmark 都直观。合规率低于 95% 的模型,在需要多步循环的 Agent 里基本不能用,因为十步下来累积失败率会高得吓人。
另外要提的是模型的大小和延迟权衡。Agent 一次任务往往要调用模型五到十五次,如果单次调用延迟是 3 秒,那一个任务就是 15 到 45 秒。用户能不能接受这个等待时间,直接决定了你能不能用大模型。我一般的做法是:规划环节用强模型,执行环节用快模型,这样既保证决策质量,又控制整体延迟。
1.2 工具层:接口设计比工具数量重要
工具层是 Agent 和外部世界交互的手。新手最容易犯的错是拼命堆工具,觉得工具越多 Agent 能力越强。实际上工具越多,模型选错的概率越大。我做过一个对比实验,同样一个任务,给模型 5 个工具时选对率是 92%,给到 20 个工具时选对率掉到 71%。原因很简单,工具描述之间的语义重叠会干扰模型判断。
所以工具层的核心工作不是"加工具",而是"设计工具边界"。每个工具应该职责单一,描述里要明确写清楚什么情况下用、什么情况下不用。比如你有一个"查询订单"工具和一个"查询物流"工具,如果描述写得含糊,模型很容易在用户问物流时去调订单查询。我的做法是在工具描述里加一句反向说明:"如果用户问的是配送进度,请使用查询物流工具,不要使用本工具。"
工具的参数设计也有讲究。参数越少越好,必填参数尽量控制在三个以内。可选参数要给默认值,并且在描述里说明默认行为。我见过一个工具设计了八个参数,结果模型每次调用都要纠结半天填哪些,最后经常漏填关键参数导致调用失败。
1.3 记忆层:短期靠上下文,长期靠检索
记忆层解决的是"Agent 怎么记住之前发生的事"。这里要区分两种记忆:短期记忆和长期记忆。
短期记忆就是当前任务的上下文,包括用户输入、之前的工具调用结果、模型的中间推理。这部分直接放在对话历史里就行,但要注意控制长度。我的经验是,当一个任务的对话历史超过模型上下文窗口的 60% 时,就要开始做压缩了。压缩的方式可以是让模型自己总结之前的步骤,也可以是把早期的工具调用结果替换成摘要。
长期记忆则是跨任务的知识沉淀,比如用户的偏好、历史交互记录、领域知识库。这部分不能直接塞进上下文,必须走检索。常见的做法是把长期记忆向量化存进向量数据库,每次任务开始时根据当前输入检索最相关的几条注入上下文。这里的关键是检索的精度,检索不准的话注入的噪声比不注入还糟糕。
我踩过的一个坑是:早期我把所有历史对话都存进长期记忆,结果检索出来的内容经常是无关的闲聊,反而干扰了模型判断。后来改成只存结构化的关键信息,比如"用户偏好简洁回复""用户所在地区是华东",检索质量立刻上来了。记忆不是越多越好,是要存对的东西。
1.4 规划层:任务分解的粒度决定成败
规划层负责把用户的模糊需求拆成可执行的步骤。这一层是 Agent 智能感的主要来源,也是最容易出问题的地方。
规划的核心难题是粒度。拆得太粗,每一步都太复杂,模型执行时容易跑偏;拆得太细,步骤数量爆炸,循环次数过多,延迟和成本都受不了。我一般的经验是,单个步骤应该是一个"可以用一到两次工具调用完成"的粒度。如果一个步骤需要调用五次以上工具,说明拆得不够细;如果连续三个步骤都是"思考一下",说明拆得太细了。
规划方式上,我推荐先用"一次性规划加动态调整"的模式。就是任务开始时让模型先出一个完整的步骤列表,然后每执行完一步,根据实际结果决定是继续下一步还是重新规划。纯静态规划的问题是现实世界经常不按预期走,比如你计划查数据库,结果数据库连不上,静态规划就卡死了。纯动态规划的问题是模型容易短视,走一步看一步最后偏离目标。两者结合最稳。
1.5 执行层:错误处理才是真正的考验
执行层就是实际调用工具、处理返回结果的地方。这一层看起来最简单,实际上最容易埋雷。
工具调用失败是常态,不是异常。网络超时、接口限流、参数错误、权限不足,这些都会发生。如果执行层没有完善的错误处理,一个工具调用失败就会让整个 Agent 崩溃。我的做法是给每个工具调用包一层重试逻辑,对于可重试的错误(比如超时)自动重试两到三次,对于不可重试的错误(比如参数错误)则把错误信息返回给模型,让模型决定是换个方式调用还是放弃这个步骤。
这里有个细节:错误信息要原样返回给模型,不要自己加工成"调用失败"这种模糊描述。模型需要知道具体错在哪,才能做出正确判断。比如"参数 user_id 格式错误,期望整数"就比"调用失败"有用得多。
1.6 编排层:循环控制是生命线
编排层管的是整个 Agent 的运行流程,核心就是循环控制。Agent 本质上是一个"思考、行动、观察"的循环,但这个循环必须有明确的终止条件,否则就会无限转下去。
终止条件我一般设三个:任务完成、达到最大步数、连续无进展。任务完成是正常终止,模型判断目标已达成。最大步数是硬性保护,防止死循环,我一般设 15 到 20 步。连续无进展是软性保护,如果连续三步工具调用返回的结果都差不多,说明卡住了,应该终止并报告。
我见过最惨的案例是一个 Agent 因为没有设最大步数,在一个查不到数据的任务上循环了上百次,烧掉了几十美元的 token 费用。循环控制不是可选项,是必选项。
1.7 评估层:没有度量就没有优化
评估层是很多团队会忽略的一层,但它决定了你的 Agent 能不能持续变好。没有评估,你改了一个 prompt 之后根本不知道是变好了还是变坏了。
评估的核心是建立一套可重复的测试集。我会收集二十到五十个真实任务,每个任务标注期望的结果,然后每次改动后跑一遍,统计成功率、平均步数、平均 token 消耗。这三个指标能覆盖大部分问题:成功率掉了说明能力退化,步数涨了说明规划变啰嗦,token 涨了说明上下文管理有问题。
评估集要定期更新,把线上发现的新 case 加进去。我一般每两周更新一次,保持测试集和真实场景的同步。
2. 七个决策点:一次完整执行链路拆解
理解了七个要素,接下来看一次完整的 Agent 执行是怎么走的。我把这条链路拆成七个决策点,每个决策点都是一个"这里选错了后面就会难受"的关键位置。
2.1 决策点一:用户输入进来,先判断要不要走 Agent
不是所有请求都值得启动 Agent。Agent 的每次执行都有成本,包括延迟和 token 费用。如果一个请求用一次普通的 LLM 调用就能解决,就没必要走 Agent 流程。
我的判断标准是:如果任务需要调用外部工具、需要多步推理、或者需要根据中间结果调整策略,才走 Agent。如果只是知识问答、文本改写、简单分类,直接单次调用就行。这个前置判断本身可以用一个轻量模型来做,成本很低但能省下大量不必要的 Agent 开销。
实际实现时,我会在入口处加一个意图分类器,把请求分成"直接回答"和"需要 Agent"两类。分类器可以用规则加小模型的方式,规则处理明显的情况,小模型处理模糊的情况。
2.2 决策点二:上下文怎么组装
确定走 Agent 之后,第一件事是组装上下文。上下文里应该包含什么,直接决定了模型能不能做出正确决策。
我的上下文组装顺序是:系统提示词、工具定义、长期记忆检索结果、短期对话历史、当前用户输入。这个顺序有讲究。系统提示词放最前面是因为它定义了 Agent 的角色和行为边界,模型对开头的内容注意力最集中。工具定义紧随其后,因为模型需要在理解任务之前就知道自己有哪些能力。长期记忆放在对话历史之前,是因为它是背景信息,而对话历史是当前任务的直接上下文。
这里有个容易忽略的点:工具定义会占用大量 token。如果你有二十个工具,每个工具描述两百字,那就是四千字,相当于每次调用都要多花四千 token。所以工具定义要精简,只保留必要信息,详细的用法说明可以放在系统提示词里按需引用。
2.3 决策点三:模型第一轮输出怎么解析
模型第一轮输出通常是规划结果或者第一个工具调用请求。解析这一步看似简单,实际上是最容易出兼容性问题的地方。
不同模型输出 JSON 的习惯不一样。有的会老老实实输出纯 JSON,有的会包在 markdown 代码块里,有的会在 JSON 前后加解释文字。你的解析器必须能处理这些情况。我的做法是先尝试直接解析,失败后用正则提取 JSON 部分再解析,再失败就返回错误让模型重试。
这里有个实用技巧:在系统提示词里明确要求模型"只输出 JSON,不要有任何其他文字",能大幅降低解析失败率。但即使这样也不能保证 100%,所以解析器的容错逻辑必须要有。
2.4 决策点四:工具调用的参数校验
模型给出的工具调用参数不能直接信任,必须校验。模型可能会给出类型错误的参数、缺失必填参数、或者给出超出范围的参数值。
校验分两层:格式校验和业务校验。格式校验检查参数类型、必填项、格式是否符合 schema。业务校验检查参数值是否合理,比如查询日期不能是未来、用户 ID 必须存在。格式校验失败直接返回错误让模型重试,业务校验失败则要把具体的业务错误信息返回给模型,让它决定怎么处理。
我一般会用 JSON Schema 来做格式校验,这是最标准也最省事的方式。业务校验则要针对每个工具单独写,因为业务规则千差万别。
2.5 决策点五:工具执行结果怎么回传给模型
工具执行完之后,结果怎么组织成模型能理解的格式,这个决策点影响很大。
我的原则是:结果要结构化,但不要过度结构化。结构化是指要有明确的字段名和层级,方便模型提取信息。不要过度结构化是指不要把结果包装得太复杂,比如套三层嵌套对象,模型解析起来容易出错。
对于返回大量数据的工具,比如查询返回一百条记录,不要全部塞进上下文。我的做法是只返回前几条加一个总数,如果模型需要更多再让它发起分页查询。这样既控制了上下文长度,又给了模型继续探索的能力。
错误结果的处理也要注意。工具执行失败时,返回给模型的不应该是一个笼统的"失败",而应该包含错误类型、错误信息、以及可能的建议。比如"数据库连接超时,建议稍后重试"就比"执行失败"有用得多。
2.6 决策点六:循环终止的判断
每轮循环结束都要判断是否继续。这个判断不能只靠模型自己说"我完成了",因为模型有时候会误判。
我的做法是三重判断:模型显式声明完成、达到最大步数、连续无进展。模型声明完成是主判断,但要有辅助验证。比如如果模型说完成了但最后一个工具调用是失败的,那就要打个问号,让模型确认一下。
连续无进展的判断我一般看工具调用结果的相似度。如果连续三次调用的工具相同、参数相似、返回结果也相似,基本可以判定卡住了。这时候应该终止循环,把当前状态返回给用户,而不是继续烧 token。
2.7 决策点七:最终结果怎么呈现
Agent 跑完之后,结果怎么呈现给用户,这是最后一个决策点,也是影响用户体验的关键。
我的经验是,最终结果要包含三部分:结论、依据、以及可选的后续建议。结论是直接回答用户问题的部分,依据是 Agent 得出这个结论的关键步骤和证据,后续建议是如果用户想深入可以做什么。
不要直接把 Agent 的完整执行日志丢给用户,那太冗长了。但也不要只给一个结论,用户会怀疑这个结论是怎么来的。折中的做法是给结论加一个简短的执行摘要,比如"我查询了订单系统,发现您的订单已发货,物流单号是 XXX"。这样用户既得到了答案,也知道答案的来源。
3. 工具调用格式:那些文档不会告诉你的坑
工具调用是 Agent 工程里最容易出问题的地方,没有之一。我在这上面踩的坑比在其他所有环节加起来都多。这一章专门讲工具调用格式的实战经验。
3.1 不同模型的工具调用格式差异
目前主流的工具调用格式有三种:OpenAI 的 function calling 格式、Anthropic 的 tool use 格式、以及自定义的 JSON 格式。如果你用的是框架,框架通常会帮你屏蔽这些差异,但如果你要自己实现,就必须了解这些差异。
OpenAI 格式的工具调用是放在 message 的 tool_calls 字段里,参数是 JSON 字符串。Anthropic 格式是放在 content 数组里,类型是 tool_use,参数是对象。自定义格式就五花八门了,有的是在文本里嵌入特定标记,有的是要求模型输出特定结构的 JSON。
我的建议是:如果你的模型支持原生工具调用,优先用原生格式,因为模型对原生格式的支持最好,格式错误率最低。如果模型不支持,再用提示词工程的方式让模型输出自定义格式,但要做好解析容错。
3.2 参数类型转换的隐形陷阱
模型输出的参数类型经常和工具期望的类型不一致。最常见的是数字和字符串的混淆。模型可能把本该是整数的参数输出成字符串 "123",也可能把本该是字符串的参数输出成数字 123。
这种类型不匹配在弱类型语言里可能没事,但在强类型语言里会直接报错。我的做法是在参数校验层做类型转换,能转的就转,转不了的就报错让模型重试。比如字符串 "123" 可以转成整数 123,但字符串 "abc" 转整数就会失败,这时候要返回明确的错误信息。
还有一个坑是布尔值。模型有时候输出 "true" 字符串而不是 true 布尔值,有时候输出 1 而不是 true。这些都要在校验层处理。
3.3 多工具并行调用的处理
有些模型支持一次输出多个工具调用,这在需要并行查询的场景下很有用。但并行调用也带来了新的问题:结果怎么对应、失败怎么处理。
我的做法是给每个工具调用分配一个唯一 ID,结果返回时带上这个 ID,这样就能对应上。失败处理上,如果并行调用中有部分失败,不要整体失败,而是把成功的部分和失败的部分都返回给模型,让模型决定下一步。
并行调用还有一个注意点:不是所有工具都适合并行。如果两个工具之间有依赖关系,比如第二个工具需要第一个工具的输出作为参数,那就不能并行。这个判断要交给模型,但要在系统提示词里说明清楚。
3.4 工具描述里的"负向说明"
前面提过工具描述要写清楚什么情况下不用,这里展开说一下。负向说明是提升工具选择准确率的利器。
举个例子,假设你有两个工具:search_product 和 search_order。如果只写正向说明,"search_product 用于搜索商品",模型在用户问订单时可能会错误地选择 search_product,因为订单里也有商品信息。但如果你加上负向说明,"search_product 用于搜索商品信息,如果用户问的是订单状态或物流,请使用 search_order",模型的选择准确率会明显提升。
负向说明的写法是:先写正向用途,再写"不要用于"的场景,最后写"应该使用"的替代工具。这个结构我实测下来效果最好。
4. 循环机制:怎么让 Agent 该停的时候停
循环机制是 Agent 的心跳,但也是最容易失控的地方。这一章讲怎么设计一个既灵活又可控的循环。
4.1 循环的基本结构
一个标准的 Agent 循环包含四个阶段:思考、行动、观察、判断。思考阶段模型分析当前状态决定下一步做什么,行动阶段执行工具调用,观察阶段处理工具返回结果,判断阶段决定是否继续循环。
这个结构看起来简单,但每个阶段都有细节。思考阶段要控制模型的输出长度,避免它写一大段无关的推理。行动阶段要做好参数校验和错误处理。观察阶段要控制结果的长度和格式。判断阶段要有明确的终止条件。
我一般会把循环的最大轮数设成 15。这个数字是经验值,大部分任务在 10 轮内能完成,15 轮足够覆盖复杂任务,同时又不至于让失控的循环跑太久。
4.2 死循环的三种典型形态
死循环是 Agent 最危险的问题,我总结下来有三种典型形态。
第一种是工具调用死循环。模型反复调用同一个工具,参数也差不多,但就是不换策略。这种通常是因为工具返回的结果没有给模型足够的信息来推进,模型只能重复尝试。解决办法是在工具返回结果里加入引导信息,比如"如果没有找到结果,请尝试使用 XX 工具"。
第二种是规划死循环。模型反复重新规划,但每次规划出来的步骤都差不多,执行不下去。这种通常是因为任务本身无法完成,但模型不愿意承认。解决办法是设置重新规划的次数上限,超过就终止并报告。
第三种是对话死循环。模型和工具之间来回交互,但一直没有实质进展。这种比较隐蔽,因为每轮看起来都在做事。解决办法是监控工具调用结果的相似度,连续相似就终止。
4.3 循环中的上下文膨胀问题
每轮循环都会往上下文里加内容,包括模型的输出和工具的结果。几轮下来,上下文就会膨胀到接近模型窗口上限。这时候如果不处理,要么报错,要么模型开始丢失早期信息。
我的处理策略是分层压缩。最近的三个轮次保留完整内容,三到六轮之前的轮次把工具结果压缩成摘要,六轮之前的轮次只保留关键结论。压缩可以用模型来做,也可以用规则来做。规则压缩更快更便宜,但质量不如模型压缩。我一般用规则压缩处理工具结果,用模型压缩处理推理过程。
还有一个技巧是把工具定义从上下文中移出去,改成在系统提示词里引用。因为工具定义是静态的,每轮都带着太浪费。有些框架支持工具定义的缓存,能省不少 token。
4.4 循环终止后的状态保存
循环终止后,不管是正常完成还是异常终止,都要保存当前状态。正常完成时保存最终结果和执行摘要,异常终止时保存已完成步骤和卡住的位置。
这个状态保存很重要,因为用户可能会想继续这个任务。比如 Agent 因为达到最大步数终止了,用户可以说"继续",这时候你需要从上次的状态接着跑,而不是从头开始。我的做法是把状态序列化成 JSON 存起来,包含对话历史、已完成步骤、当前进度。
5. 记忆管理:短期和长期的分界线在哪
记忆管理是 Agent 工程里最容易被低估的部分。很多人觉得记忆就是存对话历史,实际上短期记忆和长期记忆的设计思路完全不同。
5.1 短期记忆的压缩时机和方式
短期记忆就是当前任务的上下文。压缩的时机很关键,太早压缩会丢失信息,太晚压缩会超出窗口。
我的判断标准是上下文使用率达到 60% 时开始压缩。压缩时优先压缩工具调用结果,因为这部分通常最长且信息密度最低。压缩方式是把完整的工具返回结果替换成关键字段摘要。比如一个返回用户信息的工具,完整结果可能有二十个字段,压缩后只保留姓名、ID、状态三个关键字段。
推理过程的压缩要谨慎,因为推理过程包含了模型的决策逻辑,压缩过度会导致模型后续决策失去依据。我一般只压缩早期的推理过程,最近的推理保持完整。
5.2 长期记忆的存储结构设计
长期记忆的存储结构直接决定了检索质量。我的经验是不要存原始对话,要存结构化的事实。
结构化的事实长这样:{类型: "用户偏好", 内容: "偏好简洁回复", 来源: "对话ID", 时间: "2024-01-01"}。这种结构方便检索,也方便更新。如果用户偏好变了,直接更新对应的事实就行,不用去翻原始对话。
存储的粒度也要控制。太细的粒度会导致检索出大量碎片信息,太粗的粒度会导致检索不到具体信息。我一般以"一个独立的事实"为粒度,比如"用户是华东地区"是一个事实,"用户偏好简洁回复"是另一个事实。
5.3 检索注入的精度控制
长期记忆检索出来后,怎么注入上下文也有讲究。注入太多会干扰模型,注入太少又没用。
我的做法是每次检索 top 3 到 top 5 条最相关的事实,并且给每条事实标注相关性分数。注入时按相关性从高到低排列,并且加上一句"以下是与当前任务可能相关的背景信息"。这样模型知道这些是参考信息,不是必须遵守的指令。
检索的相似度阈值也要设。低于阈值的检索结果宁可不注入,因为不相关的信息比没有信息更糟糕。我一般把阈值设在 0.75 左右,具体值要根据实际效果调。
6. 错误处理与容错:让 Agent 崩得起
Agent 系统一定会出错,问题不是会不会错,而是错了之后能不能恢复。这一章讲错误处理和容错设计。
6.1 工具调用失败的重试策略
工具调用失败分两类:可重试和不可重试。可重试的错误包括网络超时、服务限流、临时故障。不可重试的错误包括参数错误、权限不足、资源不存在。
可重试的错误我一般重试两次,每次间隔递增,比如第一次等 1 秒,第二次等 3 秒。重试两次还失败就当作不可重试处理,把错误返回给模型。
不可重试的错误直接返回给模型,但要带上足够的上下文。比如参数错误要说明哪个参数错了、期望什么格式。模型看到具体错误后,通常能自己修正参数重试。
6.2 模型输出异常的兜底方案
模型输出异常包括格式错误、内容为空、输出截断。这些异常要有兜底方案,不能让整个流程崩掉。
格式错误的兜底是重试,但重试时要调整提示词,比如加上"请确保输出是合法的 JSON 格式"。重试两次还失败就降级处理,比如用规则解析或者返回默认值。
内容为空的兜底是重新生成,但要注意可能是模型触发了安全过滤。如果是安全过滤,重新生成也没用,要换一种问法。
输出截断的兜底是让模型继续生成,把已生成的部分作为前缀传回去。但要注意截断可能发生在 JSON 中间,继续生成前要先修复 JSON 结构。
6.3 部分失败的降级处理
一个任务有多个步骤,如果中间某步失败了,不一定要整体失败。可以降级处理,把已完成的部分结果返回给用户,并说明哪部分没完成。
比如一个查询任务包含查订单和查物流两步,查订单成功了但查物流失败了。这时候可以返回订单信息,并告诉用户物流信息暂时查不到。这比整体失败体验好得多。
降级处理的关键是要有清晰的步骤状态跟踪,知道哪些步骤完成了、哪些没完成。我一般用一个步骤列表来跟踪,每步有状态字段:pending、running、done、failed。
7. 从 Demo 到上线:那些必须补的工程课
Demo 能跑通和系统能上线之间,差的是工程化。这一章讲上线前必须补的几门课。
7.1 可观测性:日志、指标、追踪
Agent 系统比普通服务更难调试,因为它的行为是不确定的。所以可观测性尤其重要。
日志要记录每一轮的输入输出、工具调用、决策结果。日志要结构化,方便查询和分析。我一般用 JSON 格式记日志,包含时间戳、任务 ID、轮次、事件类型、详细内容。
指标要监控成功率、平均步数、平均延迟、token 消耗。这些指标能帮你发现系统性问题。比如成功率突然下降,可能是模型更新了或者工具接口变了。
追踪要能还原一次完整执行。每个任务分配一个 trace ID,所有相关日志都带上这个 ID。这样出问题时能快速定位。
7.2 成本控制:token 预算和限流
Agent 的 token 消耗比普通 LLM 调用高一个数量级,因为每次任务要调用多次模型。成本控制是上线前必须考虑的。
我的做法是给每个任务设 token 预算,超过预算就终止。预算根据任务类型设,简单任务 5000 token,复杂任务 20000 token。同时给每个用户设日限额,防止单个用户消耗过多。
限流也很重要。Agent 任务通常比较慢,如果并发太高会拖垮后端。我一般用队列来管理任务,控制并发数,超出的任务排队等待。
7.3 安全边界:工具权限和输入过滤
Agent 能调用工具,这意味着它能对真实系统产生影响。安全边界必须划清楚。
工具权限要最小化。查询类工具和写入类工具要分开,写入类工具要加确认机制。比如删除数据的工具,不能模型说删就删,要经过用户确认。
输入过滤要严格。用户输入可能包含注入攻击,试图让 Agent 执行非预期操作。我一般会对用户输入做清洗,移除可疑的指令性内容,并且在系统提示词里强调"只执行用户明确请求的操作"。
7.4 版本管理:提示词和工具的迭代
Agent 的行为高度依赖提示词和工具定义,这两者的版本管理很重要。
提示词要版本化,每次改动都要记录改了什么、为什么改、效果如何。我一般用 Git 管理提示词文件,每次改动走 code review。
工具定义也要版本化,因为工具的变化会影响模型的选择。工具增删改都要评估对现有任务的影响,最好在测试集上跑一遍再上线。
8. 一些实战中攒下来的经验
最后分享一些零散但实用的经验,都是实际项目中攒下来的。
关于提示词,我的体会是少即是多。新手喜欢写很长的提示词,把所有可能的情况都覆盖到。但实际上提示词越长,模型越容易忽略关键指令。我一般把提示词控制在 500 字以内,只写最核心的规则,细节交给模型自己判断。
关于工具数量,我的经验是控制在 10 个以内。超过 10 个就要考虑分组或者用子 Agent 来管理。工具太多不仅影响选择准确率,还占用大量上下文。
关于测试,我的做法是建一个"回归测试集",包含各种边界情况:正常任务、模糊任务、无法完成的任务、需要多步的任务。每次改动后跑一遍,确保没有退化。
关于上线节奏,我的建议是先小流量灰度,观察一周再全量。Agent 系统的不确定性高,小流量能帮你发现很多测试环境发现不了的问题。
关于用户预期管理,这个很重要但容易被忽略。Agent 不是万能的,要明确告诉用户它能做什么、不能做什么。我在产品里会加一句说明:"我可以帮你查询订单、处理退换货,但无法修改价格或跳过审核流程。"这样用户不会提出超出能力范围的请求,体验反而更好。
关于持续优化,我的做法是每周复盘一次失败案例,看看是模型问题、提示词问题还是工具问题。大部分问题其实出在工具设计和提示词上,模型本身的问题反而少。所以优化重点应该放在工程侧,而不是频繁换模型。
这套东西我前后迭代了大半年,从最开始一个只能查天气的玩具,到现在能处理多步业务流程的系统,中间踩的坑基本都在这篇里了。Agent 工程没有什么银弹,就是把每个环节都想清楚、测到位,然后持续迭代。希望这些经验能帮你少走点弯路。