Some checks failed
Sync to Gitee / sync (push) Has been cancelled
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>
445 lines
17 KiB
Markdown
445 lines
17 KiB
Markdown
---
|
||
|
||
# [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:内置混合 Rerank(Dealer.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。
|
||
|
||
#### 方案 C:RedBearRerank(LCEL 兼容封装)
|
||
|
||
**源码**:`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`)
|
||
|
||
#### 方案 D:ElasticSearchVector 层 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] 初版全文,请评审。 |