Agent 不是知道得越多越好

假设你请一位新同事修复支付 Bug,却一次性把公司五年的聊天记录、所有代码仓库、完整监控日志和几十份旧规范都塞给他。他获得的信息很多,但真正有用的线索反而更难找到。

Agent 也一样。

上下文窗口再大,也不等于应该把所有内容放进去。模型做判断时真正需要的是:与当前任务相关、来源清楚、足够新鲜、优先级明确,而且长度可控的信息。

负责选择、组织和维护这些信息的系统,就是 Harness 的第二个核心部分:上下文引擎

如果把模型比作厨师,上下文引擎不是仓库,而是备菜台。仓库里可以有一万种食材,但当前这道菜只应该摆上真正要用的那几样。

本文是《把 Harness 讲清楚:Agent 背后的运行系统》八个核心部分的第二篇。

什么是上下文

上下文是一次模型调用时,模型实际能够看到的全部信息。它通常包含:

  • 系统规则和身份说明;
  • 当前用户请求;
  • 最近的对话历史;
  • 任务契约和当前计划;
  • 项目规范与相关文件;
  • 工具定义和工具结果;
  • 长期记忆或用户偏好;
  • 当前环境、时间和运行状态。

上下文并不是数据库中“已经保存”的全部内容。只有被选中并组装进本次请求的内容,才会影响模型判断。

所以,上下文工程的核心问题不是“我们拥有什么数据”,而是“在这一刻,模型应该看到什么”。

上下文引擎的五项工作

发现

根据任务找到候选信息,例如相关文件、目录内规则、历史决策、接口文档和最近的工具结果。

筛选

移除与当前任务无关、重复、过期或可信度不足的内容。

排序

让不可违背的系统规则优先于项目建议,让当前任务信息优先于陈旧历史,让直接证据优先于模型推断。

压缩

当任务变长时,把已经完成的探索过程压缩成事实、决策和未解决问题,而不是永久保留全部原始输出。

标注来源

明确一段信息来自用户、项目文件、工具结果、外部网页,还是 Agent 自己的推断。来源不同,可信度和可执行权限也不同。

一次上下文组装的流程

任务契约 + 当前步骤
          ↓
    生成信息需求
          ↓
检索文件、记忆、规则和工具结果
          ↓
去重、过滤、检查新鲜度和权限
          ↓
按优先级与 token 预算排序
          ↓
必要时摘要或截取
          ↓
组装本次模型请求
          ↓
记录“模型实际看到了什么”

最后一步很重要。没有可追踪的上下文快照,出现错误时就无法判断到底是模型推理错了,还是 Harness 给了错误资料。

上下文不是越多越好的三个原因

噪声会稀释重点

当真正关键的三条规则埋在几万行日志中,模型更容易忽略它们。长上下文解决了容量问题,却没有自动解决注意力分配问题。

过期信息会制造冲突

旧文档说接口字段叫 userId,新代码已经改为 accountId。如果两份内容同时出现却没有时间和来源标记,模型很可能选择错误版本。

上下文有真实成本

更多 token 意味着更高延迟和费用。频繁变化的长前缀还会降低缓存复用。上下文引擎需要把信息价值当作有限预算来分配。

给信息建立优先级

可以为候选内容计算一个简单分数:

score = 相关性 × 新鲜度 × 可信度 × 必要性 - token 成本

这不是必须采用的数学公式,而是一种思维方式。

  • 相关性:是否直接帮助当前步骤;
  • 新鲜度:是否仍然有效;
  • 可信度:来自源码、工具事实,还是未经验证的推断;
  • 必要性:不提供它是否会违反规则或导致错误;
  • 成本:加入后占用多少上下文空间。

系统规则和安全边界不应只靠分数竞争,它们属于必须保留项。评分更适合在大量候选资料之间做选择。

最小上下文选择器

下面用伪代码表达一个基础实现:

def build_context(task, step, budget):
    required = load_required_rules(task)
    candidates = []

    candidates += search_workspace(step.query)
    candidates += search_memory(task.objective)
    candidates += recent_tool_results(task.id)

    candidates = remove_duplicates(candidates)
    candidates = reject_expired_or_unauthorized(candidates)
    candidates = rank_by_relevance(candidates, step)

    selected = list(required)
    remaining = budget - token_count(required)

    for item in candidates:
        rendered = compress_if_needed(item, remaining)
        if token_count(rendered) <= remaining:
            selected.append(rendered)
            remaining -= token_count(rendered)

    return attach_source_metadata(selected)

真实系统还需要处理取消、检索失败、敏感信息脱敏和缓存,但这段代码已经表达了核心:先放必须信息,再用剩余预算选择最有价值的内容。

长任务为什么需要压缩

Agent 连续工作几十轮后,历史里会积累大量搜索结果、失败尝试和重复讨论。如果把它们原样带入每一轮,请求会越来越慢,也越来越容易跑偏。

压缩的目标不是简单缩短文本,而是保留未来决策仍然需要的状态:

原始历史
├─ 尝试过哪些方案
├─ 每次工具的完整输出
├─ 大量中间讨论
└─ 已经解决的问题
          ↓ 压缩
任务状态摘要
├─ 已确认事实
├─ 已采用决策及原因
├─ 已修改对象
├─ 尚未解决的问题
├─ 不得重复的失败方案
└─ 下一步与完成标准

好的摘要应当可以让一个没有看过前文的 Agent 接手,而不会重复破坏性操作或遗失关键约束。

记忆与上下文不是同一个东西

记忆是可能在未来被再次使用的信息存储,上下文是本轮真正提供给模型的内容。

例如,系统记住用户偏好使用中文,这是记忆;当前请求组装时把“使用中文回答”加入系统提示词,才成为上下文。

两者之间应该有检索和准入层。不能因为某条信息曾经被保存,就让它永久出现在每一次请求中。长期记忆还应支持来源、过期时间、用户更正和删除。

工具结果怎样进入上下文

工具返回内容往往很大。上下文引擎不应该把数据库全部行、完整构建日志或整份网页无条件回灌给模型。

更稳妥的做法是:

  1. 工具保留完整规范结果,便于程序处理和审计;
  2. 为模型生成有长度上限的视图;
  3. 明确标注是否截断,以及完整数据的位置;
  4. 只在模型确实需要时继续读取某个片段。

例如搜索工具可以返回总命中数、按文件分组的前 20 条结果和 truncated: true,而不是假装展示的是完整结果。

必须区分事实、规则和推断

把不同来源混在一段自然语言里,是上下文系统常见的危险做法。

更清楚的结构是:

[SYSTEM RULE]
生产环境禁止自动部署。

[USER REQUEST]
修复登录错误并准备发布说明。

[TOOL FACT]
测试 auth/session.test.ts 已通过,退出码为 0。

[AGENT INFERENCE]
根因可能是刷新令牌并发更新,需要继续验证。

工具输出也不应天然被当作指令。网页、邮件和仓库文件中都可能出现“忽略之前规则”之类的文本,它们是待分析数据,不是可以覆盖系统规则的新命令。

常见的失败方式

一次性读取整个仓库

看起来信息充分,实际浪费预算,并让关键文件失去突出度。应该先搜索和建立地图,再按需深入。

只做语义相似度检索

语义相似不等于权威。旧设计稿可能比当前源码更像用户问题,却已经失效。排序必须结合时间、来源和文件层级。

摘要时丢掉否定条件

“允许修改代码,但禁止更改数据库”如果被压缩成“允许修改项目”,风险会被放大。安全约束和未完成事项不应被模糊摘要。

不记录截断

模型看到前 100 行却以为看到了完整文件,会做出错误结论。所有截断视图都应该显式说明范围。

把模型推断写成永久事实

未经工具或用户验证的猜测,应该保持推断状态。只有获得证据后才能升级为事实。

从原型开始怎么做

可以先建立四层上下文:

  1. 固定系统与安全规则;
  2. 当前任务契约;
  3. 最近对话和工具结果;
  4. 按当前步骤检索出的项目资料。

然后为每一层设置明确预算,记录来源与截断信息。任务超过一定轮次时,把旧历史压缩成结构化状态摘要,并保留原始日志供审计,而不是继续塞给模型。

上下文引擎检查清单

  • 模型本轮真正需要解决什么问题?
  • 必须始终保留的规则有哪些?
  • 候选资料是否检查了来源、新鲜度和权限?
  • 是否去除了重复与无关内容?
  • 工具结果过大时是否提供渐进式读取?
  • 截断和摘要是否明确标记?
  • 是否区分用户输入、系统规则、工具事实和 Agent 推断?
  • 长任务摘要是否保留关键决策、未完成事项和禁止条件?
  • 能否还原某次模型调用实际看到的上下文?

最后理解上下文引擎

模型能力决定了它“能不能想明白”,上下文引擎决定了它“拿什么来想”。

很多看似模型不够聪明的问题,实际是系统给了错误、过期或过量的信息。一个成熟 Harness 不追求把上下文窗口塞满,而是像一位可靠的研究助理:在正确的时间,把正确的证据摆到桌面上,并清楚说明每条信息从哪里来。

一句话总结:上下文的质量,通常比上下文的数量更重要。