Appearance
67. 上下文长度与规划缺陷
难度 P1 高频 · 岗位 应用 · 频率 ★★★★☆ · 预计阅读 8 min 公司 OpenAI · Anthropic 相关题 Q32 记忆架构 · Q60 设计模式 · Q65 多智能体
本题阅读地图
- 面试场景还原 — 1 min
- TL;DR 速记 — 30 sec
- 图解 — 30 sec
- 详细解析 — 5 min
- 4.1 问题本质:为什么上下文会撑满?
- 4.2 规划失效的表现
- 4.3 解决方案一:外部状态存储
- 4.4 解决方案二:历史摘要与压缩
- 4.5 解决方案三:结构化 Prompt 设计
- 4.6 解决方案四:分阶段执行
- 常见踩坑与反例 — 1 min
- 面试官可能继续追问 — 1 min
面试场景还原
�面试官:你在做一个能够自动完成复杂任务的 Agent,比如「调研竞争对手并输出 20 页分析报告」。用户反馈说 Agent 跑到一半就开始胡言乱语,或者忘记了最初的目标。你知道是什么原因吗?
🙋♂️我:可能是长任务导致的上下文丢失?LLM 上下文窗口有限,执行多步后前面的信息被挤掉了。
👔面试官:对,这是 Agent 长任务的核心瓶颈。你能具体讲讲这个问题的机制吗?以及有哪些解决方案?
🙋♂️我:可以把重要信息存到外部,比如数据库或者向量存储,不依赖上下文。
👔面试官:很好。还有其他的策略吗?比如怎么压缩历史信息?怎么设计 Prompt 结构?
TL;DR 速记
- 核心问题:上下文窗口有限 + 长任务执行 = 历史信息丢失 → 规划失效
- 失效链:上下文限制 → 早期信息丢失 → 目标遗忘 → 规划崩溃 → 任务失败
- 解决方案 1:外部状态存储(数据库、文件系统、向量存储)
- 解决方案 2:历史摘要(滚动窗口、关键信息提取、层次摘要)
- 解决方案 3:结构化 Prompt(目标置顶、分块组织、优先级排序)
- 解决方案 4:分阶段执行(阶段独立、阶段间传递摘要而非完整历史)
- 工程原则:不把状态完全放在上下文里
图解
图 1:上下文撑满导致规划失效
图 2:解决方案架构
详细解析
问题本质:为什么上下文会撑满?
Agent 上下文增长模型:
| 步骤 | 新增内容 | 大致 Token 数 |
|---|---|---|
| 系统 Prompt | 角色定义、工具描述 | 500-2000 |
| 用户请求 | 任务描述 | 100-500 |
| 每步思考 | Thought / Reasoning | 200-500 |
| 每步工具调用 | Tool name + parameters | 100-300 |
| 每步工具结果 | API 返回 / 搜索结果 | 500-5000 |
| 每步观察 | Observation / 总结 | 200-500 |
示例计算:
假设一个需要 20 步的复杂任务:
系统 Prompt: 1500 tokens
用户请求: 300 tokens
每步消耗: Thought(300) + Action(200) + Observation(1000) = 1500 tokens
总消耗 = 1500 + 300 + 20 × 1500 = 31,800 tokens对于 4K 上下文窗口,第 3 步就溢出了。 对于 32K 上下文窗口,第 20 步左右溢出。 即使 200K 上下文,100 步以上的长任务也会出问题。
溢出后果:
| 后果 | 表现 |
|---|---|
| 目标遗忘 | 忘记最初要做什么 |
| 重复工作 | 重复执行已完成的步骤 |
| 规划偏离 | 中途走偏,与目标无关 |
| 幻觉增加 | 基于不完整上下文产生错误 |
规划失效的表现
长期规划稳定性问题:
LLM 在长期任务中的表现远不如人类预期:
目标漂移(Goal Drift)
- 初始目标:「调研 AI 编程工具市场」
- 第 10 步后变成:「介绍 GitHub Copilot 功能」
- 范围收窄,忘记宏观目标
循环陷阱(Loop Trap)
- 在相似步骤间循环
- "搜索信息 → 发现不足 → 再搜索 → 还是不足..."
过早收敛(Premature Convergence)
- 基于部分信息就下结论
- 没有充分调研就给出答案
行动遗忘(Action Forgetting)
- 忘记已经做了什么
- 重复调用相同工具
解决方案一:外部状态存储
核心思想: 把任务状态、中间结果、待办事项存到外部存储,不占用 LLM 上下文。
存储层次:
python
class ExternalStateManager:
"""外部状态管理器"""
def __init__(self):
self.db = Database() # 结构化数据
self.vector_store = VectorStore() # 语义检索
self.file_system = FileSystem() # 大文件
async def save_state(self, session_id: str, state: dict):
"""保存任务状态"""
# 关键元数据存数据库
await self.db.save({
"session_id": session_id,
"goal": state["goal"],
"progress": state["progress"],
"current_step": state["current_step"],
"timestamp": datetime.now()
})
# 大文本内容存文件
if state.get("large_content"):
await self.file_system.save(
f"/tmp/agent/{session_id}/content.txt",
state["large_content"]
)
# 中间结果存向量库(可检索)
if state.get("findings"):
for finding in state["findings"]:
await self.vector_store.add(
text=finding["content"],
metadata={"session_id": session_id, "type": "finding"}
)
async def load_relevant_context(self, session_id: str, query: str) -> str:
"""按需加载相关上下文"""
# 只加载与当前查询相关的信息
relevant = await self.vector_store.search(query, k=5)
# 格式化为紧凑的上下文
context = "\n".join([r.content for r in relevant])
return context使用示例:
python
async def agent_with_external_state(task: str):
session_id = generate_id()
state_manager = ExternalStateManager()
# 初始化状态
await state_manager.save_state(session_id, {
"goal": task,
"progress": 0,
"findings": [],
"todo_list": ["搜索背景", "收集数据", "分析", "撰写"]
})
while not is_complete(session_id):
# 加载精简状态(不是完整历史)
state = await state_manager.load_compact_state(session_id)
# 构建 Prompt(只包含必要信息)
prompt = f"""
目标:{state['goal']}
已完成:{state['completed_steps']}
当前任务:{state['current_task']}
关键发现:{state['key_findings']}
请执行下一步。
"""
# LLM 决策
action = await llm.decide(prompt)
# 执行并更新外部状态
result = await execute(action)
await state_manager.update(session_id, action, result)优势:
- 上下文只包含必要信息
- 可以处理超长任务(100+ 步)
- 任务状态可持久化、可恢复
- 支持跨会话恢复
解决方案二:历史摘要与压缩
滚动摘要(Rolling Summary):
python
class RollingSummarizer:
"""滚动历史摘要器"""
def __init__(self, window_size: int = 5):
self.window_size = window_size # 保留最近 N 步详细历史
self.summary = "" # 更早历史的摘要
async def add_step(self, step: dict) -> str:
"""添加新步骤,返回压缩后的历史"""
# 保留最近窗口的详细历史
recent_steps = self.get_recent(self.window_size)
# 如果超出窗口,摘要旧历史
if len(self.history) > self.window_size:
old_steps = self.history[:-self.window_size]
self.summary = await self.summarize(old_steps)
self.history = recent_steps
# 组合:摘要 + 近期详细
context = f"""
历史摘要:{self.summary}
最近 {self.window_size} 步详细:
{format_steps(recent_steps)}
"""
return context
async def summarize(self, steps: list) -> str:
"""对历史步骤进行摘要"""
prompt = f"""
请将以下 Agent 执行步骤摘要为关键信息:
- 已完成的主要任务
- 获得的关键发现
- 当前的待办事项
- 任何需要注意的问题
步骤列表:{steps}
"""
return await llm.generate(prompt)层次摘要(Hierarchical Summary):
python
class HierarchicalMemory:
"""层次化记忆系统"""
def __init__(self):
self.short_term = [] # 最近 3 步(详细)
self.medium_term = "" # 最近 10 步摘要
self.long_term = "" # 整体任务摘要
async def add(self, step: dict):
self.short_term.append(step)
# 短期 -> 中期
if len(self.short_term) > 3:
old_steps = self.short_term[:-3]
self.medium_term = await self.summarize(old_steps, self.medium_term)
self.short_term = self.short_term[-3:]
# 中期 -> 长期
if len(self.medium_term) > 2000: # token 数阈值
self.long_term = await self.summarize_to_long(self.medium_term, self.long_term)
self.medium_term = ""
def get_context(self) -> str:
return f"""
任务概况:{self.long_term}
近期进展:{self.medium_term}
当前步骤:{format_recent(self.short_term)}
"""解决方案三:结构化 Prompt 设计
分块组织原则:
python
def build_structured_prompt(goal: str, state: dict, history: str) -> str:
"""构建结构化 Prompt"""
# 优先级 1:目标和约束(永远置顶)
goal_section = f"""
=== 任务目标 ===
{goal}
=== 约束条件 ===
- 输出格式:{state['output_format']}
- 截止时间:{state['deadline']}
"""
# 优先级 2:关键状态(紧凑)
state_section = f"""
=== 当前状态 ===
进度:{state['progress']}%
当前步骤:{state['current_step']}
待办:{state['todo']}
"""
# 优先级 3:关键发现(摘要)
findings_section = f"""
=== 关键发现 ===
{state['key_findings_summary']}
"""
# 优先级 4:历史(压缩后)
history_section = f"""
=== 执行历史(摘要)===
{history}
"""
# 优先级 5:当前决策
decision_section = """
=== 你的任务 ===
基于以上信息,决定下一步行动。
思考:...
行动:...
"""
# 按优先级组合(重要内容在前,避免被截断)
return "\n".join([
goal_section,
state_section,
findings_section,
history_section,
decision_section
])关键信息置顶策略:
python
class PriorityContext:
"""优先级上下文管理"""
PRIORITY_ORDER = [
"goal", # 最高:目标不能忘
"constraints", # 约束条件
"current_state", # 当前状态
"critical_findings", # 关键发现
"recent_history", # 近期历史
"detailed_history" # 详细历史(最易被截断)
]
def build_context(self, components: dict, max_tokens: int) -> str:
"""按优先级构建上下文,确保重要信息不被截断"""
context = []
used_tokens = 0
for priority in self.PRIORITY_ORDER:
component = components.get(priority, "")
component_tokens = estimate_tokens(component)
if used_tokens + component_tokens <= max_tokens:
context.append(component)
used_tokens += component_tokens
else:
# 空间不足,压缩或截断
remaining = max_tokens - used_tokens
compressed = self.compress(component, remaining)
context.append(compressed)
break
return "\n".join(context)解决方案四:分阶段执行
阶段划分原则:
python
class StageExecutor:
"""分阶段执行器"""
def __init__(self):
self.stages = [
{"name": "research", "description": "信息收集阶段", "max_steps": 10},
{"name": "analysis", "description": "数据分析阶段", "max_steps": 8},
{"name": "writing", "description": "报告撰写阶段", "max_steps": 12},
{"name": "review", "description": "审核校对阶段", "max_steps": 5},
]
async def execute(self, task: str) -> str:
# 初始规划:拆解为阶段
stage_plan = await self.plan_stages(task)
accumulated_results = {}
for stage in self.stages:
print(f"=== 进入阶段:{stage['name']} ===")
# 阶段独立执行(上下文重置)
stage_result = await self.execute_stage(
stage=stage,
original_goal=task,
previous_results=accumulated_results,
stage_plan=stage_plan[stage['name']]
)
# 保存阶段结果(摘要形式)
accumulated_results[stage['name']] = {
"summary": await self.summarize_stage_result(stage_result),
"key_outputs": self.extract_key_outputs(stage_result),
"full_result": stage_result # 存外部,不放进上下文
}
# 最终整合
return await self.synthesize_final(task, accumulated_results)
async def execute_stage(self, stage: dict, original_goal: str,
previous_results: dict, stage_plan: str) -> str:
"""执行单个阶段(独立上下文)"""
# 构建阶段专用 Prompt(上下文重置)
stage_prompt = f"""
原始任务:{original_goal}
当前阶段:{stage['name']} - {stage['description']}
阶段目标:{stage_plan}
前置阶段关键产出:
{format_previous_results(previous_results)}
请完成本阶段任务。
"""
# 本阶段内使用 ReAct 循环(上下文不会累积到下一阶段)
return await react_agent.execute(stage_prompt, max_steps=stage['max_steps'])阶段间信息传递:
python
class StageHandoff:
"""阶段交接管理"""
async def create_handoff_package(self, stage_result: dict) -> dict:
"""创建阶段交接包(精简信息)"""
return {
"stage_name": stage_result["name"],
"summary": await self.generate_summary(stage_result),
"key_findings": self.extract_findings(stage_result),
"deliverables": self.extract_deliverables(stage_result),
"open_questions": self.extract_questions(stage_result),
"reference_materials": stage_result.get("references", []),
# 不包含:详细执行步骤、工具调用历史、中间思考过程
}常见踩坑与反例
踩坑 1:完全依赖上下文记忆
错误做法: 把所有状态都放上下文,不设计外部存储。
后果: 长任务必然失败,上下文溢出后信息丢失。
踩坑 2:摘要质量差
错误做法: 用简单的截断代替智能摘要,丢失关键信息。
正确做法: 使用 LLM 进行语义摘要,保留目标和关键发现。
踩坑 3:阶段划分不合理
错误做法: 阶段之间依赖过强,无法真正独立执行。
正确做法: 阶段要有明确的输入输出定义,松耦合。
踩坑 4:忽视状态恢复
错误做法: 没有持久化机制,任务中断后无法恢复。
正确做法: 定期 checkpoint,支持从任意阶段恢复。
踩坑 5:过度优化
错误做法: 为了省 token 过度压缩,导致信息不足决策错误。
正确做法: 平衡压缩率和信息完整性,关键信息保留详细。
面试官可能继续追问
追问 1:怎么评估摘要是否保留了足够信息? 答题要点:下游任务成功率(用摘要能否正确决策)、信息覆盖率(关键信息是否都在)、摘要压缩率(压缩前后 token 比)。
追问 2:外部状态存储用数据库还是向量存储? 答题要点:结构化状态用关系型数据库(进度、待办),非结构化内容用向量存储(可语义检索),大文件存对象存储,混合使用。
追问 3:分阶段执行时,阶段间怎么保证连贯性? 答题要点:明确的阶段接口定义、阶段交接包(摘要 + 关键产出)、原始目标贯穿始终、最终整合阶段统一风格。
追问 4:200K 上下文模型还需要这些优化吗? 答题要点:仍然需要。虽然能处理更长任务,但长上下文增加延迟和成本,且模型对长距离依赖的处理能力仍有局限。优化策略可以适度放宽,但原则不变。
面试总结
上下文长度与规划缺陷是 Agent 长任务的核心瓶颈。面试时强调三点:
- 问题本质:上下文有限 → 长任务历史撑满 → 早期信息丢失 → 规划失效。这是 Agent 系统的根本性约束。
- 四大解决方案:外部状态存储(不把状态放上下文)、历史摘要(压缩信息)、结构化 Prompt(重要信息置顶)、分阶段执行(阶段独立)
- 工程原则:不把状态完全放在上下文里——这是解决长任务问题的核心指导思想
记住:上下文优化是 Agent 系统工程化的基础能力。无论模型上下文多大,合理的架构设计(外部存储 + 智能摘要)都是必要的。