docs: Day 12-13 completion summary and milestone update

This commit is contained in:
AI Clinical Dev Team
2025-10-10 20:33:18 +08:00
parent 702e42febb
commit 8afff23995
17 changed files with 2331 additions and 45 deletions

View File

@@ -0,0 +1,743 @@
# 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才能进行实际对话测试