# 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 ```typescript // 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 ```typescript // 9. 上传文档 POST /api/v1/documents/upload Multipart: { file, kbId } 逻辑: ├─ 上传文件到OSS ├─ 提取文本(PDF/Word/TXT/Markdown) ├─ 上传到Dify进行索引 └─ 创建数据库记录(状态:uploading→parsing→indexing→completed) // 10. 获取文档详情 GET /api/v1/documents/:id // 11. 删除文档 DELETE /api/v1/documents/:id 逻辑: ├─ 从Dify删除Document ├─ 从OSS删除文件 └─ 删除数据库记录 ``` ### 1.3 数据库Schema #### 表结构(在pkb_schema中) ```sql -- 知识库表 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) ``` #### 索引 ```sql -- 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 关键业务逻辑 #### 配额管理 ```typescript // 用户表(在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集成 ```typescript // 创建知识库 → 创建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) ```typescript // 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篇文献的综合分析 **实现原理:** ```typescript // 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 tokens(7篇文献全文) 对话空间:~250K tokens 输出长度:6000 tokens(综合分析需要更长回答) ``` --- #### 模式2:逐篇精读模式(Deep Read Mode) **用途**:1-5篇文献的深度分析 **实现原理:** ```typescript // 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篇文献的批量信息提取 **实现原理:** ```typescript // 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完成标准 - [x] 深入理解PKB的两个部分 - [x] 列出所有API端点 - [x] 理解数据库Schema - [x] 理解三种工作模式 - [x] 理解模块间依赖 - [ ] 创建测试用例清单 - [ ] 准备测试数据 --- ## 📊 下一步:创建测试用例 即将创建详细的测试用例清单,覆盖所有功能点... --- **审查状态:** 🟡 进行中(90%完成) **下一步:** 创建测试用例清单和测试数据准备方案