## 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% -> 鐭ヨ瘑搴撶郴缁熷紑鍙戜腑
17 KiB
17 KiB
Day 12-13 - LLM适配器与对话系统完成 ✅
完成时间: 2025-10-10
开发阶段: 里程碑1 - MVP开发
本日目标: 完成LLM适配器、对话服务和流式输出(SSE)
✅ 完成清单
LLM适配器层 ✅
1. 类型定义和接口
- types.ts - LLM适配器类型定义(57行)
Message- 消息结构(role, content)LLMOptions- LLM调用参数LLMResponse- 非流式响应StreamChunk- 流式响应块ILLMAdapter- 适配器接口ModelType- 支持的模型类型
2. DeepSeek适配器
- DeepSeekAdapter.ts - DeepSeek-V3适配器(150行)
- 非流式调用:
chat(messages, options) - 流式调用:
chatStream(messages, options) - SSE数据解析
- 错误处理和重试
- Token使用统计
- 非流式调用:
3. Qwen适配器
- QwenAdapter.ts - Qwen3适配器(162行)
- DashScope API集成
- 非流式调用
- 流式调用(X-DashScope-SSE)
- 增量输出支持
- 完整的错误处理
4. LLM工厂类
- LLMFactory.ts - 适配器工厂(75行)
getAdapter(modelType)- 获取适配器实例- 单例模式,缓存适配器
clearCache()- 清除缓存isSupported()- 检查模型支持getSupportedModels()- 获取支持列表
对话系统 ✅
5. 对话服务层
- conversationService.ts - 对话管理服务(381行)
- 创建对话:
createConversation() - 获取对话列表:
getConversations() - 获取对话详情:
getConversationById() - 上下文组装:
assembleContext()- 系统Prompt + 历史消息 + 项目背景 - 发送消息(非流式):
sendMessage()- 完整响应后保存 - 发送消息(流式):
sendMessageStream()- SSE流式输出 - 删除对话:
deleteConversation()- 软删除 - 集成知识库上下文(预留Dify RAG接口)
- 创建对话:
6. 对话控制器
- conversationController.ts - API控制器(247行)
createConversation()- 创建新对话(201)getConversations()- 获取对话列表(200)getConversationById()- 获取对话详情(200/404)sendMessage()- 非流式发送(200/400)sendMessageStream()- SSE流式发送(200)deleteConversation()- 删除对话(200/400)- 模型类型验证
7. 对话路由
- 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. 环境配置
- env.ts - 环境变量管理(56行)
- 服务器配置(port, host, logLevel)
- 数据库配置
- Redis配置
- JWT配置
- LLM API配置(DeepSeek, Qwen, Gemini)
- Dify配置
- 文件上传配置
- CORS配置
validateEnv()- 环境验证
9. 配置模板
- .env.example - 环境变量模板(36行)
- 完整的配置说明
- API Key配置指南
- 默认值参考
数据库更新 ✅
10. Prisma Schema更新
- schema.prisma - 数据模型更新
Conversation模型添加字段:metadata(Json?) - 对话元数据deletedAt(DateTime?) - 软删除时间戳
Message模型添加字段:model(String?) - 使用的模型名称
- 添加索引:
@@index([deletedAt])
11. 数据库迁移
- 迁移文件 -
add_conversation_metadata_deletedAt- 应用成功,数据库同步
依赖管理 ✅
12. 新增依赖
axios- HTTP客户端,用于LLM API调用js-yaml- YAML解析,用于智能体配置@types/js-yaml- TypeScript类型定义zod- Schema验证,用于请求验证
服务器集成 ✅
13. 主服务器更新
- 注册对话路由:
/api/v1/conversations - 添加环境验证:启动时调用
validateEnv() - 导入配置模块:
config,validateEnv
📁 新增/修改文件
后端(9个新文件 + 4个修改)
新增:
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行
修改:
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等)
- 工厂模式管理,单例缓存
接口定义:
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接收:
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. 智能上下文组装
上下文组装逻辑:
- 获取智能体的系统Prompt
- 获取最近10条历史消息
- 渲染用户Prompt模板(注入项目背景、知识库上下文)
- 组装为LLM API格式的messages数组
代码示例:
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. 模型参数配置
从智能体配置读取:
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错误:
catch (error: unknown) {
if (axios.isAxiosError(error)) {
throw new Error(
`DeepSeek API调用失败: ${error.response?.data?.error?.message || error.message}`
);
}
throw error;
}
控制器层错误:
catch (error: any) {
reply.code(400).send({
success: false,
message: error.message || '发送消息失败',
});
}
6. 知识库集成预留
Dify RAG接口预留:
// 获取知识库上下文(如果有@知识库)
let knowledgeBaseContext = '';
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
// TODO: 调用Dify RAG获取知识库上下文
knowledgeBaseContext = '相关文献内容...';
}
准备工作已完成:
- 数据库已有
KnowledgeBase和Document模型 - Dify配置已在
env.ts中定义 - 消息metadata中已保存
knowledgeBaseIds
📊 API接口文档
1. 创建对话
POST /api/v1/conversations
Content-Type: application/json
{
"projectId": "uuid",
"agentId": "topic-evaluation",
"title": "研究选题讨论"
}
响应(201):
{
"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. 获取对话列表
GET /api/v1/conversations?projectId=uuid
响应(200):
{
"success": true,
"data": [
{
"id": "uuid",
"title": "研究选题讨论",
"agentId": "topic-evaluation",
"project": {
"id": "uuid",
"name": "心血管疾病研究"
},
"_count": {
"messages": 15
},
"updatedAt": "2025-10-10T12:30:00Z"
}
]
}
3. 发送消息(非流式)
POST /api/v1/conversations/message
Content-Type: application/json
{
"conversationId": "uuid",
"content": "请评价这个研究选题:高血压患者的依从性研究",
"modelType": "deepseek-v3",
"knowledgeBaseIds": ["uuid1", "uuid2"]
}
响应(200):
{
"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. 发送消息(流式)
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. 获取对话详情
GET /api/v1/conversations/:id
响应(200):
{
"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. 删除对话
DELETE /api/v1/conversations/:id
响应(200):
{
"success": true,
"message": "对话已删除"
}
🧪 测试验证
1. 后端构建 ✅
cd backend
npm run build
✅ TypeScript编译通过
✅ 无错误
✅ 生成dist/目录
2. Prisma生成 ✅
npx prisma generate
✅ Prisma Client生成成功
✅ 类型定义更新
3. 数据库迁移 ✅
npx prisma migrate dev
✅ 迁移文件创建
✅ 数据库schema同步
4. 依赖安装 ✅
npm install axios js-yaml zod @types/js-yaml
✅ 所有依赖安装成功
⚠️ 使用前准备
1. 配置环境变量
创建.env文件:
cp .env.example .env
配置LLM API Keys:
# 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)
测试创建对话
curl -X POST http://localhost:3001/api/v1/conversations \
-H "Content-Type: application/json" \
-d '{
"projectId": "your-project-id",
"agentId": "topic-evaluation",
"title": "测试对话"
}'
测试流式发送(使用curl)
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提交
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行新代码
🎓 经验总结
做得好的地方 ✅
- 适配器统一接口:易于扩展新模型
- 流式输出实现:SSE实时响应,用户体验好
- 上下文智能组装:系统Prompt + 历史 + 项目背景
- 模型参数配置化:从智能体配置读取
- 完整的错误处理:LLM API、控制器、验证
- 知识库预留:为Dify RAG集成做好准备
改进空间 🔧
- LLM调用重试:添加指数退避重试机制
- 流式超时处理:长时间无响应的超时控制
- Token计费:实时统计和限额管理
- 缓存优化:相似问题的回复缓存
- 异步队列:高并发场景的消息队列
- 监控告警:LLM API调用成功率、延迟监控
🔜 下一步工作(Day 14-17)
1. 前端对话界面开发
- 对话消息列表组件
- 消息输入框组件
- 流式输出动画
- Markdown渲染
- 代码高亮
- 模型切换UI
2. 知识库集成
- Dify API调用
- @知识库交互
- 文档上传和处理
- 引用溯源显示
- 知识库管理界面
3. 功能完善
- 对话历史浏览
- 消息搜索
- 对话导出
- 错误重试
- 离线提示
预计完成: MVP系统100%完成,可进行端到端测试
Day 12-13 任务完成! 🎉
下一步: 前端对话界面和知识库集成
注意: 需要配置DeepSeek和Qwen API Key才能进行实际对话测试!