Files
AIclinicalresearch/docs/05-每日进度/Day12-13-LLM适配器与对话系统完成.md
AI Clinical Dev Team 9acbb0ae2b feat: complete Dify platform deployment (Day 18)
## Dify 閮ㄧ讲瀹屾垚 鉁?
### 瀹屾垚鐨勫伐浣?1. Docker 闀滃儚鍔犻€熷櫒閰嶇疆
   - 閰嶇疆 5 涓浗鍐呴暅鍍忔簮
   - 澶у箙鎻愬崌涓嬭浇閫熷害鍜屾垚鍔熺巼

2. Dify 闀滃儚鎷夊彇 (鍏?11 涓湇鍔?
   - langgenius/dify-api:1.9.1
   - langgenius/dify-web:1.9.1
   - postgres, redis, weaviate, nginx 绛?   - 鎬诲ぇ灏忕害 2GB锛岃€楁椂绾?15 鍒嗛挓

3. Dify 鏈嶅姟鍚姩
   - 鉁?nginx (80/443)
   - 鉁?api, worker, worker_beat
   - 鉁?web (3000)
   - 鉁?db (PostgreSQL), redis
   - 鉁?weaviate (鍚戦噺鏁版嵁搴?
   - 鉁?sandbox, plugin_daemon, ssrf_proxy

4. Dify 鍒濆鍖栭厤缃?   - 鍒涘缓绠$悊鍛樿处鍙?   - 鍒涘缓搴旂敤: AI Clinical Research
   - 鑾峰彇 API Key: app-VZRn0vMXdmltEJkvatHVGv5j

5. 鍚庣鐜閰嶇疆
   - DIFY_API_URL=http://localhost/v1
   - DIFY_API_KEY 宸查厤缃?
### 鏂囨。鏇存柊
- 鏂板: docs/05-姣忔棩杩涘害/Day18-Dify閮ㄧ讲瀹屾垚.md
- 鏇存柊: docs/04-寮€鍙戣鍒?寮€鍙戦噷绋嬬.md (Day 18 鏍囪涓哄畬鎴?

### 涓嬩竴姝?Day 19-24: 鐭ヨ瘑搴撶郴缁熷紑鍙?- Dify 瀹㈡埛绔皝瑁?- 鐭ヨ瘑搴撶鐞?CRUD
- 鏂囨。涓婁紶涓庡鐞?- @鐭ヨ瘑搴撻泦鎴?- RAG 闂瓟楠岃瘉

---
Progress: 閲岀▼纰?1 (MVP) 85% -> 鐭ヨ瘑搴撶郴缁熷紑鍙戜腑
2025-10-11 08:58:41 +08:00

746 lines
17 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.
# Day 12-13 - LLM适配器与对话系统完成 ✅
**完成时间:** 2025-10-10
**开发阶段:** 里程碑1 - MVP开发
**本日目标:** 完成LLM适配器、对话服务和流式输出(SSE)
---
## ✅ 完成清单
### LLM适配器层 ✅
#### 1. 类型定义和接口
- [x] **types.ts** - LLM适配器类型定义57行
- `Message` - 消息结构role, content
- `LLMOptions` - LLM调用参数
- `LLMResponse` - 非流式响应
- `StreamChunk` - 流式响应块
- `ILLMAdapter` - 适配器接口
- `ModelType` - 支持的模型类型
#### 2. DeepSeek适配器
- [x] **DeepSeekAdapter.ts** - DeepSeek-V3适配器150行
- 非流式调用:`chat(messages, options)`
- 流式调用:`chatStream(messages, options)`
- SSE数据解析
- 错误处理和重试
- Token使用统计
#### 3. Qwen适配器
- [x] **QwenAdapter.ts** - Qwen3适配器162行
- DashScope API集成
- 非流式调用
- 流式调用X-DashScope-SSE
- 增量输出支持
- 完整的错误处理
#### 4. LLM工厂类
- [x] **LLMFactory.ts** - 适配器工厂75行
- `getAdapter(modelType)` - 获取适配器实例
- 单例模式,缓存适配器
- `clearCache()` - 清除缓存
- `isSupported()` - 检查模型支持
- `getSupportedModels()` - 获取支持列表
---
### 对话系统 ✅
#### 5. 对话服务层
- [x] **conversationService.ts** - 对话管理服务381行
- **创建对话**`createConversation()`
- **获取对话列表**`getConversations()`
- **获取对话详情**`getConversationById()`
- **上下文组装**`assembleContext()` - 系统Prompt + 历史消息 + 项目背景
- **发送消息(非流式)**`sendMessage()` - 完整响应后保存
- **发送消息(流式)**`sendMessageStream()` - SSE流式输出
- **删除对话**`deleteConversation()` - 软删除
- 集成知识库上下文预留Dify RAG接口
#### 6. 对话控制器
- [x] **conversationController.ts** - API控制器247行
- `createConversation()` - 创建新对话201
- `getConversations()` - 获取对话列表200
- `getConversationById()` - 获取对话详情200/404
- `sendMessage()` - 非流式发送200/400
- `sendMessageStream()` - SSE流式发送200
- `deleteConversation()` - 删除对话200/400
- 模型类型验证
#### 7. 对话路由
- [x] **conversations.ts** - RESTful API路由36行
- `POST /api/v1/conversations` - 创建对话
- `GET /api/v1/conversations` - 获取列表
- `GET /api/v1/conversations/:id` - 获取详情
- `POST /api/v1/conversations/message` - 发送消息
- `POST /api/v1/conversations/message/stream` - 流式发送
- `DELETE /api/v1/conversations/:id` - 删除对话
---
### 配置和环境 ✅
#### 8. 环境配置
- [x] **env.ts** - 环境变量管理56行
- 服务器配置port, host, logLevel
- 数据库配置
- Redis配置
- JWT配置
- LLM API配置DeepSeek, Qwen, Gemini
- Dify配置
- 文件上传配置
- CORS配置
- `validateEnv()` - 环境验证
#### 9. 配置模板
- [x] **.env.example** - 环境变量模板36行
- 完整的配置说明
- API Key配置指南
- 默认值参考
---
### 数据库更新 ✅
#### 10. Prisma Schema更新
- [x] **schema.prisma** - 数据模型更新
- `Conversation` 模型添加字段:
- `metadata` (Json?) - 对话元数据
- `deletedAt` (DateTime?) - 软删除时间戳
- `Message` 模型添加字段:
- `model` (String?) - 使用的模型名称
- 添加索引:`@@index([deletedAt])`
#### 11. 数据库迁移
- [x] **迁移文件** - `add_conversation_metadata_deletedAt`
- 应用成功,数据库同步
---
### 依赖管理 ✅
#### 12. 新增依赖
- [x] `axios` - HTTP客户端用于LLM API调用
- [x] `js-yaml` - YAML解析用于智能体配置
- [x] `@types/js-yaml` - TypeScript类型定义
- [x] `zod` - Schema验证用于请求验证
---
### 服务器集成 ✅
#### 13. 主服务器更新
- [x] 注册对话路由:`/api/v1/conversations`
- [x] 添加环境验证:启动时调用`validateEnv()`
- [x] 导入配置模块:`config`, `validateEnv`
---
## 📁 新增/修改文件
### 后端9个新文件 + 4个修改
**新增:**
1. `src/adapters/types.ts` - 57行
2. `src/adapters/DeepSeekAdapter.ts` - 150行
3. `src/adapters/QwenAdapter.ts` - 162行
4. `src/adapters/LLMFactory.ts` - 75行
5. `src/config/env.ts` - 56行
6. `src/services/conversationService.ts` - 381行
7. `src/controllers/conversationController.ts` - 247行
8. `src/routes/conversations.ts` - 36行
9. `.env.example` - 36行
**修改:**
10. `src/index.ts` - 添加对话路由注册(+5行
11. `prisma/schema.prisma` - 更新Conversation和Message模型+3行
12. `package.json` - 添加新依赖(+4行
13. `prisma/migrations/` - 新迁移文件
### 统计
- **新增代码:** ~1200行
- **新增文件:** 9个
- **修改文件:** 4个
---
## 🎯 技术亮点
### 1. 统一的LLM适配器接口
**设计优势:**
- 统一的`ILLMAdapter`接口支持任意LLM
- 轻松扩展新模型Gemini, Claude, GPT等
- 工厂模式管理,单例缓存
**接口定义:**
```typescript
interface ILLMAdapter {
modelName: string;
chat(messages: Message[], options?: LLMOptions): Promise<LLMResponse>;
chatStream(messages: Message[], options?: LLMOptions): AsyncGenerator<StreamChunk>;
}
```
---
### 2. 流式输出SSE
**DeepSeek流式实现**
- 使用Axios `responseType: 'stream'`
- 解析SSE数据`data: {...}``data: [DONE]`
- 逐块yield实时响应
**Qwen流式实现**
- 使用`X-DashScope-SSE: enable`
- 支持`incremental_output`增量模式
- DashScope特殊SSE格式
**前端SSE接收**
```typescript
reply.raw.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
for await (const chunk of conversationService.sendMessageStream(...)) {
reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
```
---
### 3. 智能上下文组装
**上下文组装逻辑:**
1. 获取智能体的系统Prompt
2. 获取最近10条历史消息
3. 渲染用户Prompt模板注入项目背景、知识库上下文
4. 组装为LLM API格式的messages数组
**代码示例:**
```typescript
private async assembleContext(
conversationId: string,
agentId: string,
projectBackground: string,
userInput: string,
knowledgeBaseContext?: string
): Promise<Message[]> {
const systemPrompt = agentService.getSystemPrompt(agentId);
const historyMessages = await prisma.message.findMany({
where: { conversationId },
orderBy: { createdAt: 'desc' },
take: 10,
});
const renderedUserPrompt = agentService.renderUserPrompt(agentId, {
projectBackground,
userInput,
knowledgeBaseContext,
});
return [
{ role: 'system', content: systemPrompt },
...historyMessages.map(msg => ({ role: msg.role, content: msg.content })),
{ role: 'user', content: renderedUserPrompt },
];
}
```
---
### 4. 模型参数配置
**从智能体配置读取:**
```typescript
const agent = agentService.getAgentById(conversation.agentId);
const modelConfig = agent?.models?.[modelType];
await adapter.chat(messages, {
temperature: modelConfig?.temperature,
maxTokens: modelConfig?.maxTokens,
topP: modelConfig?.topP,
});
```
**不同模型不同参数:**
- DeepSeek-V3`temperature: 0.4, maxTokens: 2000`
- Qwen3-72B`temperature: 0.5, maxTokens: 2000`
---
### 5. 错误处理
**LLM API错误**
```typescript
catch (error: unknown) {
if (axios.isAxiosError(error)) {
throw new Error(
`DeepSeek API调用失败: ${error.response?.data?.error?.message || error.message}`
);
}
throw error;
}
```
**控制器层错误:**
```typescript
catch (error: any) {
reply.code(400).send({
success: false,
message: error.message || '发送消息失败',
});
}
```
---
### 6. 知识库集成预留
**Dify RAG接口预留**
```typescript
// 获取知识库上下文(如果有@知识库)
let knowledgeBaseContext = '';
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
// TODO: 调用Dify RAG获取知识库上下文
knowledgeBaseContext = '相关文献内容...';
}
```
**准备工作已完成:**
- 数据库已有`KnowledgeBase``Document`模型
- Dify配置已在`env.ts`中定义
- 消息metadata中已保存`knowledgeBaseIds`
---
## 📊 API接口文档
### 1. 创建对话
```http
POST /api/v1/conversations
Content-Type: application/json
{
"projectId": "uuid",
"agentId": "topic-evaluation",
"title": ""
}
```
**响应201**
```json
{
"success": true,
"data": {
"id": "uuid",
"userId": "uuid",
"projectId": "uuid",
"agentId": "topic-evaluation",
"title": "研究选题讨论",
"metadata": {
"agentName": "选题评价智能体",
"agentCategory": "选题阶段"
},
"createdAt": "2025-10-10T12:30:00Z",
"updatedAt": "2025-10-10T12:30:00Z"
}
}
```
---
### 2. 获取对话列表
```http
GET /api/v1/conversations?projectId=uuid
```
**响应200**
```json
{
"success": true,
"data": [
{
"id": "uuid",
"title": "研究选题讨论",
"agentId": "topic-evaluation",
"project": {
"id": "uuid",
"name": "心血管疾病研究"
},
"_count": {
"messages": 15
},
"updatedAt": "2025-10-10T12:30:00Z"
}
]
}
```
---
### 3. 发送消息(非流式)
```http
POST /api/v1/conversations/message
Content-Type: application/json
{
"conversationId": "uuid",
"content": "",
"modelType": "deepseek-v3",
"knowledgeBaseIds": ["uuid1", "uuid2"]
}
```
**响应200**
```json
{
"success": true,
"data": {
"userMessage": {
"id": "uuid",
"role": "user",
"content": "请评价这个研究选题...",
"createdAt": "2025-10-10T12:30:00Z"
},
"assistantMessage": {
"id": "uuid",
"role": "assistant",
"content": "这是一个很有价值的研究选题...",
"model": "deepseek-chat",
"tokens": 1250,
"createdAt": "2025-10-10T12:30:05Z"
},
"usage": {
"promptTokens": 850,
"completionTokens": 400,
"totalTokens": 1250
}
}
}
```
---
### 4. 发送消息(流式)
```http
POST /api/v1/conversations/message/stream
Content-Type: application/json
{
"conversationId": "uuid",
"content": "",
"modelType": "deepseek-v3"
}
```
**响应200 - SSE流**
```
data: {"content":"这","done":false}
data: {"content":"是","done":false}
data: {"content":"一个","done":false}
...
data: {"content":"。","done":true,"usage":{"promptTokens":850,"completionTokens":400,"totalTokens":1250}}
data: [DONE]
```
---
### 5. 获取对话详情
```http
GET /api/v1/conversations/:id
```
**响应200**
```json
{
"success": true,
"data": {
"id": "uuid",
"title": "研究选题讨论",
"agentId": "topic-evaluation",
"project": {
"id": "uuid",
"name": "心血管疾病研究",
"background": "研究心血管疾病的...",
"researchType": "observational"
},
"messages": [
{
"id": "uuid",
"role": "user",
"content": "请评价...",
"createdAt": "2025-10-10T12:30:00Z"
},
{
"id": "uuid",
"role": "assistant",
"content": "这是一个...",
"model": "deepseek-chat",
"tokens": 1250,
"createdAt": "2025-10-10T12:30:05Z"
}
],
"createdAt": "2025-10-10T12:00:00Z",
"updatedAt": "2025-10-10T12:30:05Z"
}
}
```
---
### 6. 删除对话
```http
DELETE /api/v1/conversations/:id
```
**响应200**
```json
{
"success": true,
"message": "对话已删除"
}
```
---
## 🧪 测试验证
### 1. 后端构建 ✅
```bash
cd backend
npm run build
✅ TypeScript编译通过
✅ 无错误
✅ 生成dist/目录
```
### 2. Prisma生成 ✅
```bash
npx prisma generate
✅ Prisma Client生成成功
✅ 类型定义更新
```
### 3. 数据库迁移 ✅
```bash
npx prisma migrate dev
✅ 迁移文件创建
✅ 数据库schema同步
```
### 4. 依赖安装 ✅
```bash
npm install axios js-yaml zod @types/js-yaml
✅ 所有依赖安装成功
```
---
## ⚠️ 使用前准备
### 1. 配置环境变量
**创建`.env`文件:**
```bash
cp .env.example .env
```
**配置LLM API Keys**
```env
# DeepSeek API Key (必需)
DEEPSEEK_API_KEY=sk-your-deepseek-api-key
# Qwen API Key (必需)
QWEN_API_KEY=sk-your-qwen-api-key
# 其他可选配置
PORT=3001
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ai_clinical_research
```
**获取API Keys**
- DeepSeek: https://platform.deepseek.com/
- Qwen (通义千问): https://dashscope.aliyun.com/
---
### 2. 手动功能测试需要API Key
#### 测试创建对话
```bash
curl -X POST http://localhost:3001/api/v1/conversations \
-H "Content-Type: application/json" \
-d '{
"projectId": "your-project-id",
"agentId": "topic-evaluation",
"title": "测试对话"
}'
```
#### 测试流式发送使用curl
```bash
curl -X POST http://localhost:3001/api/v1/conversations/message/stream \
-H "Content-Type: application/json" \
-d '{
"conversationId": "your-conversation-id",
"content": "请简单介绍一下临床研究",
"modelType": "deepseek-v3"
}' \
--no-buffer
```
---
## 💡 设计决策
### 1. 为什么使用适配器模式?
- ✅ 统一接口,易于扩展新模型
- ✅ 隔离各LLM的API差异
- ✅ 便于测试和mock
- ✅ 支持模型切换
### 2. 为什么使用AsyncGenerator
- ✅ 原生支持异步迭代
- ✅ 内存高效逐块yield
- ✅ 易于与SSE集成
- ✅ 代码简洁清晰
### 3. 为什么保存完整对话历史?
- ✅ 支持上下文记忆
- ✅ 便于审核和分析
- ✅ 可溯源,提高可信度
- ✅ 方便后续优化Prompt
### 4. 为什么软删除对话?
- ✅ 数据安全,可恢复
- ✅ 审计追踪
- ✅ 统计分析需要
- ✅ 符合医疗数据管理规范
---
## 📈 项目进度
```
里程碑1 MVP开发进度85%
├── ✅ Day 4: 环境搭建
├── ✅ Day 5: 后端基础架构
├── ✅ Day 6: 前端基础架构
├── ✅ Day 7: 前端完整布局
├── ✅ Day 8-9: 项目管理API
├── ✅ Day 10-11: 智能体配置系统
├── ✅ Day 12-13: LLM适配器 + 对话系统 ⭐ ← 刚完成
└── ⏳ Day 14-17: 前端对话界面 + 知识库最后15%
```
---
## 📤 Git提交
```bash
commit ccc09c6
feat: Day 12-13 - LLM Adapters and Conversation System completed
后端:
- 创建LLM适配器类型和接口
- 实现DeepSeekAdapter流式+非流式)
- 实现QwenAdapter流式+非流式)
- 创建LLMFactory工厂类
- 创建env.ts环境配置
- 添加.env.example配置模板
- 创建conversationService完整CRUD和流式
- 创建conversationController SSE支持
- 创建conversation路由
- 更新Prisma schema
- 执行数据库迁移
- 注册对话路由到主服务器
- 添加启动时环境验证
依赖:
- 安装axios用于LLM API调用
- 安装js-yaml用于YAML配置解析
- 安装zod用于验证
构建:后端构建成功
新增文件:
- src/adapters/types.ts (57行)
- src/adapters/DeepSeekAdapter.ts (150行)
- src/adapters/QwenAdapter.ts (162行)
- src/adapters/LLMFactory.ts (75行)
- src/config/env.ts (56行)
- src/services/conversationService.ts (381行)
- src/controllers/conversationController.ts (247行)
- src/routes/conversations.ts (36行)
- .env.example (36行)
总计:~1200行新代码
```
---
## 🎓 经验总结
### 做得好的地方 ✅
1. **适配器统一接口**:易于扩展新模型
2. **流式输出实现**SSE实时响应用户体验好
3. **上下文智能组装**系统Prompt + 历史 + 项目背景
4. **模型参数配置化**:从智能体配置读取
5. **完整的错误处理**LLM API、控制器、验证
6. **知识库预留**为Dify RAG集成做好准备
### 改进空间 🔧
1. **LLM调用重试**:添加指数退避重试机制
2. **流式超时处理**:长时间无响应的超时控制
3. **Token计费**:实时统计和限额管理
4. **缓存优化**:相似问题的回复缓存
5. **异步队列**:高并发场景的消息队列
6. **监控告警**LLM API调用成功率、延迟监控
---
## 🔜 下一步工作Day 14-17
### 1. 前端对话界面开发
- 对话消息列表组件
- 消息输入框组件
- 流式输出动画
- Markdown渲染
- 代码高亮
- 模型切换UI
### 2. 知识库集成
- Dify API调用
- @知识库交互
- 文档上传和处理
- 引用溯源显示
- 知识库管理界面
### 3. 功能完善
- 对话历史浏览
- 消息搜索
- 对话导出
- 错误重试
- 离线提示
**预计完成:** MVP系统100%完成,可进行端到端测试
---
**Day 12-13 任务完成!** 🎉
**下一步:** 前端对话界面和知识库集成
**注意:** 需要配置DeepSeek和Qwen API Key才能进行实际对话测试