模型体验模块总结
模型体验功能完整技术总结
一、功能背景与问题分析
平台新增模型体验模块,用户可在平台内直接与已接入的 AI 大模型进行交互测试。
大模型推理存在固有特点:
- 模型需逐 token 自回归生成,一次完整回复可能需要 10~30 秒
- 若采用普通 HTTP 请求等待全量响应,用户面对空白页面长时间等待,体验极差且易误判为超时
- 引入 SSE(Server-Sent Events)流式推送后,模型每生成一个 token 即实时推送给前端,用户看到"打字机效果",首字响应毫秒级,体验与 ChatGPT 一致
二、为什么选择 SSE 而非其他方案
| 方案 | 实现复杂度 | 实时性 | 适用场景 | 缺点 |
|---|---|---|---|---|
| 普通 HTTP | 低 | 差(等全量) | 短响应接口 | 大模型场景体验极差 |
| 客户端轮询 | 中 | 差(有间隔延迟) | 简单状态查询 | 频繁请求浪费资源 |
| SSE | 低 | 好(逐 token 推送) | 大模型流式输出 ✅ | 单向,不支持客户端推送 |
| WebSocket | 高 | 好 | 双向实时通信 | 需协议升级,实现复杂 |
选择 SSE 的核心理由:
- 大模型对话是单向数据流(服务端持续推送),SSE 天然契合
- 基于标准 HTTP 协议,无需额外握手和协议升级,运维成本最低
- 原生浏览器支持
EventSourceAPI,前端接入极简 - Gunicorn + Gevent 协程模型天然支持大量 SSE 长连接并发
三、SSE 完整实现细节
3.1 后端核心实现
# Django StreamingHttpResponse + 生成器实现 SSE
def _sse_error_response(msg):
"""将错误信息封装为流式 SSE 响应,统一非流式错误出口"""
import json
from django.http import StreamingHttpResponse
data = {"status": "error", "content": msg}
chunk = f"event: error\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
response = StreamingHttpResponse(
streaming_content=iter([chunk]),
content_type="text/event-stream"
)
response["Cache-Control"] = "no-cache"
response["X-Accel-Buffering"] = "no"
return response
def stream_generator():
"""
流式生成聊天回复,包含性能指标采集。
Yields SSE 格式数据块,类型包括:
- "thinking":推理思考内容(DeepSeek/豆包推理模型)
- "content":正式回复内容
- "done":流式结束信号,携带 usage 统计
- "error":异常错误信息
"""
nonlocal full_response, full_reasoning
aborted = False
client = self.init_client(requestId, env)
start_time = time.time()
first_token_time = None
# 构造请求体,开启流式模式
body = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_token,
"stream": True,
"stream_options": {"include_usage": True}, # 结束时附带 token 用量
"extra_body": final_extra_body # 厂商差异参数(top_k/top_p/thinking)
}
stream = client.chat.completions.create(**body)
for chunk in stream:
# 检查终止标志(Redis)
if cache.get(f"{CHAT_ABORT_PREFIX}{requestId}"):
cache.delete(f"{CHAT_ABORT_PREFIX}{requestId}")
aborted = True
break
delta = chunk.choices[0].delta
# 推理内容(DeepSeek / 豆包推理模型兼容)
has_reasoning = hasattr(delta, "model_extra") and delta.model_extra
if has_reasoning:
reasoning = delta.model_extra.get("reasoning_content")
if reasoning:
if first_token_time is None:
first_token_time = time.time()
full_reasoning += reasoning
yield self._format_sse_chunk("thinking", reasoning)
# 正式回复内容
if delta.content:
if first_token_time is None:
first_token_time = time.time()
full_response += delta.content
yield self._format_sse_chunk("content", delta.content)
# 计算性能指标
end_time = time.time()
total_duration = round(end_time - start_time, 3)
first_token_latency = round(first_token_time - start_time, 3) if first_token_time else None
generate_duration = total_duration - (first_token_latency or 0)
tokens_per_second = round(completion_tokens / generate_duration, 2) \
if generate_duration > 0 and completion_tokens > 0 else None
perf_stats = {
"first_token_latency": first_token_latency, # 首 token 耗时(s)
"total_duration": total_duration, # 总耗时(s)
"prompt_tokens": prompt_tokens, # 输入 tokens
"completion_tokens": completion_tokens, # 输出 tokens
"total_tokens": total_tokens,
"tokens_per_second": tokens_per_second, # 出词速率(tokens/s)
"vendor": vendor
}
# 持久化 assistant 消息
if full_response:
ChatMessage.objects.create(
trace_id=traceId,
session_id=sessionId,
request_id=requestId,
role='assistant',
content=full_response,
reasoning_content=full_reasoning,
model_name=model,
usage=json.dumps(perf_stats, ensure_ascii=False)
)
chat_session.save(update_fields=['update_time'])
# 推送结束信号,携带性能统计
yield self._format_sse_chunk("done", "\n",
extra={"sessionId": sessionId, "traceId": traceId,
"usage": perf_stats, "aborted": aborted})
# 返回 SSE 流式响应
response = StreamingHttpResponse(
streaming_content=stream_generator(),
content_type="text/event-stream"
)
response["Cache-Control"] = "no-cache" # 禁用缓存
response["X-Accel-Buffering"] = "no" # 禁用 Nginx 缓冲,保证实时推送
return response
3.2 SSE 数据格式
每个推送块统一封装为标准 SSE 格式:
codedata:{"type": "content", "data": "你好"}\n\n
data:{"type": "thinking", "data": "让我思考一下..."}\n\n
data:{"type": "done", "data": "\n", "usage": {...}, "aborted": false}\n\n
data:{"type": "error", "data": "生成失败: ..."}\n\n
3.3 关键配置
code# Nginx 必须禁用缓冲,否则 SSE 数据会被攒批推送
proxy_buffering off;
proxy_cache off;
# Django 响应头
X-Accel-Buffering: no
Cache-Control: no-cache
四、SSE 核心优势
- 首字响应快:模型生成第一个 token 即立刻推送,用户感知延迟从"等待全量"降至毫秒级首字
- 协议轻量:基于 HTTP/1.1 长连接,无需 WebSocket 协议升级握手
- 天然断线重连:浏览器
EventSource原生支持自动重连,Last-Event-ID可续传 - 服务端主动推送:无需客户端轮询,节省带宽与服务端资源
- 兼容性好:所有现代浏览器原生支持,无需额外库
五、AI 交互配套功能
5.1 多厂商模型支持
实现厂商自动识别(_detect_vendor),针对不同厂商差异化处理:
| 厂商 | 识别方式 | 特殊处理 |
|---|---|---|
| 火山引擎(volcengine) | model 名称特征 | thinking: {type: enabled/disabled/auto} |
| 阿里云 / 百度 | model 名称特征 | enable_thinking: true/false |
| DeepSeek / OpenAI | 默认 | 不注入,由模型自身决定 |
5.2 深度思考(DeepThinking)支持
- 支持 DeepSeek、豆包推理模型的
reasoning_content字段 - 推理过程以
type: "thinking"独立推送,前端可展示"思考过程"与"最终回答"分离的 UI
5.3 会话管理(ChatSession)
codeGET /api/application/chat_sessions/ # 获取用户所有会话列表
POST /api/application/update_session_title/ # 修改会话标题
- 每次对话创建独立会话,生成唯一
trace_id作为会话标识 - 新建会话时自动将该用户其他激活会话置为非激活,保证会话状态清晰
- 支持会话标题修改(最长 128 字符),方便用户管理历史对话
- 按
update_time倒序展示,最近会话置顶
5.4 消息记录(ChatMessage)
- 完整记录每轮对话:
role(user/assistant)、content、reasoning_content(推理过程) - 记录
request_id、model_name、conf(参数配置)、usage(token 用量 + 性能指标) - 多轮对话时从数据库查询历史消息,按时间顺序组装
messages列表携带上下文
5.5 对话中止功能(abort_chat)
python# 前端可随时发送中止请求
POST /api/application/abort_chat/ {"requestId": "xxx"}
# 后端在 Redis 中设置终止标志
cache.set(f"{CHAT_ABORT_PREFIX}{request_id}", True, CHAT_ABORT_TIMEOUT)
# stream_generator 每个 chunk 检查一次
if cache.get(f"{CHAT_ABORT_PREFIX}{requestId}"):
aborted = True
break
用户可在生成过程中随时点击停止,后端优雅中断流式输出。
5.6 性能指标采集
每次对话自动采集并随 done 事件返回:
| 指标 | 说明 |
|---|---|
first_token_latency |
首 token 耗时(s),反映模型响应速度 |
total_duration |
总耗时(s) |
prompt_tokens |
输入 token 数 |
completion_tokens |
输出 token 数 |
tokens_per_second |
出词速率(tokens/s),反映生成速度 |
vendor |
模型厂商 |
5.7 参数配置(conf)
支持用户自定义模型参数:
| 参数 | 范围 | 说明 |
|---|---|---|
temperature |
0.0 ~ 2.0 | 创造性,默认 0.9 |
top_p |
0~1 | 核采样,默认 0.7 |
top_k |
整数 | 候选词数,默认 50 |
max_token |
整数 | 最大输出长度,默认 2048 |
web_search |
bool | 联网搜索(模型支持时生效) |
deepThinking |
0/1/2 | 深度思考开关(0关/1开/2自动) |
5.8 多模块支持
通过 ChatSession.module 字段区分不同体验场景(chat、chat_3b 等),同一套基础设施支持多个业务模块复用,统一纳入平台权限与统计体系。
六、简历一句话描述
基于 SSE 实现多厂商大模型(火山引擎/阿里云/DeepSeek/OpenAI)流式输出,支持深度思考内容独立推送、对话中止、多轮会话记录及性能指标(首 token 延迟/出词速率)采集,首字响应毫秒级,统一纳入平台权限管控与调用统计体系。
七、面试高频追问
Q:SSE 和 WebSocket 怎么选择?
大模型对话是"客户端发一次,服务端持续推送"的单向流场景。SSE 基于 HTTP 长连接,实现更简单,无协议升级成本,天然契合。若需要双向实时通信(如多人协作、语音)才考虑 WebSocket。
Q:如何实现对话中止?
前端发送中止请求,后端在 Redis 中写入终止标志(key 为
requestId)。stream_generator在每个 chunk 迭代时检查该标志,检测到后立即 break 跳出循环,优雅中断生成,并在done事件中携带aborted: true通知前端。
Q:多轮对话上下文如何保证?
服务端无状态。每次请求携带
traceId,后端从ChatMessage表中查询该会话的所有历史消息,按时间顺序组装messages列表传给模型,上下文完全由数据库持久化保证。
Q:不同厂商模型参数差异怎么处理?
通过
_detect_vendor函数识别厂商(基于 model 名称特征),再由_build_thinking_extra_body注入厂商差异参数。如火山引擎使用thinking.type,阿里云/百度使用enable_thinking,DeepSeek/OpenAI 不注入由模型自身决定,做到对上层透明。

浙公网安备 33010602011771号