Files
AIclinicalresearch/对话系统实现方案对比.md
AI Clinical Dev Team 239c7ea85e feat: Day 21-22 - knowledge base frontend completed, fix CORS and file upload issues
- Complete knowledge base list and detail pages
- Complete document upload component
- Fix CORS config (add PUT/DELETE method support)
- Fix file upload issues (disabled state and beforeUpload return value)
- Add detailed debug logs (cleaned up)
- Create Day 21-22 completion summary document
2025-10-11 15:40:12 +08:00

861 lines
24 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.
# 对话系统实现方案详细对比
## 🎯 核心问题
**如果不使用LobeChat整体框架基于大模型的对话聊天该怎么实现**
让我为您详细对比3种方案
---
## 方案A使用Dify的对话功能
### Dify的对话能力
Dify确实提供了完整的对话功能包括
- ✅ LLM对话管理
- ✅ 流式输出
- ✅ 对话历史管理
- ✅ RAG知识库集成
- ✅ 提供WebApp界面
### 架构图
```
用户浏览器
Dify自带的WebApp界面
Dify后端对话+RAG
LLM API (DeepSeek/Qwen)
```
### 实现方式
**方式1直接使用Dify的WebApp**
```bash
# 部署Dify后直接访问其Web界面
docker-compose up -d
# 访问 http://localhost:3000
# 创建12个应用对应12个智能体
```
**方式2通过Dify API集成**
```javascript
// 调用Dify的对话API
const response = await fetch('https://api.dify.ai/v1/chat-messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${DIFY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
inputs: {},
query: "请帮我构建PICOS",
response_mode: "streaming", // 流式输出
conversation_id: "conv-xxx", // 对话ID
user: "user-123"
})
});
// 接收流式响应
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理流式数据
}
```
### 优点 ✅
1. **开发成本极低**
- 不需要自己实现对话逻辑
- 不需要自己实现流式输出
- 不需要自己管理对话历史
- RAG功能开箱即用
2. **功能完整**
- 对话管理
- Prompt管理
- 流式输出
- 知识库集成
- 文件上传
3. **快速上线**
- 1-2周即可搭建MVP
- 无需深入理解LLM API细节
### 缺点 ❌
1. **12个智能体管理复杂**
```
问题需要在Dify中创建12个独立的应用App
Dify界面
├── 应用1: 选题评价
├── 应用2: PICOS构建
├── 应用3: 论文润色
├── ... (共12个)
└── 应用12: 期刊稿约评审
影响:
- 用户需要在12个应用间切换体验差
- 无法统一管理对话历史
- 无法共享项目上下文
```
2. **项目管理功能缺失**
```
我们的需求:
- 用户创建"项目/课题"
- 在项目内使用多个智能体
- 项目背景信息自动注入
- 对话可"固定"到项目背景
Dify的能力
- ❌ 没有"项目"概念
- ❌ 无法跨应用共享上下文
- ❌ 每个对话是独立的
```
3. **前端定制困难**
```
我们的原型图:
- 左侧:项目列表 + 智能体列表
- 中间:智能体卡片选择
- 右侧:对话界面
Dify的界面
- 固定的单应用界面
- 定制需要修改Dify源码复杂
```
4. **业务逻辑受限**
```
我们的特殊需求:
- 全局快速问答 vs 项目内深度研究
- 动态背景信息管理(固定功能)
- 对话溯源(引用知识库)
- 多模型即时切换
Dify的支持
- 🟡 部分支持,但需要复杂配置
- 🟡 或需要修改源码
```
### 适用场景 ✅
**适合:** 如果您的需求是:
- 简单的知识库问答
- 单一的对话场景
- 快速验证想法MVP
**不适合:** 我们的项目
- ❌ 12个智能体需要统一管理
- ❌ 项目管理是核心功能
- ❌ 需要高度定制的UI
---
## 方案B参考LobeChat + 自研后端(推荐 ⭐)
### 核心思路
```
前端参考LobeChat的UI组件不用整体框架
后端自己实现对话逻辑调用LLM API
RAG使用Dify专注知识库检索
```
### 架构图
```
┌─────────────────────────────────────────┐
│ 前端React + Vite
│ ┌──────────────────────────────────┐ │
│ │ 聊天UI参考LobeChat实现 │ │
│ │ - ChatMessage 组件 │ │
│ │ - ChatInput 组件 │ │
│ │ - StreamRenderer 组件 │ │
│ │ - MarkdownContent 组件 │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ 业务界面(自己实现) │ │
│ │ - 项目管理 │ │
│ │ - 智能体选择 │ │
│ │ - 历史记录 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
↓ REST API
┌─────────────────────────────────────────┐
│ 后端Node.js + TypeScript
│ ┌──────────────────────────────────┐ │
│ │ 对话服务(自己实现) │ │
│ │ - 接收用户消息 │ │
│ │ - 组装上下文(项目背景+历史) │ │
│ │ - 调用LLM API │ │
│ │ - 流式返回结果 │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ 业务服务(自己实现) │ │
│ │ - 项目管理 │ │
│ │ - 智能体路由 │ │
│ │ - 权限控制 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
↓ ↓
┌──────────────────┐ ┌─────────────────┐
│ Dify (RAG) │ │ LLM API │
│ - 知识库检索 │ │ - DeepSeek-V3 │
仅RAG │ │ - Qwen3 │
└──────────────────┘ └─────────────────┘
```
### 详细实现
#### 1. 前端聊天UI参考LobeChat
**提取LobeChat的核心组件**
```tsx
// src/components/chat/ChatMessage.tsx
// 从LobeChat移植做少量修改
import React from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
export function ChatMessage({ message, onCopy, onPin }: Props) {
const isUser = message.role === 'user';
return (
<div className={`flex gap-3 ${isUser ? 'flex-row-reverse' : ''}`}>
{/* 头像 */}
<Avatar type={isUser ? 'user' : 'ai'} />
{/* 消息内容 */}
<div className={`flex-1 ${isUser ? 'items-end' : 'items-start'}`}>
<div className={`
max-w-3xl p-4 rounded-lg
${isUser ? 'bg-blue-600 text-white' : 'bg-white border'}
`}>
{isUser ? (
<p className="whitespace-pre-wrap">{message.content}</p>
) : (
<ReactMarkdown
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
}
}}
>
{message.content}
</ReactMarkdown>
)}
</div>
{/* 操作按钮 */}
{!isUser && (
<div className="flex gap-2 mt-2">
<button onClick={onCopy}>复制</button>
<button onClick={onPin}>固定到项目</button>
<button>重新生成</button>
</div>
)}
</div>
</div>
);
}
```
```tsx
// src/components/chat/ChatInput.tsx
// 从LobeChat移植
export function ChatInput({ onSend, onUpload, onKbSelect }: Props) {
const [message, setMessage] = useState('');
const handleSend = () => {
if (!message.trim()) return;
onSend(message);
setMessage('');
};
return (
<div className="border rounded-lg p-2 bg-white">
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}}
placeholder="输入您的问题..."
className="w-full resize-none"
rows={3}
/>
<div className="flex justify-between items-center mt-2">
<div className="flex gap-2">
<button onClick={onUpload}>📎 上传文件</button>
<button onClick={onKbSelect}>@ 引用知识库</button>
</div>
<button
onClick={handleSend}
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
发送
</button>
</div>
</div>
);
}
```
#### 2. 后端对话逻辑(自己实现)
**核心代码:**
```typescript
// backend/src/services/chat.service.ts
import { agentConfig } from '../config/agent-loader';
import { LLMFactory } from '../adapters/llm-factory';
import { difyClient } from '../clients/dify';
export class ChatService {
/**
* 处理对话请求(流式输出)
*/
async handleChatStream(req: Request, res: Response) {
const {
message,
projectId,
agentId,
conversationId,
modelName = 'deepseek-v3'
} = req.body;
// 1. 获取智能体配置
const agent = agentConfig.getAgent(agentId);
if (!agent) {
return res.status(404).json({ error: 'Agent not found' });
}
// 2. 组装上下文
const context = await this.buildContext({
projectId,
agentId,
conversationId,
message,
agent
});
// 3. 设置流式响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
try {
// 4. 调用LLM流式
const llm = LLMFactory.create(modelName);
const stream = await llm.chatStream({
messages: context,
temperature: agent.models[modelName]?.temperature || 0.7,
max_tokens: agent.models[modelName]?.max_tokens || 2000
});
// 5. 转发流式数据
let fullResponse = '';
for await (const chunk of stream) {
fullResponse += chunk;
res.write(`data: ${JSON.stringify({
type: 'token',
content: chunk
})}\n\n`);
}
// 6. 保存对话记录
await this.saveMessage({
conversationId,
projectId,
agentId,
userMessage: message,
aiMessage: fullResponse
});
// 7. 结束流
res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
res.end();
} catch (error) {
res.write(`data: ${JSON.stringify({
type: 'error',
message: error.message
})}\n\n`);
res.end();
}
}
/**
* 组装对话上下文
*/
private async buildContext(params: BuildContextParams) {
const { projectId, agentId, conversationId, message, agent } = params;
const messages = [];
// 1. 项目背景(如果有)
if (projectId) {
const project = await db.projects.findOne({ id: projectId });
messages.push({
role: 'system',
content: `# 项目背景\n${project.description}`
});
}
// 2. 智能体系统提示词
const systemPrompt = agentConfig.getPrompt(agent.system_prompt_file);
messages.push({
role: 'system',
content: systemPrompt
});
// 3. 历史对话最近10轮
const history = await db.messages.find({
conversation_id: conversationId
}).limit(20).sort({ created_at: -1 });
for (const msg of history.reverse()) {
messages.push(
{ role: 'user', content: msg.user_message },
{ role: 'assistant', content: msg.ai_message }
);
}
// 4. 知识库检索(如果需要)
if (agent.rag_enabled && message.includes('@')) {
const kbName = this.extractKbReference(message);
if (kbName) {
const ragResults = await difyClient.queryKnowledgeBase({
datasetId: this.getKbId(kbName),
query: message,
topK: 5
});
messages.push({
role: 'system',
content: `# 相关知识库内容\n${
ragResults.map(r => `[${r.metadata.filename}]\n${r.content}`).join('\n\n')
}`
});
}
}
// 5. 当前用户问题
messages.push({
role: 'user',
content: message
});
return messages;
}
/**
* 保存对话记录
*/
private async saveMessage(params: SaveMessageParams) {
await db.messages.create({
conversation_id: params.conversationId,
project_id: params.projectId,
agent_id: params.agentId,
user_message: params.userMessage,
ai_message: params.aiMessage,
created_at: new Date()
});
}
}
```
#### 3. LLM Adapter统一接口
```typescript
// backend/src/adapters/llm-factory.ts
// DeepSeek Adapter
export class DeepSeekAdapter {
constructor(private apiKey: string) {}
async chatStream(params: ChatParams): AsyncGenerator<string> {
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: params.messages,
temperature: params.temperature,
max_tokens: params.max_tokens,
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const json = JSON.parse(data);
const content = json.choices[0]?.delta?.content;
if (content) {
yield content;
}
} catch (e) {
// 忽略解析错误
}
}
}
}
}
}
// Qwen Adapter
export class QwenAdapter {
constructor(private apiKey: string) {}
async chatStream(params: ChatParams): AsyncGenerator<string> {
// 类似实现调用阿里云DashScope API
// ...
}
}
// 工厂
export class LLMFactory {
static create(modelName: string) {
switch (modelName) {
case 'deepseek-v3':
return new DeepSeekAdapter(process.env.DEEPSEEK_API_KEY);
case 'qwen3-72b':
return new QwenAdapter(process.env.DASHSCOPE_API_KEY);
default:
throw new Error(`Unsupported model: ${modelName}`);
}
}
}
```
#### 4. 前端调用(流式接收)
```typescript
// frontend/src/services/chat.ts
export async function sendMessage(params: SendMessageParams) {
const { message, projectId, agentId, conversationId, onToken, onDone } = params;
const response = await fetch('/api/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message,
project_id: projectId,
agent_id: agentId,
conversation_id: conversationId,
model_name: 'deepseek-v3'
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.type === 'token') {
onToken(data.content); // 实时显示
} else if (data.type === 'done') {
onDone();
} else if (data.type === 'error') {
throw new Error(data.message);
}
}
}
}
}
// React组件中使用
function ChatView() {
const [messages, setMessages] = useState([]);
const [currentResponse, setCurrentResponse] = useState('');
const handleSend = async (message: string) => {
// 添加用户消息
setMessages(prev => [...prev, { role: 'user', content: message }]);
// 添加空的AI消息准备接收流式内容
const aiMessageId = Date.now();
setMessages(prev => [...prev, {
id: aiMessageId,
role: 'assistant',
content: ''
}]);
// 发送请求
await sendMessage({
message,
projectId: currentProject.id,
agentId: currentAgent.id,
conversationId: currentConversation.id,
// 接收每个token
onToken: (token) => {
setMessages(prev => prev.map(msg =>
msg.id === aiMessageId
? { ...msg, content: msg.content + token }
: msg
));
},
// 完成
onDone: () => {
console.log('Stream completed');
}
});
};
return (
<div className="chat-container">
<div className="messages">
{messages.map(msg => (
<ChatMessage key={msg.id} message={msg} />
))}
</div>
<ChatInput onSend={handleSend} />
</div>
);
}
```
### 优点 ✅
1. **完全可控**
- ✅ 业务逻辑完全自主
- ✅ UI 100%匹配原型图
- ✅ 可以实现任何定制需求
2. **功能完整**
- ✅ 项目管理(核心功能)
- ✅ 12个智能体统一管理
- ✅ 上下文动态管理
- ✅ 对话固定到项目背景
3. **聊天体验好**
- ✅ 参考LobeChat的成熟实现
- ✅ 流式输出流畅
- ✅ Markdown渲染完美
4. **成本可控**
- ✅ 只需实现业务逻辑
- ✅ 聊天UI复用LobeChat节省9.5天)
- ✅ RAG交给Dify节省40天
### 缺点 ❌
1. **需要自己实现对话逻辑**
- 但其实不复杂(上面的代码已展示)
- 核心就是:接收消息 → 组装上下文 → 调用LLM → 返回流式结果
2. **需要处理流式输出**
- 但有成熟的实现可参考LobeChat、OpenAI SDK
- Server-Sent Events (SSE) 是标准协议
### 开发工作量
| 模块 | 工作量 | 说明 |
|------|-------|------|
| 前端聊天UI | 1天 | 复用LobeChat组件 |
| 流式输出前端 | 0.5天 | 参考LobeChat实现 |
| Markdown渲染 | 0.5天 | 使用react-markdown |
| 后端对话服务 | 2天 | 上下文组装 + LLM调用 |
| 流式输出后端 | 1天 | SSE实现 |
| LLM Adapter | 1天 | DeepSeek + Qwen接入 |
| **总计** | **6天** | vs 纯手写13天 |
---
## 方案C完全自研不推荐
### 架构
```
前端完全自己实现聊天UI
后端:完全自己实现对话逻辑
RAG完全自己实现向量检索
```
### 缺点 ❌
- 开发周期长4-6个月
- 技术难度高
- RAG系统复杂度被严重低估
- 不推荐!
---
## 📊 三种方案对比总结
| 维度 | 方案A: 用Dify对话 | 方案B: 自研对话+Dify RAG ⭐ | 方案C: 完全自研 |
|------|------------------|---------------------------|----------------|
| **开发周期** | 1-2周 | 2.5个月 | 4-6个月 |
| **开发成本** | ¥10k-20k | **¥49k** ⭐ | ¥80k-180k |
| **12个智能体管理** | 🔴 复杂 | **🟢 简单** ⭐ | 🟢 简单 |
| **项目管理** | 🔴 不支持 | **🟢 完全支持** ⭐ | 🟢 完全支持 |
| **UI定制** | 🔴 困难 | **🟢 完全可控** ⭐ | 🟢 完全可控 |
| **业务逻辑灵活性** | 🟡 受限 | **🟢 完全灵活** ⭐ | 🟢 完全灵活 |
| **聊天体验** | 🟢 好 | **🟢 好** ⭐ | 🟡 需大量调试 |
| **RAG能力** | 🟢 强 | **🟢 强** ⭐ | 🟡 需自己实现 |
| **技术难度** | ⭐ | **⭐⭐⭐** | ⭐⭐⭐⭐⭐ |
| **维护成本** | 🟢 低 | **🟡 中** ⭐ | 🔴 高 |
| **功能匹配度** | 🔴 40% | **🟢 95%** ⭐ | 🟢 100% |
---
## 🎯 最终推荐方案B
### 核心理由
**✅ Dify能帮我们什么**
- ✅ RAG知识库功能文档解析、向量检索、答案溯源
- ❌ 对话功能不适合我们12个智能体管理困难
**✅ 我们自己做什么?**
- ✅ 对话逻辑(接收消息 → 组装上下文 → 调用LLM
- ✅ 项目管理(核心功能)
- ✅ 智能体编排12个智能体统一管理
- ✅ 前端UI100%匹配原型图)
**✅ LobeChat帮我们什么**
- ✅ 聊天UI组件开源可复用
- ✅ 流式输出参考实现
- ✅ Markdown渲染方案
### 技术可行性
**对话功能其实不复杂!** 核心只有4步
```typescript
// 1. 接收消息
const { message, projectId, agentId } = req.body;
// 2. 组装上下文
const context = [
{ role: 'system', content: '项目背景...' },
{ role: 'system', content: '智能体提示词...' },
{ role: 'user', content: '历史对话...' },
{ role: 'assistant', content: '历史回复...' },
{ role: 'user', content: message }
];
// 3. 调用LLM
const stream = await deepseek.chatStream({ messages: context });
// 4. 返回流式结果
for await (const chunk of stream) {
res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
}
```
**就这么简单!** 不需要Dify的对话功能。
### 最佳实践
```
Dify擅长的RAG知识库检索
→ 让Dify做它擅长的
我们需要的:复杂业务逻辑(项目管理、智能体编排)
→ 自己实现,完全可控
聊天UI成熟方案LobeChat
→ 参考实现,节省时间
```
---
## 📝 总结
**问题如果不使用LobeChat对话聊天该怎么实现**
**答案:**
1. **前端聊天UI**参考LobeChat开源组件复用不是不用
2. **后端对话逻辑**自己实现调用DeepSeek/Qwen API
3. **RAG知识库**使用Dify专注检索
**Dify的角色**
- ✅ 用于RAG知识库功能
- ❌ 不用其对话功能(不适合我们的复杂业务)
**这个方案的优势:**
- ✅ 开发周期2.5个月(可接受)
- ✅ 开发成本¥49,300可控
- ✅ 功能匹配度95%(满足需求)
- ✅ 聊天体验优秀参考LobeChat
- ✅ 灵活性:高(业务逻辑完全自主)
**关键点:对话功能不复杂,核心是业务逻辑!**
---
**文档版本v1.0**
**更新时间2025-10-10**