上下文窗口预算
上下文窗口是有限资源,有边际收益递减。需要系统化的预算管理策略:最近 N 轮原始消息 + 早期摘要 + 结构化状态 + 动态知识注入,在有限 token 内最大化信息密度。
#type / concept
#status / evergreen
#tech / ai
[!info] related notes
- 所属 MOC: Context Engineering MOC
- 核心概念: Context Engineering
- 组装层: Context Builder 模式
- 成本优化: LLM Token 成本优化策略
- 反模式: 上下文反模式
上下文窗口预算
上下文窗口是有限资源,且有边际收益递减——无关内容会削弱模型关注重点。需要系统化的预算管理策略,在有限 token 内最大化信息密度。
核心问题
Claude 官方在 context editing 文档里明确提到:
上下文是有限资源,且有边际收益递减;无关内容会削弱模型关注重点。
LangGraph 官方也提到,开启短期记忆后,长对话可能超过上下文窗口,常见方案包括 trim messages、delete messages、summarize messages。
分层策略
推荐的上下文分层:
┌──────────────────────────────────────────────┐
│ Layer 1: System Prompt + Rules │ 固定,每轮不变
├──────────────────────────────────────────────┤
│ Layer 2: Structured State │ 结构化问诊状态
│ (consultation_state + user_profile) │ 每轮更新,精简
├──────────────────────────────────────────────┤
│ Layer 3: Recent Messages │ 最近 6~10 轮原始消息
│ (raw messages, no summarization) │ 完整保留
├──────────────────────────────────────────────┤
│ Layer 4: Conversation Summary │ 早期对话摘要
│ (generated summary of older messages) │ 压缩后的文本
├──────────────────────────────────────────────┤
│ Layer 5: Retrieved Knowledge │ 动态注入 top 3~5
│ (RAG results, tool outputs) │ 每轮重新检索
├──────────────────────────────────────────────┤
│ Layer 6: Current User Input │ 当前用户消息
└──────────────────────────────────────────────┘
各层预算分配
假设总预算为 8000 tokens(可配置):
| 层 | 预算占比 | 典型 token 数 | 说明 |
|---|---|---|---|
| System Prompt + Rules | 15% | ~1200 | 固定开销 |
| Structured State | 10% | ~800 | JSON 格式,精简 |
| Recent Messages | 40% | ~3200 | 最近 6~10 轮 |
| Conversation Summary | 15% | ~1200 | 早期对话压缩 |
| Retrieved Knowledge | 15% | ~1200 | top 3~5 条知识 |
| Current User Input | 5% | ~400 | 当前消息 |
Recent vs Summary 的分界
消息数量 < 阈值(如 12 条):
→ 全部作为 recent_messages,无 summary
消息数量 ≥ 阈值:
→ 最近 8 条作为 recent_messages
→ 更早的 messages 生成 conversation_summary
→ summary 替代那些原始消息
Summary 生成策略
def generate_summary(older_messages: list[Message]) -> str:
"""把早期消息压缩成摘要"""
prompt = f"""请将以下对话压缩成简洁的摘要,保留关键信息:
- 用户的主诉和症状
- 已确认和未确认的信息
- AI 的建议和判断
- 重要的转折点
对话记录:
{format_messages(older_messages)}
请用中文输出,不超过 200 字。"""
return llm.generate(prompt)
注意:Summary 不替代结构化状态。Summary 是辅助,帮助模型理解对话脉络;consultation_state 才是驱动逻辑的核心。
Summary 版本管理
每次生成新的 summary 时,增加版本号:
{
"conversation_summary": "...",
"summary_version": 3,
"summary_generated_at": "2026-06-30T10:00:00Z",
"summarized_message_range": ["turn_001", "turn_012"]
}
Token 估算
不同内容的 token 密度不同:
| 内容类型 | 中文 token 密度 | 说明 |
|---|---|---|
| 自然语言对话 | ~1.5 字/token | 中文字符比英文多占 token |
| JSON 结构化数据 | ~2 字/token | key 本身也占 token |
| 代码 | ~3 字/token | 英文为主 |
| Summary | ~1.2 字/token | 压缩后的文本密度更高 |
粗略估算公式:
token_count ≈ chinese_chars / 1.5 + english_words * 1.3 + json_keys * 2
动态知识注入
知识库检索结果应该每轮动态注入,而不是固定塞入:
def retrieve_for_context(user_message, state, budget):
"""按需检索,控制 token 预算"""
# 根据当前状态决定检索策略
if state.stage == "collecting_details":
query = f"{state.chief_complaint} {state.body_parts} 症状鉴别"
elif state.stage == "preliminary_assessment":
query = f"{state.chief_complaint} {state.symptoms} 康复方案"
else:
query = user_message
results = knowledge_base.search(query, top_k=5)
# 截断到预算内
total_tokens = 0
filtered = []
for r in results:
r_tokens = estimate_tokens(r.content)
if total_tokens + r_tokens > budget:
break
filtered.append(r)
total_tokens += r_tokens
return filtered
工具结果处理
工具调用结果(如 RAG 检索、知识库查询)不应全量保留:
| 策略 | 适用场景 |
|---|---|
| 只保留摘要 | 长文档检索结果 |
| 保留 top N 条 | 列表型结果 |
| 保留关键字段 | 结构化查询结果 |
| 完整保留 | 短结果(< 200 tokens) |
def summarize_tool_result(result, max_tokens=300):
if estimate_tokens(result) <= max_tokens:
return result
return llm.generate(f"请压缩以下内容到 {max_tokens} tokens 以内:\n{result}")
预算溢出处理
当总 token 超过预算时,按优先级裁剪:
优先级从高到低:
1. system_prompt(不裁剪)
2. current_user_input(不裁剪)
3. consultation_state(不裁剪)
4. recent_messages(从最旧的开始裁剪)
5. retrieved_knowledge(从相关度最低的开始裁剪)
6. conversation_summary(最后裁剪)
def trim_to_budget(context_bundle, token_budget):
total = estimate_total_tokens(context_bundle)
if total <= token_budget:
return context_bundle
# 先裁剪 recent_messages(从最旧开始)
while total > token_budget and len(context_bundle.recent_messages) > 2:
removed = context_bundle.recent_messages.pop(0)
total -= estimate_tokens(removed)
# 再裁剪 retrieved_knowledge(从最不相关开始)
while total > token_budget and len(context_bundle.retrieved_knowledge) > 1:
removed = context_bundle.retrieved_knowledge.pop()
total -= estimate_tokens(removed)
return context_bundle
测试用例
| 场景 | 验证点 |
|---|---|
| 短对话(< 12 轮) | 无 summary,全量 recent |
| 长对话(> 20 轮) | 有 summary,recent 只保留最近 8 轮 |
| 超长对话(> 50 轮) | summary 更新,recent 可能进一步缩减 |
| token 超限 | 自动裁剪,不报错 |
| 大量知识检索结果 | 只注入 top N,截断到预算内 |
| 空会话 | 不报错,context 正常组装 |