模型体验模块总结

模型体验功能完整技术总结

一、功能背景与问题分析

平台新增模型体验模块,用户可在平台内直接与已接入的 AI 大模型进行交互测试。

大模型推理存在固有特点:

  • 模型需逐 token 自回归生成,一次完整回复可能需要 10~30 秒
  • 若采用普通 HTTP 请求等待全量响应,用户面对空白页面长时间等待,体验极差且易误判为超时
  • 引入 SSE(Server-Sent Events)流式推送后,模型每生成一个 token 即实时推送给前端,用户看到"打字机效果",首字响应毫秒级,体验与 ChatGPT 一致

二、为什么选择 SSE 而非其他方案

方案 实现复杂度 实时性 适用场景 缺点
普通 HTTP 低 差(等全量) 短响应接口 大模型场景体验极差
客户端轮询 中 差(有间隔延迟) 简单状态查询 频繁请求浪费资源
SSE 低 好(逐 token 推送) 大模型流式输出 ✅ 单向,不支持客户端推送
WebSocket 高 好 双向实时通信 需协议升级,实现复杂

选择 SSE 的核心理由:

  • 大模型对话是单向数据流(服务端持续推送),SSE 天然契合
  • 基于标准 HTTP 协议,无需额外握手和协议升级,运维成本最低
  • 原生浏览器支持 EventSource API,前端接入极简
  • 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 核心优势

  1. 首字响应快:模型生成第一个 token 即立刻推送,用户感知延迟从"等待全量"降至毫秒级首字
  2. 协议轻量:基于 HTTP/1.1 长连接,无需 WebSocket 协议升级握手
  3. 天然断线重连:浏览器 EventSource 原生支持自动重连,Last-Event-ID 可续传
  4. 服务端主动推送:无需客户端轮询,节省带宽与服务端资源
  5. 兼容性好:所有现代浏览器原生支持,无需额外库

五、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 不注入由模型自身决定,做到对上层透明。

posted @ 2026-04-23 22:07  小郑[努力版]  阅读(92)  评论(0)    收藏  举报