Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
382 lines
9.1 KiB
Markdown
382 lines
9.1 KiB
Markdown
# 真实LLM集成完成报告
|
||
|
||
**日期**: 2025-11-21
|
||
**任务**: 将Mock AI替换为真实LLM调用
|
||
**状态**: ✅ 完成
|
||
|
||
---
|
||
|
||
## 📋 背景
|
||
|
||
### 之前的状态
|
||
- ✅ 已完成 Prompt 设计(v1.0.0-MVP)
|
||
- ✅ 已实现 `llmScreeningService.ts`(真实LLM调用)
|
||
- ✅ 已完成测试框架和质量验证
|
||
- ❌ **问题**: `screeningService.ts` 中使用 `mockAIScreening` 生成假数据
|
||
|
||
### 用户需求
|
||
从"设置与启动"页面上传真实文献数据后,**使用真实的 DeepSeek 和 Qwen API 进行筛选**,而不是模拟数据。
|
||
|
||
---
|
||
|
||
## ✅ 完成内容
|
||
|
||
### 1. 修改 `screeningService.ts`
|
||
|
||
**文件**: `backend/src/modules/asl/services/screeningService.ts`
|
||
|
||
#### 核心改动
|
||
|
||
**引入真实LLM服务**:
|
||
```typescript
|
||
import { llmScreeningService } from './llmScreeningService.js';
|
||
```
|
||
|
||
**替换处理逻辑**:
|
||
```typescript
|
||
// ❌ 旧代码(Mock)
|
||
const result = await mockAIScreening(projectId, literature);
|
||
|
||
// ✅ 新代码(真实LLM)
|
||
const screeningResult = await llmScreeningService.dualModelScreening(
|
||
literature.id,
|
||
literature.title,
|
||
literature.abstract,
|
||
picoCriteria,
|
||
inclusionCriteria,
|
||
exclusionCriteria,
|
||
[models[0], models[1]],
|
||
screeningConfig?.style || 'standard',
|
||
literature.authors,
|
||
literature.journal,
|
||
literature.publicationYear
|
||
);
|
||
```
|
||
|
||
#### 新增功能
|
||
|
||
1. **从项目读取PICOS标准**:
|
||
```typescript
|
||
const project = await prisma.aslScreeningProject.findUnique({
|
||
where: { id: projectId },
|
||
});
|
||
|
||
const picoCriteria = project.picoCriteria;
|
||
const inclusionCriteria = project.inclusionCriteria;
|
||
const exclusionCriteria = project.exclusionCriteria;
|
||
```
|
||
|
||
2. **支持自定义模型选择**:
|
||
```typescript
|
||
const models = screeningConfig?.models || ['deepseek-chat', 'qwen-max'];
|
||
```
|
||
|
||
3. **详细日志记录**:
|
||
```typescript
|
||
logger.info('Processing literature', {
|
||
literatureId: literature.id,
|
||
title: literature.title?.substring(0, 50) + '...',
|
||
});
|
||
```
|
||
|
||
4. **结果映射到数据库格式**:
|
||
```typescript
|
||
const dbResult = {
|
||
projectId,
|
||
literatureId: literature.id,
|
||
// DeepSeek结果
|
||
dsModelName: screeningResult.deepseekModel,
|
||
dsPJudgment: screeningResult.deepseek.judgment.P,
|
||
// ... 完整的字段映射
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 完整流程
|
||
|
||
### 用户操作流程
|
||
```
|
||
1. 访问"设置与启动"页面
|
||
↓
|
||
2. 填写 PICOS 标准
|
||
↓
|
||
3. 上传 Excel 文献列表(例如:199篇)
|
||
↓
|
||
4. 点击"开始AI初筛"
|
||
↓
|
||
5. 后端自动处理:
|
||
a. 创建项目
|
||
b. 导入文献
|
||
c. 启动筛选任务
|
||
↓
|
||
6. 真实LLM处理(每篇约10-15秒)
|
||
a. 调用 DeepSeek API
|
||
b. 调用 Qwen API
|
||
c. 对比结果,检测冲突
|
||
d. 保存到数据库
|
||
↓
|
||
7. 前端自动跳转到"审核工作台"
|
||
↓
|
||
8. 显示真实的AI筛选结果
|
||
```
|
||
|
||
### 技术流程
|
||
|
||
```
|
||
前端: TitleScreeningSettings.tsx
|
||
↓ POST /api/v1/asl/literatures/import
|
||
|
||
后端: literatureController.ts
|
||
↓ importLiteratures()
|
||
↓ startScreeningTask()
|
||
|
||
后端: screeningService.ts
|
||
↓ processLiteraturesInBackground()
|
||
↓ for each literature:
|
||
↓ llmScreeningService.dualModelScreening()
|
||
|
||
后端: llmScreeningService.ts
|
||
↓ Promise.all([
|
||
screenWithModel('deepseek-chat', ...),
|
||
screenWithModel('qwen-max', ...),
|
||
])
|
||
|
||
后端: LLMFactory
|
||
↓ getAdapter('deepseek-v3')
|
||
↓ getAdapter('qwen3-72b')
|
||
|
||
真实API调用
|
||
↓ DeepSeek API
|
||
↓ Qwen API
|
||
|
||
结果保存
|
||
↓ AslScreeningResult 表
|
||
|
||
前端: ScreeningWorkbench.tsx
|
||
↓ GET /api/v1/asl/projects/:projectId/screening-results
|
||
↓ 显示真实结果
|
||
```
|
||
|
||
---
|
||
|
||
## ⏱️ 性能预期
|
||
|
||
### 单篇文献处理时间
|
||
| 步骤 | 耗时(串行) |
|
||
|-----|------------|
|
||
| DeepSeek API 调用 | 5-10秒 |
|
||
| Qwen API 调用 | 5-10秒 |
|
||
| 结果保存 | 0.1秒 |
|
||
| **总计** | **10-20秒** |
|
||
|
||
### 批量处理时间(199篇)
|
||
| 模式 | 耗时 | 说明 |
|
||
|-----|------|-----|
|
||
| **串行处理** | 33-66分钟 | 当前实现(避免API限流)|
|
||
| 并发处理(3个) | 11-22分钟 | 可选优化(需测试) |
|
||
| 并发处理(10个) | 3-7分钟 | 风险:可能触发API限额 |
|
||
|
||
**当前策略**: 串行处理(稳定优先)
|
||
|
||
---
|
||
|
||
## 🎯 与Mock数据的对比
|
||
|
||
### Mock 数据(旧)
|
||
```javascript
|
||
// ❌ 假数据
|
||
dsPEvidence: "模拟证据: 研究人群与PICO中的P标准匹配"
|
||
dsReason: "基于标题和摘要分析,该文献符合纳入标准。"
|
||
dsConclusion: randomConclusion() // 随机!
|
||
|
||
// 特点:
|
||
- 1秒完成199篇
|
||
- 证据都是"模拟证据"
|
||
- 判断结果随机生成
|
||
```
|
||
|
||
### 真实LLM(新)
|
||
```javascript
|
||
// ✅ 真实数据
|
||
dsPEvidence: "This study included adult patients with type 2 diabetes mellitus aged 18 years or older, which matches the population criteria."
|
||
dsReason: "The study population consists of T2DM patients, the intervention is an SGLT2 inhibitor (empagliflozin), the comparator is placebo, and the study design is a randomized controlled trial. All PICO criteria are met. The study reports on cardiovascular outcomes including MACE, heart failure hospitalization, and cardiovascular death, which are the outcomes of interest."
|
||
dsConclusion: "include" // AI真实判断!
|
||
|
||
// 特点:
|
||
- 33-66分钟完成199篇
|
||
- 证据引用文献原文
|
||
- 判断基于Prompt v1.0.0-MVP
|
||
- 准确率:60%(首次测试)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 数据验证
|
||
|
||
### 验证方法
|
||
```bash
|
||
cd AIclinicalresearch/backend
|
||
node check-data.mjs
|
||
```
|
||
|
||
### 预期输出(真实数据)
|
||
```
|
||
🔬 筛选结果样本:
|
||
[1] 文献: Assessment of Thrombectomy versus Combined...
|
||
DeepSeek: include (P:match, I:partial, C:mismatch, S:match)
|
||
Qwen: exclude (P:mismatch, I:mismatch, C:partial, S:match)
|
||
冲突状态: conflict
|
||
是否有证据: DeepSeek=true, Qwen=true ✅
|
||
|
||
证据示例:
|
||
- dsPEvidence: "The study population consists of..."
|
||
- qwenPEvidence: "Patients with acute ischemic stroke..."
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 质量保障
|
||
|
||
### 已实现的质量措施
|
||
|
||
1. **JSON Schema 验证**:
|
||
- 所有LLM输出必须通过Schema验证
|
||
- 不合格的输出会被拒绝
|
||
|
||
2. **错误处理**:
|
||
- 单篇文献失败不影响整体任务
|
||
- 详细错误日志记录
|
||
|
||
3. **进度追踪**:
|
||
- 每10篇更新一次进度
|
||
- 实时统计成功/冲突/失败数
|
||
|
||
4. **可追溯性**:
|
||
- 记录原始LLM输出(`rawOutput`)
|
||
- 记录Prompt版本(`promptVersion`)
|
||
- 记录处理时间(`aiProcessedAt`)
|
||
|
||
---
|
||
|
||
## 🚀 测试步骤
|
||
|
||
### Step 1: 准备测试数据
|
||
```
|
||
使用现有测试文件:
|
||
- PICOS: docs/.../测试案例的PICOS、纳入标准、排除标准.txt
|
||
- Excel: docs/.../Test Cases.xlsx (199篇文献)
|
||
```
|
||
|
||
### Step 2: 执行测试
|
||
1. 启动后端: `cd backend && npm run dev`
|
||
2. 启动前端: `cd frontend-v2 && npm run dev`
|
||
3. 访问: `http://localhost:3001`
|
||
4. 填写PICOS + 上传Excel
|
||
5. 点击"开始AI初筛"
|
||
6. **等待30-60分钟**(199篇×20秒)
|
||
7. 查看审核工作台
|
||
|
||
### Step 3: 验证结果
|
||
```bash
|
||
cd backend
|
||
node check-data.mjs
|
||
```
|
||
|
||
**检查项**:
|
||
- [ ] 所有文献都有筛选结果
|
||
- [ ] 证据不再是"模拟证据"
|
||
- [ ] 证据包含文献原文引用
|
||
- [ ] 判断理由详细且符合逻辑
|
||
- [ ] 冲突检测准确(conclusion不同)
|
||
|
||
---
|
||
|
||
## ⚠️ 注意事项
|
||
|
||
### API密钥配置
|
||
确保环境变量已配置:
|
||
```bash
|
||
# .env
|
||
DEEPSEEK_API_KEY=sk-xxxxx
|
||
QWEN_API_KEY=sk-xxxxx
|
||
```
|
||
|
||
### API限流
|
||
- DeepSeek: 60 RPM(每分钟请求数)
|
||
- Qwen: 60 RPM
|
||
|
||
**当前策略**: 串行处理,不会触发限流
|
||
|
||
### 成本估算
|
||
- DeepSeek: ~$0.001/次 × 199 = **$0.20**
|
||
- Qwen: ~$0.001/次 × 199 = **$0.20**
|
||
- **总计**: **$0.40** / 次完整测试
|
||
|
||
---
|
||
|
||
## 💡 优化建议
|
||
|
||
### 短期优化(Week 2 - Day 4-5)
|
||
1. **并发控制**: 改为3个并发(33分钟 → 11分钟)
|
||
2. **进度显示**: 前端轮询显示进度百分比
|
||
3. **错误重试**: 失败的文献自动重试1次
|
||
|
||
### 中期优化(Week 3)
|
||
1. **消息队列**: 使用Bull Queue异步处理
|
||
2. **批量优化**: 使用批量API接口(如果有)
|
||
3. **缓存机制**: 相同文献不重复筛选
|
||
|
||
---
|
||
|
||
## 📁 相关文件
|
||
|
||
### 修改的文件
|
||
- `backend/src/modules/asl/services/screeningService.ts` ⭐
|
||
|
||
### 依赖的文件(已存在)
|
||
- `backend/src/modules/asl/services/llmScreeningService.ts`
|
||
- `backend/src/modules/asl/schemas/screening.schema.ts`
|
||
- `backend/prompts/asl/screening/v1.0.0-mvp.txt`
|
||
- `backend/src/common/llm/adapters/LLMFactory.ts`
|
||
|
||
### 测试文件
|
||
- `backend/scripts/test-llm-screening.ts`
|
||
- `backend/scripts/test-samples/asl-test-literatures.json`
|
||
|
||
---
|
||
|
||
## 🎉 成果总结
|
||
|
||
### 已实现
|
||
✅ 真实LLM调用替换Mock数据
|
||
✅ 从项目读取PICOS标准
|
||
✅ 双模型并行筛选
|
||
✅ 冲突检测与标记
|
||
✅ 完整的日志追踪
|
||
✅ 错误处理机制
|
||
|
||
### 待优化
|
||
⚠️ 处理时间较长(30-60分钟)
|
||
⚠️ 串行处理(可改为并发)
|
||
⚠️ 前端进度显示(需优化轮询频率)
|
||
|
||
---
|
||
|
||
## 🔗 参考文档
|
||
|
||
- [Prompt设计与测试完成报告](./2025-11-18-Prompt设计与测试完成报告.md)
|
||
- [卒中数据泛化测试报告](./2025-11-18-卒中数据泛化测试报告.md)
|
||
- [任务分解](../04-开发计划/03-任务分解.md)
|
||
|
||
---
|
||
|
||
**报告人**: AI Assistant
|
||
**日期**: 2025-11-21
|
||
**版本**: v1.0.0
|
||
|
||
|
||
|
||
|
||
|