Files
AIclinicalresearch/docs/02-通用能力层/03-RAG引擎/知识库引擎数据模型设计审查报告.md
HaHafeng 40c2f8e148 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
2026-01-21 20:24:29 +08:00

11 KiB
Raw Permalink Blame History

知识库引擎数据模型设计审查报告

文档状态: 已确认 (v2.0 融合版)
审查日期: 2026-01-20
审查对象: [04-数据模型设计.md] & 架构师建议方案
适用架构: Postgres-Only (Prisma + pgvector + pg-boss)
目标读者: 开发团队、架构师

1. 核心决策摘要 (Executive Summary)

经过对业务需求文献、病历、多模态和团队现状2人开发、运维极简的深度评估我们做出了以下关键技术决策

决策 1采用 "Unified Schema"(统一模型)策略

  • 结论:所有类型的知识库(个人文献库、系统规则库、病历库)共用一套数据库表结构
  • 理由
    • 运维成本避免维护多套向量索引HNSW减少 2 人团队的数据库维护负担。
    • 业务灵活性:通过 contentType 和 JSONB 字段支持未知的新内容类型,无需频繁迁移数据库 (Migration)。

决策 2引入 "Container Table"(容器表)

  • 结论:在文档表之上,必须增加 EkbKnowledgeBase 表。
  • 理由
    • 权限隔离:明确区分 "User KB" (个人私有) 和 "System KB" (全员共享)。
    • 策略配置:允许为不同库配置不同的切片大小 (ChunkSize) 或检索策略 (TopK)。

决策 3确立 "四层数据架构"

  • 结论:采纳上传文档中的 Layer 0-4 分层设计。
  • 价值:确保系统的高容错性 —— 即使 AI 提取结构化数据失败Layer 4基础的 RAG 检索Layer 0依然可用。

2. 最终融合版架构图 (ER Diagram)

我们融合了原设计的“四层架构”优势和建议方案的“容器管理”优势,形成了最终的 v2.0 模型。

erDiagram
%% 容器层(新增):管理权限和策略
Ekb_KnowledgeBase {
string id PK
string type "USER | SYSTEM"
string owner_id "userId | moduleId"
jsonb config "策略配置(chunkSize等)"
}

%% 文档层(四层架构):管理内容和元数据  
Ekb\_Document {  
    string id PK  
    string kb\_id FK  
    string content\_type "LITERATURE | CASE | ..."  
    string extracted\_text "Layer 0: 全文"  
    jsonb metadata "Layer 4: 标准属性(DOI/作者)"  
    jsonb structured\_data "Layer 4: 业务属性(PICO/诊断)"  
}

%% 切片层(检索核心):管理向量和索引  
Ekb\_Chunk {  
    string id PK  
    string document\_id FK  
    vector embedding "1024维向量"  
    string content "切片文本"  
    jsonb metadata "切片级元数据(IsAnswer)"  
}

Ekb\_KnowledgeBase ||--|{ Ekb\_Document : "contains"  
Ekb\_Document ||--|{ Ekb\_Chunk : "split into"

潜在风险(需要补充):

  1. 缺失 EkbKnowledgeBase:目前的 Schema 中 EkbDocumentkbId 字段,但没有定义 EkbKnowledgeBase 表。
    • 风险:你之前提到“用户有 3 个库,系统有业务库”。如果没有这个表,你无法管理“谁拥有这个库”、“这是系统库还是用户库”、“这个库的 RAG 策略是什么”。
  2. Chunk 还可以更灵活:目前的 EkbChunk 只有 sectionType
    • 建议:建议给 Chunk 也加上 metadata (JSONB)。比如做职业考试题时,你可能希望直接在 Chunk 层面标记 { "isAnswer": true },这样检索时可以加权。

场景模拟

场景 1ASL 模块搜文献(只搜文献库,不搜病历)

场景 2PKB 用户搜自己的知识库(只搜自己的,不搜别人的)

场景 3AIA 智能问答(既要查系统医学库,又要查用户上传的病历)

维度 我的设计 (上一版) 你的设计 (上传版) 🎯 建议融合方案
知识库管理 有 EkbKnowledgeBase 表。 明确区分 USER vs SYSTEM 类型。 。 仅在 Document 中引用 kbId。 采纳我的。 必须有这张表来管理权限和配置。
文档元数据 单个 metadata JSONB 字段。 简单,但混杂。 分离设计。 metadata (标准) + structuredData (业务)。 采纳你的。 分离设计对医学场景更友好,利于 TS 类型定义。
全文存储 隐含在 Chunk 中。 显式存储 extractedText。 存放在 Document 表中。 采纳你的。 保留全文很有必要,万一以后要换切片策略(比如 512 改为 1024不用重新解析文件。
切片属性 metadata JSONB。 sectionType 枚举 + pageNumber。 融合。 保留 sectionType但增加 metadata JSONB 以备不时之需。

3. 详细数据模型定义 (Prisma Schema)

开发者注意:这是最终落地的 Schema请直接复制到 prisma/schema.prisma 的 ekb_schema 部分。

3.1 知识库容器 (EkbKnowledgeBase)

解决痛点:解决了“怎么区分这是用户的库还是系统的库”的问题。

model EkbKnowledgeBase {
id String @id @default(uuid())
name String
description String?

// 核心隔离字段
// USER: 用户创建的ownerId = userId受配额限制 (如最多3个)
// SYSTEM: 系统预置的ownerId = moduleId (如 "AIA", "IIT"),全员可读
type KbType @default(USER)
ownerId String

// 策略配置 (JSONB)
// 允许未来针对特定库进行优化,例如:
// { "chunkSize": 1024, "enableRerank": true, "embeddingModel": "v3" }
config Json? @db.JsonB

documents EkbDocument[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([ownerId])
@@index([type])
@@schema("ekb_schema")
}

enum KbType {
USER
SYSTEM
}

3.2 通用文档对象 (EkbDocument)

解决痛点:解决了“医学数据类型极其复杂”的问题。采用双 JSONB 策略

model EkbDocument {
id String @id @default(uuid())

// 归属关系
kbId String
knowledgeBase EkbKnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade)
userId String // 冗余存储上传者,方便快速查询

// ===== Layer 1: 基础文件信息 =====
filename String
fileType String // pdf, docx, md, txt
fileSizeBytes BigInt
fileHash String // 用于实现“秒传”和去重
fileUrl String // OSS 路径
status DocStatus @default(PENDING) // 状态机PENDING -> PROCESSING -> COMPLETED
errorMessage String? @db.Text

// ===== Layer 0: RAG 核心层 =====
// 必须保留全文,以便未来更改切片策略时,无需重新解析原始文件
extractedText String? @db.Text

// ===== Layer 2: 内容增强层 =====
summary String? @db.Text
tokenCount Int?
pageCount Int?

// ===== Layer 3: 分类标签层 =====
contentType String? // 枚举字符串literature, case, exam, drug
tags String[]
category String?

// ===== Layer 4: 结构化数据层 (核心设计) =====
// 1. 通用元数据:所有文献都有的 (DOI, Journal, Year, Author)
metadata Json? @db.JsonB

// 2. 业务结构化数据:特定类型特有的 (PICO, Diagnosis, Dosage, ExamAnswer)
// 使用 JSONB + GIN 索引,实现 NoSQL 般的灵活性
structuredData Json? @db.JsonB

chunks EkbChunk[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([kbId])
@@index([contentType])
// GIN 索引:支持对 metadata 和 structuredData 内部字段的高速查询
@@index([metadata], type: Gin)
@@index([structuredData], type: Gin)
@@schema("ekb_schema")
}

enum DocStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}

3.3 向量切片 (EkbChunk)

解决痛点:解决了“如何让检索更精准”的问题。

model EkbChunk {
id String @id @default(uuid())
documentId String
document EkbDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)

// 核心内容
content String @db.Text
chunkIndex Int

// 向量数据 (PostgreSQL pgvector)
// 注意:需要手动执行 SQL 创建 HNSW 索引
embedding Unsupported("vector(1024)")?

// 溯源信息
pageNumber Int?
sectionType String? // abstract, methods, results...

// 扩展元数据 (新增)
// 允许在切片粒度做标记。例如:考试题的答案切片标记 { "isAnswer": true }
// 检索时可降低纯答案切片的权重,优先展示问题
metadata Json? @db.JsonB

@@index([documentId])
@@schema("ekb_schema")
}

4. 关键设计问答 (Q&A)

Q1: 为什么要把 metadata 和 structuredData 分开?

  • 清晰度metadata 存放通用的、标准化的属性如标题、作者、时间这是所有文档共有的structuredData 存放业务特定的深层数据(如 PICO、诊断结果
  • 前端适配:前端组件可以统一渲染 metadata而针对 structuredData 则根据 contentType 动态加载不同的展示组件。

Q2: 为什么要保留 extractedText (全文)?只存 Chunk 不行吗?

  • 未来兼容性:如果我们下个月发现 chunkSize=512 效果不好,想改成 1024。如果有 extractedText我们可以直接在数据库里重新切片 (Re-chunking);如果没有,我们就得去 OSS 重新下载文件并解析,成本巨大。

Q3: 为什么 Chunk 也要加 metadata

  • 精准检索:在“职业考试”场景中,一个题目可能被切成“题干”和“解析”两段。我们希望用户搜问题时,优先匹配“题干”。如果在 Chunk 上标记 { "type": "question" },检索算法就可以加权。

5. 业务场景演练

场景 APKB 个人文献库

  • 用户操作:上传一篇 PDF。
  • 数据流
    1. 创建 EkbDocument (kbId=用户的个人库ID)。
    2. status = PROCESSING。
    3. 后台解析 -> 填充 extractedText。
    4. 后台切片 -> 插入 EkbChunk。
    5. status = COMPLETED。

场景 BASL 智能文献筛选

  • 系统操作:批量导入 1000 篇文献,并进行 PICO 提取。
  • 数据流
    1. 创建 EkbDocument (kbId=ASL临时库ID, contentType='literature')。
    2. 调用 LLM 提取 PICO。
    3. 更新 structuredData = { "pico": { "P": "...", "I": "..." } }。
    4. 查询时SELECT * FROM EkbDocument WHERE structuredData->'pico'->>'I' LIKE '%Aspirin%' (利用 GIN 索引加速)。

6. 下一步行动 (Action Plan)

  1. 数据库迁移
    • 更新 prisma/schema.prisma。
    • 运行 npx prisma migrate dev --name init_ekb_v2。
  2. 索引创建 (SQL)
    • 手动执行 HNSW 索引创建语句Prisma 暂不支持自动生成 vector 索引)。
    • 手动执行 pg_bigm 索引创建语句。
  3. Service 层更新
    • 更新 KnowledgeBaseEngine在入库时必须要求传入 kbId。
    • 在检索时,增加对 structuredData 的 JSON 过滤支持。