# 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; chatStream(messages: Message[], options?: LLMOptions): AsyncGenerator; } ``` --- ### 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 { 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才能进行实际对话测试!