AI:
好的,那我来完整讲解这道题的标准答案。
上下文窗口溢出的解决方案
这个问题可以从四个层面来解决:输入优化、架构设计、检索增强、模型选择。
一、输入端优化
1. 截断策略
- 最简单粗暴:直接截掉最早的消息
- 问题:丢失历史上下文,用户问"刚才说的那个"就懵了
- 适用场景:对历史依赖不强的任务
2. 滑动窗口
- 保持最近 N 轮对话,旧的自然淘汰
- 比如只保留最近 10 轮
- 实现:维护一个队列,超过阈值就
popleft()
from collections import deque
class ConversationBuffer:
def __init__(self, max_turns=10):
self.buffer = deque(maxlen=max_turns)
def add_message(self, role, content):
self.buffer.append({"role": role, "content": content})
3. 摘要压缩
- 把旧对话压缩成摘要,大幅减少 token 数
- 比如前 20 轮对话 → 让 LLM 总结成 200 字
- 优点:保留关键信息,压缩比高
- 缺点:需要额外调用 LLM,有信息损失
async def summarize_history(messages):
prompt = f"请将以下对话压缩成简洁摘要,保留关键信息:\n{messages}"
summary = await llm.call(prompt)
return summary
4. 分层记忆
- 短期记忆:最近几轮原文保留
- 中期记忆:摘要形式
- 长期记忆:向量数据库存储
二、架构设计
1. 对话记忆管理
- LangChain 的
ConversationBufferMemory系列提供了多种策略 ConversationBufferWindowMemory:滑动窗口ConversationSummaryMemory:自动摘要VectorStoreRetrieverMemory:向量检索
2. 分层上下文设计
[系统提示词] 固定
↓
[用户画像/长期记忆] 向量检索按需加载
↓
[近期对话摘要] 压缩版
↓
[最近 N 轮原文] 完整保留
↓
[当前用户输入]
三、检索增强(RAG)优化
当你的应用是知识库问答时,问题往往出在检索回来的文档太多:
1. 精准检索
- 提高检索质量,只拿最相关的 top-k
- 用 hybrid search(关键词 + 向量)提升召回精度
2. 重排序
- 先检索 top-50,再用 reranker 模型精排取 top-5
- BGE-reranker、Cohere rerank 都是常用方案
3. 文档压缩
- LLM 压缩:让模型提取文档中和问题相关的部分
- 嵌入式压缩:用小模型过滤无关句子
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever
)
4. 分块策略优化
- 文档切分时控制 chunk size
- 小 chunk(200-500 tokens)更灵活,但可能切断语义
- 大 chunk(1000+ tokens)语义完整,但占空间
- 方案:父子索引,小 chunk 检索,返回大 chunk
四、模型层面
1. 选择大上下文模型
- GPT-4-Turbo:128K
- Claude 3:200K
- Kimi、GLM-4-Long:支持超长文本
2. 长文本专项优化
- 有些场景就是需要全文(比如分析一份 100 页合同)
- 这时候不是"压缩",而是直接换模型
五、工程实践建议
Token 预算管理:
def build_context(budget=8000):
used = 0
context = []
# 系统提示词(固定)
system_prompt = "你是..."
used += count_tokens(system_prompt)
context.append(system_prompt)
# 摘要(压缩后)
summary = get_summary()
used += count_tokens(summary)
context.append(summary)
# 最近对话(剩余预算)
remaining = budget - used - 500 # 留给用户输入和回复
recent_msgs = get_recent_messages(remaining)
context.extend(recent_msgs)
return context
监控与告警:
- 记录每次请求的 token 数
- 接近阈值时主动压缩或清理
- 避免等到报错才处理
面试加分点
如果你能提到以下几点,会显得更有深度:
- 区分场景:闲聊机器人 vs 代码助手 vs 知识库问答,策略不同
- 成本意识:摘要要调 LLM,有延迟和费用开销,需要权衡
- 用户体验:不能为了塞进去就乱删,要保留对话连贯性
- 实际项目经验:比如"我们线上用的是滑动窗口 + 向量记忆的混合方案"
这就是这道题的完整解答。核心思路是:不要只想着"塞进去",而是思考什么信息真正需要保留,用什么形式保留最高效。
需要我针对某个具体方案再深入展开吗?
阅读全文
