面试讲稿:上下文工程——让工具输出不撑爆 LLM 上下文#
用途:面试口头讲述”上下文工程”这个案例的脚本。 结构:一句话定位 → 30 秒电梯版 → 3 分钟详细版(STAR)→ 关键决策深挖 → 追问应答 → 收尾。 配套技术文档:
docs/interview/CONTEXT_ENGINEERING_HOLMESGPT.md。
0. 一句话定位#
“我在一个多 Agent 运维诊断系统里做过一块上下文工程,解决的是 工具输出撑爆 LLM 上下文的问题。我参考了 CNCF 生态里的 AI SRE 项目 HolmesGPT, 但没照搬——结合我们自己的架构做了取舍,落地成三层: 单工具输出预算截断、源端过滤、到顶强制收尾。“
1. 30 秒电梯版#
“我们系统是 LangGraph 编排的多 Agent 诊断图,好几个专业 Agent 并行查日志、查指标、 查容器,再汇总定位根因。上线后我发现:明明加了
max_iters限制轮数,上下文还是会爆。定位下来根因是——
max_iters限的是轮数,不是单轮体量。一次docker inspect或一段全量日志,单条就几万字符,一条就能占掉大半个上下文窗口。我调研了 HolmesGPT 的上下文工程,借鉴它’单工具输出预算’的思想,但没抄它的 溢写磁盘实现——因为它依赖 bash 工具回读,我们没这个闭环。最后落地成三层: 源端过滤根治、输出截断兜底、到顶强制收尾。改动集中在一个文件加两个配置项, 不碰图结构,风险很低。“
2. 详细版(STAR)#
S — 背景#
“我做的是一个 hybrid RAG 的多 Agent 运维诊断系统,LangGraph 编排。 一次告警进来,先做证据规划,然后 fan-out 出 log、metric、infra、runbook 四个 隔离的专业 subagent 并行取证——每个 agent 在自己的 ReAct 小循环里调工具、拿数据、 下结论,只把结论压成一条 Evidence 写回共享状态;最后 fan-in 汇总、判根因、出报告。 这些工具背后是真实数据源,psutil、docker、网络诊断这些,通过 MCP server 暴露。“
T — 问题#
“跑真实告警时有两类症状:一是偶发的上下文超长报错;二是有的专业 agent 该产证据, 却回来一条空 Evidence,导致根因判定少一路依据。 我要解决的就是:别让工具输出撑爆上下文,也别让 agent 空手而归。“
A — 动作#
“先定位第一个问题。 我一开始以为是轮数多,但我们
max_iters才 3、4 轮。 把工具返回值打出来看,一次docker inspect的 JSON 就一万多字符,全量日志更夸张。 所以问题不在轮数,在单条工具输出的体量没人管。然后调研 HolmesGPT。 它对单个工具输出有预算:
min(上下文15%, 25000 token), 超了就溢写磁盘,只给 LLM 文件路径加预览,让 LLM 自己用 bash grep 读回。这里我做了个关键判断:溢写磁盘不能照抄。 它能溢写的前提是 LLM 有 bash 能读回来, 我们没这个闭环——溢写了 LLM 就永远拿不回,等于数据丢了还多一堆文件 IO。 所以我借它’单工具预算’的思想,实现改成头尾截断:超预算保留头 60%、尾 40%, 丢中间,插一句’已截断,原长多少’的标注。为什么留头尾——运维数据头部是状态摘要、 尾部是最新日志和错误栈,中间是重复的正常日志,最该丢。 另外我用字符数、没用 token 数——DeepSeek 和通义千问没有稳定的本地 tokenizer, 字符数是够用的近似(中文约 1 字 1.5 token),零依赖零延迟。这是兜底。
再加一层根治——源端过滤。 给日志类工具加 grep 和 tail_n,源头就只回匹配的、 最近 N 行;docker inspect 只回关键字段。源头减 90%,截断兜住漏网的 10%,深度防御。
第三层解决空 Evidence。 我看循环代码用的是 Python 的 for-else,到 max_iters 直接退出,但最后一条消息可能还是 LLM’我要调工具’的请求——它没来得及下结论就被踹出去。 HolmesGPT 到上限时会再调一次 LLM 但禁掉工具,逼它用已有信息总结。我抄了这个: 到顶补一次不带 tools 的调用,提示’别调工具了,基于现有信息给结论’。 关键是这次一定不能 bind 工具,否则它又想调工具,到顶就形同虚设。“
R — 结果#
“改动很集中——一个
tool_runner.py加两个配置项,不碰图结构、状态、RAG 链路, 三层能独立开关、独立回滚。上下文超长报错消掉了,专业 agent 到顶也能稳定产出 有内容的 Evidence,根因判定的证据完整度上来了。我还写了配套技术文档。“
3. 关键决策深挖(主动讲,加分项)#
决策一:为什么是”借鉴”不是”照搬”。
这是我最想强调的。HolmesGPT 的溢写磁盘很优雅,但它有隐含前提——LLM 能 bash 回读, 我们没有。判断一个方案能不能抄,要看它依赖的前提在你这儿成不成立,而不是看它 多先进。前提不成立还抄,就是给自己埋坑。所以我抄思想、不抄实现,改成对我们成立的 头尾截断。
决策二:理念不同,但手段可借鉴。
HolmesGPT 是纯 fetch-on-demand、完全弃用向量 RAG——因为它面对 Prometheus、K8s 这种海量高时效数据,预索引必然过期。我们是 hybrid:有稳定的内部 SOP 知识库, 走 pgvector RAG 合理。但我采纳了它一条边界原则:实时状态永远走工具现拉、 绝不进 RAG 索引;只有静态 SOP 和历史复盘进向量库。理念不同,控 context 的手段通用。
决策三:为什么三层而不是一层。
源端过滤是根治、截断是兜底。只做过滤不够——LLM 可能忘带过滤参数、有些工具没法过滤; 只做截断也不够——那是拿回来再砍,网络和数据源开销已经花了。两层叠加,既省成本又 保证’无论如何单条不超预算’。
4. 预埋追问 & 应答#
Q:截断会不会丢关键信息导致误判?
有风险,两点缓解:一是留头尾不是只留头,覆盖状态和最新错误两头;二是截断有标注和 warning 日志,LLM 和排查的人都看得到”这被截断了”,不会当成”工具没数据”。 真正的根治是源端过滤,让数据压根不超预算,截断只是保险。
Q:截断是按位置砍,有没有更精细的压缩方式?
有。HolmesGPT 还有一层叫 output transformer——每个工具挂一个领域相关的后处理 函数,按语义而不是按位置压缩输出。比如
docker inspect返回 10KB+ 的 JSON, transformer 只抽State(运行状态)、RestartCount、Config.Env、HostConfig.Memory这几个诊断相关字段,从 10KB 压到几百字符;Prometheus 查询返回 几百个时间点,transformer 压成”均值/峰值/当前值”三个统计量。本质是把”盲砍”升级成 “按领域知识结构化提取”。我调研过但没做,原因是两层成本:一是每种工具要写一个领域提取函数,维护成本高; 二是我们的源端过滤已经在数据源侧做了类似的事——docker inspect 只回关键字段、 日志加 grep/tail_n——效果等价,只是切入点不同。截断作为最后兜底,覆盖源端过滤 漏网的 10%,够用了。
如果后续要加,实现上在
ToolMeta挂一个可选的output_transformer: Callable, 在tool_runner截断之前先过一遍,架构上一行改动就能接入,不需要动图结构。 主要的工程量在写各个工具的 transformer 函数本身。
Q:字符数近似 token,误差大了怎么办?
阈值本身保守——12000 字符约 18k token,只占 128k 上下文的 14%,安全边际很大。 就算中英文混排偏 20% 也远没到爆。而且它可配置,真要精确随时能换 tokenizer, 架构上没锁死。
Q:到顶收尾那次调用失败了怎么办?
加了 try/except 兜底,失败就记 warning、直接返回,不拖垮整轮。最坏退回改造前行为, 不会更差。fail-safe。
Q:怎么测?
三条:截断——造一个返回超 12k 字符的假工具,断言回灌进消息的长度受控且有标注; 收尾——把某 agent 的 max_iters 设 1、强制它想调工具,断言最后一条是有内容的结论 而非空壳;源端过滤——断言传了 grep/tail_n 后返回行数受控。
Q:为什么改动能这么集中、风险这么低?
因为我把上下文控制收敛在**工具执行层(tool_runner)**这一个切面。所有 agent、 所有工具调用都走这条路径,我在这一个点加截断和收尾就全局生效。这是有意的设计—— 在正确的抽象层做一次,胜过在每个 agent 里各做一遍。
5. 收尾(“你从这个案例学到什么?”)#
“两个体会。一是判断方案可移植性比追新更重要——HolmesGPT 的溢写磁盘很好, 但我得先问’它依赖的前提在我这儿成不成立’,不成立就借思想不借实现。 二是找对抽象层,一次改动全局生效——我没在四个 agent 里各写一遍截断, 而是收敛到工具执行层这一个切面,改一处、全覆盖、好回滚。 这两点我觉得比功能本身更值钱。”