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:
136
docs/09-架构实施/01-知识库引擎架构设计.md
Normal file
136
docs/09-架构实施/01-知识库引擎架构设计.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# **知识库引擎架构设计**
|
||||
|
||||
文档版本: v2.0 (架构修正版)
|
||||
创建日期: 2026-01-20
|
||||
最后更新: 2026-01-21
|
||||
核心原则: 引擎负责“执行”,业务负责“思考”。
|
||||
|
||||
## **📋 概述**
|
||||
|
||||
### **能力定位修正**
|
||||
|
||||
**KnowledgeBaseEngine** 是一个**纯粹的检索执行器**。它不负责“理解用户意图”,只负责“执行检索指令”。
|
||||
|
||||
**❌ 错误的设计:**
|
||||
|
||||
* 引擎内部调用 LLM 分析聊天记录。
|
||||
* 引擎依赖 Chat History 数据结构。
|
||||
|
||||
**✅ 正确的设计:**
|
||||
|
||||
* **业务模块 (AIA/ASL)**:调用 DeepSeek 分析意图 \-\> 生成 \[Query1, Query2, Query3\]。
|
||||
* **引擎模块 (EKB)**:接收 queries\[\] \-\> 执行向量/关键词检索 \-\> RRF 融合 \-\> 返回结果。
|
||||
|
||||
## **🏗️ 交互流程图 (The "Brain-Hand" Model)**
|
||||
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Biz as 业务模块 (AIA/ASL)
|
||||
participant LLM as DeepSeek V3 (Brain)
|
||||
participant Engine as 知识库引擎 (Hand)
|
||||
participant DB as PostgreSQL
|
||||
|
||||
User-\>\>Biz: "副作用大吗?" (带着上下文)
|
||||
|
||||
rect rgb(240, 248, 255\)
|
||||
Note over Biz, LLM: 🧠 思考阶段 (策略层)
|
||||
Biz-\>\>LLM: Prompt: "结合历史,生成检索词"
|
||||
LLM--\>\>Biz: \["NSCLC一线治疗副作用", "Pembrolizumab AE"\]
|
||||
end
|
||||
|
||||
rect rgb(255, 240, 245\)
|
||||
Note over Biz, Engine: ✋ 执行阶段 (机制层)
|
||||
Biz-\>\>Engine: search(queries=\[...\])
|
||||
Engine-\>\>DB: 并行向量检索 \+ 关键词检索
|
||||
Engine-\>\>Engine: RRF 融合 & Rerank
|
||||
Engine--\>\>Biz: 返回 Top-K 文档
|
||||
end
|
||||
|
||||
Biz-\>\>LLM: Prompt: "基于这些文档回答用户"
|
||||
LLM--\>\>User: 最终回答
|
||||
|
||||
## **📦 API 设计 (KnowledgeBaseEngine)**
|
||||
|
||||
引擎的 API 变得更加干净、通用:
|
||||
|
||||
export class KnowledgeBaseEngine {
|
||||
/\*\*
|
||||
\* 纯粹的检索接口
|
||||
\* @param kbIds 知识库 ID 列表
|
||||
\* @param searchQueries 检索词列表(由业务层生成好的)
|
||||
\*/
|
||||
async search(
|
||||
kbIds: string\[\],
|
||||
searchQueries: string\[\], // 👈 接收一组词,而不是一个 query
|
||||
options?: {
|
||||
topK?: number;
|
||||
filters?: SearchFilters; // 结构化过滤,如 { year: 2024 }
|
||||
}
|
||||
): Promise\<SearchResult\[\]\> {
|
||||
// 1\. 并行执行所有 query 的检索 (Fan-out)
|
||||
const allResults \= await Promise.all(
|
||||
searchQueries.map(q \=\> this.vectorSearchInternal(kbIds, q))
|
||||
);
|
||||
|
||||
// 2\. RRF 融合
|
||||
const fusedResults \= this.rrfFusion(allResults.flat());
|
||||
|
||||
// 3\. Rerank (可选)
|
||||
// 使用第一个 Query 作为基准进行重排序
|
||||
return this.rerank(fusedResults, searchQueries\[0\]);
|
||||
}
|
||||
}
|
||||
|
||||
把“语义理解”和“意图分析”强行塞进底层的 RAG 引擎,确实会让引擎变得臃肿且不通用。
|
||||
|
||||
### **为什么你的思路是“架构级”的提升?**
|
||||
|
||||
1. **解耦 (Decoupling)**:
|
||||
* **RAG 引擎 (KnowledgeBaseEngine)** 应该是一个\*\*“精准的执行者”\*\*(机制)。它的职责是:“你给我一组词,我给你找出最匹配的文档”。它不应该关心“这组词是来自一个愤怒的医生,还是来自一个做作业的学生”。
|
||||
* **业务模块 (AIA/ASL)** 才是\*\*“聪明的决策者”\*\*(策略)。只有业务模块才知道当前的对话上下文(Context)、用户的角色(Role)以及当前任务的目标(Goal)。
|
||||
2. **场景化差异 (Context Matters)**:
|
||||
* **AIA(聊天)**:上下文是 `Chat History`,策略是“意图补全”。
|
||||
* **ASL(文献筛选)**:上下文是 `Inclusion Criteria`(入排标准),策略是“PICO 拆解”。
|
||||
* **如果都塞进引擎**:引擎内部就要写一堆 `if (isChat) ... else if (isLiterature) ...`,这是架构的大忌。
|
||||
|
||||
---
|
||||
|
||||
我根据你的这个关键修正,将 **《01-知识库引擎架构设计》** 和 **《08-技术方案》** 进行了**最终架构对齐**。
|
||||
|
||||
### **修改点概览**
|
||||
|
||||
1. **RAG 引擎**:回归纯粹。删除 `search(history)` 这种带业务逻辑的接口,只保留 `search(queries[])`。
|
||||
2. **业务层**:新增 **“检索策略层”**。DeepSeek 的思考过程发生在业务层。
|
||||
|
||||
也就是说,利用DeepSeek做翻译,做语义理解都不应该放在RAG引擎里,对吗?还是说我们只保留翻译?还是都不保留,在业务模块调用DeepSeek的时候,就考虑中英文翻译的事
|
||||
|
||||
**结论:是的,应该全部剥离。**
|
||||
|
||||
在 KnowledgeBaseEngine(RAG 引擎)里,**既不保留语义理解,也不保留翻译**。它应该是一个“没有任何感情的杀手”,只负责执行。
|
||||
|
||||
### **1\. 最终架构定调:各司其职**
|
||||
|
||||
为了让你跟团队沟通更明确,我把这种模式总结为 **“决策与执行分离”**:
|
||||
|
||||
| 层级 | 角色 | 职责 (谁做?) | 核心逻辑 |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **业务层** (AIA/ASL) | **决策者** (The Brain) | **DeepSeek V3** | **“怎么搜?”** 它负责看懂用户的话,看懂历史记录,然后决定:“我要用中文搜一遍,再用英文搜一遍,还要查一下 K 药的学名”。 最终产出:\["K药", "Keytruda", "Pembrolizumab"\] |
|
||||
| **引擎层** (EKB) | **执行者** (The Hand) | **Postgres \+ Vector** | **“去搜!”** 它根本不知道什么是“K药”,它只收到一个指令:search(queries: string\[\])。 它负责把这 3 个词扔进数据库,并行跑向量检索和关键词检索,然后把结果捞上来。 |
|
||||
|
||||
### **2\. 为什么要连“翻译”都移出去?**
|
||||
|
||||
你可能会问:“翻译不是通用的吗?为什么不留在引擎里?”
|
||||
|
||||
因为在医学场景下,**“翻译”往往依赖“上下文”**,而引擎是没有上下文的。
|
||||
|
||||
* **场景 A(AIA 聊天)**:
|
||||
* 用户说:“阳性吗?”(上文在聊 **EGFR 基因突变**)。
|
||||
* **业务层 DeepSeek**:结合历史,生成检索词 \["EGFR mutation positive", "EGFR 突变 阳性"\]。
|
||||
* **如果是引擎层做翻译**:引擎只看到“阳性吗?”,翻译成 Is it positive?,去搜可能会搜出“新冠阳性”或“艾滋病阳性”,完全跑偏。
|
||||
* **场景 B(ASL 文献筛选)**:
|
||||
* 用户设定:“年龄 \> 60岁”。
|
||||
* **业务层 DeepSeek**:生成检索词 \["Elderly patients", "Geriatric", "Age \> 60"\]。
|
||||
* **如果是引擎层做翻译**:引擎根本不知道这代表“老年人”,可能直译。
|
||||
|
||||
**所以,只有业务层才有资格做“精准的翻译(即意图理解)”。**
|
||||
|
||||
151
docs/09-架构实施/07-架构决策-新增EKB独立Schema.md
Normal file
151
docs/09-架构实施/07-架构决策-新增EKB独立Schema.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# **架构决策记录 (ADR-013):关于新增第 13 个 Schema (ekb\_schema) 的提案**
|
||||
|
||||
状态: 🟢 提议中 (Proposed)
|
||||
日期: 2026-01-20
|
||||
决策者: 架构师 & 开发团队
|
||||
涉及模块: 知识库引擎 (EKB), 个人知识库 (PKB), 智能文献 (ASL), 智能问答 (AIA)
|
||||
|
||||
## **1\. 背景与问题**
|
||||
|
||||
目前我们的 Postgres-Only 架构已成功实施了 12 个 Schema 的隔离策略(platform, common, pkb, asl 等)。
|
||||
|
||||
随着 **知识库引擎 (Knowledge Base Engine)** 的引入,我们需要存储海量的向量切片数据(EkbChunk 表)和多模态文档数据(EkbDocument 表)。
|
||||
|
||||
**当前面临的问题是:这些数据应该存放在哪里?**
|
||||
|
||||
我们面临三个选项:
|
||||
|
||||
1. **选项 A**:放在 pkb Schema 中(因为 PKB 是第一个使用者)。
|
||||
2. **选项 B**:放在 common 或 capability Schema 中(因为它是通用能力)。
|
||||
3. **选项 C**:新建第 13 个独立 Schema —— **ekb\_schema**。
|
||||
|
||||
## **2\. 决策结论**
|
||||
|
||||
**我们决定采用【选项 C】:创建独立的 ekb\_schema。**
|
||||
|
||||
这意味着我们的 Prisma datasource 配置将包含 13 个 Schema:
|
||||
schemas \= \[..., "ekb\_schema"\]
|
||||
|
||||
## **3\. 决策详细理由**
|
||||
|
||||
### **3.1 架构分层:避免依赖倒置 (Dependency Inversion)**
|
||||
|
||||
知识库引擎是**底层基础设施(Infrastructure Layer)**,类似于“图书馆大楼”;而 PKB、ASL、AIA 是**上层业务应用(Application Layer)**,类似于“租户”。
|
||||
|
||||
* 如果将引擎表放在 pkb Schema 中,会导致逻辑上的“ASL 依赖 PKB”,这是错误的依赖关系。
|
||||
* 通过独立 ekb\_schema,所有业务模块(PKB, ASL, AIA)都平等地依赖 EKB,架构层次清晰。
|
||||
|
||||
### **3.2 运维隔离:重型数据的特殊需求**
|
||||
|
||||
向量表 (EkbChunk) 具有显著的\*\*“重数据”\*\*特征:
|
||||
|
||||
* **数据量大**:可能迅速增长到百万/千万行级别。
|
||||
* **索引特殊**:使用 HNSW 向量索引,构建和维护成本高。
|
||||
* **维护频繁**:向量表对 UPDATE/DELETE 敏感,需要更激进的 VACUUM (垃圾回收) 策略。
|
||||
|
||||
将其隔离在独立 Schema 中,允许 DBA 未来针对该 Schema 进行独立的性能调优(如分配特定的 Tablespace 或调整内存参数),而不影响 users 或 orders 等轻量级业务表。
|
||||
|
||||
### **3.3 业务边界清晰**
|
||||
|
||||
* common:存放纯技术组件(Log, Storage 记录)。
|
||||
* capability:存放轻量级业务配置(Prompt 模板)。
|
||||
* ekb\_schema:存放核心知识资产和向量数据。
|
||||
|
||||
混在一起会导致 common 变得极其臃肿,增加后续拆分微服务的难度。
|
||||
|
||||
## **4\. 性能影响评估 (Performance Review)**
|
||||
|
||||
团队可能担心:“13 个 Schema 会不会太多?会不会拖慢速度?”
|
||||
|
||||
**技术评估结论:对性能几乎无负面影响(Zero Overhead)。**
|
||||
|
||||
| 关注点 | 技术事实 | 结论 |
|
||||
| :---- | :---- | :---- |
|
||||
| **查询速度** | PostgreSQL 内部使用 OID 查找表,Schema 只是逻辑命名空间。跨 Schema Join (JOIN ekb.Chunk) 与同 Schema Join 性能完全一致。 | ✅ 无损耗 |
|
||||
| **连接资源** | 我们使用的是 Prisma 单一连接池。所有 Schema 复用同一个 TCP 连接,不增加数据库连接数。 | ✅ 无损耗 |
|
||||
| **内存占用** | Schema 本身只占用极少的元数据空间。Postgres 支持单库数千个 Schema 毫无压力。 | ✅ 可忽略 |
|
||||
| **维护效率** | 独立的 Schema 让 pg\_dump 备份和 VACUUM 维护更灵活(可只备份业务数据,单独备份向量数据)。 | ✅ 正向收益 |
|
||||
|
||||
## **5\. 潜在风险与应对 (Risks & Mitigation)**
|
||||
|
||||
虽然性能无忧,但在多 Schema 开发中存在以下“坑”,需提前规避:
|
||||
|
||||
### **🔧 坑 1:Prisma 的跨 Schema 关联**
|
||||
|
||||
* **问题**:跨 Schema 定义外键(如 EkbDocument 关联 User)时,容易因缺少标记报错。
|
||||
* **解决方案**:
|
||||
1. **显式标记**:关联的两个 Model 必须都带有 @@schema("...") 标记。
|
||||
2. **双向定义**:在两边都定义 @relation 字段,确保 Prisma Client 能正确生成跨 Schema 的 Join 查询。
|
||||
3. **弱关联推荐**:对于非强一致性业务,建议仅存储 ID 字符串(如 userId),减少数据库层面的硬外键约束,提升灵活性。
|
||||
|
||||
### **🔧 坑 2:原生 SQL 的写法复杂度**
|
||||
|
||||
* **问题**:在使用 prisma.$queryRaw 进行向量检索时,很容易忘记加 Schema 前缀,导致 Relation "EkbChunk" does not exist 错误。
|
||||
* **解决方案**:
|
||||
* **强制带前缀**:在写 SQL 时必须使用双引号包裹 Schema 和表名,例如 FROM "ekb\_schema"."EkbChunk"。
|
||||
* **封装服务**:禁止在 Controller 层写 SQL,所有向量检索逻辑必须封装在 KnowledgeBaseEngine 类中,屏蔽底层细节。
|
||||
|
||||
### **🔧 坑 3:迁移文件管理 (Migration Clutter)**
|
||||
|
||||
* **问题**:Prisma 将所有 Schema 的变更都放在同一个 prisma/migrations 文件夹下,文件多了容易混乱。
|
||||
* **解决方案**:
|
||||
* **命名规范**:执行迁移时强制加前缀。
|
||||
* ✅ npx prisma migrate dev \--name ekb\_init\_vector\_table
|
||||
* ✅ npx prisma migrate dev \--name aia\_update\_agents
|
||||
* 这样在排查问题时,能一眼看出该 Migration 属于哪个模块。
|
||||
|
||||
## **6\. 实施计划 (Implementation)**
|
||||
|
||||
### **步骤 1: 更新 Prisma 配置**
|
||||
|
||||
在 prisma/schema.prisma 中:
|
||||
|
||||
generator client {
|
||||
provider \= "prisma-client-js"
|
||||
previewFeatures \= \["multiSchema", "postgresqlExtensions"\]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider \= "postgresql"
|
||||
url \= env("DATABASE\_URL")
|
||||
extensions \= \[vector, pg\_bigm\] // 确保启用插件
|
||||
// 添加新的 schema
|
||||
schemas \= \[..., "capability", "ekb\_schema"\]
|
||||
}
|
||||
|
||||
### **步骤 2: 定义模型**
|
||||
|
||||
将 04-数据模型设计.md 中的模型放入:
|
||||
|
||||
model EkbKnowledgeBase {
|
||||
// ... 字段定义
|
||||
@@schema("ekb\_schema") // 👈 关键点
|
||||
}
|
||||
|
||||
model EkbDocument {
|
||||
// ... 字段定义
|
||||
kbId String
|
||||
kb EkbKnowledgeBase @relation(fields: \[kbId\], references: \[id\])
|
||||
@@schema("ekb\_schema")
|
||||
}
|
||||
// ... EkbChunk 同理
|
||||
|
||||
### **步骤 3: 跨 Schema 关联 (注意事项)**
|
||||
|
||||
如果业务表(如 pkb\_schema.UserPkbConfig)需要关联知识库:
|
||||
|
||||
// 在 pkb\_schema 中
|
||||
model UserPkbConfig {
|
||||
id String @id
|
||||
kbId String
|
||||
kb EkbKnowledgeBase @relation(fields: \[kbId\], references: \[id\]) // 👈 跨 Schema 关联支持良好
|
||||
@@schema("pkb")
|
||||
}
|
||||
|
||||
## **7\. 常见问题 (FAQ)**
|
||||
|
||||
Q: 以后如果要把 EKB 拆成独立微服务怎么办?
|
||||
A: 正因为我们现在用了独立的 Schema,拆分微服务时只需要把这个 Schema 导出,部署到新数据库即可。如果混在 pkb 里,拆分反而极其痛苦。
|
||||
Q: 为什么不放在 capability Schema?
|
||||
A: capability 目前主要存 Prompt 模板,数据量极小。而 ekb 未来会有大量向量数据,体量差异过大,建议物理上保持逻辑距离。
|
||||
**结论:** 创建第 13 个 Schema 是符合我们“Postgres-Only \+ 模块化”架构原则的最佳实践,既保证了性能,又为未来的运维和扩展留足了空间。
|
||||
Reference in New Issue
Block a user