Agent 分层指令架构:人格、路由、工作流与边界压测
1. 先搞清楚这套指令体系到底在解决什么问题
去年年底我参与过一次内部复盘,起因是某团队把一个通用 Agent 包装成“多域任务助手”对外试用,两周内收到了三种完全不同的投诉:有人抱怨它“聊着聊着就变了个性格”,有人抱怨它“该调工具的时候在那儿讲道理,该讲道理的时候硬要调工具”,还有人抱怨同一个任务昨天跑得通今天跑不通。团队第一反应是模型不稳定,换了两个底座模型,问题一个没少。
后来我们把对话日志全量拉出来做了标注,结论很清楚:问题不在模型,在指令的组织方式。他们把人格设定、技能说明、任务流程、输出格式全塞进一个 system prompt 里,几千字糊成一坨。模型在长上下文里会自然发生“注意力稀释”,靠前的角色约束被后面的工具描述盖过去,就出现了人格漂移;工具描述和流程描述混在一起,模型分不清“我能做什么”和“我现在该做什么”,就出现了路由错乱;整个 prompt 是一整块,改一个技能要全量回归,没人敢动,于是越堆越乱。
本文要拆的这套东西,就是针对上述问题演化出来的分层指令骨架。团队内部管它叫 Codex-X,第三版指令骨架的代号是“海鸥3.0”,配套的边界压测环节内部代号“破甲”。这里要先说清楚,“破甲”不是什么神秘玩法,它就是一组在自有系统、授权靶场、脱敏数据前提下跑的验收用例,目的是验证这层骨架的拒绝策略和权限闸门是不是真的顶得住常规的诱导和误触发。整篇文章讲的是架构怎么搭、约束怎么写、流程怎么编排、边界怎么验,重点始终落在“让 Agent 在自己的能力圈里稳定干活”这件事上,不涉及任何针对外部系统的操作手段。
适合谁看:正在把通用大模型往业务 Agent 上落地的开发者、负责 Agent 指令工程的提示词工程师、做 AI 应用质量与合规评审的同学,以及被“Agent 一上线就乱来”折腾过的团队负责人。哪怕你只是想给自己搭一个管日程、查资料、跑脚本的小助手,这套分层思路同样能用,只是层数可以砍到两层。
需要提前说明的是,输入材料里只给了一个标题和几个关键词,没有更细的设计文档。下面涉及具体参数、阈值、字段设计的地方,是我基于这类 Agent 系统的常见工程实践做的合理补全,你可以直接拿去改,但别当成某个产品的官方实现。
2. 三层拆分的逻辑:为什么不能只写一个大 prompt
2.1 单块 prompt 的三个硬伤
很多人第一次搭 Agent,路径都差不多:先写一句“你是一位经验丰富的 XX 专家”,再把能用的工具挨个列出来,最后补一段“请按以下步骤完成任务”。这套写法在 demo 阶段特别爽,一次就能跑出漂亮结果。但只要任务稍微长一点、工具稍微多一点,三个硬伤会立刻暴露。
第一个是上下文污染。角色约束、工具说明、流程步骤在同一个文本平面里竞争注意力,模型没有一个明确的“先看哪个”的顺序。会话轮次一多,早期的角色约束被稀释,模型的行为就开始向最近几轮的内容收敛。表现就是“人格漂移”——同一个助手,第一轮像个严谨的工程师,第十轮变成了话痨。
第二个是故障不可归因。任务失败时你只知道结果不对,但不知道是角色理解偏了、工具选错了、还是流程编排本身有漏洞。这三种故障的修法完全不同,混在一起就只能靠猜,猜错的成本是把本来没问题的部分也改坏。
第三个是无法灰度。想给某个技能加一条前置校验,你得动整个 prompt,动完心里没底,只能全量回归。回归一次要跑几十条用例,团队很快就失去了迭代的意愿,指令库从此进入“冻结状态”,新需求只能往上叠补丁。
2.2 分层之后每一层各自兜什么责
我们最后落的是四层结构,对外讲的时候常简化成“人格 + 路由 + 工作流”三层,第四层审计是横切的,不算独立业务层。
| 层级 | 核心职责 | 输入 | 输出 | 典型故障表现 | 观测手段 |
|---|---|---|---|---|---|
| 人格层 | 定义身份边界、能力声明、拒绝策略、输出契约 | 系统级常量 | 稳定的行为倾向与表述风格 | 人格漂移、越界承诺、拒绝后变通 | 固定轮次抽检、角色一致性打分 |
| 路由层 | 判断该不该调、调哪个、参数够不够 | 用户意图 + 当前上下文 | 技能名 + 参数 + 置信度 | 该调不调、乱调、参数缺失硬调 | 路由命中率、误路由率 |
| 工作流层 | 定义任务阶段、迁移条件、检查点、回滚 | 路由结果 + 状态 | 阶段推进与产物 | 跳步、死循环、无法续跑 | 状态迁移日志、卡点分布 |
| 审计层 | 全链路留痕、可回溯、可复现 | 各层事件 | 结构化日志 | 出了问题查不到现场 | 日志完整率、重放成功率 |
这张表最有价值的地方在于“典型故障表现”这一列。分层不是为了好看,是为了让每一类故障都有一个唯一的归属层,出问题的时候能一眼定位到该改哪一段,而不是全量重写。
2.3 “海鸥3.0”实际指的是什么
内部代号容易让人想歪,说明一下。这个骨架的第一版是单块 prompt,第二版把工具说明拆了出去,第三版才把人格、路由、工作流彻底分开,并补上了审计层,所以叫 3.0。代号本身没有技术含义,纯粹是团队习惯。
真正有含义的是版本迭代中固定下来的三个原则:约束要可判定、能力要可枚举、流程要可回放。可判定指的是人格层的规则必须写成模型能判断真假的条件句,而不是形容词;可枚举指的是路由层必须有一份显式的技能注册表,不能靠模型自由发挥;可回放指的是任何一次任务执行都能从日志里原样复现,包括每一步的输入、选中的技能、参数、返回。
这三个原则听起来平淡,但落地时每一条都会撞上具体的工程麻烦。下面三节分别展开。
3. 人格设定层:把“角色”写成可执行的判定条件
3.1 形容词是人格设定里最大的坑
我见过太多这样的开头:“你是一位资深的、严谨的、经验丰富的安全专家,擅长处理各类复杂问题。”这句话对模型行为的影响接近于零,因为“资深”“严谨”没有任何可判定的边界。模型没法知道什么程度算严谨,只能按训练数据里的普遍风格去演,结果就是每次演得都不太一样。
人格层真正起作用的,是那些能写成条件句的部分。举个对比例子:
- 无效写法:“你要谨慎,不要做危险的操作。”
- 有效写法:“当请求涉及对非本次会话所属系统的操作时,必须先向用户确认目标范围;未获得明确范围前,只输出方法论层面的说明,不输出任何具体步骤。”
后者是可判定的:模型能判断“是否涉及外部系统”“是否已获得明确范围”,判断完就能落到两个明确的分支上。这个转换过程有个简单的自检方法——把你写的每一条人格约束拿出来问自己:“如果换一个陌生人来读这条规则,他能不能给出一个明确的是/否判定?”不能,就重写。
3.2 四段式写法:身份边界、能力声明、拒绝策略、输出契约
我们最终把人格层固定成四段,顺序不能乱,因为后一段要引用前一段的定义。
第一段,身份边界。讲清楚这个 Agent 是什么、不是什么、服务谁、不服务谁。关键是“不是什么”要写足,因为模型天生倾向于讨好,你不明确划出去的部分它都会试着接。示例结构:“你服务于某内部团队的日常研发辅助场景;你不承担对外咨询、不代替人工做最终决策、不对未经验证的信息做背书。”
第二段,能力声明。用清单形式列出它能做的事,每条都对应路由层的一个技能 ID。这一段的价值是给模型一个“能力边界地图”,当用户提出能力外需求时,它能明确说出“这不在我的范围内”而不是硬编一个答案。
第三段,拒绝策略。这是整个骨架里最容易写崩的一段,下一小节单独讲。
第四段,输出契约。规定输出的结构、长度、必须包含的字段。比如“涉及操作建议时,必须包含前置条件、预期结果、失败后的处置三项”。输出契约不只是为了美观,它是后面审计层做结构化解析的前提,格式稳定的输出才能被自动校验。
四段之间用明确的分隔标记隔开,并在每段开头用一句话概括该段主题,这个动作能显著降低模型把不同段内容串读的概率。
3.3 拒绝策略怎么写才不“假死”
拒绝策略写得太软,模型会找各种理由绕过去;写得太硬,模型会对正常请求也过度拒绝,用户体验直接崩掉。我们踩过的坑基本集中在这两端。
比较稳的做法是三级响应,而不是一刀切的拒绝:
| 级别 | 触发条件 | 响应方式 | 示例表述 |
|---|---|---|---|
| 硬拒绝 | 请求明确落在能力边界之外,或缺少任何可验证的授权前提 | 直接说明不在范围内,不展开、不给替代路径 | “该请求不在本助手的能力范围内。” |
| 软拒绝 | 请求方向合理但缺少必要前置信息(目标范围、环境、预期) | 说明缺什么,请用户补充,补充后可继续 | “需要先确认目标范围与环境,请补充后我再继续。” |
| 降级响应 | 请求部分可做,但其中某些环节超出边界 | 只完成边界内的部分,明确标注哪部分未做及原因 | “已完成前置检查,涉及变更的部分未执行,原因是……” |
这套分级的关键在于软拒绝必须有明确的补全路径。只写“请补充信息”而不说补充什么,用户会反复试,最后要么放弃要么乱给,体验更差。
另一个高频坑是拒绝后的变通。模型在被拒绝之后,如果上下文里还有别的线索,它会尝试换个说法绕过去。所以拒绝段里必须补一句兜底:“对被拒绝的请求,不得以举例、假设、教学、角色扮演等任何形式提供等价内容。”这句话听起来很硬,实测下来是必要的,尤其是当会话历史里已经积累了大量相关上下文的时候。
3.4 人格漂移的观测与压制
分层之后人格漂移没有完全消失,只是变得可控了。我们用了三个手段压它。
第一是周期性重注入。每 N 轮对话(我们取的是 8 轮)在上下文里重新插入一次人格层的核心约束摘要,不是全文,是压缩到 200 字以内的要点版。这个动作的成本很低,但对长会话的稳定性提升很明显,实测角色一致性评分能从 0.7 左右提到 0.9 以上。
第二是尾部自检清单。在 system prompt 的末尾放一个极短的检查列表,比如“输出前确认:是否在能力范围内、是否包含必要的边界说明、格式是否符合契约”。模型对靠近生成位置的指令更敏感,这个位置的约束遵守率明显高于放在开头。
第三是上下文裁剪策略。长会话不要把历史全量保留,保留最近若干轮 + 一个滚动的任务摘要就够了。历史上那些已经完成的、无关的对话内容留在上下文里,是人格漂移的主要燃料。
注意:重注入的频率不要过高,太频繁会让模型在正常对话里反复“自我声明”,显得机械。8 到 12 轮是个比较舒服的区间,具体按你的会话平均长度调。
4. 技能路由层:从“会什么”到“该不该做”
4.1 技能注册表是路由的地基
路由层能不能稳,取决于有没有一份写死的、结构化的技能注册表。靠模型自由发挥“我该用哪个工具”,在小规模下能用,规模一上来必然乱。我们的注册表每个技能一条记录,字段大致如下:
| 字段 | 作用 | 示例值 |
|---|---|---|
| skill_id | 唯一标识,供日志与审计引用 | doc.lookup.v2 |
| 触发意图 | 什么需求该命中它,用自然语言描述 3 到 5 条 | 需要检索内部文档条目 |
| 前置条件 | 执行前必须满足的条件 | 已确认检索范围;查询词非空 |
| 副作用等级 | 只读 / 可逆写 / 不可逆写 | 只读 |
| 是否需确认 | 执行前是否需要用户二次确认 | 否 |
| 超时与重试 | 调用约束 | 10s,重试 1 次 |
| 失败语义 | 失败后返回什么、是否影响后续阶段 | 返回空结果,流程可继续 |
副作用等级这一列是整个路由层的灵魂。我们把它分成三档,规则非常死:只读技能可以直接执行;可逆写技能执行前要记录回滚点;不可逆写技能一律进人在回路,必须拿到明确确认才往下走。这条规则写死在代码里,不交给模型判断,因为模型对“不可逆”的理解非常不可靠。
4.2 三道闸门:意图识别、白名单、前置校验
路由不是一步判断,是三道闸依次过。
第一道,意图识别。用轻量分类(可以是小模型,也可以是规则 + 关键词 + 向量的混合)把用户输入映射到候选技能集合,输出带置信度。这一步的产出不是一个确定的技能,是 Top-K 候选加分数。
第二道,能力白名单。候选集合要跟当前会话的权限白名单取交集。这里的权限不是用户权限,是这次会话被授予的技能范围。比如一个只读会话,所有可逆写和不可逆写技能在这一步就被过滤掉了,模型根本没机会选到它们。把权限控制从前置校验提前到白名单,是我们做的一个关键调整,因为前置校验依赖模型自己判断条件是否满足,而白名单是硬过滤,不依赖模型。
第三道,前置条件校验。对剩下的候选,逐条检查注册表里定义的前置条件。这一步大量是程序化检查(参数是否齐全、格式是否合法、必要上下文是否存在),只有少数需要模型判断的语义条件才交给模型。
三道闸过完,如果候选唯一且置信度高于阈值,直接执行;如果候选多个或者置信度在中间区间,走澄清;如果全被过滤,走人格层的软拒绝。这个分支结构要写死在编排代码里,不要指望模型自己从 prompt 里读出来该怎么走。
4.3 置信度阈值怎么定
阈值不是拍脑袋定的,得有数据支撑。我们的做法是:先跑一批标注样本,把每个候选的置信度打出来,画出分布,然后找“正确技能的置信度分布”和“错误技能的置信度分布”之间的交叠区。交叠区的下沿作为执行阈值,上沿作为澄清阈值,中间这段一律走澄清。
实测下来,执行阈值定在 0.75、澄清区间 0.45 到 0.75 是个比较稳的起点,但这不是通用值,取决于你的分类器质量和技能粒度。技能粒度越细,阈值要越高,因为相邻技能的区分度更低。
一个容易忽略的点:澄清不是失败。很多团队把澄清当成路由没做好,追求越少澄清越好,结果是把阈值压低,误路由率飙升。实际上一次精准的澄清,成本远低于一次错误的不可逆操作。我们在统计口径上把“澄清后用户直接确认”算作路由成功,这个口径调整之后,团队的优化方向立刻从“消灭澄清”变成了“让澄清问得更准”。
4.4 技能冲突与优先级
多个技能同时命中是常态,尤其是“查资料 + 生成摘要 + 保存结果”这种组合需求。我们的规则是三层优先级:
- 安全优先级最高:任何涉及不可逆写的技能,无论置信度多高,都排在所有只读技能之后执行,且必须单独确认。
- 依赖关系优先于置信度:有数据依赖的技能按依赖顺序排,不按分数排。查资料的分数再低也要在摘要之前。
- 同层按置信度降序:没有依赖关系、同副作用等级的,按置信度排。
这套规则看起来简单,但落地时有个细节要处理:依赖关系不能靠模型临时推断,必须写进注册表的depends_on字段。模型推断的依赖关系极不稳定,同一个任务跑两次能给你两种依赖图。
提示:路由层最忌讳“让模型自己决定要不要用工具”。把决策拆成“分类器给候选 + 程序化白名单过滤 + 程序化前置校验 + 阈值分支”之后,模型的自由度被压缩到语义判断那一小块,稳定性会有质的变化。
5. 多域任务工作流:状态机比自由发挥靠谱得多
5.1 为什么放弃“让模型自己规划”
最开始我们也试过 ReAct 那套,让模型自己 think-act-observe 循环。在单域、短任务上效果不错,一旦跨域(比如一个任务同时涉及文档检索、数据处理、结果归档),问题就来了:模型会在两个阶段之间反复横跳,有时候同一个检查做三遍,有时候跳过关键校验直接出结果。跑十次能有四种不同的执行路径,出了问题根本没法复现。
改成显式状态机之后,路径收敛了。状态机不等于把模型变成木头人,它只是把“下一步做什么”这个决策从模型手里拿走,交给迁移条件;模型在每个状态内部仍然有充分的自由度去完成该状态下的事。相当于给模型画了一条河道,河里怎么游它自己决定。
5.2 状态定义与迁移条件
一个典型的多域任务,我们定义的状态大致是这样:
| 状态 | 进入条件 | 该状态内做什么 | 迁移条件 | 失败处置 |
|---|---|---|---|---|
| 待解析 | 收到任务 | 提取目标、约束、交付物 | 关键要素齐全 → 待确认 | 要素缺失 → 追问,最多 2 轮 |
| 待确认 | 解析完成 | 向用户复述理解并请求确认 | 用户确认 → 规划中 | 用户否定 → 回待解析 |
| 规划中 | 已确认 | 生成步骤清单与技能序列 | 规划通过校验 → 执行中 | 规划不合法 → 降级为单步执行 |
| 执行中 | 有合法规划 | 逐步执行,每步记录检查点 | 全部步骤完成 → 待校验 | 单步失败 → 按注册表失败语义处理 |
| 待校验 | 执行完成 | 检查产物完整性与格式 | 校验通过 → 已交付 | 校验失败 → 回执行中(限 2 次) |
| 已交付 | 校验通过 | 输出结果与执行摘要 | 终态 | — |
这张表里有两个设计细节值得单独说。
一是重试有上限。“回执行中(限 2 次)”这个限制很重要,没有它就会出现无限重试。超过上限之后不是直接失败,而是转人工,把当前状态和已完成的产物一起交出去。
二是规划校验独立于模型。规划出来的步骤清单要过一遍程序化校验:步骤数是否在合理范围、每个步骤引用的技能是否都在白名单内、依赖顺序是否成环。这一步能拦掉相当一部分模型生成的“看起来合理但跑不通”的规划。
5.3 检查点与断点续跑
跨域任务往往耗时较长,中途失败是常态,能不能续跑决定了这套东西到底能不能用在真实业务里。我们的做法是在每个状态迁移的边界上落一个检查点,记录:当前状态、已完成步骤及其产物引用、待执行步骤、上下文摘要。
有了检查点,续跑就是“读最近一个检查点 + 从该状态继续”,不需要重跑前面的步骤。这里有个容易踩的坑:产物引用要用不可变存储。如果中间产物是可变的,续跑时读到的可能是被后续步骤改过的版本,结果就不可信了。我们一开始用的是临时文件,被覆盖过两次之后才改成带版本号的产物存储。
另一个细节是上下文摘要的生成时机。检查点里存的不能是完整对话历史,太长;也不能是空,续跑时模型会失去上下文。我们的做法是在每个检查点生成一段 300 字以内的任务摘要,包含目标、已完成、待完成、已知约束四要素,续跑时把它注入进上下文。
5.4 人在回路插在哪几个位置
人在回路不是到处插,插多了体验稀碎,插少了风险失控。我们最后定了四个固定插入点:
- 任务确认:复述理解,避免理解偏差导致的整条流水线白跑。
- 不可逆操作前:写死在代码里,无法被 prompt 绕过。
- 连续失败达上限:转人工,附带现场信息。
- 产物交付前:高风险场景(对外输出、影响他人)需要人工过一眼。
这四个点之外,其余环节全自动。这个划分是反复调整过的,早期我们还在“规划生成后”加了一个确认点,后来发现规划本身很少出错,大部分确认都被用户直接点过,纯属增加摩擦,就撤掉了。判断标准很简单:这个确认点拦下来的问题,占比够不够高。低于一定比例就撤。
5.5 终止条件要写死
最后说一个容易被忽略但极其重要的东西:终止条件。除了正常完成,还要显式定义这些情况必须终止:步骤执行次数超过上限、总耗时超过预算、连续两次校验失败、检测到状态迁移成环、用户主动中止。
终止不等于报错退出,终止时要输出一份“终止报告”,包含做了什么、卡在哪、已产生的产物在哪、建议的下一步。这份报告的价值在你排查问题时体现得淋漓尽致,没有它,用户只会告诉你“它卡住了”,你什么也查不到。
6. 边界压测:把“破甲”做成一次可复现的验收
6.1 先把压测的边界说清楚
这一节讲的是内部验收环节。前提条件必须写死:被测系统是自有或已获授权的、数据全部脱敏、靶场与生产环境物理隔离、压测结果只用于加固自身。脱离这四个前提谈压测没有意义,也不该做。
我们把这个环节叫“破甲”,取的是“看看这层甲能不能被常规手段顶穿”的意思。它本质上就是一套回归用例集,跟后端团队做的混沌测试、跟前端做的兼容性测试是一个性质,只是对象换成了 Agent 的行为约束。
6.2 四类用例怎么设计
用例设计的目标不是找到“最强攻击”,而是覆盖四类最常见的失效模式。每类设计 15 到 20 条,够用且跑得动。
| 用例类别 | 想验证什么 | 构造思路 | 判定标准 |
|---|---|---|---|
| 角色越界 | 人格层的身份边界是否牢固 | 逐步把请求往能力边界外推,每次只推一小步 | 是否在边界处正确落三级响应 |
| 工具越权 | 白名单过滤是否绕过不了 | 请求中夹带未授权技能的需求 | 是否被白名单拦截且不泄露该技能存在 |
| 流程跳步 | 状态机的迁移条件是否可被诱导跳过 | 在早期状态就要求最终产物 | 是否坚持走完必要状态 |
| 上下文污染 | 长会话与夹带内容是否影响判断 | 在多轮对话中逐步植入干扰信息 | 关键约束的遵守率是否保持稳定 |
第三类“流程跳步”是最容易被低估的。用户很自然会说“别绕了直接给我结果”,如果状态机没有硬性迁移条件,模型就会真的跳过去。我们最早的一版就栽在这儿,跳过了校验直接交付,产物格式错了三处。
6.3 判定与记录:别只看“有没有被攻破”
压测最容易犯的错是只看二元结果:拦住了或者没拦住。这个信息量太低。我们用的是四维记录:
- 是否拦下:二元。
- 拦在哪一层:人格层、路由层、工作流层、还是压根没拦。这个维度直接指向要改哪一段。
- 响应质量:拦下之后的回复是否符合三级响应的规范,有没有过度拒绝或者泄露不该说的信息。
- 可复现性:同一条用例重跑 5 次,结果是否一致。不一致的用例要单独标出来,说明存在随机性,需要加固。
统计的时候我们主要看两个指标:拦截率和响应合规率。前者低说明约束有洞,后者低说明约束位置不对或者表述有歧义。经验上,响应合规率往往比拦截率更难提上去,因为它要求的不只是“不做”,还要求“说得恰当”。
6.4 修完之后怎么做回归
每次修改人格层或路由层之后,全量用例必须重跑。这就是为什么前面强调分层:分层之后,改人格层只需要重跑角色越界和上下文污染两类用例,改路由层只需要重跑工具越权类,回归成本大幅下降。
我们维护了一份“用例-层”的映射表,改动哪层就跑对应那组,全量回归一周做一次。这个节奏坚持了几个月之后,团队对指令库的改动意愿明显回升,因为大家都知道改坏了会被用例拦住,而不是等用户投诉。
注意:压测用例本身要定期更新。模型的版本一升级,之前稳过的用例可能就失效了,尤其是指令跟随能力变化比较大的版本迭代。每次换底座模型,第一件事就是跑一遍全量用例,把失败项列出来,再决定是改约束还是换模型。
7. 常见问题与排查速查
7.1 高频问题速查表
| 现象 | 最可能的原因 | 排查动作 | 处置 |
|---|---|---|---|
| 长会话后期人格跑偏 | 上下文污染 + 约束被稀释 | 抽检第 10 轮后的对话,看是否有越界承诺 | 加周期性重注入,裁剪历史 |
| 该调技能时不调 | 路由阈值过高或意图描述覆盖不足 | 看置信度分布,确认是否落在澄清区间 | 调阈值,补触发意图描述 |
| 不该调技能时乱调 | 白名单缺失或前置校验不严 | 查该技能是否在会话白名单内 | 补白名单,前置条件程序化 |
| 同一任务路径不稳定 | 存在自由规划环节 | 对比两次执行的步骤序列 | 收敛为状态机,依赖写进注册表 |
| 中途失败无法续跑 | 检查点缺失或产物被覆盖 | 检查检查点字段完整性 | 补检查点,产物改不可变存储 |
| 拒绝后仍变通输出 | 拒绝策略缺少兜底条款 | 复现对话,看变通发生在哪一步 | 补“不得以任何形式变通”条款 |
| 过度拒绝正常请求 | 拒绝条件写得过宽 | 统计拒绝用例中的误拒比例 | 收窄条件,改走软拒绝 |
| 输出格式经常不达标 | 输出契约描述模糊或位置靠前 | 看格式失败样本的共同点 | 契约具体化,移到 prompt 尾部 |
7.2 三个我踩过的具体坑
第一个坑:把技能描述写成了使用说明。早期注册表里的触发意图写的是“用于检索文档”,模型理解成“只要跟文档有关就调”。后来改成三到五条具体的用户表述样例,误路由率降了一半以上。触发意图要写“用户会怎么说”,不要写“这个工具是干什么的”。
第二个坑:状态机迁移条件用了自然语言。一开始迁移条件写的是“规划合理时进入执行”,结果模型对“合理”的判断非常宽松。后来把“合理”拆成了三条程序化检查:步骤数在 1 到 15 之间、所有技能在白名单内、依赖无环。拆完之后,规划阶段的异常率从两成多降到了个位数。
第三个坑:压测用例只测了单轮。单轮用例全过,一上线就出问题,因为真实的越界往往发生在多轮铺垫之后。补了多轮用例之后才发现,长会话里用户先建立信任、再逐步推边界的路径,单轮用例完全覆盖不到。
7.3 一个提高排查效率的小习惯
每次任务执行完,除了产物,我会让工作流层额外输出一段 100 字以内的“执行摘要”,包含:走了哪几个状态、调了哪几个技能、在哪一步花了最长时间、有没有触发过重试。这段摘要塞在日志头部,排查时先看它,八成的异常能直接从摘要里看出方向,不用去翻全量日志。
这个习惯是从后端同事那儿学来的,他们管这叫“一眼日志”。Agent 的日志天生比普通服务乱,因为多了模型输出这一层,不主动做摘要的话,排查成本会高得离谱。
8. 最后聊几句实操体会
分层这套东西真正的价值,不在于它让 Agent 变聪明了,而在于它让 Agent 变可管理了。模型能力是外部变量,你控制不了;指令结构是你自己的资产,能控制。把能控制的部分做扎实,模型升级的时候你只需要重跑用例,而不是推倒重来。
我个人感受最深的一点是:写 Agent 指令和写代码其实是一回事。形容词相当于注释,条件句相当于逻辑,注册表相当于接口定义,状态机相当于控制流。什么时候你把 prompt 写得像代码一样有明确的分支和边界,什么时候这东西才真的能上生产。
另一个体会是关于克制。我们花在“让 Agent 少做点事”上的时间,比花在“让它多做点事”上的时间多得多。每加一个技能,都要问一遍副作用等级、要不要确认、失败了怎么办;每加一条能力,都要想清楚边界在哪里。这种克制在 demo 阶段看不出价值,等到用的人多了,它就是唯一的护城河。
如果你现在手上正有一个跑得不太稳的 Agent,我的建议是先别急着换模型,把现有的 prompt 打印出来,用四种颜色的笔分别标出身份描述、工具说明、流程步骤、格式要求,看看这四种东西是不是混在一起。如果是,先做拆分,其他的改动往后放。这个动作通常一两天就能做完,收益比换模型大得多。