Files
AIclinicalresearch/docs/08-项目管理/PKB功能审查报告-阶段0.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

21 KiB
Raw Blame History

PKB个人知识库功能审查报告 - 阶段0

审查日期: 2026-01-06
审查人员: AI助手
审查目标: 深入理解PKB现有功能为安全迁移做准备
状态: 进行中


📋 执行摘要

关键发现

🎯 PKB系统实际上是两个紧密关联的功能模块

Part 1: PKB知识库管理模块
├─ 位置backend/src/legacy/controllers/knowledgeBaseController.ts
├─ 功能:创建、编辑、删除知识库;上传、管理文档
└─ 数据库pkb_schema独立Schema无需迁移

Part 2: AIA智能问答模块中的PKB应用
├─ 位置backend/src/legacy/controllers/chatController.ts
├─ 功能使用知识库进行智能问答3种工作模式
└─ 工作模式:
    ├─ 全文阅读模式35-50篇文献综合分析
    ├─ 逐篇精读模式1-5篇文献深度分析
    └─ 批处理模式3-50篇文献批量提取

📊 Part 1: PKB知识库管理模块

1.1 文件结构

backend/src/legacy/
├─ controllers/
│  ├─ knowledgeBaseController.ts    # API控制器342行
│  └─ documentController.ts         # 文档上传控制器
├─ services/
│  ├─ knowledgeBaseService.ts       # 业务逻辑365行
│  ├─ documentService.ts            # 文档处理服务
│  └─ tokenService.ts               # Token计算和文档选择
└─ routes/
   └─ knowledgeBases.ts             # 路由定义

1.2 核心API端点

知识库管理API

// 1. 创建知识库
POST /api/v1/knowledge/create
Body: { name: string, description?: string }
逻辑:
  ├─ 检查用户配额(kbQuota vs kbUsed
  ├─ Dify创建Dataset
  ├─ 在数据库创建记录
  └─ 更新用户配额计数

// 2. 获取知识库列表
GET /api/v1/knowledge/list
返回:用户所有知识库 + 文档数量统计

// 3. 获取知识库详情
GET /api/v1/knowledge/:id
返回:知识库信息 + 所有文档列表

// 4. 更新知识库
PUT /api/v1/knowledge/:id
Body: { name?: string, description?: string }

// 5. 删除知识库
DELETE /api/v1/knowledge/:id
逻辑:
  ├─ 删除Dify Dataset
  ├─ 级联删除数据库记录(documents自动删除
  └─ 减少用户配额计数

// 6. 检索知识库RAG
GET /api/v1/knowledge/:id/search?query=xxx&top_k=15
逻辑:
  ├─ 验证权限
  ├─ 调用Dify retrieveKnowledge API
  └─ 返回检索结果(默认15个片段)

// 7. 获取知识库统计
GET /api/v1/knowledge/:id/stats
返回:文档数、完成数、处理中、错误数、总Token数

// 8. 获取文档选择(全文阅读模式)
GET /api/v1/knowledge/:id/document-selection?max_files=7&max_tokens=750000
返回:智能选择的文档列表(基于Token限制

文档管理API

// 9. 上传文档
POST /api/v1/documents/upload
Multipart: { file, kbId }
逻辑:
  ├─ 上传文件到OSS
  ├─ 提取文本(PDF/Word/TXT/Markdown
  ├─ 上传到Dify进行索引
  └─ 创建数据库记录(状态:uploadingparsingindexingcompleted

// 10. 获取文档详情
GET /api/v1/documents/:id

// 11. 删除文档
DELETE /api/v1/documents/:id
逻辑:
  ├─ Dify删除Document
  ├─ OSS删除文件
  └─ 删除数据库记录

1.3 数据库Schema

表结构在pkb_schema中

-- 知识库表
knowledge_bases
├─ id (UUID, PK)
├─ userId (String)
├─ name (String)
├─ description (String?)
├─ difyDatasetId (String, UNIQUE) -- Dify中的Dataset ID
├─ fileCount (Int, default: 0)
├─ totalSizeBytes (BigInt, default: 0)
├─ createdAt (DateTime)
└─ updatedAt (DateTime)

-- 文档表
documents
├─ id (UUID, PK)
├─ kbId (String, FK  knowledge_bases.id)
├─ userId (String)
├─ filename (String)
├─ fileType (String) -- pdf/docx/txt/md
├─ fileSizeBytes (BigInt)
├─ fileUrl (String) -- OSS URL
├─ difyDocumentId (String) -- Dify中的Document ID
├─ status (String) -- uploading/parsing/indexing/completed/error
├─ progress (Int, 0-100)
├─ errorMessage (String?)
├─ segmentsCount (Int?) -- Dify索引的片段数
├─ tokensCount (Int?) -- 总Token数
├─ charCount (Int?) -- 字符数
├─ language (String?)
├─ extractedText (String?) -- 提取的全文(用于全文阅读模式)
├─ extractionMethod (String?) -- marker/pymupdf/docx
├─ extractionQuality (Float?)
├─ uploadedAt (DateTime)
└─ processedAt (DateTime?)

-- 批处理任务表
batch_tasks
├─ id (UUID, PK)
├─ userId (String)
├─ kbId (String, FK  knowledge_bases.id)
├─ name (String)
├─ templateType (String)
├─ templateId (String?)
├─ prompt (String)
├─ status (String) -- pending/running/completed/failed
├─ totalDocuments (Int)
├─ completedCount (Int, default: 0)
├─ failedCount (Int, default: 0)
├─ modelType (String)
├─ concurrency (Int, default: 3)
├─ startedAt (DateTime?)
├─ completedAt (DateTime?)
├─ durationSeconds (Int?)
├─ createdAt (DateTime)
└─ updatedAt (DateTime)

-- 批处理结果表
batch_results
├─ id (UUID, PK)
├─ taskId (String, FK  batch_tasks.id)
├─ documentId (String, FK  documents.id)
├─ status (String) -- success/failed
├─ data (Json?) -- 提取的结构化数据
├─ rawOutput (String?) -- LLM原始输出
├─ errorMessage (String?)
├─ processingTimeMs (Int?)
├─ tokensUsed (Int?)
└─ createdAt (DateTime)

-- 任务模板表
task_templates
├─ id (UUID, PK)
├─ userId (String)
├─ name (String)
├─ description (String?)
├─ prompt (String)
├─ isPublic (Boolean, default: false)
├─ outputFields (Json) -- 期望的输出字段
├─ createdAt (DateTime)
└─ updatedAt (DateTime)

索引

-- knowledge_bases
idx_pkb_knowledge_bases_user_id (userId)
idx_pkb_knowledge_bases_dify_dataset_id (difyDatasetId)

-- documents
idx_pkb_documents_kb_id (kbId)
idx_pkb_documents_user_id (userId)
idx_pkb_documents_status (status)
idx_pkb_documents_dify_document_id (difyDocumentId)
idx_pkb_documents_extraction_method (extractionMethod)

-- batch_tasks
idx_pkb_batch_tasks_kb_id (kbId)
idx_pkb_batch_tasks_user_id (userId)
idx_pkb_batch_tasks_status (status)
idx_pkb_batch_tasks_created_at (createdAt)

-- batch_results
idx_pkb_batch_results_task_id (taskId)
idx_pkb_batch_results_document_id (documentId)
idx_pkb_batch_results_status (status)

1.4 关键业务逻辑

配额管理

// 用户表在platform_schema.users中的字段
kbQuota: Int @default(3)   // 知识库配额
kbUsed: Int @default(0)    // 已使用数量

// 创建知识库时检查
if (user.kbUsed >= user.kbQuota) {
  throw new Error('配额已满');
}

// 创建成功后增加计数
await prisma.user.update({
  data: { kbUsed: { increment: 1 } }
});

// 删除知识库时减少计数
await prisma.user.update({
  data: { kbUsed: { decrement: 1 } }
});

Dify集成

// 创建知识库 → 创建Dify Dataset
const difyDataset = await difyClient.createDataset({
  name: `${userId}_${name}_${Date.now()}`,
  description,
  indexing_technique: 'high_quality',
});

// 检索知识库 → 调用Dify RAG
const results = await difyClient.retrieveKnowledge(
  difyDatasetId,
  query,
  {
    retrieval_model: {
      search_method: 'semantic_search',
      top_k: 15,
    },
  }
);

文档Token计算tokenService.ts

// Token计算规则
const TOKEN_LIMITS = {
  MAX_FILES: 7,           // 最多7篇文献
  MAX_TOTAL_TOKENS: 750000, // 总Token限制Qwen-Long: 1M上下文 - 250K对话空间
  MAX_SINGLE_DOC_TOKENS: 200000, // 单篇文献最大Token数
};

// 智能选择算法
function selectDocumentsForFullText(
  documentTokens,
  maxFiles,
  maxTokens
) {
  // 按Token数升序排序
  const sorted = documentTokens.sort((a, b) => a.tokens - b.tokens);
  
  // 贪心算法选择
  let totalTokens = 0;
  let selectedCount = 0;
  const selected = [];
  
  for (const doc of sorted) {
    if (selectedCount >= maxFiles) break;
    if (totalTokens + doc.tokens > maxTokens) break;
    if (doc.tokens > MAX_SINGLE_DOC_TOKENS) continue; // 跳过超大文档
    
    selected.push(doc);
    totalTokens += doc.tokens;
    selectedCount++;
  }
  
  return { selected, totalTokens, excludedDocs };
}

📊 Part 2: AIA模块中的PKB应用

2.1 文件结构

backend/src/legacy/controllers/
└─ chatController.ts    # 通用对话控制器包含3种模式

frontend/src/
├─ pages/ChatPage.tsx   # 主对话页面
└─ components/
   ├─ FullTextMode.tsx    # 全文阅读模式组件
   ├─ DeepReadMode.tsx    # 逐篇精读模式组件
   └─ BatchMode.tsx       # 批处理模式组件

2.2 三种工作模式详解

模式1全文阅读模式Full Text Mode

用途35-50篇文献的综合分析

实现原理:

// 1. 前端:用户进入知识库模式 → 选择"全文阅读"
const modeState = {
  baseMode: 'knowledge_base',
  kbMode: 'full_text',
  selectedKbId: 'xxx',
};

// 2. 前端:智能加载文献
const selection = await knowledgeBaseApi.getDocumentSelection(kbId, {
  max_files: 7,
  max_tokens: 750000,
});
// 返回:{ selectedDocuments[], excludedDocuments[], totalTokens }

// 3. 前端自动切换到Qwen-Long模型
if (modeState.kbMode === 'full_text') {
  setSelectedModel('qwen-long'); // 1M上下文
  showToast('已自动切换到Qwen-Long模型支持1M上下文');
}

// 4. 前端发送消息时传递文档ID列表
await chatApi.sendMessageStream({
  content: userQuestion,
  modelType: 'qwen-long',
  fullTextDocumentIds: loadedDocs.map(d => d.id), // ✅ 关键参数
  conversationId,
});

// 5. 后端:加载完整全文
if (fullTextDocumentIds && fullTextDocumentIds.length > 0) {
  const documents = await prisma.document.findMany({
    where: { id: { in: fullTextDocumentIds } },
    select: { id, filename, extractedText, tokensCount },
  });
  
  // 6. 组装全文上下文
  const fullTextParts = [];
  for (let i = 0; i < documents.length; i++) {
    const doc = documents[i];
    const docNumber = i + 1;
    
    // 格式【文献N文件名】\n全文内容
    fullTextParts.push(
      `【文献${docNumber}${doc.filename}\n\n${doc.extractedText}`
    );
    
    // 添加引用信息
    allCitations.push({
      id: docNumber,
      fileName: doc.filename,
      score: 1.0, // 全文相关度100%
      content: doc.extractedText.substring(0, 200),
    });
  }
  
  knowledgeBaseContext = fullTextParts.join('\n\n---\n\n');
}

// 7. 传递给LLM
const systemPrompt = '你是专业的学术文献分析助手。每篇文献用【文献N文件名】标记。请认真阅读所有文献进行深入的综合分析。在回答时请引用具体文献使用【文献N】格式。';

const userContent = `${userQuestion}\n\n## 参考资料(文献全文)\n\n${knowledgeBaseContext}`;

const messages = [
  { role: 'system', content: systemPrompt },
  ...historyMessages, // 对话历史
  { role: 'user', content: userContent },
];

// 8. 调用Qwen-Long
const response = await LLMFactory.getAdapter('qwen-long').chatStream(messages, {
  temperature: 0.7,
  maxTokens: 6000, // 全文模式需要更长的回答空间
});

关键特点:

  • 传递完整全文不是RAG片段
  • 智能选择文献基于Token限制
  • 文献来源标记【文献N文件名】
  • 自动切换到Qwen-Long模型1M上下文
  • 100%相关度(因为是全文)
  • 适合跨文献比较、趋势分析、研究方法归纳

Token使用

上下文:~750K tokens7篇文献全文
对话空间:~250K tokens
输出长度6000 tokens综合分析需要更长回答

模式2逐篇精读模式Deep Read Mode

用途1-5篇文献的深度分析

实现原理:

// 1. 前端:用户选择"逐篇精读"
const modeState = {
  baseMode: 'knowledge_base',
  kbMode: 'deep_read',
  selectedKbId: 'xxx',
};

// 2. 前端:用户选择要精读的文档
const selectedDocs = [doc1, doc2, doc3]; // 用户手动选择

// 3. 前端:切换到某个文档
const currentDoc = selectedDocs[0];

// 4. 前端发送消息时传递当前文档ID用于RAG过滤
await chatApi.sendMessageStream({
  content: userQuestion,
  modelType: selectedModel,
  knowledgeBaseIds: [kbId], // 知识库ID
  documentIds: [currentDoc.id], // ✅ 关键:只检索当前文档
  conversationId: currentDocConversationId, // 每个文档独立对话
});

// 5. 后端RAG检索限定在特定文档
if (documentIds && documentIds.length > 0) {
  // 调用Dify RAG但会限定在指定文档范围
  const results = await difyClient.retrieveKnowledge(
    difyDatasetId,
    query,
    {
      retrieval_model: {
        search_method: 'semantic_search',
        top_k: 15,
        document_ids: documentIds, // ✅ Dify会只检索这些文档
      },
    }
  );
}

关键特点:

  • 基于RAG检索不是全文
  • 限定在当前文档范围
  • 每个文档有独立的对话历史
  • 用户可以在文档间切换
  • 适合深度理解单篇文献

模式3批处理模式Batch Mode

用途3-50篇文献的批量信息提取

实现原理:

// 1. 用户创建批处理任务
POST /api/v1/batch-tasks/create
Body: {
  kbId: 'xxx',
  name: '提取研究方法',
  prompt: '请从这篇文献中提取:研究设计、样本量、统计方法',
  templateType: 'custom' | 'preset',
  modelType: 'deepseek-v3',
  concurrency: 3, // 并发数
}

// 2. 后端:创建任务
const task = await prisma.batchTask.create({
  data: {
    userId,
    kbId,
    name,
    prompt,
    templateType,
    modelType,
    status: 'pending',
    totalDocuments: documentsCount,
    concurrency,
  },
});

// 3. 后端启动批处理Worker
async function processBatchTask(taskId) {
  // 3.1 获取任务和文档列表
  const task = await prisma.batchTask.findUnique({
    where: { id: taskId },
    include: { knowledgeBase: { include: { documents: true } } },
  });
  
  const documents = task.knowledgeBase.documents.filter(d => d.status === 'completed');
  
  // 3.2 更新任务状态
  await prisma.batchTask.update({
    where: { id: taskId },
    data: { status: 'running', startedAt: new Date() },
  });
  
  // 3.3 并发处理文档
  const concurrency = task.concurrency || 3;
  const chunks = chunkArray(documents, concurrency);
  
  for (const chunk of chunks) {
    await Promise.all(chunk.map(async (doc) => {
      try {
        // 3.3.1 对每个文档使用其extractedText + prompt调用LLM
        const llmPrompt = `${task.prompt}\n\n文献内容\n${doc.extractedText}`;
        
        const response = await LLMFactory.getAdapter(task.modelType).chat([
          { role: 'user', content: llmPrompt },
        ]);
        
        // 3.3.2 解析LLM输出期望JSON格式
        const data = parseJSONResponse(response.content);
        
        // 3.3.3 保存结果
        await prisma.batchResult.create({
          data: {
            taskId: task.id,
            documentId: doc.id,
            status: 'success',
            data,
            rawOutput: response.content,
            tokensUsed: response.usage.totalTokens,
            processingTimeMs: Date.now() - startTime,
          },
        });
        
        // 3.3.4 更新任务进度
        await prisma.batchTask.update({
          where: { id: taskId },
          data: { completedCount: { increment: 1 } },
        });
        
      } catch (error) {
        // 3.3.5 处理失败
        await prisma.batchResult.create({
          data: {
            taskId: task.id,
            documentId: doc.id,
            status: 'failed',
            errorMessage: error.message,
          },
        });
        
        await prisma.batchTask.update({
          where: { id: taskId },
          data: { failedCount: { increment: 1 } },
        });
      }
    }));
  }
  
  // 3.4 任务完成
  await prisma.batchTask.update({
    where: { id: taskId },
    data: {
      status: 'completed',
      completedAt: new Date(),
      durationSeconds: Math.floor((Date.now() - task.startedAt) / 1000),
    },
  });
}

// 4. 前端:查看批处理结果
GET /api/v1/batch-tasks/:id/results
返回:
{
  task: { /* 任务信息 */ },
  results: [
    {
      documentId: 'xxx',
      filename: 'paper1.pdf',
      status: 'success',
      data: {
        研究设计: '随机对照试验',
        样本量: '300人',
        统计方法: 't检验、卡方检验',
      },
    },
    // ...
  ],
}

// 5. 前端导出结果Excel/CSV

关键特点:

  • 批量处理多个文档
  • 并发控制默认3个并发
  • 结构化信息提取
  • 进度实时更新
  • 支持自定义模板
  • 结果可导出Excel/CSV
  • 错误处理和重试

2.3 三种模式的对比

维度 全文阅读 逐篇精读 批处理
文档数量 7篇左右 1-5篇 3-50篇
数据来源 完整全文 RAG检索片段 完整全文
LLM调用 对话式(多轮) 对话式(多轮) 批量(单次)
上下文 ~750K tokens ~15K tokens 单篇全文
输出方式 流式SSE 流式SSE 批量保存
适用场景 综合分析、跨文献比较 深度理解单篇 信息提取、数据表格
用户交互 实时问答 实时问答 后台处理
对话历史 全局共享 每篇独立 无对话

📋 API端点完整清单

PKB管理模块API

POST   /api/v1/knowledge/create              # 创建知识库
GET    /api/v1/knowledge/list                # 获取知识库列表
GET    /api/v1/knowledge/:id                 # 获取知识库详情
PUT    /api/v1/knowledge/:id                 # 更新知识库
DELETE /api/v1/knowledge/:id                 # 删除知识库
GET    /api/v1/knowledge/:id/search          # RAG检索
GET    /api/v1/knowledge/:id/stats           # 统计信息
GET    /api/v1/knowledge/:id/document-selection  # 文档选择(全文模式)

POST   /api/v1/documents/upload              # 上传文档
GET    /api/v1/documents/:id                 # 获取文档详情
DELETE /api/v1/documents/:id                 # 删除文档
GET    /api/v1/documents/:id/content         # 获取文档内容(全文)

POST   /api/v1/batch-tasks/create            # 创建批处理任务
GET    /api/v1/batch-tasks/list              # 获取批处理任务列表
GET    /api/v1/batch-tasks/:id               # 获取任务详情
GET    /api/v1/batch-tasks/:id/results       # 获取任务结果
DELETE /api/v1/batch-tasks/:id               # 删除任务

GET    /api/v1/task-templates/list           # 获取模板列表
POST   /api/v1/task-templates/create         # 创建模板
DELETE /api/v1/task-templates/:id            # 删除模板

AIA对话模块API含PKB集成

POST   /api/v1/chat/send-message-stream      # 发送消息(流式)
参数:
  - content: string
  - modelType: 'deepseek-v3' | 'qwen3-72b' | 'qwen-long'
  - knowledgeBaseIds?: string[]          # RAG模式
  - documentIds?: string[]               # 逐篇精读模式(限定文档)
  - fullTextDocumentIds?: string[]       # 全文阅读模式(传递全文)
  - conversationId?: string

GET    /api/v1/chat/conversations            # 获取对话列表
GET    /api/v1/chat/conversations/:id        # 获取对话历史
DELETE /api/v1/chat/conversations/:id        # 删除对话

🔗 模块间依赖关系

AIA智能问答模块
│
├─ 依赖 PKB知识库管理模块
│  ├─ 获取知识库列表(选择知识库)
│  ├─ 获取文档列表(选择文档)
│  ├─ 获取文档全文(全文阅读)
│  ├─ RAG检索逐篇精读
│  └─ 文档智能选择(全文阅读)
│
├─ 依赖 LLM网关
│  ├─ DeepSeek V3
│  ├─ Qwen3-72B
│  └─ Qwen-Long
│
└─ 依赖 Dify RAG引擎
   └─ retrieveKnowledge API

🎯 迁移关键点

1. PKB模块迁移

✅ 简单:
  - 数据库已在pkb_schema无需迁移
  - API端点清晰易于复制
  - 业务逻辑独立

⚠️ 注意:
  - Dify集成需要保持
  - OSS文件上传需要保持
  - 配额管理需要保持

2. AIA模块中的PKB集成迁移

✅ 简单:
  - 接口清晰fullTextDocumentIds/documentIds
  - 三种模式逻辑独立

⚠️ 注意:
  - chatController.ts需要同时迁移
  - 前端3个模式组件需要迁移
  - 对话历史管理需要保持

3. 测试要点

必须测试:
  ✅ PKB CRUD功能
  ✅ 文档上传和提取
  ✅ RAG检索功能
  ✅ 全文阅读模式7篇文献
  ✅ 逐篇精读模式(文档切换)
  ✅ 批处理模式(并发处理)
  ✅ 配额管理
  ✅ 对话历史管理
  ✅ 模型切换

阶段0完成标准

  • 深入理解PKB的两个部分
  • 列出所有API端点
  • 理解数据库Schema
  • 理解三种工作模式
  • 理解模块间依赖
  • 创建测试用例清单
  • 准备测试数据

📊 下一步:创建测试用例

即将创建详细的测试用例清单,覆盖所有功能点...


审查状态: 🟡 进行中90%完成)
下一步: 创建测试用例清单和测试数据准备方案