
AI对话系统开发实践
大约 12 分钟
AI对话系统开发实践
想象一下,你有一个24小时在线的测试专家,能够实时回答你的问题,提供专业建议,还能记住你们的对话历史!今天我们要开发一个智能的AI对话系统,让人机交互变得自然流畅,就像和老朋友聊天一样轻松!
🎯 为什么对话体验这么重要?
传统AI交互的"机器味"
作为一个经常使用各种AI工具的测试工程师,我发现很多AI系统就像"冷冰冰的机器人":
1. 响应延迟的"卡顿感" ⏰
- 问个问题要等半天才有回复
- 长回答要等很久才能看到完整内容
- 用户体验像在用"老式传真机"
2. 上下文丢失的"失忆症" 🧠
- 每次对话都要重新介绍背景
- 无法进行连续的深入讨论
- 就像和一个"健忘的专家"对话
3. 交互方式的"单调性" 📱
- 只能文字问答,形式单一
- 无法处理复杂的多轮对话
- 缺乏个性化的交互体验
现代对话系统的"人性化"
好的AI对话系统应该像一个"贴心助手":
- 流式响应:实时显示回答内容,就像真人在打字
- 上下文记忆:记住对话历史,支持连续讨论
- 多模态交互:支持文字、图片、文件等多种输入
- 个性化体验:根据用户习惯调整回答风格
🏗️ 对话系统架构设计
整体架构:智能对话的"神经网络"
┌─────────────────────────────────────────────────────────────┐
│ 🎨 前端交互层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 聊天界面 │ │ 输入组件 │ │ 历史记录 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 🔄 通信层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ WebSocket │ │ 流式传输 │ │ 状态同步 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 🧠 对话引擎 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 意图识别 │ │ 上下文管理 │ │ 回答生成 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 📚 知识层 │
│ RAG检索 + 智能体协作 + 工具调用 │
└─────────────────────────────────────────────────────────────┘技术栈选择:现代化的对话技术
前端技术:
- Vue 3 + TypeScript:响应式的用户界面
- WebSocket:实时双向通信
- Markdown渲染:支持富文本显示
- 代码高亮:技术内容友好展示
后端技术:
- FastAPI + WebSocket:高性能的实时通信
- Redis:会话状态和缓存管理
- SQLite:对话历史持久化存储
- 异步处理:非阻塞的流式响应
AI技术:
- 流式API调用:实时生成回答内容
- 上下文窗口管理:智能的历史对话管理
- 意图识别:理解用户真实需求
- 多模态处理:文本、图片、文件支持
💬 前端对话界面开发
1. 聊天组件设计
构建一个现代化的聊天界面:
<template>
<div class="chat-container">
<!-- 聊天头部 -->
<div class="chat-header">
<div class="ai-avatar">
<img src="/ai-avatar.png" alt="AI助手" />
</div>
<div class="ai-info">
<h3>AI测试助手</h3>
<span class="status" :class="connectionStatus">
{{ connectionStatus === 'connected' ? '在线' : '离线' }}
</span>
</div>
<div class="chat-actions">
<button @click="clearHistory" class="clear-btn">清空历史</button>
<button @click="exportChat" class="export-btn">导出对话</button>
</div>
</div>
<!-- 消息列表 -->
<div class="message-list" ref="messageList">
<div
v-for="message in messages"
:key="message.id"
class="message-item"
:class="message.role"
>
<div class="message-avatar">
<img
:src="message.role === 'user' ? '/user-avatar.png' : '/ai-avatar.png'"
:alt="message.role === 'user' ? '用户' : 'AI助手'"
/>
</div>
<div class="message-content">
<div class="message-header">
<span class="sender">{{ message.role === 'user' ? '我' : 'AI助手' }}</span>
<span class="timestamp">{{ formatTime(message.timestamp) }}</span>
</div>
<div class="message-body">
<!-- 支持Markdown渲染 -->
<div v-if="message.type === 'text'" v-html="renderMarkdown(message.content)"></div>
<!-- 支持代码块 -->
<pre v-else-if="message.type === 'code'" class="code-block">
<code :class="message.language">{{ message.content }}</code>
</pre>
<!-- 支持图片 -->
<img v-else-if="message.type === 'image'" :src="message.content" alt="图片" />
<!-- 流式输入指示器 -->
<div v-if="message.streaming" class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
<!-- 消息操作 -->
<div class="message-actions">
<button @click="copyMessage(message)" class="action-btn">
<i class="icon-copy"></i> 复制
</button>
<button @click="regenerateResponse(message)" class="action-btn" v-if="message.role === 'assistant'">
<i class="icon-refresh"></i> 重新生成
</button>
</div>
</div>
</div>
<!-- 加载指示器 -->
<div v-if="isLoading" class="loading-indicator">
<div class="ai-thinking">
<img src="/ai-avatar.png" alt="AI助手" />
<div class="thinking-dots">
<span></span><span></span><span></span>
</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="input-area">
<div class="input-container">
<!-- 文件上传 -->
<button @click="triggerFileUpload" class="upload-btn">
<i class="icon-attachment"></i>
</button>
<input
ref="fileInput"
type="file"
@change="handleFileUpload"
accept=".txt,.md,.pdf,.doc,.docx"
style="display: none"
/>
<!-- 文本输入 -->
<textarea
v-model="inputMessage"
@keydown="handleKeyDown"
@input="adjustTextareaHeight"
placeholder="输入你的问题..."
class="message-input"
ref="messageInput"
:disabled="isLoading"
></textarea>
<!-- 发送按钮 -->
<button
@click="sendMessage"
:disabled="!inputMessage.trim() || isLoading"
class="send-btn"
>
<i class="icon-send"></i>
</button>
</div>
<!-- 快捷操作 -->
<div class="quick-actions">
<button
v-for="action in quickActions"
:key="action.id"
@click="useQuickAction(action)"
class="quick-action-btn"
>
{{ action.label }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { marked } from 'marked'
import hljs from 'highlight.js'
// 响应式数据
const messages = ref<Message[]>([])
const inputMessage = ref('')
const isLoading = ref(false)
const connectionStatus = ref<'connected' | 'disconnected'>('disconnected')
const messageList = ref<HTMLElement>()
const messageInput = ref<HTMLTextAreaElement>()
const fileInput = ref<HTMLInputElement>()
// WebSocket连接
let websocket: WebSocket | null = null
// 消息类型定义
interface Message {
id: string
role: 'user' | 'assistant'
content: string
type: 'text' | 'code' | 'image'
language?: string
timestamp: number
streaming?: boolean
}
// 快捷操作
const quickActions = [
{ id: 1, label: '如何写测试用例?', content: '请教我如何编写高质量的测试用例' },
{ id: 2, label: 'API测试最佳实践', content: '分享一些API测试的最佳实践' },
{ id: 3, label: '性能测试指标', content: '性能测试应该关注哪些关键指标?' },
{ id: 4, label: '自动化测试框架', content: '推荐一些好用的自动化测试框架' }
]
// 初始化WebSocket连接
const initWebSocket = () => {
const wsUrl = `ws://localhost:8000/ws/chat`
websocket = new WebSocket(wsUrl)
websocket.onopen = () => {
connectionStatus.value = 'connected'
console.log('WebSocket连接已建立')
}
websocket.onmessage = (event) => {
handleWebSocketMessage(event.data)
}
websocket.onclose = () => {
connectionStatus.value = 'disconnected'
console.log('WebSocket连接已关闭')
}
websocket.onerror = (error) => {
console.error('WebSocket错误:', error)
connectionStatus.value = 'disconnected'
}
}
// 处理WebSocket消息
const handleWebSocketMessage = (data: string) => {
try {
const message = JSON.parse(data)
if (message.type === 'stream_start') {
// 开始流式响应
const newMessage: Message = {
id: message.message_id,
role: 'assistant',
content: '',
type: 'text',
timestamp: Date.now(),
streaming: true
}
messages.value.push(newMessage)
scrollToBottom()
} else if (message.type === 'stream_chunk') {
// 流式内容块
const lastMessage = messages.value[messages.value.length - 1]
if (lastMessage && lastMessage.id === message.message_id) {
lastMessage.content += message.content
scrollToBottom()
}
} else if (message.type === 'stream_end') {
// 流式响应结束
const lastMessage = messages.value[messages.value.length - 1]
if (lastMessage && lastMessage.id === message.message_id) {
lastMessage.streaming = false
isLoading.value = false
}
}
} catch (error) {
console.error('解析WebSocket消息失败:', error)
}
}
// 发送消息
const sendMessage = async () => {
if (!inputMessage.value.trim() || !websocket || isLoading.value) return
const userMessage: Message = {
id: generateId(),
role: 'user',
content: inputMessage.value.trim(),
type: 'text',
timestamp: Date.now()
}
messages.value.push(userMessage)
// 发送到后端
websocket.send(JSON.stringify({
type: 'user_message',
content: userMessage.content,
message_id: userMessage.id
}))
inputMessage.value = ''
isLoading.value = true
scrollToBottom()
}
// 工具函数
const generateId = () => Math.random().toString(36).substr(2, 9)
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString()
}
const renderMarkdown = (content: string) => {
return marked(content, {
highlight: (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value
}
return hljs.highlightAuto(code).value
}
})
}
const scrollToBottom = () => {
nextTick(() => {
if (messageList.value) {
messageList.value.scrollTop = messageList.value.scrollHeight
}
})
}
// 生命周期
onMounted(() => {
initWebSocket()
})
onUnmounted(() => {
if (websocket) {
websocket.close()
}
})
</script>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 100vh;
background: #f5f5f5;
}
.chat-header {
display: flex;
align-items: center;
padding: 16px;
background: white;
border-bottom: 1px solid #e8e8e8;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.message-item {
display: flex;
margin-bottom: 16px;
}
.message-item.user {
flex-direction: row-reverse;
}
.message-content {
max-width: 70%;
background: white;
border-radius: 8px;
padding: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.input-area {
padding: 16px;
background: white;
border-top: 1px solid #e8e8e8;
}
.input-container {
display: flex;
align-items: flex-end;
gap: 8px;
}
.message-input {
flex: 1;
min-height: 40px;
max-height: 120px;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
resize: none;
font-family: inherit;
}
.send-btn {
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.send-btn:disabled {
background: #d9d9d9;
cursor: not-allowed;
}
</style>🔄 后端WebSocket服务
1. FastAPI WebSocket实现
构建高性能的实时通信服务:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import json
import asyncio
import uuid
from typing import Dict, List
from datetime import datetime
app = FastAPI(title="AI对话系统")
# 添加CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class ConnectionManager:
"""WebSocket连接管理器"""
def __init__(self):
self.active_connections: Dict[str, WebSocket] = {}
self.user_sessions: Dict[str, dict] = {}
async def connect(self, websocket: WebSocket, session_id: str):
"""建立连接"""
await websocket.accept()
self.active_connections[session_id] = websocket
self.user_sessions[session_id] = {
"connected_at": datetime.now(),
"message_count": 0,
"context": []
}
print(f"用户 {session_id} 已连接")
def disconnect(self, session_id: str):
"""断开连接"""
if session_id in self.active_connections:
del self.active_connections[session_id]
if session_id in self.user_sessions:
del self.user_sessions[session_id]
print(f"用户 {session_id} 已断开连接")
async def send_message(self, session_id: str, message: dict):
"""发送消息"""
if session_id in self.active_connections:
websocket = self.active_connections[session_id]
await websocket.send_text(json.dumps(message, ensure_ascii=False))
async def send_stream_start(self, session_id: str, message_id: str):
"""发送流式开始信号"""
await self.send_message(session_id, {
"type": "stream_start",
"message_id": message_id,
"timestamp": datetime.now().isoformat()
})
async def send_stream_chunk(self, session_id: str, message_id: str, content: str):
"""发送流式内容块"""
await self.send_message(session_id, {
"type": "stream_chunk",
"message_id": message_id,
"content": content
})
async def send_stream_end(self, session_id: str, message_id: str):
"""发送流式结束信号"""
await self.send_message(session_id, {
"type": "stream_end",
"message_id": message_id,
"timestamp": datetime.now().isoformat()
})
# 全局连接管理器
manager = ConnectionManager()
class ChatService:
"""对话服务类"""
def __init__(self):
self.ai_client = None # 初始化AI客户端
self.rag_retriever = None # 初始化RAG检索器
async def process_user_message(self, session_id: str, message: str, message_id: str):
"""处理用户消息"""
try:
# 获取用户会话上下文
session = manager.user_sessions.get(session_id, {})
context = session.get("context", [])
# 添加用户消息到上下文
context.append({
"role": "user",
"content": message,
"timestamp": datetime.now().isoformat()
})
# 发送流式开始信号
await manager.send_stream_start(session_id, message_id)
# 检索相关知识
relevant_docs = await self._retrieve_knowledge(message)
# 构建增强的提示词
enhanced_prompt = self._build_enhanced_prompt(message, context, relevant_docs)
# 流式生成回答
full_response = ""
async for chunk in self._stream_ai_response(enhanced_prompt):
full_response += chunk
await manager.send_stream_chunk(session_id, message_id, chunk)
# 添加小延迟,模拟真实打字效果
await asyncio.sleep(0.05)
# 添加AI回答到上下文
context.append({
"role": "assistant",
"content": full_response,
"timestamp": datetime.now().isoformat()
})
# 更新会话信息
session["context"] = context[-10:] # 保留最近10轮对话
session["message_count"] += 1
manager.user_sessions[session_id] = session
# 发送流式结束信号
await manager.send_stream_end(session_id, message_id)
except Exception as e:
print(f"处理消息失败: {e}")
await manager.send_message(session_id, {
"type": "error",
"message": "抱歉,处理您的消息时出现了错误,请稍后重试。"
})
async def _retrieve_knowledge(self, query: str) -> List[dict]:
"""检索相关知识"""
# 这里集成RAG检索系统
# 返回相关文档片段
return []
def _build_enhanced_prompt(self, query: str, context: List[dict], docs: List[dict]) -> str:
"""构建增强的提示词"""
prompt = f"""你是一个专业的测试工程师AI助手,具有丰富的软件测试经验。
对话历史:
{self._format_context(context[-6:])} # 最近3轮对话
相关知识:
{self._format_docs(docs)}
用户问题:{query}
请基于以上信息,提供专业、准确、有用的回答。回答要:
1. 结合对话历史的上下文
2. 引用相关知识库内容
3. 提供具体的建议和示例
4. 保持专业但友好的语调
"""
return prompt
def _format_context(self, context: List[dict]) -> str:
"""格式化对话上下文"""
formatted = []
for msg in context:
role = "用户" if msg["role"] == "user" else "助手"
formatted.append(f"{role}: {msg['content']}")
return "\n".join(formatted)
def _format_docs(self, docs: List[dict]) -> str:
"""格式化文档内容"""
if not docs:
return "暂无相关知识库内容"
formatted = []
for doc in docs[:3]: # 最多3个相关文档
formatted.append(f"- {doc.get('title', '未知文档')}: {doc.get('content', '')[:200]}...")
return "\n".join(formatted)
async def _stream_ai_response(self, prompt: str):
"""流式生成AI回答"""
# 这里集成实际的AI模型API
# 模拟流式响应
response = "这是一个模拟的AI回答,展示流式输出效果。在实际应用中,这里会调用真实的AI模型API,如OpenAI、DeepSeek等。"
for char in response:
yield char
await asyncio.sleep(0.02) # 模拟网络延迟
# 初始化对话服务
chat_service = ChatService()
@app.websocket("/ws/chat")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket端点"""
session_id = str(uuid.uuid4())
try:
await manager.connect(websocket, session_id)
while True:
# 接收客户端消息
data = await websocket.receive_text()
message_data = json.loads(data)
if message_data["type"] == "user_message":
# 处理用户消息
await chat_service.process_user_message(
session_id,
message_data["content"],
message_data["message_id"]
)
elif message_data["type"] == "ping":
# 心跳检测
await manager.send_message(session_id, {"type": "pong"})
except WebSocketDisconnect:
manager.disconnect(session_id)
except Exception as e:
print(f"WebSocket错误: {e}")
manager.disconnect(session_id)
@app.get("/api/chat/history/{session_id}")
async def get_chat_history(session_id: str):
"""获取聊天历史"""
session = manager.user_sessions.get(session_id, {})
return {
"session_id": session_id,
"context": session.get("context", []),
"message_count": session.get("message_count", 0)
}
@app.post("/api/chat/clear/{session_id}")
async def clear_chat_history(session_id: str):
"""清空聊天历史"""
if session_id in manager.user_sessions:
manager.user_sessions[session_id]["context"] = []
manager.user_sessions[session_id]["message_count"] = 0
return {"message": "聊天历史已清空"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)🧠 上下文管理系统
智能的对话记忆
from typing import List, Dict, Any
import json
from datetime import datetime, timedelta
class ContextManager:
"""上下文管理器 - 对话的'记忆管家'"""
def __init__(self, max_context_length: int = 4000):
self.max_context_length = max_context_length
self.sessions: Dict[str, dict] = {}
def add_message(self, session_id: str, role: str, content: str, metadata: dict = None):
"""添加消息到上下文"""
if session_id not in self.sessions:
self.sessions[session_id] = {
"messages": [],
"created_at": datetime.now(),
"last_active": datetime.now(),
"metadata": {}
}
message = {
"role": role,
"content": content,
"timestamp": datetime.now().isoformat(),
"metadata": metadata or {}
}
self.sessions[session_id]["messages"].append(message)
self.sessions[session_id]["last_active"] = datetime.now()
# 自动清理过长的上下文
self._trim_context(session_id)
def get_context(self, session_id: str, max_messages: int = 10) -> List[dict]:
"""获取上下文消息"""
if session_id not in self.sessions:
return []
messages = self.sessions[session_id]["messages"]
return messages[-max_messages:] if max_messages else messages
def get_context_summary(self, session_id: str) -> str:
"""获取上下文摘要"""
messages = self.get_context(session_id)
if not messages:
return "这是一个新的对话会话。"
# 简单的摘要生成逻辑
user_messages = [msg for msg in messages if msg["role"] == "user"]
topics = self._extract_topics(user_messages)
return f"用户主要询问了关于 {', '.join(topics)} 的问题。"
def _trim_context(self, session_id: str):
"""修剪过长的上下文"""
session = self.sessions[session_id]
messages = session["messages"]
# 计算总长度
total_length = sum(len(msg["content"]) for msg in messages)
# 如果超过限制,保留最近的消息
while total_length > self.max_context_length and len(messages) > 2:
removed_msg = messages.pop(0)
total_length -= len(removed_msg["content"])
def _extract_topics(self, messages: List[dict]) -> List[str]:
"""提取对话主题"""
# 简化的主题提取
keywords = []
for msg in messages:
content = msg["content"].lower()
if "测试" in content:
keywords.append("测试")
if "api" in content:
keywords.append("API")
if "性能" in content:
keywords.append("性能")
if "自动化" in content:
keywords.append("自动化")
return list(set(keywords))[:3] # 返回前3个主题
def cleanup_expired_sessions(self, expire_hours: int = 24):
"""清理过期会话"""
cutoff_time = datetime.now() - timedelta(hours=expire_hours)
expired_sessions = [
session_id for session_id, session in self.sessions.items()
if session["last_active"] < cutoff_time
]
for session_id in expired_sessions:
del self.sessions[session_id]
return len(expired_sessions)🎯 实战案例:智能测试咨询
让我们看一个完整的对话场景:
async def demo_intelligent_consultation():
"""智能测试咨询演示"""
# 模拟用户对话场景
conversation_flow = [
{
"user": "我需要测试一个登录接口,应该怎么设计测试用例?",
"expected_ai_behavior": "分析登录接口测试要点,提供系统化的测试用例设计建议"
},
{
"user": "那安全测试方面需要注意什么?",
"expected_ai_behavior": "基于前面的登录接口上下文,专门讲解安全测试要点"
},
{
"user": "能给我一个具体的SQL注入测试用例吗?",
"expected_ai_behavior": "提供具体的SQL注入测试用例,结合登录接口场景"
}
]
print("🎭 智能测试咨询演示")
print("=" * 50)
for i, turn in enumerate(conversation_flow, 1):
print(f"\n第{i}轮对话:")
print(f"👤 用户: {turn['user']}")
print(f"🤖 AI预期行为: {turn['expected_ai_behavior']}")
# 这里会调用实际的AI对话系统
# response = await chat_service.process_message(turn['user'])
# print(f"🤖 AI实际回答: {response}")
if __name__ == "__main__":
import asyncio
asyncio.run(demo_intelligent_consultation())🎉 总结
AI对话系统是测试平台用户体验的核心,通过精心设计的架构和实现,我们可以提供:
- 流畅的交互体验:实时响应,自然对话
- 智能的上下文管理:记住对话历史,支持连续讨论
- 丰富的功能支持:文本、代码、图片等多模态交互
- 个性化的服务:基于用户习惯和项目特点定制回答
下一篇我们将学习智能测试用例生成系统的开发,让AI真正成为测试用例设计的专家!
💡 对话系统开发小贴士:好的对话系统不是技术的炫耀,而是用户体验的提升。记住,用户不关心你用了什么技术,只关心能不能快速解决问题!
