Files
MemoryBear/docs/rag/pipeline/05-reranking-prompt-llm.md
Multica PM Agent 343a5eebe3
Some checks failed
Sync to Gitee / sync (push) Has been cancelled
docs(rag): add MemoryBear RAG implementation docs v1.0
Submit the formed RAG documentation set produced across Sprint-1/2/3
(WS-12 through WS-26) under docs/rag/. Includes:

- README.md / INDEX.md: landing + total index (responsibility matrix,
  review verdicts, dual-link to source issues)
- overview/: full-pipeline architecture (4 .mmd diagrams),
  11-stage boundary contracts, doc map, source-code inventory
- pipeline/: 5 deep-dives (Loader/Parser/Chunking, Embedding,
  VDB & retrieval, GraphRAG, Rerank/Prompt/LLM)
- graphrag/, end-to-end/: v1.0 formal versions with full source
  retained as reference
- evolution/: 11 architecture-refactor proposals,
  6-direction roadmap, capability map
- review/: S3-T1 / S3-T2 final reviews, S2-T7 final summary
- _indexes/: glossary (81 terms), source->doc reverse index, chart index
- _release/: v1.0-RC1 release manifest, versioning convention,
  ops & freshness plan
- _meta/README.md: placeholder noting WS-12 governance assets gap

Aggregate review score 92.6/100 (8/8 PASS, 31/31 source-code spot
checks hit). The legacy docs/ ignore in .gitignore is narrowed to
docs/* with an explicit allowlist for docs/rag/.

Refs: WS-26
Co-authored-by: multica-agent <github@multica.ai>
2026-05-09 10:51:48 +08:00

445 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# [S2-T5] 检索后处理与生成Reranking / Prompt 工程 / LLM 调用 / 后处理)实现详解
**author:** Python 开发工程师
**source-commit:** `feae2f2e` (Merge PR #1033 release/v0.3.2)
**reviewer:** 待 [S2-T7] 评审
**last-reviewed-at:** 2026-05-08
---
## 一句话定位
本文档覆盖 MemoryBear RAG 链路的后半段:从检索结果进入系统,到最终 LLM 生成答案并输出给用户的全过程包括重排序、Prompt 组装、多模型 LLM 调用、流式输出、工具调用及生成后处理。
## 设计目标与适用场景
- **设计目标**:在多知识库、多检索策略(关键词 / 向量 / 混合 / GraphRAG返回的原始结果上通过重排序提升相关性通过 Prompt 工程高效利用上下文,通过多提供商 LLM 封装实现高可用调用,最终输出带引用溯源、支持流式/非流式的答案。
- **适用场景**
- Agent 聊天(`app_chat_service.py` / `draft_run_service.py`
- Workflow 知识检索节点(`workflow/nodes/knowledge/node.py`
- 独立 chunk 检索 API`chunk_controller.py`
## 关键概念与术语表
| 术语 | 含义 |
|------|------|
| Rerank | 在初步召回后对 chunk 进行精细重排序 |
| RedBearRerank | 基于 LangChain `BaseDocumentCompressor` 的 rerank 封装 |
| Dealer | 底层检索调度器,负责混合搜索、内置 rerank、引用插入 |
| KnowledgeRetrievalNode | Workflow 引擎中的知识检索节点 |
| LangChainAgent | 基于 `create_agent` 的 ReAct Agent负责工具调用循环 |
| citation | 生成后处理阶段向答案文本中插入 `[ID:N]` 引用标记 |
| rank_feature | 基于 tag 特征和 PageRank 的辅助排序分 |
## 实现概览Mermaid 流程图)
```
检索结果输入
┌─────────────────┐
│ Rerank 层 │
│ A:内置混合 │
│ B:外部模型 │
│ C:RedBearRerank │
│ D:ES层封装 │
└────────┬────────┘
┌─────────────────────────┐
│ Prompt 工程与上下文组装 │
│ 系统 Prompt + 技能 Prompt │
│ 知识上下文拼接 │
│ Token 预算管理 │
└────────┬────────────────┘
┌─────────────────────────┐
│ LLM 调用层 (LangChainAgent)│
│ ReAct 工具调用循环 │
│ 流式/非流式 │
│ 多模态 + 深度思考 │
└────────┬────────────────┘
┌─────────────────────────┐
│ 生成后处理 │
│ 引用过滤 + 下载链接 │
│ 引用插入 (embedding 匹配) │
│ JSON 结构化校验 │
└─────────────────────────┘
```
---
## 1. Reranking 章节
### 1.1 是否使用显式 Rerank
**是**。MemoryBear 在多处实现了 rerank采用"多方案并存、按场景选择"策略。
### 1.2 Rerank 方案全景
#### 方案 A内置混合 RerankDealer.rerank
**源码**`api/app/core/rag/nlp/search.py:606-643`
核心融合公式:
```
score = tkweight * token_similarity + vtweight * vector_similarity + rank_feature
```
- `tkweight` 默认 0.3`vtweight` 默认 0.7
- `token_similarity`:基于 rag_tokenizer 分词后的 Jaccard 风格相似度
- `vector_similarity`query_vector 与 chunk 向量的余弦相似度
- `rank_feature`tag 特征 TF-IDF 余弦 + PageRank缩放 10 倍(`search.py:579-604`
- token 权重分配:`content_ltks + title_tks*2 + important_kwd*5 + question_tks*6`
#### 方案 B外部 Rerank 模型Dealer.rerank_by_model
**源码**`api/app/core/rag/nlp/search.py:645-666`
将向量相似度替换为外部 rerank 模型的 `similarity()` 输出,保留 token 相似度和 rank_feature。
#### 方案 CRedBearRerankLCEL 兼容封装)
**源码**`api/app/core/models/rerank.py:11-84`
- 继承 `langchain_core.documents.BaseDocumentCompressor`
- 支持 `XINFERENCE` / `GPUSTACK``JinaRerank`
- 支持 `DASHSCOPE``DashScopeRerank`
- 端点自动规范化:补齐 `/v1/rerank`
使用场景:
- Workflow `KnowledgeRetrievalNode.rerank()``node.py:108-155`
- `ElasticSearchVector.rerank()``elasticsearch_vector.py:560-607`
- `nlp/search.py:rerank()``search.py:284-343`
#### 方案 DElasticSearchVector 层 Rerank
ES Vector 初始化时注入 `reranker_config``rerank()` 中调用 `self.reranker.compress_documents()`
### 1.3 阈值与延迟
- **内置 rerank**:本地 numpy 计算,毫秒级延迟
- **外部 rerank**:网络调用,本地 Xinference <10ms远程 DashScope 100-500ms
- **相似度阈值**`similarity_threshold` 默认 0.2,低于此值的 chunk 被过滤(`search.py:674-768`
### 1.4 为什么没有统一使用 Cross-Encoder
- Cross-Encoder 需额外部署,对小型部署不友好
- 内置 `Dealer.rerank` 在多数场景已足够
- RedBearRerank 作为可选增强,仅在显式配置 `reranker_id` 时启用
---
## 2. Prompt 工程与上下文组装
### 2.1 Prompt 模板组织
**目录**`api/app/core/rag/prompts/`
| 模板文件 | 用途 |
|----------|------|
| `ask_summary.md` | 知识库问答主 Prompt |
| `citation_prompt.md` | 引用标注规范(`[ID:i]` 格式) |
| `citation_plus.md` | 引用回填 Agent Prompt |
| `question_prompt.md` | 文本生成问题 |
| `keyword_prompt.md` | 关键词提取 |
| `structured_output_prompt.md` | JSON Schema 约束 |
| `cross_languages_*.md` | 跨语言查询扩展 |
| `analyze_task_*.md` | 任务分析与工具选择 |
**加载机制**`api/app/core/rag/prompts/template.py:9-20`,启动时加载并缓存。
### 2.2 上下文组装流程
**Agent 层**`api/app/core/agent/langchain_agent.py:230-271`
```python
def _prepare_messages(self, message, history, context, files):
messages = []
for msg in history:
if msg["role"] == "user": messages.append(HumanMessage(...))
elif msg["role"] == "assistant": messages.append(AIMessage(...))
user_content = message
if context:
user_content = f"参考信息:\n{context}\n\n用户问题:\n{user_content}"
messages.append(HumanMessage(content=user_content))
return messages
```
### 2.3 知识检索工具中的 Chunk 拼接
**源码**`api/app/services/draft_run_service.py:227-255`
```python
retrieve_chunks_result = knowledge_retrieval(query, kb_config)
retrieval_knowledge = [i.page_content for i in retrieve_chunks_result]
context = '\n\n'.join(retrieval_knowledge)
return f"检索到以下相关信息:\n\n{context}"
```
- chunk 间用 `\n\n` 分隔
- 引用信息document_id、file_name、score由外部 `citations_collector` 收集,与上下文字符串分离
- 属于"隐式引用"策略LLM 看不到 `[ID:N]`,引用回填在生成后完成
### 2.4 Token 预算管理
**源码**`api/app/core/rag/prompts/generator.py:46-80`
策略:
1. 计算总 token未超限直接返回
2. 超限后保留 `system` + 最后一条消息,丢弃中间历史
3. 仍超限则按比例截断 system 或 user 内容
### 2.5 System / User 分层结构
```
system: {用户自定义 system_prompt} + {技能 Prompt} + {文档图片识别指令}
user: {历史消息...}
user: 参考信息:\n\n{chunks}\n\n用户问题\n{query}
```
System Prompt 组装见 `app_chat_service.py:77-96`:先变量替换,再追加 skill_prompts。
---
## 3. LLM 调用
### 3.1 支持的模型与切换机制
**核心封装**`api/app/core/rag/llm/chat_model.py:52-63`
`Base` 类基于 OpenAI 兼容 API子类覆盖
| 类名 | 提供商 |
|------|--------|
| `GptTurbo` | OpenAI |
| `XinferenceChat` | Xinference |
| `HuggingFaceChat` | HuggingFace |
| `ModelScopeChat` | ModelScope |
| `AzureChat` | Azure OpenAI |
| `BaiChuanChat` | 百川 |
| `LocalAIChat` | LocalAI |
| `VolcEngineChat` | 火山引擎 |
| `OpenAI_APIChat` | VLLM / OpenAI-API-Compatible |
| `GPUStackChat` | GPUStack |
**切换机制**`ModelApiKeyService.get_available_api_key()` 根据 `model_id` 从数据库读取 provider/api_key/base_url/model_name运行时动态实例化。
### 3.2 流式 vs 非流式
**非流式**`Base._chat()``chat_model.py:122-150`
- `stream=False`,返回 `(text, total_tokens)`
- QWQ 推理模型强制内部走流式聚合,过滤 `<think>` 标签
**流式**`Base._chat_streamly()``chat_model.py:152-185`
- `stream=True`yield `(delta, token_count)`
- 支持 `reasoning_content` 提取
- `finish_reason == "length"` 时自动追加截断提示(中英文自适应)
**Agent 流式**`LangChainAgent.chat_stream()`
- `agent.astream_events(version="v2")`
- 处理 `on_chat_model_stream` / `on_llm_stream`
- 支持多模态响应解析OpenAI + 通义千问格式)
### 3.3 超时、重试、降级
**源码**`chat_model.py:64-89, 192-215`
- 超时:`LLM_TIMEOUT_SECONDS`(默认 600s
- 重试:`LLM_MAX_RETRIES`(默认 5+ 随机抖动延迟
- 仅对 `RATE_LIMIT` / `SERVER_ERROR` 重试
- **降级**:无自动模型降级,失败返回 `"**ERROR**: ..."`
### 3.4 函数调用 / 工具使用
**源码**`chat_model.py:251-303, 335-436`
- 最多 `max_rounds`(默认 5轮工具调用循环
- 工具参数解析使用 `json_repair.loads()` 增强容错
- 流式工具调用:`chat_streamly_with_tools()`
**Agent 工具循环**`LangChainAgent`
- `create_agent(model, tools, system_prompt)`
- `max_iterations = 5 + len(tools) * 2`
- 单个工具最大连续调用:`max_tool_consecutive_calls = 3`
- `_wrap_tools_with_tracking()` 防循环
### 3.5 CV 模型与序列到文本模型
**CV 模型**`cv_model.py``QWenCV``AzureGptV4` — 用于图片/版面分析。
**序列到文本**`sequence2txt_model.py``QWenSeq2txt`(带时间戳 ASR`GPTSeq2txt`Whisper— 用于音视频预处理。
---
## 4. 生成后处理
### 4.1 引用回填Citation Insertion
**源码**`api/app/core/rag/nlp/search.py:489-577`
流程:
1. 将答案按句子切分(避开代码块 ```` ``` ````
2. 对每句话 embedding与 chunk embeddings 计算 hybrid similarity
3. 阈值从 0.63 开始动态衰减×0.8),最低 0.3
4. 每句最多引用 4 个 chunk句末插入 `[ID:N]`
### 4.2 引用过滤与下载链接
**源码**`api/app/services/draft_run_service.py:474-490`
- `features_config.citation.enabled` 开关控制
- `allow_download=True` 时附加 `download_url`
### 4.3 安全过滤
当前版本无显式敏感词过滤模块。安全依赖:
- LLM 提供商自带内容过滤
- `ERROR_CONTENT_FILTER` 错误码捕获
### 4.4 输出结构化JSON Schema
**源码**`api/app/core/agent/langchain_agent.py:85-92`
通过 system prompt 注入 `"\n请以JSON格式输出。"` 实现(非 `response_format` API因为 LangChain Agent 有工具时无法使用原生 API。
---
## 5. 端到端示例
### 场景Agent 聊天触发知识库检索
**Step 1** — 用户提问:`"MemoryBear 的 Rerank 策略是什么?"`
**Step 2** — System Prompt 组装:
```
你是一个专业的 AI 知识库助手,名为 Miss R。
任务:根据知识库中的信息回答用户问题。
要求:不要编造信息;使用 Markdown用用户提问的语言回答。
```
(来自 `ask_summary.md`
**Step 3** — LLM 判断调用 `knowledge_retrieval_tool`
工具内部:
```python
retrieve_chunks_result = knowledge_retrieval(query, kb_config)
context = '\n\n'.join([i.page_content for i in retrieve_chunks_result])
return f"检索到以下相关信息:\n\n{context}"
```
**Step 4** — 若配置 `reranker_id`,执行 RedBearRerank
```python
reranker = RedBearRerank(RedBearModelConfig(...))
reranked_docs = list(reranker.compress_documents(documents, query))
```
**Step 5** — Agent 组装消息并调用 LLM
```
system: 你是一个专业的 AI 知识库助手...
user: 参考信息:\n\nChunk 0...\n\nChunk 1...\n\n用户问题\nMemoryBear 的 Rerank 策略是什么?
```
**Step 6** — 输出后处理:
```python
filtered_citations = _filter_citations(features_config, citations_collector)
```
最终返回content + citations含 document_id、file_name、score、可选 download_url
---
## 6. 关键源码索引
| 功能 | 文件 | 类/函数 | 行号 |
|------|------|---------|------|
| Rerank 封装 | `api/app/core/models/rerank.py` | `RedBearRerank` | 11-84 |
| 内置混合 Rerank | `api/app/core/rag/nlp/search.py` | `Dealer.rerank` | 606-643 |
| 外部模型 Rerank | `api/app/core/rag/nlp/search.py` | `Dealer.rerank_by_model` | 645-666 |
| rank_feature | `api/app/core/rag/nlp/search.py` | `_rank_feature_scores` | 579-604 |
| 独立 rerank | `api/app/core/rag/nlp/search.py` | `rerank()` | 284-343 |
| 知识检索入口 | `api/app/core/rag/nlp/search.py` | `knowledge_retrieval()` | 36-147 |
| ES Vector rerank | `api/app/core/rag/vdb/elasticsearch/elasticsearch_vector.py` | `ElasticSearchVector.rerank` | 560-607 |
| Workflow 节点 rerank | `api/app/core/workflow/nodes/knowledge/node.py` | `KnowledgeRetrievalNode.rerank` | 108-155 |
| Workflow 执行 | `api/app/core/workflow/nodes/knowledge/node.py` | `KnowledgeRetrievalNode.execute` | 303-378 |
| LLM 基类 | `api/app/core/rag/llm/chat_model.py` | `Base` | 52-319 |
| 流式 LLM | `api/app/core/rag/llm/chat_model.py` | `_chat_streamly` | 152-185 |
| 工具调用 | `api/app/core/rag/llm/chat_model.py` | `chat_with_tools` | 251-303 |
| 流式工具调用 | `api/app/core/rag/llm/chat_model.py` | `chat_streamly_with_tools` | 335-436 |
| 错误分类 | `api/app/core/rag/llm/chat_model.py` | `_classify_error` | 69-89 |
| CV 模型 | `api/app/core/rag/llm/cv_model.py` | `QWenCV`, `AzureGptV4` | 1-497 |
| 音频转录 | `api/app/core/rag/llm/sequence2txt_model.py` | `QWenSeq2txt`, `GPTSeq2txt` | 1-215 |
| Prompt 加载 | `api/app/core/rag/prompts/template.py` | `load_prompt` | 9-20 |
| Prompt 生成器 | `api/app/core/rag/prompts/generator.py` | `message_fit_in` 等 | 1-744 |
| Agent 封装 | `api/app/core/agent/langchain_agent.py` | `LangChainAgent` | 26-641 |
| Agent 消息准备 | `api/app/core/agent/langchain_agent.py` | `_prepare_messages` | 230-271 |
| 知识检索工具 | `api/app/services/draft_run_service.py` | `create_knowledge_retrieval_tool` | 195-263 |
| 引用过滤 | `api/app/services/draft_run_service.py` | `_filter_citations` | 474-490 |
| 聊天服务 | `api/app/services/app_chat_service.py` | `agnet_chat` | 43-239 |
| 流式聊天 | `api/app/services/app_chat_service.py` | `agnet_chat_stream` | 340-550 |
| 引用插入 | `api/app/core/rag/nlp/search.py` | `Dealer.insert_citations` | 489-577 |
---
## 7. 配置项与可调参数
**环境变量**
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `LLM_TIMEOUT_SECONDS` | 600 | LLM 超时 |
| `LLM_MAX_RETRIES` | 5 | 最大重试 |
| `LLM_BASE_DELAY` | 2.0 | 重试基础延迟 |
**知识检索配置**
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `retrieve_type` | `participle` | participle/semantic/hybrid/graph |
| `similarity_threshold` | 0.2 | 关键词相似度阈值 |
| `vector_similarity_weight` | 0.3 | 向量权重 |
| `top_k` | 4 | 单次检索 chunk 数 |
| `reranker_id` | `None` | Rerank 模型 ID |
| `reranker_top_k` | 4 | Rerank 后最终返回数 |
**Agent 参数**
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `max_iterations` | `5 + len(tools) * 2` | Agent 最大迭代 |
| `max_tool_consecutive_calls` | 3 | 单工具最大连续调用 |
| `max_rounds` | 5 | LLM 工具调用最大轮数 |
| `temperature` | 0.7 | 生成温度 |
| `max_tokens` | 2000 | 最大生成 token |
| `json_output` | `False` | 强制 JSON 输出 |
| `deep_thinking` | `False` | 深度思考 |
---
## 8. 边界条件与已知限制
1. **外部 Rerank 延迟高**RedBearRerank 调用 Jina/DashScope API无本地缓存。
2. **Token 裁剪较粗糙**`message_fit_in` 丢弃中间历史,可能丢失上下文;按比例截断可能切断语义。
3. **引用回填非 LLM 原生**:基于 embedding 相似度匹配,表述不同可能漏引。
4. **JSON 输出兼容性差**:通过 system prompt 注入实现,可靠性低于原生 `response_format`
5. **无模型降级**LLM 失败返回错误文本,不自动切换备用模型。
6. **混合检索融合简单**:仅去重取并集,无 RRF 或加权分数融合。
7. **GraphRAG 结果前置**:始终 `insert(0, ...)`,优先级最高但无分数参与 rerank。
---
## 9. 优化建议与未来扩展点
1. **Rerank 缓存**:对高频 query 做 LRU 缓存,降低外部 API 成本。
2. **引用增强**:将 `citation_prompt.md` 注入 system prompt让 LLM 生成阶段就输出 `[ID:N]`
3. **Token 预算精细化**:引入 `tiktoken` 精确计数,实现滑动窗口历史管理。
4. **模型降级**:在 `Base.chat()` 中增加 fallback 模型链。
5. **混合检索 RRF**:在 ES 查询层面实现 Reciprocal Rank Fusion。
6. **流式引用**:在 `on_tool_end` 事件中实时 emit citation 元数据。
7. **输出校验中间件**:对 `json_output=True` 增加 JSON Schema 强制校验层。
---
以上为 [S2-T5] 初版全文,请评审。