外观
从被漏读到稳定召回:长上下文防迷失中间实战
问题背景:长上下文不是“塞进去就会用”
很多团队把 128K、200K 甚至 1M token 上下文窗口当成“万能记忆”。在实际的 AI 编程助手、代码库问答、合同审查和智能体工作流里,却经常出现一种反直觉现象:关键信息明明已经放进提示词,模型仍然回答“未提供”或引用了无关片段。更典型的是,位于开头和结尾的信息更容易被使用,位于中间的证据被忽略,这就是常说的 lost-in-middle(迷失中间)。
这不是简单的“模型不认真”。长上下文中的注意力分配、位置编码、训练数据分布以及指令与证据之间的距离,都会影响信息召回。对于工程落地而言,真正要解决的问题不是如何把更多 token 塞进去,而是如何让模型在需要时稳定找到、理解并使用关键信息。
本文给出一套可直接落地的策略:先识别长上下文风险,再通过信息前置、分段摘要、证据重排和最终答案约束,把“模型可能看见”变成“模型明确使用”。
方案对比:四种 long-context 处理策略怎么选
方案一:全文直接拼接
最常见做法是把检索结果、日志、代码文件或历史对话全部拼进一个 prompt。优点是实现简单;缺点是关键事实可能落在中间,且模型容易被重复信息、噪声和格式变化干扰。上下文越长,答案稳定性越差。
方案二:信息前置
把最关键的约束、结论、异常日志或高相关代码放到用户问题之前,并明确标注“必须优先使用”。这适合事实集中、证据数量不多的场景,成本低,收益明显。
方案三:分段摘要
先让模型对每个长片段生成结构化摘要,再基于摘要回答。它适合多文档、多文件、多轮日志分析,但摘要阶段可能丢失细节,因此必须保留可回溯的原文编号。
方案四:检索后重排加分层阅读
先按相关性召回,再用 rerank 或规则把高相关证据前移;回答时要求模型先列出证据编号,再生成结论。这是 RAG 和智能体工作流中最稳健的方案,但系统复杂度更高。
实操步骤:用 Python 构建“前置 + 摘要”提示词
下面示例不依赖特定厂商 SDK,只用字符串模板模拟处理流程。你可以把 call_llm 替换成任意大模型 API。示例输入是一组长文档片段,输出包含证据摘要、关键证据和最终答案提示。
python
import json
import textwrap
# 可替换为 OpenAI、Anthropic、通义、DeepSeek 等 API 调用
# 这里用伪函数表示,便于在本地或测试环境中替换实现
def call_llm(prompt: str) -> str:
"""调用大模型,并返回文本结果。"""
return "模型返回内容"
def build_chunks(documents: list[str], chunk_size: int = 900) -> list[dict]:
"""把长文档切成带编号的小块,方便摘要和引用回溯。"""
chunks = []
chunk_id = 1
for doc_id, doc in enumerate(documents, start=1):
for start in range(0, len(doc), chunk_size):
text = doc[start:start + chunk_size]
chunks.append({
"id": f"C{chunk_id:03d}",
"doc_id": f"D{doc_id:02d}",
"text": text.strip(),
})
chunk_id += 1
return chunks
SUMMARY_PROMPT = """
你是长文档分析助手。请阅读每个证据片段,并输出 JSON 数组。
要求:
1. 不得编造片段中没有的信息;
2. 每条摘要保留原始片段 id;
3. 标注与用户问题的相关性:high、medium、low;
4. 摘录最多 2 个关键原句,用于最终回答引用。
用户问题:
{question}
证据片段:
{chunks}
输出格式:
[
{
"id": "C001",
"relevance": "high",
"summary": "用中文概括该片段",
"key_quotes": ["关键原句"]
}
]
"""
def summarize_chunks(question: str, chunks: list[dict]) -> list[dict]:
"""调用模型生成分段摘要;生产环境应增加 JSON 修复与校验。"""
chunks_text = "\n\n".join(
f"[{c['id']}] 来源={c['doc_id']}\n{c['text']}" for c in chunks
)
prompt = SUMMARY_PROMPT.format(question=question, chunks=chunks_text)
raw = call_llm(prompt)
try:
return json.loads(raw)
except json.JSONDecodeError:
# 简单兜底:要求模型修复 JSON;更稳妥的方式是使用 structured output
fix_prompt = f"请把下面内容修复为合法 JSON,不要输出解释:\n{raw}"
return json.loads(call_llm(fix_prompt))
def rerank_summaries(summaries: list[dict]) -> list[dict]:
"""把高相关片段前置,缓解关键信息落在中间的问题。"""
order = {"high": 0, "medium": 1, "low": 2}
return sorted(summaries, key=lambda x: order.get(x.get("relevance"), 3))
ANSWER_PROMPT = """
请严格基于【优先证据】和【分段摘要】回答用户问题。
回答规则:
- 优先使用 high 相关性证据;
- 每个关键结论后用 [证据ID] 标注来源;
- 如果证据不足以回答,明确写出“缺少哪类信息”,不要猜测;
- 先给结论,再给依据,最后给风险或下一步建议。
【优先证据】
{front_loaded_evidence}
【分段摘要】
{all_summaries}
【用户问题】
{question}
"""
def build_final_prompt(question, summaries, top_k=5):
ranked = rerank_summaries(summaries)
front = ranked[:top_k]
return ANSWER_PROMPT.format(
front_loaded_evidence=json.dumps(front, ensure_ascii=False, indent=2),
all_summaries=json.dumps(ranked, ensure_ascii=False, indent=2),
question=question,
)
# 示例:实际使用时可替换为代码库文件、工单记录或合同条款
docs = [
"服务默认超时时间为 3 秒。若调用批处理接口,需要在请求头设置 X-Request-Timeout。",
"批处理任务单次最多提交 500 条记录。超过限制会返回 400,错误码为 BATCH_TOO_LARGE。",
"系统支持重试,但只有幂等请求可以安全重试;非幂等请求必须先查询任务状态。"
]
question = "批处理提交 800 条记录失败,可能是什么原因?应该如何处理?"
chunks = build_chunks(docs)
# summaries = summarize_chunks(question, chunks)
# final_prompt = build_final_prompt(question, summaries)
# print(final_prompt)可复用提示词模板:让模型先找证据再回答
如果暂时不想做两阶段调用,也可以先用下面的单模板降低风险。它的核心不是要求模型“认真一点”,而是强制建立证据顺序和引用关系。
text
你将阅读一组按编号排列的资料。资料可能很长,位于任意位置的信息都同等重要。
请按以下步骤回答:
1. 先找出与问题直接相关的证据,列出证据编号和关键原句;
2. 区分“明确事实”和“需要推断”的内容;
3. 如果多个证据冲突,指出冲突点,不要自行选择其中一个;
4. 如果没有足够证据,明确说明缺失信息;
5. 最终答案中的每个结论都必须带证据编号。
【最高优先级资料】
{must_use_evidence}
【全部资料】
{all_chunks}
【问题】
{question}在 AI 编程助手中,must_use_evidence 可以放报错堆栈、用户明确需求、接口约束和不可修改的文件;all_chunks 再放代码库检索结果。这样模型不会先被大量实现细节带偏。
评测方法:不要只看答案“像不像对的”
长上下文策略必须用可重复评测验证。建议构造三类样本:第一类,关键证据放在开头;第二类,放在中间;第三类,放在结尾。每类样本统计证据命中率、引用正确率、幻觉率和“缺失信息”误报率。
一个轻量评测表可以包含:问题、证据位置、期望证据 ID、模型引用 ID、答案是否正确、是否发生幻觉。如果中间位置的正确率显著低于首尾,就说明仍存在 lost-in-middle 问题。此时不要只通过扩大上下文窗口解决,而应调整摘要粒度、重排策略或前置证据数量。
避坑总结:长上下文提示设计的 5 个具体注意事项
1. 只说“请阅读全部内容”通常无效
常见错误现象是提示词末尾写上“以下所有资料都很重要”,但模型仍忽略中间片段。原因是自然语言强调不能稳定改变注意力分配。修正方法是把高相关证据物理位置前移,并用编号、标题和 JSON 字段降低查找成本。
2. 摘要不能替代原文证据
分段摘要后,模型可能把摘要中的概括当成事实,甚至把相邻片段信息错误合并。典型报错是最终答案引用了不存在的条款或函数。修正方法是在摘要中保留 id、key_quotes 和来源文件,最终回答必须引用证据 ID;关键业务场景还要把高相关原文片段二次前置。
3. 前置证据不是越多越好
有些系统把十几个“重点片段”全部塞到最前面,结果重点区域本身又变成新的长上下文。修正方法是只前置 3 到 5 条最关键证据,并在提示中说明它们与问题的关系,例如“这是错误码定义”“这是用户硬性约束”“这是不可变接口”。其余证据放摘要区供交叉验证。
4. 切分过大或过小都会降低召回
切分过大时,一个片段包含多个主题,摘要相关性判断变模糊;切分过小时,函数签名、条件判断和异常处理被拆开,模型容易断章取义。修正方法是按语义边界切分,例如函数、类、接口说明、日志请求链路,而不是固定按字符数硬切;必须重叠时保留标题和编号。
5. 没有结构化校验会放大不稳定性
常见现象是模型返回“看起来像 JSON”的文本,但缺少括号、夹带解释文字,导致工作流解析失败。修正方法是使用工具调用或结构化输出模式;如果平台不支持,就在提示中固定 schema,并用 json.loads、重试和字段校验兜底。生产环境不要依赖模型每次自觉遵守格式。
结语
应对 long-context 的核心不是挑战模型极限,而是设计一条可靠的信息消费路径:先切分和编号,再摘要和重排,把关键证据前置,最后要求模型带证据编号回答。这样即使上下文很长,模型也能围绕明确证据工作。真正稳定的 LLM 应用,往往不是使用了最大窗口的那个,而是最会组织信息的那个。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。