- Add core design principle: provide building blocks, no strategy selection - Remove chat() method, strategy determined by business modules - Add new capabilities: getDocumentFullText(), getAllDocumentsText() - Add new capabilities: getDocumentSummary(), getAllDocumentsSummaries() - Add business module strategy examples (PKB/AIA/ASL/RVW) - Add strategy selection guide (by scale, by scenario) - Update data model with summary and tokenCount fields - Add SummaryService to code structure
34 KiB
34 KiB
知识库引擎架构设计
文档版本: v1.1
创建日期: 2026-01-20
最后更新: 2026-01-20
能力定位: 通用能力层
状态: 🔄 升级中(Dify → PostgreSQL + pgvector)
核心原则: 提供基础能力(乐高积木),不做策略选择
📋 概述
能力定位
知识库引擎是平台的核心通用能力,提供知识库相关的基础能力(乐高积木),供业务模块根据场景自由组合。
⭐ 核心设计原则
┌─────────────────────────────────────────────────────────────┐
│ │
│ ✅ 提供基础能力(乐高积木) │
│ ❌ 不做策略选择(组装方案由业务模块决定) │
│ │
│ 原因: │
│ • 不同业务场景需要不同的知识库使用策略 │
│ • 小知识库(10个文件)可能直接全文塞给 LLM 更好 │
│ • 大知识库(1000+文件)必须用 RAG 向量检索 │
│ • 有的场景需要 摘要筛选 → Top-K 全文 的两阶段策略 │
│ • 策略选择是业务逻辑,不是基础设施 │
│ │
└─────────────────────────────────────────────────────────────┘
基础能力清单
| 能力分类 | 基础能力 | 说明 |
|---|---|---|
| 文档入库 | ingestDocument() |
文档解析 → 切片 → 向量化 → 存储 |
ingestBatch() |
批量入库 | |
| 全文获取 | getDocumentFullText() |
获取单个文档全文 |
getAllDocumentsText() |
获取知识库所有文档全文 | |
| 摘要获取 | getDocumentSummary() |
获取单个文档摘要 |
getAllDocumentsSummaries() |
获取知识库所有文档摘要 | |
| 向量检索 | vectorSearch() |
基于向量的语义检索 |
| 关键词检索 | keywordSearch() |
基于 PostgreSQL FTS 的关键词检索 |
| 混合检索 | hybridSearch() |
向量 + 关键词 + RRF 融合 |
| 管理操作 | deleteDocument() |
删除文档 |
clearKnowledgeBase() |
清空知识库 |
⚠️ 注意:不提供
chat()方法!问答策略由业务模块根据场景决定。
🎯 业务模块策略选择
知识库引擎提供基础能力,业务模块根据场景自由组合:
策略示例
┌─────────────────────────────────────────────────────────────┐
│ 业务模块层(策略选择) │
├─────────────────────────────────────────────────────────────┤
│ │
│ PKB 个人知识库 │
│ ├─ 小知识库(< 20 文档)→ 全文模式 │
│ │ getAllDocumentsText() → 直接塞给 LLM │
│ └─ 大知识库(100+ 文档)→ RAG 模式 │
│ hybridSearch() → 检索 Top-K → LLM 回答 │
│ │
│ AIA AI智能问答 │
│ └─ 摘要筛选 + Top-K 全文 │
│ getAllDocumentsSummaries() → LLM 筛选 Top 5 │
│ → getDocumentFullText() × 5 → LLM 回答 │
│ │
│ ASL 智能文献 │
│ └─ 向量检索 + Rerank │
│ vectorSearch(topK=50) → Rerank(topK=10) → 返回 │
│ │
│ RVW 稿件审查 │
│ └─ 全文比对查重 │
│ getAllDocumentsText() → 逐篇相似度计算 │
│ │
├─────────────────────────────────────────────────────────────┤
│ 通用能力层(提供积木) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 全文获取 │ │ 摘要获取 │ │ 向量检索 │ │ 关键词检索│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 文档入库 │ │ 混合检索 │ │ 文档删除 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
代码示例
场景 1:PKB 小知识库(10个文件)→ 全文模式
// PKB 模块:小知识库直接全文
async function pkbSmallKbChat(kbId: string, query: string) {
const kbEngine = new KnowledgeBaseEngine(prisma);
// 获取所有文档全文
const docs = await kbEngine.getAllDocumentsText(kbId);
// 直接塞给 LLM
const context = docs.map(d => `## ${d.filename}\n${d.extractedText}`).join('\n\n---\n\n');
const systemPrompt = `你是医学专家。以下是知识库的完整内容:\n\n${context}`;
return llmChat(systemPrompt, query);
}
场景 2:AIA 摘要筛选 + Top-K 全文
// AIA 模块:摘要筛选 + 全文精读
async function aiaSmartChat(kbIds: string[], query: string) {
const kbEngine = new KnowledgeBaseEngine(prisma);
// 1. 获取所有文档摘要
const summaries = await kbEngine.getAllDocumentsSummaries(kbIds);
// 2. LLM 筛选最相关的 Top 5 文档
const topDocIds = await llmSelectTopK(summaries, query, 5);
// 3. 获取 Top 5 文档全文
const fullTexts = await Promise.all(
topDocIds.map(id => kbEngine.getDocumentFullText(id))
);
// 4. 基于全文回答
const context = fullTexts.map(d => `## ${d.filename}\n${d.text}`).join('\n\n');
return llmChat(context, query);
}
场景 3:ASL 大规模文献检索
// ASL 模块:向量检索 + Rerank
async function aslLiteratureSearch(kbIds: string[], query: string) {
const kbEngine = new KnowledgeBaseEngine(prisma);
// 1. 向量检索 Top 50
const candidates = await kbEngine.vectorSearch(kbIds, query, 50);
// 2. Rerank 精排 Top 10
const reranked = await rerankService.rerank(candidates, query, 10);
// 3. 返回带引用的结果
return reranked.map(r => ({
content: r.content,
source: r.documentName,
score: r.score,
}));
}
场景 4:RVW 稿件查重
// RVW 模块:全文比对
async function rvwPlagiarismCheck(manuscriptText: string, kbId: string) {
const kbEngine = new KnowledgeBaseEngine(prisma);
// 获取所有文献全文
const docs = await kbEngine.getAllDocumentsText(kbId);
// 逐篇比对相似度
const similarities = docs.map(d => ({
document: d,
similarity: calculateTextSimilarity(manuscriptText, d.extractedText),
}));
// 返回可疑重复段落
return similarities
.filter(s => s.similarity > 0.3)
.sort((a, b) => b.similarity - a.similarity);
}
🏗️ 整体架构
架构图
┌─────────────────────────────────────────────────────────────────┐
│ 业务模块层(策略选择) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ PKB │ │ AIA │ │ ASL │ │ RVW │ │
│ │ 全文/RAG │ │摘要+全文 │ │向量+Rerank│ │ 全文比对 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ │ 根据场景自由组合基础能力 │ │
│ └────────────┴────────────┴────────────┘ │
│ │ │
│ ▼ │
├─────────────────────────────────────────────────────────────────┤
│ 知识库引擎(通用能力层 - 提供积木) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ KnowledgeBaseEngine │ │
│ │ 提供基础能力,不做策略选择 │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 文档入库能力 │ │ │
│ │ │ ingestDocument() / ingestBatch() │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 内容获取能力 │ │ │
│ │ │ getDocumentFullText() / getAllDocumentsText() │ │ │
│ │ │ getDocumentSummary() / getAllDocumentsSummaries()│ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 检索能力 │ │ │
│ │ │ vectorSearch() / keywordSearch() / hybridSearch()│ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 文档处理引擎(独立通用能力) │ │
│ │ PDF/Word/Excel/PPT → Markdown │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 数据存储层 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL + pgvector (Postgres-Only) │ │
│ │ ┌───────────────┐ ┌───────────────────────────┐ │ │
│ │ │ EkbDocument │ │ EkbChunk │ │ │
│ │ │ (文档 + JSONB)│───>│ (切片 + vector(1024)) │ │ │
│ │ └───────────────┘ └───────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
📦 代码结构
目录规划
backend/src/common/rag/
├── index.ts # 统一导出
├── KnowledgeBaseEngine.ts # 统一入口类(基础能力)
│
├── services/
│ ├── ChunkService.ts # 文档切片服务
│ ├── EmbeddingService.ts # 向量化服务(阿里云 DashScope)
│ ├── SummaryService.ts # 摘要生成服务
│ ├── VectorSearchService.ts # 向量检索服务
│ ├── KeywordSearchService.ts # 关键词检索服务(PostgreSQL FTS)
│ ├── HybridSearchService.ts # 混合检索服务(RRF 融合)
│ └── ClinicalExtractionService.ts # 临床要素提取(可选)
│
├── types/
│ ├── index.ts # 类型定义
│ ├── chunk.types.ts
│ ├── search.types.ts
│ └── document.types.ts
│
├── utils/
│ ├── rrfFusion.ts # RRF 算法
│ └── jsonParser.ts # JSON 容错解析
│
└── __tests__/ # 单元测试
├── embedding.test.ts
├── chunk.test.ts
└── search.test.ts
基础能力 API
// KnowledgeBaseEngine.ts
export class KnowledgeBaseEngine {
constructor(private prisma: PrismaClient) {}
// ==================== 文档入库 ====================
/**
* 入库文档(完整流程:提取 → 切片 → 向量化 → 存储)
*/
async ingestDocument(params: {
kbId: string;
userId: string;
file: Buffer;
filename: string;
options?: {
extractClinicalData?: boolean;
generateSummary?: boolean;
chunkSize?: number;
chunkOverlap?: number;
};
}): Promise<IngestResult>;
/**
* 批量入库
*/
async ingestBatch(documents: IngestParams[]): Promise<IngestResult[]>;
// ==================== 内容获取(全文) ====================
/**
* 获取单个文档全文
*/
async getDocumentFullText(documentId: string): Promise<DocumentText>;
/**
* 获取知识库所有文档全文
*/
async getAllDocumentsText(kbId: string): Promise<DocumentText[]>;
/**
* 批量获取多个知识库的所有文档全文
*/
async getAllDocumentsTextBatch(kbIds: string[]): Promise<DocumentText[]>;
// ==================== 内容获取(摘要) ====================
/**
* 获取单个文档摘要
*/
async getDocumentSummary(documentId: string): Promise<DocumentSummary>;
/**
* 获取知识库所有文档摘要
*/
async getAllDocumentsSummaries(kbId: string): Promise<DocumentSummary[]>;
/**
* 批量获取多个知识库的所有文档摘要
*/
async getAllDocumentsSummariesBatch(kbIds: string[]): Promise<DocumentSummary[]>;
// ==================== 检索能力 ====================
/**
* 向量检索(语义搜索)
*/
async vectorSearch(
kbIds: string[],
query: string,
topK?: number
): Promise<SearchResult[]>;
/**
* 关键词检索(PostgreSQL FTS)
*/
async keywordSearch(
kbIds: string[],
query: string,
topK?: number
): Promise<SearchResult[]>;
/**
* 混合检索(向量 + 关键词 + RRF 融合)
*/
async hybridSearch(
kbIds: string[],
query: string,
topK?: number,
options?: {
vectorWeight?: number;
keywordWeight?: number;
filters?: SearchFilters;
}
): Promise<SearchResult[]>;
// ==================== 管理操作 ====================
/**
* 删除文档(级联删除切片和向量)
*/
async deleteDocument(documentId: string): Promise<void>;
/**
* 删除知识库所有文档
*/
async clearKnowledgeBase(kbId: string): Promise<void>;
/**
* 获取知识库统计信息
*/
async getKnowledgeBaseStats(kbId: string): Promise<{
documentCount: number;
chunkCount: number;
totalTokens: number;
}>;
// ==================== ❌ 不提供 chat() 方法 ====================
// 策略由业务模块根据场景决定
}
🔄 核心流程
文档入库流程(Ingest)
用户上传 PDF/Word/...
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 1: 文档处理引擎 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DocumentProcessor.toMarkdown(file) │ │
│ │ • PDF → pymupdf4llm │ │
│ │ • Word → mammoth │ │
│ │ • Excel → pandas │ │
│ │ 输出:Markdown 文本 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: 文本切片 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ChunkService.split(markdown) │ │
│ │ • 递归字符切分 │ │
│ │ • chunkSize: 512, overlap: 50 │ │
│ │ 输出:Chunk[] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: 摘要生成(可选) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SummaryService.generate(fullText) │ │
│ │ • LLM 生成 200-500 字摘要 │ │
│ │ 输出:summary 字段 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 4: 临床要素提取(可选) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ClinicalExtractionService.extract(fullText) │ │
│ │ • PICO、用药方案、安全性数据等 │ │
│ │ • LLM 提取 + JSON 容错解析 │ │
│ │ 输出:ClinicalData (JSONB) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 5: 向量化 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ EmbeddingService.embedBatch(chunks.map(c => c.text))│ │
│ │ • 阿里云 text-embedding-v3 │ │
│ │ • 1024 维向量 │ │
│ │ 输出:number[][] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 6: 存储 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PostgreSQL + pgvector │ │
│ │ • EkbDocument: 文档元数据 + 摘要 + 临床数据 (JSONB) │ │
│ │ • EkbChunk: 切片文本 + 向量 (vector(1024)) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
🗄️ 数据模型
Prisma Schema
// schema.prisma
model EkbDocument {
id String @id @default(uuid())
kbId String // 所属知识库
userId String // 上传用户
// 基础信息
filename String
fileType String
fileSizeBytes BigInt
fileUrl String // OSS 地址
extractedText String? @db.Text // 提取的 Markdown 全文
summary String? @db.Text // 文档摘要(200-500字)
// 临床数据(JSONB,可选)
pico Json? // { P, I, C, O }
studyDesign Json? // { design, sampleSize, ... }
regimen Json? // [{ drug, dose, ... }]
safety Json? // { ae_all, ae_grade34 }
criteria Json? // { inclusion, exclusion }
endpoints Json? // { primary, secondary }
// 状态
status String @default("pending") // pending | processing | completed | failed
errorMessage String? @db.Text
// 统计信息
tokenCount Int? // 文档 token 数量
chunks EkbChunk[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([kbId])
@@index([status])
@@schema("ekb_schema")
}
model EkbChunk {
id String @id @default(uuid())
documentId String
content String @db.Text // 切片文本
pageNumber Int? // 来源页码
sectionType String? // 章节类型
// pgvector 字段
embedding Unsupported("vector(1024)")?
document EkbDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@index([documentId])
@@schema("ekb_schema")
}
索引设计
-- HNSW 向量索引(高性能近似最近邻)
CREATE INDEX IF NOT EXISTS ekb_chunk_embedding_idx
ON "ekb_schema"."EkbChunk"
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 全文检索索引
CREATE INDEX IF NOT EXISTS ekb_chunk_content_idx
ON "ekb_schema"."EkbChunk"
USING gin (to_tsvector('simple', content));
-- JSONB GIN 索引(临床数据查询)
CREATE INDEX IF NOT EXISTS ekb_document_pico_idx
ON "ekb_schema"."EkbDocument" USING gin (pico);
CREATE INDEX IF NOT EXISTS ekb_document_safety_idx
ON "ekb_schema"."EkbDocument" USING gin (safety);
📊 策略选择指南
根据知识库规模选择策略
| 知识库规模 | 文档数量 | 估算 Tokens | 推荐策略 | 基础能力组合 |
|---|---|---|---|---|
| 微型 | 1-10 | < 30K | 全文塞给 LLM | getAllDocumentsText() |
| 小型 | 10-30 | 30K-80K | 全文 + 长上下文模型 | getAllDocumentsText() + Qwen-Long |
| 中型 | 30-100 | 80K-300K | 摘要筛选 + Top-K 全文 | getAllDocumentsSummaries() + getDocumentFullText() |
| 大型 | 100+ | > 300K | RAG 向量检索 | hybridSearch() |
根据场景选择策略
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 精准问答 | 全文模式 | 无信息丢失 |
| 快速检索 | 向量检索 | 速度快 |
| 综合分析 | 摘要筛选 + 全文 | 兼顾广度和深度 |
| 文献综述 | 混合检索 + Rerank | 召回率高 |
| 查重对比 | 全文比对 | 精确匹配 |
🔗 与其他通用能力的关系
依赖关系图
┌─────────────────────────────────────────────────────────────┐
│ 知识库引擎 (本模块) │
│ │
│ 依赖: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 文档处理引擎 │ │
│ │ • 调用 DocumentProcessor.toMarkdown() │ │
│ │ • 获取 PDF/Word/Excel 的 Markdown 文本 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 网关 │ │
│ │ • 调用 LLMFactory.getAdapter('deepseek-v3') │ │
│ │ • 用于摘要生成、临床要素提取 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存储服务 │ │
│ │ • 调用 storage.upload() / storage.download() │ │
│ │ • 存储原始文档到 OSS │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 外部依赖: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 阿里云 DashScope API │ │
│ │ • text-embedding-v3 (向量化) │ │
│ │ • gte-rerank (重排序,可选) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
📅 开发计划
里程碑
| 阶段 | 内容 | 工期 | 状态 |
|---|---|---|---|
| M1 | 数据库设计 + 核心服务 | 5 天 | 🔜 待开始 |
| M2 | PKB 模块接入 + 测试 | 3 天 | 📋 规划中 |
| M3 | 数据迁移 + 上线 | 2 天 | 📋 规划中 |
📚 相关文档
📅 更新日志
v1.1 (2026-01-20)
设计原则重大更新:
- ✅ 明确"提供基础能力,不做策略选择"原则
- ❌ 移除
chat()方法,策略由业务模块决定 - 🆕 新增
getDocumentFullText()/getAllDocumentsText()全文获取 - 🆕 新增
getDocumentSummary()/getAllDocumentsSummaries()摘要获取 - 🆕 新增业务模块策略选择示例(PKB/AIA/ASL/RVW)
- 🆕 新增策略选择指南(按规模、按场景)
- 📝 数据模型添加
summary和tokenCount字段
v1.0 (2026-01-20)
- 初始版本
维护人: 技术架构师
最后更新: 2026-01-20