- 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
24 KiB
24 KiB
对话系统实现方案详细对比
🎯 核心问题
如果不使用LobeChat整体框架,基于大模型的对话聊天该怎么实现?
让我为您详细对比3种方案:
方案A:使用Dify的对话功能
Dify的对话能力
Dify确实提供了完整的对话功能,包括:
- ✅ LLM对话管理
- ✅ 流式输出
- ✅ 对话历史管理
- ✅ RAG知识库集成
- ✅ 提供WebApp界面
架构图
用户浏览器
↓
Dify自带的WebApp界面
↓
Dify后端(对话+RAG)
↓
LLM API (DeepSeek/Qwen)
实现方式
方式1:直接使用Dify的WebApp
# 部署Dify后,直接访问其Web界面
docker-compose up -d
# 访问 http://localhost:3000
# 创建12个应用(对应12个智能体)
方式2:通过Dify API集成
// 调用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;
// 处理流式数据
}
优点 ✅
-
开发成本极低
- 不需要自己实现对话逻辑
- 不需要自己实现流式输出
- 不需要自己管理对话历史
- RAG功能开箱即用
-
功能完整
- 对话管理
- Prompt管理
- 流式输出
- 知识库集成
- 文件上传
-
快速上线
- 1-2周即可搭建MVP
- 无需深入理解LLM API细节
缺点 ❌
-
12个智能体管理复杂
问题:需要在Dify中创建12个独立的应用(App) Dify界面: ├── 应用1: 选题评价 ├── 应用2: PICOS构建 ├── 应用3: 论文润色 ├── ... (共12个) └── 应用12: 期刊稿约评审 影响: - 用户需要在12个应用间切换(体验差) - 无法统一管理对话历史 - 无法共享项目上下文 -
项目管理功能缺失
我们的需求: - 用户创建"项目/课题" - 在项目内使用多个智能体 - 项目背景信息自动注入 - 对话可"固定"到项目背景 Dify的能力: - ❌ 没有"项目"概念 - ❌ 无法跨应用共享上下文 - ❌ 每个对话是独立的 -
前端定制困难
我们的原型图: - 左侧:项目列表 + 智能体列表 - 中间:智能体卡片选择 - 右侧:对话界面 Dify的界面: - 固定的单应用界面 - 定制需要修改Dify源码(复杂) -
业务逻辑受限
我们的特殊需求: - 全局快速问答 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的核心组件:
// 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>
);
}
// 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. 后端对话逻辑(自己实现)
核心代码:
// 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(统一接口)
// 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. 前端调用(流式接收)
// 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>
);
}
优点 ✅
-
完全可控
- ✅ 业务逻辑完全自主
- ✅ UI 100%匹配原型图
- ✅ 可以实现任何定制需求
-
功能完整
- ✅ 项目管理(核心功能)
- ✅ 12个智能体统一管理
- ✅ 上下文动态管理
- ✅ 对话固定到项目背景
-
聊天体验好
- ✅ 参考LobeChat的成熟实现
- ✅ 流式输出流畅
- ✅ Markdown渲染完美
-
成本可控
- ✅ 只需实现业务逻辑
- ✅ 聊天UI复用LobeChat(节省9.5天)
- ✅ RAG交给Dify(节省40天)
缺点 ❌
-
需要自己实现对话逻辑
- 但其实不复杂(上面的代码已展示)
- 核心就是:接收消息 → 组装上下文 → 调用LLM → 返回流式结果
-
需要处理流式输出
- 但有成熟的实现可参考(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个智能体统一管理)
- ✅ 前端UI(100%匹配原型图)
✅ LobeChat帮我们什么?
- ✅ 聊天UI组件(开源可复用)
- ✅ 流式输出参考实现
- ✅ Markdown渲染方案
技术可行性
对话功能其实不复杂! 核心只有4步:
// 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,对话聊天该怎么实现?
答案:
- 前端聊天UI:参考LobeChat开源组件(复用,不是不用)
- 后端对话逻辑:自己实现(调用DeepSeek/Qwen API)
- RAG知识库:使用Dify(专注检索)
Dify的角色:
- ✅ 用于RAG知识库功能
- ❌ 不用其对话功能(不适合我们的复杂业务)
这个方案的优势:
- ✅ 开发周期:2.5个月(可接受)
- ✅ 开发成本:¥49,300(可控)
- ✅ 功能匹配度:95%(满足需求)
- ✅ 聊天体验:优秀(参考LobeChat)
- ✅ 灵活性:高(业务逻辑完全自主)
关键点:对话功能不复杂,核心是业务逻辑!
文档版本:v1.0
更新时间:2025-10-10