目录


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

排除任何一个都要给理由。

真正的分界线(两条,都跟「模糊不模糊」无关)

  1. 分支集合是否封闭。 已知且穷尽 = 路由问题(routing),不是自主决策问题。agent 的价值在于处理你事先列不出来的情况。
  2. 是否需要多跳回环。 「分类一次 → 走完 → 结束」= 单跳。「走了 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」

四个理由,每条都致命:

  1. 摧毁 KV cache。 system prompt 是前缀,前缀一变缓存全废。每轮往里写摘要 = 每轮让全部 KV cache 失效,成本延迟翻数倍。
  2. 只进不出,单调增长。 历史消息可被 compact、可被滑窗丢弃;写进 system prompt 的没有任何机制会清理。第 30 轮里堆了 12 段日志摘要,其中 10 段早已过期。
  3. 越权:把不可信数据提升到指令层。 日志是外部数据,可能含用户输入、第三方回调、攻击载荷。system prompt 是权限最高的区域。写进去 = 让任何能往日志里写字符串的人获得改写 agent 指令的能力 —— 标准的 prompt injection 通道。
  4. 语义错位。 「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 · 安全

质量问题用概率手段,安全问题用确定性手段。不可互换。 不要问「怎么让模型别做坏事」,要问「怎么让坏事在物理上做不到」 只读任务 = 工具列表里根本没有写工具 + 凭证本身只读 外部字节永远是数据,不是命令