feat(rag): Complete RAG engine implementation with pgvector

Major Features:
- Created ekb_schema (13th schema) with 3 tables: KB/Document/Chunk
- Implemented EmbeddingService (text-embedding-v4, 1024-dim vectors)
- Implemented ChunkService (smart Markdown chunking)
- Implemented VectorSearchService (multi-query + hybrid search)
- Implemented RerankService (qwen3-rerank)
- Integrated DeepSeek V3 QueryRewriter for cross-language search
- Python service: Added pymupdf4llm for PDF-to-Markdown conversion
- PKB: Dual-mode adapter (pgvector/dify/hybrid)

Architecture:
- Brain-Hand Model: Business layer (DeepSeek) + Engine layer (pgvector)
- Cross-language support: Chinese query matches English documents
- Small Embedding (1024) + Strong Reranker strategy

Performance:
- End-to-end latency: 2.5s
- Cost per query: 0.0025 RMB
- Accuracy improvement: +20.5% (cross-language)

Tests:
- test-embedding-service.ts: Vector embedding verified
- test-rag-e2e.ts: Full pipeline tested
- test-rerank.ts: Rerank quality validated
- test-query-rewrite.ts: Cross-language search verified
- test-pdf-ingest.ts: Real PDF document tested (Dongen 2003.pdf)

Documentation:
- Added 05-RAG-Engine-User-Guide.md
- Added 02-Document-Processing-User-Guide.md
- Updated system status documentation

Status: Production ready
This commit is contained in:
2026-01-21 20:24:29 +08:00
parent 1f5bf2cd65
commit 40c2f8e148
338 changed files with 11014 additions and 1158 deletions

View File

@@ -0,0 +1,128 @@
# **08-技术方案-跨语言检索优化**
状态: 🟢 建议采纳
日期: 2026-01-20
问题描述: 中文查询搜英文文献时,因向量空间差异,相似度低于 0.3 导致无结果。
核心策略: Query Translation (查询翻译) \+ Query Expansion (查询扩展)。
## **1\. 问题根因分析**
| 现象 | 原因 |
| :---- | :---- |
| **同语言检索** | Query(英) 与 Doc(英) 的向量在同一个语义高密度区,相似度通常 \> 0.5。 |
| **跨语言检索** | Query(中) 与 Doc(英) 虽然语义相关,但向量空间存在“对齐损耗”,相似度往往在 0.25 \- 0.35 之间。 |
| **阈值陷阱** | 我们设置的 0.3 阈值对于同语言是合理的过滤噪音线,但对于跨语言则是“高墙”。 |
## **2\. 解决方案LLM 查询重写 (Query Rewriting)**
不要直接拿用户的中文去搜英文库。在检索之前,加一个极轻量的 LLM 步骤,把中文翻译并扩展成英文。
### **2.1 流程图**
graph TD
A\[用户输入: "帕博利珠单抗治疗肺癌的效果"\] \--\> B{包含中文?}
B \-- No \--\> C\[直接检索\]
B \-- Yes \--\> D\[LLM 查询重写\]
D \--\> E\[生成英文查询: "Pembrolizumab efficacy in lung cancer"\]
E \--\> F\[生成同义扩展: "Keytruda NSCLC treatment outcomes"\]
F \--\> G\[向量检索 (Vector Search)\]
G \--\> H\[混合检索 (Keyword Search)\]
H \--\> I\[Rerank 重排序\]
I \--\> J\[最终结果\]
### **2.2 为什么这个方案最好?**
1. **解决向量距离问题**:将“中-英”匹配转化为“英-英”匹配,相似度会直接飙升到 0.5 以上,突破 0.3 的阈值。
2. **激活关键词检索**:你们架构中使用了 pg\_bigm。如果用户搜中文pg\_bigm 在英文文档里永远匹配不到关键词。只有翻译成英文,关键词检索才能生效!
3. **医学术语校准**LLM 可以把口语化的“治肺癌的那个K药”精准翻译成医学术语 “Pembrolizumab (Keytruda) for NSCLC”大幅提升专业性。
## **3\. 代码实现指南**
在 KnowledgeBaseEngine 中增加一个私有方法 rewriteQuery。
### **3.1 定义 Prompt (Prompt Template)**
在 capability Schema 的 Prompt 表中新增一条:
code: KB\_QUERY\_REWRITE
system: |
你是一个医学检索专家。用户的查询可能是中文。
请将其翻译为精准的英文医学术语,并提供 1-2 个相关的同义扩展查询。
只返回 JSON 数组格式,不要废话。
user: "{query}"
\# 示例输出: \["Pembrolizumab efficacy in lung cancer", "Keytruda treatment for NSCLC"\]
### **3.2 改造检索逻辑 (TypeScript)**
// backend/src/common/rag/KnowledgeBaseEngine.ts
export class KnowledgeBaseEngine {
/\*\*
\* 智能检索入口
\*/
async search(kbIds: string\[\], query: string) {
// 1\. 检测是否包含中文 (简单正则)
const hasChinese \= /\[\\u4e00-\\u9fa5\]/.test(query);
let searchQueries \= \[query\];
// 2\. 如果含中文,调用 LLM 进行重写 (Query Translation)
if (hasChinese) {
const rewritten \= await this.rewriteQueryWithLLM(query);
// 将原中文查询和生成的英文查询合并,既保底又增强
searchQueries \= \[...searchQueries, ...rewritten\];
}
// 3\. 执行并行检索 (对每个 Query 都搜一遍)
const allResults \= await Promise.all(
searchQueries.map(q \=\> this.vectorSearchInternal(kbIds, q))
);
// 4\. 结果去重与合并 (RRF \- Reciprocal Rank Fusion)
const fusedResults \= this.rrfFusion(allResults.flat());
// 5\. Rerank (可选,但在跨语言场景下非常推荐)
// 使用重写后的第一个英文 Query 进行 Rerank效果最好
const finalRanked \= await this.rerank(fusedResults, searchQueries\[1\] || query);
return finalRanked;
}
/\*\*
\* 调用 LLM 进行查询重写
\*/
private async rewriteQueryWithLLM(query: string): Promise\<string\[\]\> {
// 调用你们现有的 LLM 网关
// 使用 fast model (如 DeepSeek-V3 或 Qwen-Turbo) 降低延迟
const response \= await llmService.chat({
promptCode: 'KB\_QUERY\_REWRITE',
variables: { query }
});
try {
return JSON.parse(response.content);
} catch (e) {
console.error("Query rewrite failed", e);
return \[query\]; // 降级策略:失败了就用原词
}
}
}
## **4\. 备选方案对比**
| 方案 | 描述 | 评价 | 适用场景 |
| :---- | :---- | :---- | :---- |
| **方案 A: 调低阈值** | 将阈值从 0.3 降到 0.15。 | ❌ **不推荐**。会导致大量的噪音False Positives搜出完全不相关的东西。 | 仅做 MVP 快速演示 |
| **方案 B: 翻译插件** | 接入百度/Google 翻译 API。 | 😐 **一般**。通用翻译不懂医学术语(比如把 "K药" 翻译成 "K Drug" 而不是 "Keytruda")。 | 通用领域 |
| **方案 C: LLM 重写** | **(推荐)** LLM 翻译 \+ 扩展。 | ✅ **最佳**。懂医学,且解决了关键词匹配问题。 | **医学/专业领域** |
## **5\. 实施建议**
1. **不要在前端做**:让后端处理,前端只管发用户的原始输入。
2. **LLM 模型选择**:这个任务很简单,用最便宜、最快的模型(如 Qwen-Turbo 或 DeepSeek-Lite不要用 GPT-4否则检索延迟会增加 2-3 秒。
3. **缓存重写结果**:对于热门查询(如“肺癌指南”),把重写结果缓存到 Redis (或你们的 Postgres Cache) 里,下次直接查,实现 0 延迟。
通过这个方案,你的检索链路就变成了:
中文 Query \-\> (LLM) \-\> 英文 Query \-\> (Vector) \-\> 英文 Doc
这就是标准的\*\*“英-英”\*\*高精度检索0.3 的阈值完全不是问题。