跳到主要内容

51 篇博文 含有标签「context-engineering」

查看所有标签

没人使用的长尾:工具带是如何变得尾大不掉的

· 阅读需 11 分钟
Tian Pan
Software Engineer

没人会刻意决定给智能体 (agent) 配备 40 个工具。这就像车库堆满杂物一样自然而然地发生了。你接入一个搜索工具,然后是一个数据库读取器,接着团队里有人发布了 Slack 集成,然后又安装了 ticketing MCP 服务器,因为这只需要一行配置。每次添加在当时看来都是合理的。没人会移除任何东西,因为移除工具感觉就像是在剥夺能力,而剥夺能力感觉就像是一种倒退。

六个月后,你的智能体有了一个工具带,其中有三个它经常使用的工具,十几个偶尔使用的工具,以及二十多个在生产环境中技术上从未被选中的长尾工具。这种“长尾”并非免费,甚至代价高昂。目录中每一个未使用的工具都在主动降低智能体从那些重要工具中做出正确选择的能力。

与先验对抗:当模型掌握了错误版本的技术栈时

· 阅读需 12 分钟
Tian Pan
Software Engineer

有一种特定的争论,你只会和语言模型发生。你粘贴进代码。它把一个能运行的调用改写成了早在几个大版本前就不存在的形式。你纠正它。它道歉、表示同意,然后在下一轮对话中故技重施。你不是在对抗无知,而是在对抗一段对 不同 版本技术栈的、自信且经过充分排练的记忆——这段记忆被远超你纠正信息的训练样本所强化。

这就是我所认为的“与先验知识对抗”(fighting the prior)故障模式。模型的参数化知识——它在训练期间吸收的一切——包含了流行的、过时的,或者仅仅是与你实际使用的框架版本不同的内容。当你的上下文与它的先验知识发生冲突时,先验知识往往会胜出。与纯粹的幻觉不同,这种错误之所以危险,恰恰是因为它具有误导性的合理性:那个被弃用的 API 曾经是正确的,所以代码看起来没问题,能通过随意的阅读,甚至有时还能编译通过。

技能是过程性知识的包管理器

· 阅读需 12 分钟
Tian Pan
Software Engineer

每个构建 Agent 的团队最终都会遇到同样的瓶颈。系统提示词(System Prompt)起初只有 400 个 token。接着有人添加了数据库迁移检查清单。然后是复盘模板、部署运行手册、客户邮件风格指南。18 个月后,它变成了一个拥有 9,000 个 token 的巨石,没人敢去改动它,因为修改关于回滚流程的一行内容,竟然会降低 Agent 在支持工单中的语气表现。你构建了一个相当于 50,000 行的 main.c 的提示词——而且每个人都在对其进行静态链接。

直觉的反应是使用 RAG:将运行手册切片、嵌入(embed),然后按需检索。但这会以一种更隐蔽的方式失败。RAG 是为检索“事实”而生的,而事实在被碎片化时可以平滑退化——关于你计费模型的五个相关切片中检索到三个,仍然能告诉 Agent 大部分所需信息。但程序(Procedures)无法平滑退化。检索到 60% 的数据库迁移运行手册并不是 60% 有用,而是意味着一场生产事故。有第 1 到 4 步但缺失了第 5 步(“在切换前验证复制延迟”)比完全没有运行手册更糟,因为 Agent 现在的行为带着一种它尚未赢得的自信。

智能体流水线中的“传声筒”游戏

· 阅读需 13 分钟
Tian Pan
Software Engineer

你可能目睹过这样一种失败,只是没有给它命名。你的编排者(orchestrator)读取用户请求并向工作代理(worker agent)下达简报。工作代理运行了十几次工具调用,消化输出,然后汇报一份简洁的摘要。编排者将该摘要并入下一名工作代理的任务简报中,后者如法炮制。五跳(hop)之后,系统给出了一个自信的最终答案——却违反了用户在请求的第二句话中明确提出的约束。没有人故意丢掉这个约束。每一跳只是稍微压缩了一下上下文,方向无人选择,而这些压缩不断叠加。

这就是“传声筒游戏”(the telephone game),而多智能体系统(multi-agent systems)本质上就在玩这个游戏。流水线中的每一次交接都是一个有损压缩步骤:编排者的简报是对用户的转述,工作代理的报告是对其工具输出的转述,而最终答案则是转述之上的转述。问题不在于信息是否丢失——它确实丢失了,而且是可衡量的——而在于你是否决定了哪些信息允许被丢失,还是将这个决定留给了采样温度(sampling temperature)。

你的上下文流水线需要一份新鲜度 SLA

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的智能体(agent)用上个季度的价格回答了客户的计费问题,复盘报告会将此归咎于模型。但不应该如此。Prompt 组装正确,检索评分很高,模型对所给的所有信息进行了合理的推理——而所给的一切在三天前都是真实的。在 CRM 导出、文档同步和向量索引重建之间的某个环节,“世界的当前状态”悄然变成了“截至周二的世界状态”,而你的技术栈中没有任何环节在衡量这种差异。

数据工程师在几年前就解决了这类问题。一个消费十张上游表的下游仪表板拥有血缘关系(lineage)、新鲜度检查以及一个在夜间任务出错时进行呼叫的轮值机制。你的智能体所消费的上下文窗口也是同样的东西——一个由文档、工单、代码、CRM 和记忆连接而成的物化视图(materialized view)——只不过没有人负责这个连接(join),没有任何东西衡量它的陈旧度,当它提供昨天的真相时,失败却被归类为“模型幻觉”。

文档复兴:你的 README 是 Agent 的核心上下文界面

· 阅读需 11 分钟
Tian Pan
Software Engineer

二十年来,文档一直是良好初衷的坟墓。你在第一个 Sprint 期间编写 README,那时架构整洁,你的热情高涨。但没人读它。到了第三个 Sprint,它就开始在构建命令上误导人,到了第六个 Sprint,它描述的是一个早已被删除的服务。文档是每个人都同意缴纳但实际上没人在缴的税 —— 一种没有反馈循环的道德准则。写了烂文档,什么也不会发生。不写文档,同样什么也不会发生,因为高级工程师把架构都记在脑子里。

然后我们将编程 Agent 指向我们的代码仓库,反馈循环一夜之间降临。README 现在是你拥有的杠杆率最高的文件 —— 这并不是因为有人做了关于文档规范的励志演讲,而是因为该文件的质量现在直观地决定了你的 Agent 是交付正确的代码,还是会信誓旦旦地对一个已不存在的架构产生幻觉。

这就是文档的复兴,而它与我们过去编写的文档几乎毫无关系。

你的 Agent 每一轮都在重新生成对话摘要,只因缓存键包含了一个时间戳

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个只被写入却从未被读取的缓存算不上缓存。它只是一个增加了额外延迟、按 KB 计费的日志系统。而这种失效模式最残酷的版本是,从每个角度看缓存都是健康的:set 调用成功,get 调用返回迅速,键(key)格式正确,值(value)有效,TTL 设置合理。唯一的问题是,没有任何一次 get 调用能找到之前 set 调用写入的键,因为键中的一个字段在每次计算时都会发生变化。

这是一个关于调试过程的故事:为了“能分辨出我正在看的是哪条缓存记录”,一位工程师在缓存键中添加了一个时间戳。结果,在没人察觉的两个星期里,系统悄悄地为每场对话多支付了 14 次额外的 LLM 调用费用。

那个把用户的字面问题“改写”没了的摘要器

· 阅读需 9 分钟
Tian Pan
Software Engineer

一个用户问:“这是否符合第 28 条规定的‘转移’(transfer)?”四十轮对话后,模型给出了一个针对不同问题的答案。对话记录显示,模型回答了它收到的问题。用户正在阅读一份看起来像幻觉的投诉。两者都对。模型从未看到用户的提问——它看到的是你的摘要生成器对其进行的礼貌改写:“用户询问了第 28 条的适用性。”

“转移”一词就是问题所在。摘要生成器把它丢弃了,因为摘要生成器的损失函数被调优为保留事实而非措辞,而且评估准则从未学会区分改写主题和改写约束。主题被保留了。约束变成了迷雾。

这种失效模式是结构性的,而非偶发性的。任何通过模型生成的摘要来压缩长对话的应用,在关键路径上都有第二个模型——其质量契约通常被视为 Token 预算旋钮,而非一段产品逻辑。这种不对称性正是 Bug 所在。

那个基于已被你的上下文剪枝器丢弃的事实进行分支的智能体计划

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个运行时间较长的 Agent 在第 3 步生成了一个计划。计划的内容大致是:“如果第 1 步中 get_order 返回的订单状态为 shipped,则向客户发送一封物流追踪邮件;否则开启退款工单。”Agent 自信地选择了邮件分支。但客户从未收到追踪号码,因为订单实际上处于 pending 状态。你查看 Trace,期望能发现幻觉。但你发现的情况更糟:第 1 步的工具结果已经不在上下文中了。Pruner 在第 2 步和第 3 步之间将其剔除了——因为它在最近性排名中较低,而且为了给 12KB 的对话记录腾出空间。计划仍在运行。分支仍被选中。现在的决策指向了一个根本不存在的证据。

这在通常意义上并不是模型失败。模型生成了语法正确的计划,按顺序执行,并做出了分支决策。分支是基于一个曾经在上下文中但现在已不在其中的事实做出的。思维链编码了条件(if status == "shipped");而实际的状态在传递到需要它的步骤时被丢弃了。计划看起来是确定性的,但它已经被悄悄地从证据中剥离了。

那个‘总结’掉用户原始提问的压缩策略

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户询问我们的支持智能体:“为什么发票 INV-2025-08-44719 在 4 月 3 日被扣了两次款?”45 分钟和 18 次工具调用之后,智能体自信地回复道:该账户在该季度没有任何重复计费的证据。用户理所当然地投诉了。当我们回放追踪记录(trace)时,答案变得显而易见。智能体在第九轮对话时对内容进行了压缩(compacted)。摘要显示用户“在询问 4 月初的一笔重复收费”。其中并不包含字符串 “INV-2025-08-44719”。随后的每一次工具调用——账本查询、退款 API 查询、审计日志扫描——都是针对转述后的意图发出的,而不是用户输入的字面意义上的发票编号。

Bug 不在工具里,也不在模型的推理中。问题在于我们的上下文管理器(context manager)与每个下游组件之间都有一个契约,但没有人把它写下来。契约写着:“我会保留语义。”而组件需要的是:“我会保留字符串。”

抹除后续问题所需上下文的对话记忆修剪启发法

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个用户打开你的长会话智能体(agent),在第 3 轮对话时说:“我是素食主义者,而且预算有限。”对话继续进行。11 轮之后,裁剪器(pruner)开始运行。它计算 Token 数量,发现第 3 轮内容既陈旧又短小,于是为了将窗口维持在预算范围内,将其丢弃了。第 14 轮用户问:“我今晚该做点什么菜?”模型查看一个约束条件已不存在的窗口,推荐了一份 40 美元的肋眼牛排。用户觉得智能体变得越来越难用,打开满意度调查,给这次会话打了一个 2 分。

!["https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=%E6%8A%B9%E6%8E%89%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%97%AE%E9%A2%98%E6%89%80%E9%9C%80%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9A%84%E5%AF%B9%E8%AF%9D%E8%AE%B0%E5%BF%86%E8%A3%81%E5%89%AA%E5%90%AF%E5%8F%91%E6%B3%95"]辨

你的技术栈中没有任何环节会报告记忆失败。Token 预算仪表盘会显示窗口健康地维持在上限之下。延迟仪表盘会显示绿色。评估套件——将单轮回答与预留集进行对比评分——会报告没有退化。唯一能体现智能体能力下降的信号是一个差评,而你的产品团队会将其归咎于“模型方差”。但这并不是模型方差,而是裁剪启发法在错误的目标上,精准地执行了它被调优后该做的任务。

对话树:你的服务器作为日志存储的对话结构

· 阅读需 12 分钟
Tian Pan
Software Engineer

用户输入“其实,我的意思是五十,而不是十五”,点击最后一条消息上的铅笔图标并进行编辑。UI 表现得非常出色:它向用户展示修改后的消息,淡出旧消息,将助手过时的回复变为带删除线的“幽灵”状态,并呈现一段流畅的对话,读起来就像最初的错误从未发生过一样。用户心满意足地发送了下一轮对话。而智能体却用“十五”进行了回答。

Bug 不在模型身上。模型准确地接收了服务器发送的内容,而服务器发送的是:原始消息、原始助手回复、撤回动作、修改后的消息以及新的请求——所有内容按顺序拼接在一起,实时发送。用户在进行一场已经编辑过的对话。而智能体在进行一场从未被编辑过的对话。两份对话记录在第三轮开始分叉,此后再未统一,之后的每一轮对话都在为这一差距支付“利息”。