homework_agent stage0
目录
- Q1 · 我的场景为什么需要 Agent 而不是 workflow
- Q2 · 分支只有 5 种、条件难描述,选哪个
- Q3 · 8% 的请求分不进任何 label
- Q4 · 线上怎么发现「分类错了」
- Q5 · Context Engineering vs Prompt Engineering
- Q6 · 第 30 轮、180k 快满、任务没完成
- Q7 · 8000 行日志进上下文前怎么处理
- Q8 · 工具报错该怎么写
- Q9 · 30 个工具,选错 + 越权,怎么解
- 附录 A · Harness 各层分别解决什么问题
- 附录 B · 现在的问题
- 附录 C · 速查卡片
Q1 · 我的场景为什么需要 Agent 而不是 workflow
判据的轴:不是「复杂 / 简单」,是「路径可否预先枚举」
反例直接击穿复杂度这条轴:
- 200 步 ETL 管道 → 极复杂,但每步固定 → workflow,用 agent 是灾难
- 「查这个报错为什么发生」→ 一句话,但要查几层事先不知道 → agent
真正的三条判据(成立其一即可,但必须落到具体场景):
| 判据 | 含义 | 检验方式 |
|---|---|---|
| 路径不可预先枚举 | 决策发生在 run time 而非 design time | 能画出完整流程图吗?画得出 → workflow |
| 需要基于反馈自我修正 | 存在可验证信号(报错、测试失败、返回空) | 有没有一个「对错」能反馈回模型? |
| 工具调用次数/顺序是变量 | 不是固定 N 步 | 事先能确定调几次工具吗? |
必须报出的代价
token 成倍 · 延迟不可控 · 非确定性导致无法回归测试 · 误差沿轨迹累积放大 · 故障难定位。
结论形态
成熟架构几乎都是「workflow 骨架 + 局部 agent」,而不是全盘交给 agent 自由发挥。 agent 只挂在真正需要 run-time 决策的那一层。
Q2 · 分支只有 5 种、条件难描述,选哪个
答案:LLM 分类器 + 固定 workflow。不是 agent。
陷阱在哪
「判断条件用自然语言描述很麻烦」这句话,只排除了 if-else 硬编码,没有排除 LLM 分类器——语义模糊恰恰是 LLM 分类器的主场,不是它的短板。
中间选项原封不动地还在。选型题永远至少三个候选:
硬编码规则 | LLM 单次调用(分类/路由) | agent
排除任何一个都要给理由。
真正的分界线(两条,都跟「模糊不模糊」无关)
- 分支集合是否封闭。 已知且穷尽 = 路由问题(routing),不是自主决策问题。agent 的价值在于处理你事先列不出来的情况。
- 是否需要多跳回环。 「分类一次 → 走完 → 结束」= 单跳。「走了 3 号才发现该走 1 号」= 才需要 agent。
口诀:封闭 + 单跳 → LLM 路由器;开放 或 多跳 → agent。
Q3 · 8% 的请求分不进任何 label
正确顺序是三段。直接给方案是「不看化验单就开处方」。
① 止血(当天可上线,优先级最高)
加第 6 个出口「不确定 / 其他」+ 置信度阈值 → 转人工(abstention & escalation)。
三个好处,别的方案都没有:
- 成本近零,一天上线
- 立刻止损,8% 不再产出错误结果
- 人工队列自动变成数据采集器——它不只是一个方案,它是让你有资格做后续选择的前置动作
工程原则(fail-safe):不确定时的正确行为是停下来交给人,不是猜一个。
② 诊断(用那 8% 说话)
- 聚成 1~2 个簇 → 不是模型问题,是分类体系缺类目 → 加 label + 配套 handler,架构一行不动
- 长尾、每类几条 → 穷举无望 → 才进入方案升级讨论
同样是 8%,这两种情况的处方完全相反。
③ 分流处置
| 判据 | 加 label / 修分类 | 升级为 agent |
|---|---|---|
| 8% 的分布 | 聚簇 | 长尾 |
| 是否需多跳 | 单跳够 | 查了才知道属于哪类 |
| 错误代价 | 低,可事后补 | 高,须当场兜住 |
| 趋势 | 稳定 8% | 持续上涨(世界在变) |
| 单位经济 | 高频低价值 → 转人工更划算 | 低频高价值 → 扛得起 agent |
最后一行最容易被忽略:高频低价值流量,转人工可能永远比 agent 便宜。
两个具体错误提醒
- 「改进 label 质量」是模糊表述。 改描述文本(5 分钟)/ 改边界(要重跑评测)/ 改分类体系(要写新 handler),代价差三个量级。不带动作的表述等于没说。
- 「让 agent 运行时创造新 label」有硬伤。 新 label 没有对应 handler,落地就是个字符串,什么也执行不了。分类的价值不在起名字,在于名字后面挂着一个能跑的动作。长期还会 taxonomy drift:三个月后 200 个语义重叠的 label,评测体系报废。
收尾
全程保留「分类结果 ↔ 人工修正」对照,沉淀为回归评测集。
Q4 · 线上怎么发现「分类错了」
题眼:线上系统没有 ground truth
没人告诉你对错。所有方法都在回答同一个问题——在没有标准答案的情况下,如何构造标准答案的替身。
而且置信度不可信:模型把 case 硬塞进 3 号分支时,往往还给出 0.9。
前置:可观测性是设计出来的,不是事后捞的
日志至少要记这些,这一条不做后面全部无效:
input_text / label / confidence /
handler 执行结果(成功·报错·空返回)/
是否被人工改派 → 改派后的 label /
处理时长 / 用户是否二次进线 / 最终结局
关键是后半截:记录世界的反应,而不是模型的说法。
信号一 · 隐式反馈(价值最高)
分错的工单会在系统里留下物理痕迹,不需要任何人专门标注:
| 痕迹 | 为什么是错误信号 |
|---|---|
| 人工改派率 | 3 号队列的人手动转到 1 号 —— 一条免费且干净的标注 |
| handler 报错 / 空返回 | 分支代码拿不到预期字段 |
| 处理时长异常 | 分错的工单会滞留、绕圈 |
| 用户二次进线 / 追问 | 第一次没解决 |
| 撤销 / 回退 / 升级投诉 | 强负信号 |
通用原则:在系统里找那些「人为了修正错误而不得不做的动作」,把它们埋点。 把人工改派记下来,等于让全体客服在无意识中给你标了一整年数据。
信号二 · 分布监控(对置信度的合法救回)
单条置信度不可信,但置信度的分布变化可信。
- label 分布漂移:3 号昨天 12%,今天 31%
- 输入分布漂移 / OOD 检测:向量化后监控远离已知簇的新聚落 ← Q3 那 8% 会在这露头
- 置信度分布整体左移
特点:发现快(小时级),但只说「有事发生」,不说「哪条错了」。
信号三 · 主动采样(唯一无偏的锚点)
前两类都有偏——只覆盖「被人发现的错误」,静悄悄错掉的看不见。
分层随机抽样,每天按 label 抽 N 条送人工标注。作用不只是测准确率,更是校准前两类信号:让你知道「改派率 5%」对应真实错误率是 8% 还是 20%。
便宜信号量大但有偏,人工标注无偏但量小 → 用少量人工标注去标定大量隐式信号的可信度。 这是整套体系的支点。
信号四 · 分歧检测(成本效率最高)
第二个独立判断者跑同样输入,shadow mode 只记录不生效(换 prompt / 换模型 / LLM-as-judge)。两者不一致的样本优先送人工。
把人工预算从「随机撒在 100% 流量」集中到「最可能出错的 5%」,同样人力发现数倍错误。
⚠️ judge 必须独立。同源模型犯同源错误——它给 0.9 置信的地方,自我复核时依然说对。
反馈延迟分层
| 信号 | 尺度 | 用途 |
|---|---|---|
| 改派、报错 | 分钟级 | 实时告警 |
| 二次进线、满意度 | 天级 | 趋势监控 |
| 人工抽样标注 | 周级 | 版本评估与回归 |
别拿慢信号做告警,也别拿快信号下结论。
Q5 · Context Engineering vs Prompt Engineering
最大的误解:CE ≠ PE + 更多东西
如果 CE 只是「往窗口里多塞几样」,它不配成为独立学科——多塞东西不需要工程。
CE 存在的前提恰恰相反:上下文窗口是有限、且边际收益递减的稀缺资源。
- 塞得越多,注意力越稀释(attention dilution)
- 中间位置的信息会被读丢(lost in the middle)
- 无关内容不是中性的,它会主动干扰决策
所以 CE 有一大半工作量花在「不放什么」和「什么时候撤掉」上。
PE 问:这段话怎么写才清楚。 CE 问:在这一轮、这有限的 180k token 里,哪些信息必须在场,哪些必须滚出去。
另一个误解:PE 的对象不是用户输入
PE 的主要对象是开发者写的那一层:system prompt、指令模板、few-shot、输出格式约束、角色设定。用户输入是 run time 的变量,不是你能「工程」的东西。搞混会导致你去优化控制不了的部分,放过真正能控制的模板层。
本质轴:静态 vs 动态
| Prompt Engineering | Context Engineering | |
|---|---|---|
| 对象 | 一段字符串 | 整个上下文窗口的状态 |
| 时机 | design time,写好即固定 | run time,每轮重新组装 |
| 目标 | 让模型理解意图 | 让正确的信息在正确的时刻在场 |
| 单位 | 一次调用 | 一条轨迹(N 轮) |
| 手段 | 措辞、示例、CoT、格式约束 | 检索、压缩、隔离、卸载、排序、缓存 |
| 失败模式 | 指令歧义、格式不符 | 关键信息被淹没 / 污染 / 截断 |
| 指标 | 单次输出质量 | 全轨迹成功率 + token 成本 + 延迟 |
PE 是写作,CE 是信息物流调度。 PE 是写一封措辞讲究的信;CE 是给一个员工设计工位——桌上摆哪几份、哪些进档案柜、什么时候归档、交接班怎么写交接单。
CE 是被 agent 逼出来的
单次问答里,上下文是你写进去的,写完就定了。agent 循环里,上下文是自己长出来的:
第 1 轮:system + user → 2k
第 5 轮:+ 4 次工具调用与返回 → 25k
第 20 轮:+ 报错、重试、半成品结论 → 130k
第 30 轮:爆窗 / 或已被自己早期的错误结论带偏
上下文变成了带正反馈的累积系统,所以需要的是一套策略,而不是一次性的书写。
这就是为什么 2023 谈 prompt、之后开始谈 context——不是术语升级,是上下文从「输入」变成了「状态」。
六类核心动作
| 动作 | 做什么 |
|---|---|
| 选择 / 检索 | 只召回本轮需要的 |
| 压缩 / 摘要 | 长历史折叠成结论;工具返回只留关键字段 |
| 隔离 | 脏活丢给 sub-agent,主上下文只收结论 |
| 卸载 | 中间产物写文件,上下文里只留路径 |
| 排序 / 位置 | 关键指令放头尾,规避 lost-in-the-middle |
| 缓存友好 | 前缀稳定、append-only,别在开头插变量 |
最后一条最能区分「读过」和「做过」:在 system prompt 里塞一个时间戳,会让整条 KV cache 每轮全部失效,成本和延迟直接翻数倍。 这类问题在 PE 的框架里根本不存在。
四种失败模式(要能叫出名字)
- 污染 Poisoning — 错误结论进了历史,被反复引用、越来越像事实
- 分心 Distraction — 历史太长,压过了指令
- 混淆 Confusion — 塞了不相关的工具定义
- 冲突 Clash — 前后信息互相矛盾
代价
压缩会丢信息 · 检索会漏召回 · 隔离会断上下文 · 缓存友好会牺牲灵活性。
PE 没有被取代,它降级成了 CE 的一个子模块。
Q6 · 第 30 轮、180k 快满、任务没完成
元答案(比任何方案都重要)
跑到这一步本身就是失败信号。你在这个位置做的一切都是急救。
真正的答案是不要走到这里:
- 从第 1 轮就有 token 预算意识,工具返回在入口处截断
- 从第 1 轮就写状态文件,而不是第 30 轮才想起压缩
- 设轮次上限与检查点:第 10 轮强制自检「我在朝目标前进吗」
- 任务分解:一开始拆成 3 个 60k 子任务,而不是 1 个 180k
上下文管理是设计问题,不是抢救问题。到了要 compact 的时候,你已经输掉一半了。 (与 Q4「可观测性是设计出来的」是同一句话的两个版本。)
方案一 · Compact
四个工程难点:
| 追问 | 答案 |
|---|---|
| 谁来做摘要 | 不要让主 agent 边干边总结(污染主上下文、且它有立场)。用独立的一次调用,可用更便宜的模型,给定固定 schema |
| 摘要放哪 | 替换被压的那段,但 system prompt 与最近 N 轮必须原样保留。头尾注意力最强,别拿来放摘要 |
| 原文能否找回 | 必须能。原文落盘,摘要里带指针(路径 / 轮次号 / message id),配 read_history(ref) 工具 |
| 引用了摘要外的细节 | 这就是上一条存在的理由。没有指针 → 模型只能编,而且编得很自信 |
具体丢什么(比「一些细节」精确):
- 精确值:ID、路径、变量名、版本号、数值、报错原文
- 中间推理过程 → 无法复盘,也无法发现早期推错
- 不可逆:有损压缩多次叠加,第三次 compact 后常已失真
- 错误会被压实:早期错误结论进了摘要,就从「一句话」升级成「既定事实」,后面永远纠不回来 —— 上下文污染的固化
方案二 · 把历史做成 RAG(本题最危险的答案)
它听起来最工程化,在 agent 场景里却是重灾区。丢五类东西:
1. 时序与因果(最致命)
检索按语义相似度返回,不按时间顺序返回。而轨迹的本质是因果链:
第 8 轮:试了方案 A
第 9 轮:A 报错,放弃
第 12 轮:改用方案 B
第 30 轮检索「方案 A」,很可能只召回第 8 轮(语义最相关),第 9 轮的失败结论没被召回 → agent 兴高采烈又去试了一遍 A → 看起来很合理的死循环。
2. 否定信息几乎检索不到
「我已经试过什么、排除了什么」是轨迹里最宝贵的信息,但它:弥散、不集中;语义特征弱(「这条路走不通」和「走得通」在向量空间里挨着);你没法写出一个 query 去检索「我还没做过的事」。这是 RAG 的结构性短板,不是调参能解决的。
3. 需要 query,而 query 需要「知道自己不知道什么」
agent 面对的是 unknown unknowns:它不知道第 11 轮有个关键约束,所以它不会去检索那个约束。检索解决「我知道要找什么但记不清细节」,解决不了「我压根忘了有这回事」。
4. 隐式状态不会被命中
用户偏好(「别用第三方库」)、当前目标、已达成的中间约定、输出风格、角色设定——这些不出现在任何 query 里,一旦掉出上下文,行为立刻漂移,而且静悄悄,你只觉得「这 agent 后半程怎么变笨了」。
5. 全局性问题天然做不了
「我目前总共完成了哪几项」需要全量,top-k 永远答不了。任何聚合、计数、去重,RAG 都会给你一个自信的错误答案。
RAG 适合检索外部静态知识库,不适合检索自己的工作轨迹。 前者是无序的事实集合,后者是有序的因果链。用错了地方,是把一部电影切成剧照来看。
方案三 · 结构化状态外置(通常是首选)
不靠「总结历史」保存状态,而是从第一轮起主动维护一份状态文件:
目标:...
约束:...
已完成:[1] ... [2] ...
已排除:[方案A - 超时] [方案B - 依赖冲突]
当前阻塞:...
下一步:...
每轮更新,重建上下文时以它为准。
- 好在哪:主动写入而非事后压缩,「已排除」这类否定信息被显式保住 —— 正好补上 compact 和 RAG 共同的漏洞
- 代价:只有 schema 覆盖的字段能活,schema 外全丢;维护本身消耗 token 和轮次
方案四 · 子 agent 隔离
脏活(翻日志、试错、搜索)派给 sub-agent,主上下文只收结论。 代价:子 agent 看不到主上下文,可能误解意图;结论带宽窄,「过程中发现的意外情报」传不回来。
方案五 · 卸载到文件系统
大块产物写文件,上下文里只留路径 + 一句描述。 代价:需要读回来时仍占窗口,只是延后;模型可能「忘了去读」。
方案六 · 交接重启
写交接单,开一条全新轨迹。 代价:所有隐式上下文全部押在交接单质量上。 但有时这是最优解——跑了 30 轮的轨迹往往已被自己的错误结论污染,重启的价值不只是省窗口,是清毒。
Q7 · 8000 行日志进上下文前怎么处理
答案在工具设计里,不在事后清洗
最优解是让它压根不要返回 8000 行。search_logs 是你自己设计的工具,它的签名不该是:
search_logs(query) -> str # 返回 8000 行,然后开始抢救
而该是:
search_logs(
query,
level="ERROR", # 服务端过滤
since, until, # 时间窗
limit=50, # 硬上限
fields=[...], # 只要需要的字段
mode="count|sample|full", # 先数数 → 再取样 → 最后才全量
) -> {
"total": 8213,
"returned": 50,
"truncated": True,
"ref": "logs/abc.txt"
}
在工具边界上强制预算,是唯一零成本的方案。 后面所有清洗、摘要、sub-agent,都是在为工具设计的失败买单。
⚠️ 返回值必须带 total 和 truncated。否则 agent 看到 50 条会以为总共 50 条,基于残缺样本下结论——静默截断比爆窗更危险,因为它不报错。
处理顺序:按成本从零往高走
| 层 | 手段 | 成本 | 8000 行 → |
|---|---|---|---|
| 0 | 工具参数过滤 | 0 token | 压根不产生 |
| 1 | 确定性代码:grep / 去重 / 抽字段 / 折叠时间戳 | 0 token | ~200 行 |
| 2 | 头尾截断保留(错误常在边界) | 0 token | ~100 行 |
| 3 | 落盘 + 只回传摘要与路径 | 极低 | 10 行 + ref |
| 4 | sub-agent / 小模型总结 | 一次完整调用 | 5 行 |
能用 grep 解决的,不要用模型。 确定性、免费、可测、可复现——模型这三样一样都没有。
sub-agent 不是免费的:它得真的把 8000 行读完,token 照样烧,只是不烧在主上下文;还多一次调用延迟;以及「它不知道主 agent 到底想找什么」的信息损失(必须把 query intent 一起传给它)。
白名单保留,不做黑名单删除
- 「重复内容合并」 → 安全、高收益。8000 行里常 90% 是心跳/轮询/重试的同构行,折叠成
[×1240] heartbeat ok纯赚。 - 「清除无意义内容」 → 危险。日志里最有价值的那一行,在统计上恰恰是最罕见、最不像正常内容的那一行。任何「过滤掉不常见的」的启发式,都在系统性地朝着删掉答案的方向使劲。
所以规则反过来:白名单保留(ERROR / WARN / 异常栈 / 状态跃迁),而不是黑名单删除。
并且必须留逃生口:原文落盘 + 返回 ref + 配 read_log_chunk(ref, offset)。让 agent 发现摘要不够时能回去查,而不是只能编。(与 Q6「摘要带指针」同一条原则。)
❌ 绝对不要「精炼后放进 system prompt」
四个理由,每条都致命:
- 摧毁 KV cache。 system prompt 是前缀,前缀一变缓存全废。每轮往里写摘要 = 每轮让全部 KV cache 失效,成本延迟翻数倍。
- 只进不出,单调增长。 历史消息可被 compact、可被滑窗丢弃;写进 system prompt 的没有任何机制会清理。第 30 轮里堆了 12 段日志摘要,其中 10 段早已过期。
- 越权:把不可信数据提升到指令层。 日志是外部数据,可能含用户输入、第三方回调、攻击载荷。system prompt 是权限最高的区域。写进去 = 让任何能往日志里写字符串的人获得改写 agent 指令的能力 —— 标准的 prompt injection 通道。
- 语义错位。 「14:32 数据库连接超时」是事件,不是规则。
边界铁律:system prompt 放「你是谁、遵守什么规则」;工具返回放「世界发生了什么」。两者永不混合。 外部数据必须有明确的包裹标记,让模型知道它是资料不是命令。
代价表
| 手段 | 换来 | 代价 |
|---|---|---|
| 工具端过滤 | 零成本 | 过滤条件写错就查不到,agent 未必会调参重试 |
| 确定性清洗 | 免费、可复现 | 规则是死的,遇到没见过的格式会误伤 |
| 截断 | 简单 | 中间段全丢;不标 truncated 会导致静默错误结论 |
| 落盘 + ref | 可回溯 | 模型经常「忘了去读」,需在描述里明确提示 |
| sub-agent | 主上下文干净 | 调用成本 + 延迟;意图传递有损;结论带宽窄 |
Q8 · 工具报错该怎么写
A: "Error"
B: "TimeoutError: connection to log-service timed out after 30s"
C: "查询超时(30s)。可能原因:时间范围过大。
建议:将 since/until 缩小到 1 小时内,或加 level=ERROR 过滤后重试。
本工具剩余重试次数:2"
题眼:错误信息不是日志,是下一轮的 prompt
传统软件里,报错给人看,用途是事后排查。 agent 系统里,报错给模型看,它原样进入上下文,成为决定下一步动作的那段文本。
错误信息是整个系统中信噪比最高的一段 prompt——因为它恰好在模型最迷茫的时刻抵达。
由此推出:它的评价标准不是「准确描述了故障」,而是「读完之后,模型下一步会干什么」。写报错 = 写 prompt = CE 的一部分。
A 的失败模式:不是「信息少」,是「制造错误的世界模型」
"Error" 与「查询成功但没有匹配结果」在文本上难以区分。模型很可能理解成「日志里没有这类记录」,然后继续往下走——它不会重试,因为它以为拿到了答案。
这比死循环更危险。 死循环至少会撞上超时告警;错误的世界模型会安静地产出一个看起来很合理的错误结论。
硬约束:「失败」和「空结果」必须在返回值里可区分。
B 的失败模式:目标读者是人
TimeoutError ... after 30s 对盯着终端的工程师是完美的;对必须立刻决定下一步的模型,它不含任何可执行的 delta——没有一个字说明「该改什么」,于是最高概率的下一步就是原封不动再调一次 → 死循环。
C 的代价(这才是分水岭)
C 方向对,但绝不免费:
1. 建议错了,比不给建议更糟。 「可能原因:时间范围过大」是一个猜测。若真实原因是 log-service 挂了,这条建议会把 agent 牢牢锚定在错误方向:缩小时间窗、再缩小、加过滤,连败五轮,而真正该做的是换数据源或上报。 模型对指令的顺从度极高,你写进错误信息里的每条建议都会被当成命令执行。 只在有把握时给建议;没把握就只给事实和分类,把判断权还给模型。
2. Token 成本 × 触发频率。 报错会连发。三个工具各失败两次 = 360 token 纯噪声,且高度重复——重复内容还会稀释注意力。
3. 「剩余重试次数:2」如果只是文本,它什么也不是。 模型完全可以无视它。真正的限流必须由 harness / runtime 强制执行:计数在代码里,超限直接拒绝调用并返回终态。
护栏写在 prose 里叫建议,写在 code 里才叫限制。
4. 维护成本。 5 工具 × 8 种失败 = 40 段手写文案,还会随 API 变更过期。必须做成模板化的错误层,不可能人肉逐条写。
比 C 更好的形态:结构化 + 分类
{
"ok": false,
"error_type": "TIMEOUT",
"retryable": "WITH_CHANGES",
"retry_after_ms": null,
"hint": "时间范围过大是常见原因(未确认)",
"suggested_params": { "since": "-1h", "level": "ERROR" },
"retries_left": 2,
"trace_id": "abc123"
}
最重要的字段不是 hint,是 retryable。 agent 面对失败只有三种正确反应,它必须知道自己在哪一格:
| 分类 | 含义 | 正确动作 |
|---|---|---|
AS_IS |
瞬时故障(限流、网络抖动) | 退避后原样重试 |
WITH_CHANGES |
确定性失败(参数不对、范围过大) | 原样重试必然再失败,必须改参数 |
NO |
权限不足 / 资源不存在 / 额度耗尽 | 停止重试,换路径或上报人工 |
A 和 B 的共同致命伤,就是让模型无法判断自己在哪一格,而这三格的动作完全相反。
两个细节:hint 里的「(未确认)」不是废话,它在降低建议的权重,避免错误锚定;trace_id 让模型能把失败报给人,而不是自己硬扛。
⚠️ 报错里常回显外部输入 → 同样是 injection 面,需包裹标记。
Q9 · 30 个工具,选错 + 越权,怎么解
现象一:经常选错工具(该用 A 用了 B) 现象二:偶尔调了不该调的(只读任务里调了写接口)
第一步:这是两个问题,不是一个
| 现象一 · 选错 | 现象二 · 越权 | |
|---|---|---|
| 性质 | 质量问题 | 安全问题 |
| 目标 | 让它更可能选对 | 让它不可能选错 |
| 手段 | 概率性(prompt / 描述 / 示例) | 确定性(权限 / 拦截 / 审批) |
| 可接受的失败率 | 95% 就能上线 | 必须是 0 |
铁律:安全问题绝不能用概率手段解决。 「在 system prompt 里写『只读任务不要调写接口』」是概率手段——它把 100% 的失败率降到 2%,而 2% 的写操作可能是删了生产库。 凡是不可逆的动作,都不能靠模型自觉。
把两者混在一起答,是这题最常见的失分点。
层面一 · 工具设计层(治「选错」的根,且成本最低)
选错工具,绝大多数是工具自己的问题,不是模型笨。
| 手段 | 做什么 |
|---|---|
| 合并重叠工具 | get_user / fetch_user / query_user_info 三个功能重叠 → 模型必错。合成一个带参数的 |
| 消除歧义命名 | search vs find vs lookup 模型分不清;search_logs vs search_tickets 就分得清 |
| 描述写「何时用 / 何时不用」 | 大多数工具描述只写了「做什么」。真正减少误选的是 “Use this when… Do NOT use this when… Use X instead.” |
| 参数 schema 严格化 | enum 代替自由字符串、必填项、格式校验 → 让错误在调用前被 schema 挡住,而不是执行后 |
| 返回值自带下一步线索 | 见 Q8 |
诊断口诀:如果两个工具的描述互换一下,模型的行为不会变,那它们就是同一个工具,应该合并。
代价:合并会让单个工具参数变复杂(模型可能填错参数,只是把错误从「选错工具」搬到了「填错参数」);描述写详细会占 token,30 个工具的定义常年驻留在上下文里,是固定开销。
层面二 · 上下文层(治「选错」的量)
30 个工具全量塞进每一轮上下文,本身就是错误的设计。
- 按阶段裁剪:这一步只可能用到 5 个工具,就只给这 5 个。工具集是上下文的一部分,同样适用 Q5 的稀缺资源原则
- 工具检索 / 动态加载:工具多到几十上百时,先用一次检索选出候选子集
- 分组 + 两级选择:先选「日志类 / 工单类 / 配置类」,再选具体工具
- few-shot 只针对易混对:不要给 30 个例子,给「A 和 B 怎么区分」的 3 个例子
代价:动态裁剪破坏 KV cache 前缀稳定(Q5 的老问题);裁错了会让 agent 拿不到本该有的能力,且它不知道自己少了工具,只会觉得「做不到」;两级选择增加一次调用延迟。
层面三 · 权限与执行层(治「越权」,这一层跟模型完全无关)
这是提示里说的那一层。它的特点是:无论模型说什么,它都执行同一套规则。
| 手段 | 说明 |
|---|---|
| 能力按任务下发 | 只读任务,根本不给写工具。不是「告诉它别用」,是它的工具列表里没有 |
| 凭证最小权限 | agent 持有的 token 本身就是只读的。即使它想调写接口,后端拒绝 |
| 写操作白名单 + 拦截器 | 在 harness 里做 policy check,不在 prompt 里做 |
| dry-run / 两段式提交 | 写操作先返回「将要发生什么」,需二次确认才真正执行 |
| 人工审批(HITL) | 不可逆 + 高代价的动作强制走人工 |
| 沙箱与配额 | 限制作用域(只能改这个 namespace)、限制频次 |
| 审计日志 | 每次调用记录 who / what / why / 结果,可回溯可回滚 |
核心思想:不要问「怎么让模型别做坏事」,要问「怎么让坏事在物理上做不到」。 这与 Q3 的 fail-safe、Q8 的「护栏写在 code 里」是同一条原则的三次出现。
代价:这是全部方案里唯一能真正解决现象二的,但它也最贵—— 权限体系要设计和维护;能力给少了 agent 会卡死(它想干正事但没权限,而且往往不会正确上报,只会绕路或胡说);HITL 引入人的延迟,把「自动化」打回「半自动化」;dry-run 要求每个写接口都实现一个预演模式,工程量翻倍。
层面四 · 编排层(用架构消解问题)
与其让一个 agent 拿 30 个工具,不如让 5 个 agent 各拿 6 个。
- 按职责拆 sub-agent:查询 agent(全只读)、变更 agent(有写权限但只在被明确调用时激活)
- 用 workflow 固定「只读阶段 → 人工确认 → 写入阶段」的骨架 ← 回到 Q1 的结论
- 只读与写入跨阶段隔离,写权限只在特定阶段存在
代价:Q6 讲过的 sub-agent 代价(意图传递有损、结论带宽窄、多一层延迟);架构复杂度上升,调试链路变长。
层面五 · 评测与监控层(让问题可度量)
没有这一层,前面四层都是在盲改。
- 工具选择准确率要有评测集:固定 N 个任务 → 期望调用序列 → 每次改描述后回归
- 线上监控:工具调用分布、单任务调用次数、失败率、「调用后立刻调另一个工具纠正」的模式(= 选错的隐式信号,Q4 的思路直接复用)
- 越权尝试要单独告警,哪怕被拦截了——被拦截的越权尝试是最有价值的信号,它说明模型的意图跑偏了
代价:评测集要人工构造和维护,会随工具变更而过期;埋点本身有工程成本。
Q9 答案骨架
① 分性质:选错 = 质量问题(概率手段);越权 = 安全问题(确定性手段)。两者不可混用 ② 工具设计层:合并重叠、消歧命名、写清何时不用、schema 严格化 —— 治根,最便宜 ③ 上下文层:按阶段裁剪工具集、动态加载、易混对 few-shot —— 代价是 KV cache 与能力缺失 ④ 权限执行层(与模型无关):能力按需下发、最小权限凭证、拦截器、dry-run、HITL、审计 —— 唯一能把越权降到 0 的层 ⑤ 编排层:拆 sub-agent、workflow 固定只读/写入阶段 ⑥ 评测层:工具选择准确率回归集 + 线上纠正模式监控 + 越权尝试告警 ⑦ 总代价:每一层都在用「灵活性 / 成本 / 延迟 / 工程量」换「正确率 / 安全性」,必须显式声明换了什么
附录 A · Harness 各层分别解决什么问题
Harness = 模型之外的一切运行时脚手架。
一个常见的误解是「agent = 模型 + prompt」。真实系统里,模型只是其中一层,而且是你最不可控的那一层。工程能力体现在其余各层。
总原则:模型负责「决定做什么」,harness 负责「保证做得成、做得起、做不坏」。
分层表
| 层 | 解决什么问题 | 典型构件 | 失效时的症状 | 对应题目 |
|---|---|---|---|---|
| 1. 模型 / 推理层 | 在给定信息下做出决策 | 模型选择、温度、结构化输出、CoT | 判断质量差 | — |
| 2. 上下文组装层 | 本轮该看到什么、不该看到什么 | 检索、压缩、排序、卸载、缓存前缀、工具集裁剪 | 被淹没 / 被污染 / 爆窗 / 成本失控 | Q5 Q6 Q7 |
| 3. 工具层 | 能力边界与接口契约 | 工具定义、参数 schema、返回值规范、错误契约、入口预算 | 选错工具、静默截断、死循环 | Q7 Q8 Q9 |
| 4. 执行与策略层 | 让危险的事在物理上做不到 | 权限凭证、policy 拦截、沙箱、配额、dry-run、审计 | 越权、不可逆破坏 | Q9 |
| 5. 控制循环层 | 让它一定会停下来 | 轮次上限、重试预算、退避、终止条件、检查点、状态机 | 死循环、无限重试、烧钱 | Q6 Q8 |
| 6. 状态与记忆层 | 跨轮次、跨会话记住什么 | 状态文件、compact 策略、外部存储、交接单 | 遗忘约束、重复已排除的方案、行为漂移 | Q6 |
| 7. 可观测与评测层 | 知道它是不是在正常工作 | trace、埋点、隐式反馈、分布监控、回归集、分歧检测 | 静默失败、无法迭代 | Q4 Q9 |
| 8. 人机接口层(HITL) | 不确定时把方向盘交回给人 | 阈值转人工、审批门、升级路径、交接格式 | 该停不停、猜一个然后错到底 | Q3 Q9 |
分层的四个要点
① 每层的失败模式不同,不能互相替代。 上下文层修不好越权,权限层修不好选错,控制循环修不好判断质量。定位问题时先问「这是哪一层的病」。
② 越往下(4、5 层)越确定性,越往上(1、2 层)越概率性。 凡是不可接受失败的需求(安全、成本上限、必须终止),一律往确定性层放;凡是允许失败的需求(选得更准、答得更好),才放在概率层。
③ 三条原则在每一层都出现一次:
| 原则 | 在各层的体现 |
|---|---|
| 预算在入口 | 工具层截断(Q7)· 控制循环设轮次上限(Q6)· 上下文层从第一轮就管 |
| fail-safe | 不确定 → 转人工(Q3)· 报错分三类而非硬重试(Q8)· 越权 → 拒绝而非放行(Q9) |
| 可回溯 | 摘要带指针(Q6)· 落盘 + ref(Q7)· trace_id(Q8)· 审计日志(Q9) |
④ 数据 / 指令的信任边界横穿所有层。 工具返回、检索文档、日志内容、用户输入 —— 全是外部数据,必须包裹标记,绝不进 system prompt,绝不被当作指令执行。(Q7 的硬伤、Q8 的回显面。)
附录 B · 现在的问题
1. 知道很多名词,但我说不出它们的动作
九道题里,我给出过这些答案:「复杂任务用 agent」「改进 label 质量」「查看 log」「memory、result、error」。
它们都不算错。但它们都停在名词层——我说出了正确的词,却没有说出这个词对应的动作、参数、成本和第一步。
在评审桌和面试里,这有一个固定的读法:「他听过这个词,但没做过这件事。」
我的自检方法:写完任何方案,问自己一句——
明天早上开始干,我第一步敲什么命令、改哪个文件、找谁开会?
答不上来,这个方案就还没写完。
到 Q7、Q8 我已经能给出可执行动词了。这条基本改掉了,但它随时会复发,尤其在我不熟的领域。
2. 从来不主动说代价 —— 这是最顽固的毛病
我的思维里缺这个动作。我在想「怎么做得到」,还没开始想「做这件事要花多少、会碰到什么边界」。
记住这句话:只说好处的方案没有说服力,因为所有人都知道方案有代价。我不说,只有两种解释——没想过,或者在藏。前者是能力问题,后者是信任问题。
强制习惯:说出任何一个技术方案后,立刻补一句
「它换来 ____,代价是 ____。」
写不出代价,说明我只看过它的宣传页,没看过它的事故报告。
3. 会跳过诊断,直接开药
Q3 给了我「8%」这个数字,我没用它,直接给了两个方案。
同样是 8%,它是聚成一簇还是长尾,处方完全相反。我不看分布就给方案,等于医生不看化验单就开处方。
要记住:题干里出现的每一个具体数字和条件都是考点,答案里必须回指。看到异常,先问「这些 case 长什么样」,而不是「换什么方案」。
正确顺序永远是:止血 → 诊断 → 处置。 我的顺序一直是:处置 → 猜原因。
4. 会漏掉中间选项
Q2 里我看到「不能用硬编码」,就直接跳到了「那就用 agent」,中间那个 LLM 分类器我完全没看见。
而且我把题干里「自然语言描述很麻烦」这句话,当成了支持 agent 的证据——它其实只排除了 if-else,语义模糊恰恰是 LLM 分类器最擅长的。
我的毛病是二选一思维。选型题至少三个候选:
**硬编码规则 LLM 单次调用 agent**
排除任何一个都要给理由。
5. 用形容词当判据
我说过「复杂任务用 agent」「条件是否规整」「无意义的内容清除」。
复杂、规整、无意义——这些都是程度形容词,无法证伪,因此无法用来做决策。
工程判据必须是可检验的二元问题:能画出完整流程图吗?分支能穷举吗?路径能预先枚举吗?
6. 论证经常是循环的
我写过:「我认为该用 agent…因为自然语言描述麻烦…所以我认为该用 agent。」
两个「我认为」之间没有增加任何新证据。这在评审里读成只有立场,没有论证。
给结论必须至少带一样:反例 / 判据 / 代价。
7. 还没有边界意识
Q7 里我说「重要内容精炼后放进 system prompt」。这句话同时踩了四个雷:毁掉 KV cache、只进不出、语义错位,以及最严重的——把不可信的外部数据提升到了权限最高的指令层,开出一条 prompt injection 通道。
而这之前一题我刚学过「别在开头插变量」。我听过,但没内化成检查动作。
要建立的边界感:
system prompt 放「你是谁、遵守什么规则」;工具返回放「世界发生了什么」。两者永不混合。 凡是外部来源的字节,一律是数据,不是命令。
8. 不排成本顺序,直接跳到最贵的方案
Q7 里我直接说「让 sub-agent 来做」。但 grep、去重、抽字段是 0 token 的,sub-agent 是一次完整模型调用。
我跳过了第 0、1、2、3 层,直接到了第 4 层。
能用代码解决的,不要用模型。 确定性、免费、可测、可复现——模型这三样一样都没有。
9. 我做对的一件事,要保持
Q6 里我写了「会丢失什么我也不知道」。
这是九道题里我最有价值的一句话。在技术评审里,把不确定标记出来,比编一个像样的答案有价值得多——编出来的答案会被采纳,然后在生产环境里炸掉。
这个习惯要保持。
我的检查清单(写完任何技术方案后逐条过)
- 换个场景这段话还成立吗?(成立 = 太泛,等于没答)
- 我的判据是可检验的二元问题,还是程度形容词?
- 代价写了吗?换来什么、损失什么、花多少?
- 候选方案有三个吗?中间选项排除的理由是什么?
- 题干给的数字和条件,我用上了吗?
- 我是先诊断还是直接开药?
- 这里面哪些是数据、哪些是指令?边界画清楚了吗?
- 按成本从低到高排过序吗?最便宜的那层试过没有?
- 这是质量问题还是安全问题?我用的是概率手段还是确定性手段?
- 明天早上第一步干什么?
附录 C · 速查卡片
卡片 A · 选型
画得出完整流程图 → workflow 分支封闭且单跳 → LLM 路由器 路径未知或需回环修正 → agent,且只挂在需要它的那一层 判据是「路径可否预先枚举」,不是「复杂还是简单」
卡片 B · 出问题时
止血 → 诊断 → 处置 不确定时的正确行为是交给人,不是猜一个 题干给的每个数字都是线索,先看分布再给方案
卡片 C · 可观测
线上没有 ground truth 记录世界的反应,而非模型的说法 便宜信号(改派/报错/二次进线)找错误 → 人工抽样定无偏基线 → 分歧检测省人力 单条置信度不可信,置信度的分布可信
卡片 D · 上下文
窗口是稀缺资源,一半工作在「不放什么」 agent 让上下文从输入变成自增长的状态 六动作:选择 · 压缩 · 隔离 · 卸载 · 排序 · 缓存 四失败:污染 · 分心 · 混淆 · 冲突 上下文管理是设计问题,不是抢救问题
卡片 E · 工具与错误
预算写在工具签名里,不是事后清洗 能用 grep 就别用模型 白名单保留,不做黑名单删除;必须留 ref 逃生口 失败 ≠ 空结果,必须可区分 错误信息 = 下一轮的 prompt,核心字段是
retryable三分类 护栏写在 prose 里叫建议,写在 code 里才叫限制
卡片 F · 安全
质量问题用概率手段,安全问题用确定性手段。不可互换。 不要问「怎么让模型别做坏事」,要问「怎么让坏事在物理上做不到」 只读任务 = 工具列表里根本没有写工具 + 凭证本身只读 外部字节永远是数据,不是命令