RAG 中的上下文注入

上下文注入在干什么?

前面几步已经完成了检索,拿到了相关文档。但 LLM 并不会自动"知道"这些文档的内容。上下文注入就是把检索到的文档"喂"给 LLM 的过程。

检索结果: 3段相关文档
    │
    ▼
上下文注入: 把文档组织好,拼进 Prompt
    │
    ▼
LLM 读到这些文档,基于它们生成回答

听起来简单,但怎么拼、拼多少、拼什么顺序、加什么指令,直接决定了最终回答的质量。


一、最简单的注入方式

Prompt = 指令 + 检索到的文档 + 用户问题

具体形式:
┌──────────────────────────────────────────────┐
│ 请根据以下参考资料回答用户的问题。              │  ← 系统指令
│ 如果资料中没有相关信息,请如实说明。            │
│                                              │
│ 【参考资料】                                  │  ← 检索到的文档
│ [1] iPhone 15 Pro 搭载 A17 Pro 芯片,        │
│     采用3nm工艺,性能提升显著...              │
│ [2] 电池容量为3274mAh,支持USB-C充电...       │
│ [3] 起售价7999元,有四种颜色可选...            │
│                                              │
│ 【用户问题】                                  │  ← 用户原始问题
│ iPhone 15 Pro 值不值得买?                    │
└──────────────────────────────────────────────┘

这就是最基础的上下文注入。但实际工程中远比这复杂。


二、Prompt 结构设计

2.1 基础三段式

┌──────────────────────────────────────────┐
│  System Prompt (系统指令)                 │
│  告诉 LLM 它的角色和行为规则              │
├──────────────────────────────────────────┤
│  Context (检索到的上下文)                 │
│  从向量数据库检索到的文档片段              │
├──────────────────────────────────────────┤
│  User Query (用户问题)                   │
│  用户的原始提问                           │
└──────────────────────────────────────────┘

2.2 完整工程级结构

┌──────────────────────────────────────────────────┐
│  System Prompt                                   │
│  ┌────────────────────────────────────────────┐  │
│  │ 角色定义: 你是一个专业的数码产品顾问         │  │
│  │ 行为约束: 只根据提供的资料回答               │  │
│  │ 输出格式: 分点回答,给出明确建议             │  │
│  │ 兜底策略: 资料不足时如实说明                 │  │
│  └────────────────────────────────────────────┘  │
├──────────────────────────────────────────────────┤
│  Context (上下文)                                │
│  ┌────────────────────────────────────────────┐  │
│  │ 元数据: [来源: Apple官网] [更新: 2024-09]   │  │
│  │ 内容: iPhone 15 Pro 搭载 A17 Pro 芯片...   │  │
│  ├────────────────────────────────────────────┤  │
│  │ 元数据: [来源: 评测文章] [更新: 2024-10]    │  │
│  │ 内容: 电池实测续航8.5小时...                │  │
│  ├────────────────────────────────────────────┤  │
│  │ 元数据: [来源: 价格平台] [更新: 2024-11]    │  │
│  │ 内容: 当前最低价格6899元...                 │  │
│  └────────────────────────────────────────────┘  │
├──────────────────────────────────────────────────┤
│  Chat History (对话历史, 可选)                    │
│  ┌────────────────────────────────────────────┐  │
│  │ 用户: 我在考虑换手机                        │  │
│  │ 助手: 您目前用的是什么机型?                 │  │
│  │ 用户: iPhone 13                            │  │
│  └────────────────────────────────────────────┘  │
├──────────────────────────────────────────────────┤
│  User Query                                      │
│  ┌────────────────────────────────────────────┐  │
│  │ iPhone 15 Pro 值不值得买?                  │  │
│  └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘

三、上下文注入的核心策略

3.1 文档数量控制(Top-K 选择)

检索返回很多结果,不能全塞进去:

问题: LLM 上下文窗口有限(4K/8K/32K/128K tokens)

检索返回 20 条结果:
  #1  score=0.96  "iPhone 15 Pro 芯片..."        ← 高度相关
  #2  score=0.94  "iPhone 15 Pro 电池..."        ← 高度相关
  #3  score=0.91  "iPhone 15 Pro 价格..."        ← 高度相关
  #4  score=0.85  "iPhone 15 对比 14..."         ← 相关
  #5  score=0.80  "苹果发布会总结..."            ← 有点相关
  ...
  #15 score=0.55  "三星 Galaxy S24..."           ← 不太相关
  #20 score=0.40  "手机壳推荐..."               ← 无关

策略:
  方式1: 固定 K=5,取前5条
  方式2: 设置阈值 score>0.85,动态取
  方式3: 固定 K + 阈值结合,取 Top-5 且 score>0.8

太多的问题:

  • 超出上下文窗口
  • 无关内容干扰 LLM,降低回答质量("噪声淹没信号")
  • 成本增加(按 token 收费)

太少的问题:

  • 信息不够,LLM 无法生成完整回答
  • 遗漏关键细节

经验值:

简单事实问答:  K = 3~5
复杂分析问答:  K = 5~10
长文档总结:    K = 10~20
多轮对话:      K = 3~5 (留空间给对话历史)

3.2 文档排序策略

相关性排序(默认):

按检索分数从高到低:
[1] score=0.96  最相关的放最前面
[2] score=0.94
[3] score=0.91

Lost in the Middle 问题:

研究发现 LLM 有个特点——对开头和结尾的内容记忆最深,中间容易忽略。

LLM 对不同位置信息的关注度:

位置:    [开头]  [中间偏前]  [正中间]  [中间偏后]  [结尾]
关注度:   高        中         低         中        高

示意图:
关注度
  ▲
  │ ██                                         ██
  │ ████                                     ████
  │ ██████                                 ██████
  │ ████████         ████████████         ████████
  │ ██████████████████████████████████████████████
  └──────────────────────────────────────────────▶
   开头                 中间                  结尾

应对策略——三明治排列:

普通排列:
[1] 最相关    ← LLM 重点关注 ✓
[2] 次相关    ← LLM 关注减弱
[3] 第三相关  ← LLM 最容易忽略 ✗
[4] 第四相关  ← LLM 关注回升
[5] 第五相关  ← LLM 重点关注 ✓

三明治排列 (把最重要的放两头):
[1] 最相关    ← LLM 重点关注 ✓
[3] 第三相关  ← 无所谓
[5] 第五相关  ← 无所谓
[4] 第四相关  ← 无所谓
[2] 次相关    ← LLM 重点关注 ✓

3.3 元数据注入

光注入原文不够,加上元数据能让 LLM 更好地判断和引用:

不带元数据:
[1] iPhone 15 Pro 搭载 A17 Pro 芯片...

带元数据:
[1] [来源: Apple官网] [日期: 2024-09-15] [类型: 产品介绍]
    iPhone 15 Pro 搭载 A17 Pro 芯片...

LLM 就能回答:
"根据 Apple 官网 2024年9月 的信息,iPhone 15 Pro 采用了..."
  → 有出处,可验证,更可信

常见元数据类型:

文档级:
  - 来源 (source): Apple官网 / 评测文章 / 用户评价
  - 日期 (date): 2024-09-15
  - 作者 (author): xxx评测
  - 文档标题 (title): iPhone 15 Pro 详细评测

chunk级:
  - 所属章节 (section): 第三章 - 性能测试
  - 页码 (page): P.23
  - chunk编号: chunk_3 of 15
  - 相似度分数: 0.94

3.4 上下文压缩(Context Compression)

当检索到的内容太长,可以先压缩再注入:

原始 chunk (500 tokens):
"iPhone 15 Pro 是苹果公司于2023年9月12日在Apple Park
 发布的旗舰智能手机。发布会由Tim Cook主持。当天天气晴朗,
 现场座无虚席。该手机搭载了全新的A17 Pro芯片,基于台积电
 3nm工艺制造。芯片拥有190亿个晶体管。GPU性能比上一代
 提升了20%。支持硬件级光线追踪。存储方面提供256GB、
 512GB和1TB三个版本..."

用户问的是 "A17芯片性能如何"

压缩后 (150 tokens):
"iPhone 15 Pro 搭载 A17 Pro 芯片,台积电3nm工艺,
 190亿晶体管。GPU性能比上一代提升20%,支持硬件级光线追踪。"

只保留和问题相关的信息,去掉无关描述

三种压缩方式:

方式 做法 优缺点
LLM 提取 用一个小模型先提取相关句子 效果好,但多一次 LLM 调用
关键句抽取 计算每句话与 query 的相似度,取高分句 快,不需要额外模型
摘要生成 用 LLM 生成文档摘要后注入 适合长文档,但可能丢细节

3.5 多轮对话中的上下文管理

多轮对话时,Prompt 里不只有检索文档,还有对话历史,空间更紧张:

总上下文窗口: 8192 tokens

分配:
  System Prompt:     ~200 tokens
  检索文档:          ~3000 tokens
  对话历史:          ~2000 tokens
  用户当前问题:       ~100 tokens
  保留给回答:        ~2500 tokens
  安全余量:          ~400 tokens

对话历史的管理策略:

策略1: 滑动窗口 —— 只保留最近 N 轮对话

  第1轮: 用户问A → 回答A      ← 丢弃
  第2轮: 用户问B → 回答B      ← 丢弃
  第3轮: 用户问C → 回答C      ← 保留
  第4轮: 用户问D → 回答D      ← 保留
  第5轮: 用户问E (当前)        ← 当前

策略2: 摘要压缩 —— 早期对话压缩成摘要

  [摘要] 用户在咨询iPhone 15 Pro,关注芯片性能和价格
  第4轮: 用户问D → 回答D      ← 保留原文
  第5轮: 用户问E (当前)        ← 当前

策略3: 关键轮次保留 —— 只保留和当前问题相关的历史轮次

  对话历史5轮,只有第2轮和第4轮与当前问题相关
  → 只保留第2轮和第4轮

3.6 查询改写(Query Rewriting)

有时用户的问题不适合直接拿去检索,先改写再检索效果更好:

场景: 多轮对话

第1轮: 用户: "介绍一下 iPhone 15 Pro"
第2轮: 用户: "它的电池怎么样?"
                ↑
         "它"指什么? 直接拿 "它的电池怎么样" 去检索
         检索不到有效结果!

改写后: "iPhone 15 Pro 的电池续航怎么样?"
         → 现在能检索到了 ✓

改写策略:

策略1: 指代消解
  "它的电池" → "iPhone 15 Pro 的电池"

策略2: 意图扩展
  "iPhone好不好" → "iPhone 15 Pro 优缺点评测"

策略3: 多查询生成 (一个问题变多个)
  "iPhone 15 Pro 值不值得买?"
    → 查询1: "iPhone 15 Pro 性能评测"
    → 查询2: "iPhone 15 Pro 价格性价比"
    → 查询3: "iPhone 15 Pro 用户口碑"
  分别检索,合并结果,覆盖更全面

策略4: HyDE (假设性文档嵌入)
  让 LLM 先"假装回答"用户问题,生成一个假设性文档
  用这个假设性文档去检索(而不是用问题本身)
  因为文档和文档的语义比问题和文档更接近

四、Reranker 重排序

检索返回的 Top-K 是用 Embedding 向量的相似度排序的。但 Embedding 是分别编码 query 和 document 的(双塔模型),精度有限。

Reranker 用交叉编码器(Cross-Encoder)对 query 和 document 联合打分,更精准:

双塔模型 (Embedding检索):
  Query  → Encoder → 查询向量 ┐
                               ├→ 余弦相似度 → 0.92
  Doc    → Encoder → 文档向量 ┘

  Query 和 Doc 分开编码,交互不够充分

交叉编码器 (Reranker):
  [Query + Doc] → Encoder → 相关性分数 → 0.87

  Query 和 Doc 拼在一起编码,每个词都能看到对方
  理解更深入,但计算更慢

两阶段流程:

向量检索 (快,粗排): 100万条 → Top-50     ~10ms
Reranker (慢,精排): Top-50  → Top-5      ~100ms

先用向量快速缩小范围,再用 Reranker 精细排序

五、完整的上下文注入流水线

用户: "iPhone 15 Pro 值不值得买?"
  │
  ▼
① 查询改写
  │ 结合对话历史消解指代,扩展意图
  │ → "iPhone 15 Pro 性能价格优缺点评测"
  ▼
② 向量检索 (Top-20)
  │ Encoder编码 → 向量库检索 → 20条候选
  ▼
③ Reranker 重排序 (Top-5)
  │ Cross-Encoder 精细打分 → 5条精选
  ▼
④ 上下文压缩
  │ 去掉与问题无关的句子,保留关键信息
  ▼
⑤ 元数据附加
  │ 给每条文档加上来源、日期等标签
  ▼
⑥ 排序策略
  │ 三明治排列 / 相关性排序
  ▼
⑦ Prompt 组装
  │ System + Context + History + Query
  ▼
⑧ LLM 生成回答
  │
  ▼
"根据 Apple 官网和多个评测的信息,
 iPhone 15 Pro 搭载 A17 Pro 芯片...
 综合来看,如果你目前使用 iPhone 13,
 升级到 15 Pro 是值得的,因为..."

六、常见问题与调优

问题 原因 解决方案
LLM 回答"我不知道"但文档里有答案 文档被放在了中间位置,LLM 忽略了 用三明治排列,或减少文档数量
回答和文档内容矛盾 LLM 的预训练知识干扰 System Prompt 强调"只根据提供的资料回答"
回答太笼统不具体 检索到的是泛泛而谈的文档 减小 chunk_size,让检索更精准
回答包含过时信息 检索到了旧文档 元数据加时间过滤,优先取最新文档
多轮对话越聊越差 对话历史占用太多空间 用滑动窗口或摘要压缩历史
回答混入了无关内容 Top-K 太大,混入了低相关文档 降低 K 值,或加相似度阈值过滤
用户问题太模糊检索不准 短查询信息量不足 加查询改写,或多查询扩展
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容