Files
AIclinicalresearch/docs/08-项目管理/PKB功能审查报告-阶段0.md
HaHafeng 96290d2f76 feat(aia): Implement Protocol Agent MVP with reusable Agent framework
Sprint 1-3 Completed (Backend + Frontend):

Backend (Sprint 1-2):
- Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection)
- Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules)
- Create protocol_schema with 2 tables (protocol_contexts, protocol_generations)
- Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder)
- Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude)
- 6 API endpoints with full authentication
- 10/10 API tests passed

Frontend (Sprint 3):
- Add Protocol Agent entry in AgentHub (indigo theme card)
- Implement ProtocolAgentPage with 3-column layout
- Collapsible sidebar (Gemini style, 48px <-> 280px)
- StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints)
- ChatArea with sync button and action cards integration
- 100% prototype design restoration (608 lines CSS)
- Detailed endpoints structure: baseline, exposure, outcomes, confounders

Features:
- 5-stage dialogue flow for research protocol design
- Conversation-driven interaction with sync-to-protocol button
- Real-time context state management
- One-click protocol generation button (UI ready, backend pending)

Database:
- agent_schema: 6 tables for reusable Agent framework
- protocol_schema: 2 tables for Protocol Agent
- Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules

Code Stats:
- Backend: 13 files, 4338 lines
- Frontend: 14 files, 2071 lines
- Total: 27 files, 6409 lines

Status: MVP core functionality completed, pending frontend-backend integration testing

Next: Sprint 4 - One-click protocol generation + Word export
2026-01-24 17:29:24 +08:00

812 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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进行索引
uploadingparsingindexingcompleted
// 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 tokens7篇文献全文
对话空间:~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%完成)
**下一步:** 创建测试用例清单和测试数据准备方案