RAG知识库多跳查询方案
1. 方案解决的问题
在传统的RAG(检索增强生成)知识库问答场景中,当用户提问涉及文档附件内容时,存在以下问题:
- 附件内容检索不精准:附件文档通常包含多个章节,在知识库中作为单个文档存储时,检索容易带出无关章节内容,影响答案准确性
- 附件引用信息缺失:大模型回答中虽然可能提到”详见附件X”,但无法直接展示附件具体内容,用户体验不完整
- 多跳查询效率低:如果需要通过引用关系进行二次检索,传统方式需要再次调用大模型,消耗大量token和计算资源
- 知识库检索范围过大:在包含大量文档的知识库中检索附件,容易产生噪音,影响检索精度
本方案通过多跳查询和知识库治理相结合的方式,在传统知识库问答工作流之后,补充一个轻量级的附件内容检索流程,实现精准、高效、低成本的附件内容补充输出。
2. 主要思路
本方案采用两阶段查询的设计思路:
- 第一阶段:传统的知识库问答流程
- 用户提问 → 知识库检索 → 大模型生成回答
- 在提示词中强制要求大模型输出格式化的”相关附件”章节,包含附件引用信息
- 第二阶段:附件内容补充查询流程(本方案核心)
- 从大模型回答中提取附件引用信息
- 在专门的附件文档知识库集合中进行精准检索
- 通过API批量获取附件完整内容
- 格式化输出附件内容,补充到最终回答中
关键设计点:
- 附件文档独立治理,每个附件章节拆分为独立文档,提升检索精准度
- 使用关键词匹配而非大模型推理,降低token消耗
- 通过代码节点和批量处理节点组合,实现高效的多跳查询流程
3. 一阶段对话提示词要求
在传统知识库问答的提示词中,需要强制要求大模型在回答末尾输出格式化的”相关附件”章节。
输出格式要求
大模型必须在回答中包含以下格式的章节:
1
2
3
4
5
| **相关附件**
附件11-上级、地方及政府特服应急电话
附件12-公司组织机构及内部应急联系电话
附件13-外部应急救援资源公共机构及联系电话
|
关键要求
- 章节标题:必须使用
**相关附件** 作为章节标题 - 附件格式:每行一个附件,格式为
附件XX-附件名称附件XX:附件编号,必须是数字附件名称:完整的附件名称,用于后续匹配
- 文档名-章节名规范:
- 附件名称应遵循”文档名-章节名”的命名规范
- 文档名用于定位和区分关联的附件
- 例如:
附件16-社会主要应急物资和装备清单,其中”社会主要应急物资和装备清单”是文档名
提示词示例片段
1
2
3
4
5
6
7
8
9
10
11
| 在回答末尾,必须输出"相关附件"章节,格式如下:
**相关附件**
附件X-附件名称
附件Y-附件名称
注意:
- 如果回答中提到了"详见附件X"或"参考附件X",必须在"相关附件"章节中列出
- 附件名称必须完整准确,用于后续检索匹配
- 附件编号必须与文档中的实际编号一致
|
4. 附件文档知识治理
治理原则
核心原则:将单文档的每个章节附件,拆分成独立的多文档。
具体操作
- 文档拆分:
- 不要将所有附件章节都放到一个文档里
- 每个附件章节应作为独立的文档上传到知识库
- 例如:原文档”应急预案.docx”包含”附件11”、”附件12”等多个附件,应拆分为:
附件11-上级、地方及政府特服应急电话.docx附件12-公司组织机构及内部应急联系电话.docx
- 独立知识库集合:
- 创建专门的”附件文档”知识库集合
- 将所有拆分后的附件文档上传到此集合
- 在二阶段查询时,指定在此集合中检索,减少检索范围
- 文件命名规范:
- 文件名称格式:
附件XX-附件名称.docx - 与提示词中要求的输出格式保持一致
- 便于后续通过文件名进行匹配
治理效果
- 精准检索:每个附件作为独立文档,检索时不会带出其他附件内容
- 减少噪音:独立的附件文档知识库集合,避免与主文档库混合检索
- 提升效率:缩小检索范围,提升检索速度和准确度
5. 二阶段补充查询流程
流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| 大模型回答输出
↓
[代码节点1] 附件文件名提取
↓
附件名称数组: ["附件11-...", "附件12-..."]
↓
[批量执行节点] 循环知识库检索
├─ 循环项1: 检索"附件11-..."
├─ 循环项2: 检索"附件12-..."
└─ ...
↓
知识库引用数组(二维数组)
↓
[代码节点2] 附件文档Id提取
↓
collection_ids: [["id1"], ["id2"], ...]
collection_attachment_map: {"id1": "附件11-...", ...}
↓
[批量执行节点] 循环HTTP请求
├─ 循环项1: 请求collectionId="id1"的数据块
├─ 循环项2: 请求collectionId="id2"的数据块
└─ ...
↓
HTTP响应数组
↓
[代码节点3] 附件内容合并和格式化
↓
格式化后的附件内容字符串
↓
[指定输出节点] 输出最终内容
|
技术细节
(1)代码节点:附件文件名提取
功能:从大模型输出对话里,提取约定的”相关附件”章节内容。
输入变量:
answer_content(类型:string)- 大模型的回答内容
输出变量:
attachment_info(类型:array)- 附件信息数组,格式为 `["附件11-上级、地方及政府特服应急电话", "附件12-公司组织机构及内部应急联系电话"]`
实现逻辑:
- 使用正则表达式匹配
**相关附件** 章节 - 提取每行的
附件XX-附件名称 格式内容 - 自动去重,保持顺序
查看完整代码
```python """ FastGPT智能体平台代码节点:提取"相关附件"章节中的附件信息 使用说明: 1. 在FastGPT中创建代码节点 2. 输入变量名:answer_content(类型:string) 3. 输出变量名:attachment_info(类型:array) 功能: - 从回答内容的"相关附件"章节中提取附件信息 - 格式:附件XX-附件名称 - 自动去重 - 返回字符串数组 """ import re from typing import List # FastGPT代码节点入口函数 # 注意:FastGPT会自动将输入变量作为函数参数传入 def main(answer_content: str = "") -> dict: """ FastGPT代码节点主函数 输入参数(FastGPT会自动传入): answer_content: str - 回答内容文本 返回值: dict - 包含以下字段: attachment_info: List[str] - 附件信息数组(去重后),格式为"附件XX-附件名称" """ # 输入验证 if not answer_content: return { "attachment_info": [] } if not isinstance(answer_content, str): # 如果不是字符串,尝试转换 answer_content = str(answer_content) try: # 首先提取"相关附件"章节的内容 # 匹配"相关附件"章节,从"**相关附件**"开始到下一个"**"标题或文件结束 attachment_section_pattern = r'\*\*相关附件\*\*[::]?\s*\n(.*?)(?=\n\*\*|$)' attachment_section_match = re.search(attachment_section_pattern, answer_content, re.DOTALL | re.IGNORECASE) if not attachment_section_match: # 如果没有找到"相关附件"章节,返回空数组 return { "attachment_info": [] } attachment_section = attachment_section_match.group(1) # 匹配格式:附件XX-附件名称(可能后面有引用编号,但不提取) # 正则表达式说明: # ^\s* - 行首可能有空格 # 附件(\d+) - 匹配"附件"后跟数字 # \s*-\s* - 匹配分隔符"-"(前后可能有空格) # (.+?) - 匹配附件名称(所有字符,非贪婪,直到遇到 -[ 或行尾) # 可选:后面可能有 -[引用编号](CITE),但不捕获 # 使用前瞻断言,匹配到 -[ 之前或行尾 pattern = r'^\s*附件(\d+)\s*-\s*(.+?)(?=\s*-\s*\[|$)' # 按行处理 lines = attachment_section.split('\n') attachment_info_list = [] for line in lines: line = line.strip() if not line: continue match = re.search(pattern, line, re.IGNORECASE) if match: attachment_num = match.group(1) attachment_name = match.group(2).strip() # 只输出附件编号和名称,不包含引用编号 attachment_info = f"附件{attachment_num}-{attachment_name}" attachment_info_list.append(attachment_info) # 去重并保持顺序 unique_info = list(dict.fromkeys(attachment_info_list)) # 返回结果 # FastGPT会自动将返回值中的字段映射到输出变量 return { "attachment_info": unique_info } except Exception as e: # 错误处理:返回空数组 return { "attachment_info": [] } ``` </details> #### (2)批量执行节点:循环知识库检索 **功能**:循环执行知识库检索节点,获取对应的文件引用。 **配置要点**: 1. **循环变量**: - 使用 `attachment_info` 数组作为循环源 - 每次循环处理一个附件名称 2. **知识库检索节点配置**: - **指定知识库集合**:选择"附件文档"知识库集合(治理后的独立集合) - **查询方式**:关键字检索 - **查询内容**:使用变量引用,引用循环中的附件名称(如 ``) - **检索策略**:可以先使用"文档名-章节名"方式测试命中效果 3. **输出**: - 输出知识库引用数组(二维数组) - 每个元素对应一个附件的检索结果 - 检索结果中包含治理后的真实附件文档Id(`collectionId`) **注意事项**: - 用户问题字段应使用变量引用数组变量,而不是原始用户问题 - 确保检索的知识库集合是专门的附件文档集合,避免在主文档库中检索 #### (3)代码节点:附件文档Id提取 **功能**:从知识库引用数组中提取 `collectionId`,构建文档集合数组。 **输入变量**: - `knowledge_base_refs`(类型:array<array