Skip to content

上下文越长越好吗?Agent 如何选择、压缩与预算 ​

内容摘要:模型每次只能看到你放进上下文窗口的东西。上下文工程(Context Engineering) 就是在有限 Token 预算内,决定"放什么、怎么压、丢什么、缓什么"。它比"写更长的 Prompt"更能决定 Agent 的成败。 难度:进阶 | 前置知识:01 基础、03 模型 API

常见误区

把全部聊天记录、检索文档和工具结果都塞进上下文,模型掌握的信息就会更多、答案也会更准。

先给结论 ​

上下文工程(Context Engineering)的目标是在有限 Token 预算内装配当前步骤真正需要的信息。过长上下文会增加成本、延迟、干扰与泄露面,必须进行选择、压缩、分区、截断保护和缓存。

带着这三个问题阅读:

  1. 一次 Agent 调用的上下文由哪些来源组成,优先级如何确定?
  2. 摘要压缩和直接截断分别可能丢失什么关键事实?
  3. 如何用预算器让系统在超限前显式降级,而不是静默丢指令?

一、概念与用途 ​

Agent 的每一次模型调用,上下文通常由多个来源拼装而成:

核心矛盾:来源很多,预算有限。放太多 → 超窗被截断、成本高、"大海捞针"式遗漏;放太少 → 模型缺关键信息、幻觉。上下文工程就是在这两者间做受控取舍。

不适用场景:如果任务极短、上下文远小于窗口,过度工程化反而增加复杂度。先测量,再优化。

二、上下文预算:先算账,再装配 ​

把窗口当成预算来分配。给每类内容设上限,超出就触发压缩或丢弃策略,而不是任由拼接:

python
from dataclasses import dataclass

@dataclass
class ContextBudget:
    """按来源分配 Token 预算(示例值,应按模型窗口调整)。"""
    total: int = 8000
    reserve_output: int = 1500      # 为输出预留
    system: int = 500
    tools: int = 800
    rag: int = 2500
    history: int = 2000
    # 剩余给当前输入与状态

    def input_budget(self) -> int:
        return self.total - self.reserve_output

def rough_tokens(text: str) -> int:
    ascii_n = sum(1 for c in text if ord(c) < 128)
    return round(ascii_n / 4 + (len(text) - ascii_n) / 1.5)

关键:一定要为输出预留(reserve_output)。只算输入不算输出,是超窗与截断的常见原因。

三、选择:只放"这一步需要的" ​

不是所有历史、所有检索结果都要进上下文。选择策略:

  • 相关性优先:RAG 结果按相似度取 Top-K,而非全塞(11)。
  • 近因优先:会话历史保留最近若干轮,早期轮次摘要化。
  • 任务相关:只注入与当前子任务有关的状态字段(09)。
  • 工具按需:不要把几十个工具声明全给模型,按场景路由可用工具子集(08)。

四、压缩:把长历史变短 ​

当历史超预算,用"滚动摘要(rolling summary)"压缩早期内容,保留近期原文:

python
def compress_history(history: list[dict], budget: int,
                     summarize) -> list[dict]:
    """保留最近消息原文;更早的用 summarize() 压成一条摘要。
    summarize 是注入的函数(可用一次便宜的模型调用实现)。"""
    kept: list[dict] = []
    used = 0
    # 从最近往前保留原文
    for msg in reversed(history):
        t = rough_tokens(msg["content"])
        if used + t > budget:
            break
        kept.append(msg)
        used += t
    kept.reverse()
    older = history[: len(history) - len(kept)]
    if older:
        summary = summarize(older)  # [非确定性] 摘要由模型生成
        kept.insert(0, {"role": "system", "content": f"[早期对话摘要] {summary}"})
    return kept

权衡:摘要会丢细节、可能引入摘要幻觉。关键事实(如用户确认过的决定、幂等键)应存入状态/记忆(09、10),不要只依赖摘要。

五、截断:最危险的"静默失败" ​

超窗时若简单地"砍掉最前面的内容",可能把系统指令或关键约束砍掉,导致 Agent 突然"失忆"、越界。

正确做法:

  • 系统指令与安全约束固定保留,永不参与截断。
  • 截断发生时记录日志/告警(可观测性,17),不要静默。
  • 优先压缩可再生内容(历史、检索结果),保护不可再生内容(状态、幂等键)。

六、缓存:省钱又提速 ​

  • 提示缓存(Prompt Caching):部分厂商支持缓存长而稳定的前缀(如系统指令、长工具声明),命中后更便宜更快。把稳定内容放前面、变动内容放后面以提高命中率。
  • 检索缓存:相同查询的 RAG 结果可短期缓存。
  • 语义缓存:对高度相似的请求复用答案(需谨慎,避免返回过期/串号结果)。

[待验证]:提示缓存的可用性、命中规则、计费方式各厂商不同,使用前查官方文档(verified_at: 2026-09-07)。

七、失败路径 ​

  • 只算输入不算输出 → 输出被截断成半截 JSON。
  • 历史无限增长 → 逐渐超窗,模型"忘记"早期约束。
  • RAG 全量注入 → 相关信息被噪声淹没("lost in the middle")。
  • 摘要丢关键事实 → Agent 重复已完成的动作。

八、测试与验证 ​

  • 对典型会话构造"超长历史"用例,断言压缩后系统指令仍在、Token 在预算内。
  • 对截断路径写测试:确认关键约束不被砍、且有日志。
  • 度量:上下文 Token 分布、超窗率、截断触发率(17)。

九、安全与权限 ​

  • 进入上下文的外部内容(RAG/工具结果)默认不可信,需按 04、14 做隔离与过滤。
  • 摘要/缓存中不得混入其他用户的数据(多租户串号是严重事故)。

十、性能与成本 ​

  • 上下文长度直接决定成本与延迟。压缩、选择、缓存是 Agent 降本的三大杠杆(18)。

十一、框架差异 ​

  • LangChain 1.0 用"中间件"(如 SummarizationMiddleware)做上下文压缩;其他框架各有机制。原则一致:预算化、选择、压缩、保护关键内容。

十二、关联章节 ​

面试复述 ​

上下文不是越长越好,而是要与当前决策相关、可信且在预算内。系统应先保留指令与任务状态,再选择检索证据和历史,最后用可观测的压缩或降级处理超限。

复述时先给结论,再补机制、适用边界和一个可验证指标;不要只罗列框架名称或产品功能。

迁移练习 ​

为一个包含系统指令、20 轮历史、5 段检索结果和3个工具返回的请求制定 Token 预算表,并明确超限时的保留顺序与降级动作。

检索关键词 ​

上下文工程 Context Engineering 上下文预算 滚动摘要压缩 截断风险 提示缓存 Prompt Caching

官方来源 ​

  • 各模型厂商"context window / prompt caching"文档;RAG"lost in the middle"相关研究(verified_at: 2026-09-07)。

ICP 备案号待补充 · Gitee 主页