chore: project initialization - Day 4 environment setup

This commit is contained in:
AI Clinical Dev Team
2025-10-10 15:14:54 +08:00
commit bdc7de8043
22 changed files with 12276 additions and 0 deletions

View File

@@ -0,0 +1,857 @@
# 对话系统实现方案详细对比
## 🎯 核心问题
**如果不使用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**