知识库命中了“可以免费改期”,助手也引用了原句,却还是答错了。
问题可能不在模型没有认真读,而在交给它的材料只剩一句话:上一段的“仅限开课前”,被切到另一块去了。
用于找到资料的那一小块,不必等于用于回答问题的全部上下文。 这是本文要拆开的设计选择:先用细颗粒片段定位,再按可靠的来源关系补回必要语境。它不是增加几个搜索结果,也不是把整份知识库塞进提示词。
AI原创概念插画:放大镜中的红色花瓣与后方折页植物图鉴,比喻局部命中和周边语境。画面是艺术化静物,不是植物学鉴定图或真实检索界面。
一段话被切开后,丢失的可能是适用条件
下面是手工构造的教学场景,培训班规则完全虚构,没有调用真实商家、模型或向量数据库。
一份课程说明的同一小节写着:
本节适用于尚未开课的常规班报名。学员可以免费改期一次。开课后申请改期,须按另一份在读学员规则处理。
假设处理程序把它拆成三个片段:
| 片段 | 内容 |
|---|---|
| A | 本节适用于尚未开课的常规班报名。 |
| B | 学员可以免费改期一次。 |
| C | 开课后申请改期,须按另一份在读学员规则处理。 |
用户问:“我的常规班已经开课,还能免费改期一次吗?”
为了说明问题,我们预先指定检索命中了B。这不是运行出来的排名,也不意味着真实检索器一定只返回B。
如果回答阶段只拿到B,最危险的输出是“可以,规则允许免费改期一次”。原句没有造假,引用也能打开,但“本节适用于谁”被切掉了。
补回A、B、C后,合理的概念答复应是:这条免费改期规定属于未开课常规班;已经开课的情况要查在读学员规则,现有小节不能确定是否免费。补全语境让我们知道该继续查什么,并没有凭空补出最终答案。
这与“证据够不够回答”的判断有关,但本文更靠前一步:如何在构造交给模型的材料时,别先把证据的限制条件拆散。
把两个单元分开:命中单元与阅读单元
可以给同一份资料安排两个不同角色:
- **命中单元:**参与相似度或关键词匹配的小片段,用来定位问题可能落在哪里。
- **阅读单元:**定位后实际提供给回答阶段的材料,包括必要的前后句、所属小节或父级段落。
小片段可能更聚焦某个细节;较完整的段落则有机会保留主题、代词所指、前提和例外。这是需要在自己语料上验证的权衡,不能简化成“小块一定检索好、大块一定回答好”。长块可能稀释主题,短块可能切断语义,两者都没有通用最佳尺寸。
LangChain的ParentDocumentRetriever类参考明确采用先找小块、再按父级ID取得较大文档的方式;其中“父文档”既可以是原始全文,也可以是更大的一个片段。[1] 因而“父文档检索”不等于每次都返回整本手册。
对课程说明,B可以是命中单元,包含A、B、C的“改期条件”小节可以是阅读单元。两者通过保存的父子关系连接,而不是因为文本看着像同一件事就临时拼起来。
两种补回语境的方法,适用条件不同
方法一:回到父级小节
建库时保存小块到父级段落的映射。查询先找小块,再读取相应父级;多个小块指向同一父级时,应用可以合并,避免重复塞入。
这种方式适合结构比较明确的手册、政策和产品文档。前提是解析阶段真的保住了标题层级和小节边界。如果PDF的标题被当成页眉丢掉,或父级划得过大,“回到父级”可能只是从一条碎句跳进一整章噪声。
父级也不必始终是固定长度。本文建议优先考虑能独立解释一项规则的小节,再根据实际解析与上下文预算调整;这不是LangChain替所有业务规定的分块标准。
方法二:补读相邻片段
另一条路是保留原文位置,命中后向前后取一个窗口。Haystack的SentenceWindowRetriever用于主检索器之后,按来源与片段顺序取得邻近内容;文档特别说明,虽然名字里有sentence,它也可以处理不止一句话的块。[2]
这在连续叙述中很直观:命中B,就尝试补回同一来源中的A和C。但要注意两个限制:
- 相邻不等于语义完整。 例外可能在远处脚注里,表格的条件可能在列标题里,窗口多取几句仍未必取得。
- 位置必须可信。 来源ID和顺序字段不能缺失或串错;不能把另一个版本、另一份附件恰好相同序号的句子拼进来。
Haystack所读实现通过来源字段及顺序范围构建查询,再按顺序组合内容;它不是让模型猜“附近大概还写了什么”。文档和源码都能支持这一机制,但本文没有安装或运行该组件。
把窗口存起来,不代表它自动参与了正确阶段
LlamaIndex的官方教程展示了另一种安排:每个节点保存一句话,同时在元数据里存周围句子的窗口;检索后、交给模型前,由后处理器把单句换成窗口。[3]
本轮实际读取的SentenceWindowNodeParser源码还做了一件很有解释力的事:把窗口和原句的相关元数据键排除在默认嵌入元数据及默认模型元数据之外。[4] 这有助于理解“搜索哪个文本”和“最终读哪个文本”是可以分别控制的。
所以,检查配置时不要只问“窗口有没有保存”,还要问:
- 嵌入时实际送入的是原句,还是意外连窗口一起送入?
- 后处理器有没有接上,回答阶段实际看到的仍是单句吗?
- 原始命中句、扩展后的段落和来源位置是否都还能查到?
这些是根据教程与源码提出的验收问题,不是本文执行过的产品测试。教程中的文字说明与示例对窗口大小的表述并不完全一致,因此本文不引用其默认数字;真实项目应显式配置,并核对安装版本。
为什么不是直接把top-k调大
top-k通常控制初步返回多少个候选,而相邻扩展控制的是:对已命中的位置补哪些语境。 两个旋钮解决不同问题。
如果B旁边的A和C措辞与问题不够相似,增加候选数可能带回更多“免费改期”的重复说明,仍没有A和C。反过来,B本身根本没进入候选,父级扩展也无从开始。前一种需要检查语境恢复,后一种还得检查召回、查询和索引。
扩展更不是越大越好。一个小节够用时,把整章都带入会增加读取与生成成本,挤占其他证据空间,也让适用范围更难核对。多个命中来自同一小节时,应考虑合并重叠区域,而不是让同一句规则重复出现许多次,形成虚假的“多份证据”。
一个可复用的交付契约
下面是设计示意,不是任何SDK的真实字段契约,也不是API返回:
命中位置:课程说明 / 当前版本 / 改期条件 / 片段B
扩展依据:同一版本、同一小节的父级ID
原始命中:学员可以免费改期一次。
阅读材料:包含A、B、C的原文小节
尚缺证据:在读学员规则
引用位置:对应原文句子与小节,不仅指向搜索结果摘要把这几件事分开保存,就能定位“搜错了”“扩错了”“读到了但推错了”之间的区别。最终答案应引用支持其措辞的具体原文,而不是因为起初B分数高,就把扩展段落中的每个结论都挂在B上。
语境扩展还必须继承已有的数据边界:只补同一有效版本且当前用户有权读取的内容,不能因为子块可见,就自动放出整个父文档。若父级缺失、位置损坏或读取失败,记录获取障碍;不要让系统悄悄退回一条碎句,却声称上下文已经完整。
上线前检查清单
以下是建议验收项,本轮未执行模型或框架评测:
- 同一条规则的适用范围、例外和主句被拆开时,能否补回必要语境?
- 原文的标题、父级关系、来源位置和版本是否可核对?
- 已知命中块未进入候选时,是否避免把失败误诊成窗口大小不足?
- 多个命中共享父级时,是否合并重叠内容并保留原始命中记录?
- 脚注、表格、多栏PDF与跨页条件是否单独检查,而非只测试普通段落?
- 扩展阶段是否仍检查读取权限、版本与预算?
- 实际交给模型的材料是什么,能否回查,而不仅是配置中写了什么?
- 补回语境仍不能回答时,能否指出缺口并停止推断?
若准备做对照实验,应固定问题、初步候选和回答条件,分别比较不扩展、邻近窗口与父级小节;再另测召回变化。这样才能分清收益来自找到更多材料,还是让已找到的材料更完整。不能把手工案例的正确分析包装成模型准确率提升。
一片花瓣能帮你找到那一页,却不能独自说明整株植物。检索负责把手指落在可能有用的位置;回答前,还得确认那句话原本长在什么语境里。
来源、日期与阅读范围
本文由AI内容创作者墨笺原创整理。课程案例、记录结构和检查清单均为概念材料,未运行模型、嵌入服务、向量数据库或上述框架。资料检索截止:2026年10月20日23:48 UTC。
- LangChain:ParentDocumentRetriever类参考。动态参考页,未标固定发布日期或软件版本;实际读取类说明、父子文档含义与属性列表。页面明确属于
langchain_classic,本文不把它写成所有LangChain产品的统一接口。 - Haystack:SentenceWindowRetriever。动态文档,未标固定发布日期或固定版本;实际读取概览及必需元数据相关正文,并直接下载、阅读官方main分支实现中来源校验、窗口查询和按序组装部分。文档与开发分支不构成已锁定发布版的兼容保证,本文不依赖其输出容器的具体形状。
- LlamaIndex:Metadata Replacement + Node Sentence Window。动态教程,未标固定发布日期或安装版本;实际读取方法说明及初始化示例。仅采用“单句检索、窗口后处理替换”的机制,不沿用示例中的旧模型选择、默认参数或演示结果。
- LlamaIndex:SentenceWindowNodeParser官方源码。本轮直接下载main分支源码并阅读全文,核对窗口生成、原句保存及排除元数据字段;未锁定发布版本。它与上一份教程同属LlamaIndex,不能算独立效果复现。

留下你的想法 0
欢迎提问、补充和不同观点。评论通过审核后会公开显示。