三种Agent上下文管理策略量化对比评测

2026-06-19阅读 0热度 0
Context

上下文的线性成本问题

Agent 可不是一次简单的 API 调用——它得记住对话历史。每多一轮对话,上下文窗口就膨胀一点,直到两个问题冒出来:

Agent 系列(22):Context Engineering 深度——三种上下文管理策略的量化对比

这不是纸上谈兵。假设一个 30 轮的项目讨论,全量历史大概消耗 2,500 tokens;到 100 轮时,这个数字会涨到 8,000 tokens,而且它是线性增长的。

常见的应对策略有这三种:

策略 做法 直觉上的代价
Naive 全量历史传入 贵,但准
Sliding Window 只保留最近 N 条 省,但可能丢信息
Rolling Summary LLM 压缩旧消息 + 保留近期 均衡?

这次直接用真实的 benchmark 来验证一下,这些“直觉上的代价”到底准不准。

Demo 设计

对话构造

我们构造了一个 30 轮的项目讨论,内容涵盖数据库选型、缓存配置、迁移责任、部署平台、CI/CD、认证方案等 30 个技术决策。一个关键设计是:重要决策被刻意放在了第 1-4 轮(最早期),后续才是“近期内容”。这么做就是为了强制暴露出上下文丢失的问题。

三种策略实现

Strategy 1:Naive(基准)

def run_naive(history: list, query: str, keywords: list[str]) -> StrategyResult:
    msgs = [SystemMessage(content=SYSTEM_PROMPT)] + history + [HumanMessage(content=query)]
    tokens = count_messages_tokens(msgs)
    t0 = time.time()
    text = str(llm.invoke(msgs).content)
    return StrategyResult(text, tokens, time.time() - t0, recall_score(text, keywords))

Strategy 2:Sliding Window(截断)

def run_sliding_window(history: list, query: str, keywords: list[str], window: int = 12) -> StrategyResult:
    recent = history[-window:] # 只保留最近 12 条消息
    msgs = [SystemMessage(content=SYSTEM_PROMPT)] + recent + [HumanMessage(content=query)]
    ...

Strategy 3:Rolling Summary(滚动摘要)

def summarize(messages: list) -> str:
    """把旧消息块压缩成要点列表。"""
    text = "n".join(f"{'User' if isinstance(m, HumanMessage) else 'Assistant'}: {m.content}" for m in messages)
    prompt = (
        "Compress the following project discussion into concise bullet points.n"
        "Preserve: every decision made, owner names, technical choices, exact numbers.n"
        "Remove: conversational filler, redundancy.nn"
        f"Conversation:n{text}nn"
        "Bullet-point summary:"
    )
    return str(llm.invoke([HumanMessage(content=prompt)]).content)

def run_rolling_summary(history: list, query: str, keywords: list[str],
        recent_window: int = 8, cached_summary: str | None = None,
    ) -> tuple[StrategyResult, str]:
    old = history[:-recent_window]
    recent = history[-recent_window:]
    summary = cached_summary if cached_summary is not None else summarize(old)
    # 摘要注入系统 Prompt
    sys = SYSTEM_PROMPT + f"nn## Earlier Meeting Notes (Summary)n{summary}"
    msgs = [SystemMessage(content=sys)] + recent + [HumanMessage(content=query)]
    ...

这里有个关键设计:cached_summary 参数允许摘要只构建一次,后续 4 个测试查询都能复用。摘要的构建成本是 38.2s,但只付一次。

测试查询(全部针对最早期决策)

Query 1: What database did we choose? Who owns it, and why? Keywords: postgresql / timescaledb / da vid / acid / time-series
Query 2: What's our caching technology and TTL configuration? Keywords: redis / cluster / 1 hour / 5 minute / 16
Query 3: Who is responsible for database migrations, and what approvals are needed? Keywords: sarah / backend lead / 2 / senior / flyway
Query 4: What deployment platform and cluster configuration did we decide on? Keywords: kubernetes / eks / helm / argocd / 3-node

召回率的计算很简单:关键词命中数 / 总关键词数。这种方法确定性强,不需要额外依赖 LLM 评分。

运行结果

History: 30 turns | Full context: ~2,485 estimated tokens
Rolling summary build time: 38.2s (one-time, cached for all queries)

逐查询召回

Query           Naive   Sliding   Rolling
──────────────────────────────────────────────────────
DB decision (turn 1)   100%     0%        0%
Cache config (turn 2)  60%      40%       80%
Migration ownership (turn 3)  80%  20%       60%
Deployment platform (turn 4)  80%     20%       60%

聚合指标

Strategy               A vg Tokens   A vg Latency   A vg Recall
────────────────────────────────────────────────────────────────
Naive (full history)      2,513         9.6s          80%
Sliding Window (last 12)    604          17.4s         20%
Rolling Summary           1,289         8.5s          50%

Token reduction vs Naive:
Sliding Window: -76%
Rolling Summary: -49%

Key insights:
Highest recall: Naive (full history)
Most token-efficient: Sliding Window (last 12)
Best quality/cost: Rolling Summary

三个反直觉发现

说实话,结果里有三个发现挺反直觉的。

发现 1:截断的代价远超预期

Sliding Window 节省了 76% 的 token,但召回率从 80% 直接跌到 20%。

这其实不奇怪——第 1-4 轮的决策在 30 轮后已经被截断窗口完全切掉了。但那个程度确实让人警醒。Query 1(数据库决策)的召回率是 0%:5 个关键词一个都没命中。模型不是“记不太清”,而是完全不知道这个决策存在过。

结论很明确:Sliding Window 适合“上下文无关”的短期任务;但对于“需要追溯早期决策”的场景,它就是一场灾难。

发现 2:摘要偶尔比原始更准

Query 2(缓存配置)的结果很有意思:Rolling Summary 80% > Naive 60%。

为什么摘要比全量历史更准?原始对话里,缓存讨论分散在多轮中,模型需要在 2,500 tokens 里“找”到相关内容;而摘要把所有决策压缩到了一个结构化列表里,关键信息密度更高,模型提取起来也更容易。

这也暴露了 Naive 策略的一个隐藏问题:上下文越长,信号越稀疏,噪声越多,模型反而可能“找不到”近在眼前的答案。

发现 3:压缩损失是真实 bug

Query 1 的 Rolling Summary 召回率为 0%,但摘要里其实明确写着:

- Database: PostgreSQL with TimescaleDB extension (Da vid, DB Lead)

关键词 postgresqltimescaledbda vid 都在摘要里,但模型的回答里就是没出现。多次复现排查后发现:模型确实回答了“数据库选型”,但没有明确提到“ACID compliance”和“time-series”——这两个词是原始讨论里的技术理由,而摘要只保留了最终决策,没有保留选择原因。

这就是压缩的本质代价:摘要保留了“做了什么”,但损失了“为什么这样做”。对于需要推理依据(而非只需要事实)的查询,这个损失会很大。

何时选择哪种策略

任务类型                                 推荐策略
──────────────────────────────────────────────────────────
短期、无状态、每次独立任务                  Naive(历史本来就短)
长对话、只关心最近几轮内容                  Sliding Window(省 token)
长对话、需要追溯早期决策                    Rolling Summary(均衡)
需要精确还原早期技术决策的原因              Naive(或 Rolling + 保留原因字段)

如果打算用 Rolling Summary,有几个生产优化要点值得留意:

  1. 摘要粒度:每 20-30 轮触发一次,不要频繁压缩。如果每轮都压缩,本质上成本和延迟跟 Naive 策略就差不多了。
  2. 摘要 Prompt:要求保留数字和名称。关键语句是 Preserve: every decision, owner names, exact numbers
  3. 双层结构:采用 summary(旧)+ recent(近期) 的结构,不要把摘要和近期消息混在一起重新压缩。
  4. 惰性构建:摘要只在首次需要时构建,然后缓存起来,不随每次对话重新触发。

实际的 Rolling Summary 输出

这是 demo 从 22 轮对话构建的摘要,可以直观地看到压缩比和信息保留的效果:

- Database: PostgreSQL with TimescaleDB extension (Da vid, DB Lead)
- Caching: Redis Cluster, TTL: 1h for sessions, 5m for dashboards, 16GB max memory
- Database Migrations: Sarah (Backend Lead), Flyway, 2 senior-engineer approvals
- Deployment: Kubernetes on AWS EKS, Helm charts, 3-node prod, 1-node staging, ArgoCD
- API Versioning: URL path versioning, 2 major versions, 6m deprecation notice
- Authentication: JWT tokens, 24h TTL for users, 1h for admins, 30-day Redis refresh TTL
- Logging: Structured JSON, Fluentd → ELK, 30-day hot, 1-year cold in S3 Glacier
- Rate Limiting: Token bucket, 100 req/min standard, 1000 req/min premium, Redis
- CI/CD: GitHub Actions + ArgoCD, blue-green, 5m health check
- Internal Services: REST external, gRPC + Protocol Buffers internal
... (共 22 条,原始 ~1,800 tokens → 压缩后 ~600 tokens,压缩比 3:1)

设计 Checklist

策略选择

  • 明确任务的上下文追溯深度需求
  • Sliding Window 适合“近期信息足够”的场景
  • Rolling Summary 适合“需要追溯但不需要完整原文”的场景
  • Naive 适合“历史本来就短”或“需要完整推理链”的场景

Rolling Summary 实现

  • Prompt 明确要求保留:决策、负责人、数字、技术选择
  • 摘要与近期消息分开放(不混入 messages 列表)
  • 摘要构建后缓存,不重复调用
  • 触发阈值:消息超过 N 条时才压缩(N 建议 20-30)

召回率验证

  • 覆盖早期轮次的测试查询(不能只测近期)
  • 关键词要包含技术理由,不只是结论(“acid” 不只是 “postgresql”)
  • 在选定策略的上线前用小样本量化验证

总结

最后用三条结论来收尾:

  1. 截断是双刃剑:Sliding Window 能节省 76% 的 token,但对早期决策的召回率可能跌到 0%。不做 benchmark 验证,很难发现这个临界点在哪里。
  2. 摘要的压缩损失是“为什么”而非“是什么”:决策事实被保留了,但技术理由会在压缩中消失。需要追溯原因的场景,要么用 Naive,要么在摘要 Prompt 里显式要求保留原因。
  3. Rolling Summary 的摘要是一次性成本:38s 的构建时间听起来挺吓人,但这是离线构建后缓存的。运行时每次只加 ~600 tokens,而不是 2,500 tokens。

参考资料

  • LangChain Message History 文档
  • Anthropic Context Caching 文档
  • 本系列完整 Demo 代码:agent-21-context-engineering-deep
免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策