前后端贯通:从用户点击到 AI 响应的完整链路

2026/07/02 · 辰辰 · 架构解析

cbcai.online(原 PHP 项目"柔释")迁移到 Python Flask 后,前端页面部署在 caibingchen.site 作为静态子页面,后端作为独立 Flask 服务运行在 5001 端口, 两者通过 Nginx 反向代理桥接。这篇文章用模型选择功能为例,完整拆解从前端点击到 DeepSeek API 返回结果的全过程。

一、整体架构

┌──────────────────────────────────────────────────────────────┐ │ 用户浏览器 │ │ caibingchen.site/roushi/story.html │ │ │ │ │ │ 用户选择 "DeepSeek V4 Pro (深度思考)" │ │ │ script.js 读取下拉框 value = "deepseek-v4-pro" │ │ │ │ │ ▼ POST /api/roushi/chat │ │ Body: { messages: [...], model: "deepseek-v4-pro" } │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ Nginx (caibingchen.site) │ │ │ │ location /api/roushi/ { │ │ proxy_pass http://127.0.0.1:5001/; ← 尾部斜杠去掉前缀 │ │ } │ │ │ │ /api/roushi/chat → 127.0.0.1:5001/chat │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ Flask 后端 (app.py :5001) │ │ │ │ model = request.json['model'] // → "deepseek-v4-pro" │ │ │ │ if model == "deepseek-v4-pro": │ │ kwargs['reasoning_effort'] = 'high' │ │ kwargs['extra_body'] = {'thinking': {'type': 'enabled'}} │ │ │ │ client.chat.completions.create( │ │ model="deepseek-v4-pro", │ │ messages=[...], │ │ reasoning_effort="high", │ │ extra_body={"thinking": {"type": "enabled"}} │ │ ) │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ DeepSeek API (api.deepseek.com) │ │ │ │ 收到 model="deepseek-v4-pro" │ │ + reasoning_effort="high" → 高推理强度 │ │ + thinking.enabled → 展示思维链 │ │ │ │ 返回 response.choices[0].message.content │ └──────────────────────┬───────────────────────────────────────┘ │ AI 回复文本 ▼ 前端 chat box 渲染结果

二、前端:只做一件事

前端不需要知道 DeepSeek API 长什么样。它只负责把用户的选择打包成一个字符串,塞进请求体发给后端。

2.1 模型下拉框

<select id="modelSelect">
    <option value="deepseek-v4-flash">Flash (快速)</option>
    <option value="deepseek-v4-pro">Pro (深度思考)</option>
</select>

2.2 script.js 发送请求

// 读取用户选择的模型名 — 只是一个字符串
const model = document.getElementById('modelSelect').value;
// model = "deepseek-v4-flash" 或 "deepseek-v4-pro"

// 和第149行收集的 messages 一起发给后端
fetch('/api/roushi/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages, model })
    // → { messages: [...], model: "deepseek-v4-pro" }
});
💡 关键点:前端看到的只是一个普通字符串 "deepseek-v4-pro"。 没有 SDK、没有 API Key、没有 reasoning_effort 参数。
所有 DeepSeek 相关的逻辑全部在后端,前端零感知。

三、Nginx:路径翻译

Nginx 在这里扮演"翻译官"的角色:

用户访问Nginx 规则转发到 Flask
/api/roushi/chat location /api/roushi/ 127.0.0.1:5001/chat
/api/spine/analyze location /api/spine/ 127.0.0.1:5000/analyze
🔧 配置细节proxy_pass http://127.0.0.1:5001/; 结尾的 / 是关键——它会把 location 里匹配的前缀 /api/roushi/ 替换成 /。 所以 /api/roushi/chat 变成 /chat,完美匹配 Flask 的 @app.route('/chat')。 如果忘了这个 /,Flask 会收到 /api/roushi/chat,就匹配不上了。

四、后端:模型选择的真正执行者

后端接收到 model 参数后,做了两件事:

4.1 白名单校验

ALLOWED_MODELS = ['deepseek-v4-flash', 'deepseek-v4-pro']

model = data.get('model', 'deepseek-v4-flash')  # 从请求体读取
if model not in ALLOWED_MODELS:
    model = 'deepseek-v4-flash'                  # 非法值回退到 flash

4.2 根据模型名附加不同参数

# 基础参数 — 两个模型都一样
kwargs = {
    'model': model,
    'messages': data['messages'],
    'stream': False,
}

# Pro 专属参数 — 只有选了 Pro 才会加上
if model == 'deepseek-v4-pro':
    kwargs['reasoning_effort'] = 'high'     # 高推理强度
    kwargs['extra_body'] = {
        'thinking': {'type': 'enabled'}     # 启用思维链展示
    }

# Flash 不需要这些参数,直接调用即可
response = client.chat.completions.create(**kwargs)
⚠️ 为什么 Flash 不加这两个参数?
reasoning_effortthinking 是 DeepSeek V4 Pro 独有的功能。 Flash 定位是快速轻量模型,不支持思维链。如果对 Flash 传了这些参数,API 会报错。 所以后端必须做条件判断,只有 model 是 pro 时才附加

五、Flash vs Pro:参数差异对比

对比维度 deepseek-v4-flash deepseek-v4-pro
定位 快速响应 深度推理
reasoning_effort ❌ 不支持 "high"
thinking ❌ 不支持 {"type": "enabled"}
响应速度 快(秒级) 慢(10-60秒)
适用场景 日常对话、简单问答 故事创作、复杂推理
返回内容 直接回复 先展示思考过程,再回复

在实际使用中,故事模式建议用 Pro——它需要构思一个完整的故事,逻辑链长; 对话模式建议用 Flash——日常问答不需要深度推理,速度快体验好。

六、完整调用时序

时间轴 →

[用户]  选择模型下拉框 → "Pro (深度思考)"
   │
[前端]  script.js:161  → model = "deepseek-v4-pro"
[前端]  script.js:167  → fetch('/api/roushi/chat', { model, messages })
   │
   │  ──── HTTP POST /api/roushi/chat ────▶
   │
[Nginx] 匹配 location /api/roushi/
[Nginx] 去掉前缀 → 127.0.0.1:5001/chat
   │
   │  ──── HTTP POST /chat ────▶
   │
[Flask] app.py:62  → model = "deepseek-v4-pro"
[Flask] app.py:77  → 命中 if 分支,附加 reasoning_effort + thinking
[Flask] app.py:81  → client.chat.completions.create(**kwargs)
   │
   │  ──── POST https://api.deepseek.com/chat/completions ────▶
   │         model="deepseek-v4-pro"
   │         reasoning_effort="high"
   │         extra_body={"thinking":{"type":"enabled"}}
   │
[DeepSeek] 使用 V4 Pro 推理引擎
[DeepSeek] 生成思维链 → 生成最终回复
   │
   ◀──── { choices: [{ message: { content: "从前有一个..." } }] }
   │
[Flask] 提取 content → jsonify 返回前端
   │
   ◀──── { choices: [{ message: { content: "从前有一个..." } }] }
   │
[前端]  script.js:172 → data.choices[0].message.content
[前端]  displayMessage('bot', content) → 渲染到聊天框
   │
[用户]  看到 AI 回复

七、设计原则总结

  1. 前端无感知:前端不知道 DeepSeek API Key、不知道 reasoning_effort 参数、甚至不知道调的是 DeepSeek——只知道把一个模型名字符串传给后端。
  2. 后端集中决策:所有模型相关逻辑(白名单、参数附加、错误处理)都在 app.py 一个文件里,修改模型行为只需改后端。
  3. Nginx 做桥梁:前端静态文件和后端 API 统一挂在 caibingchen.site 域名下,用户感知不到背后是两个独立进程。
  4. 解耦可替换:想换模型?改 app.py 的 ALLOWED_MODELS 和对应参数即可。想换前端 UI?改 HTML 即可。互不影响。
📌 这个架构同样适用于脊柱侧弯分析系统:前端页面(spine/index.html)通过 /api/spine/analyze 调用 Flask :5000,用户在前端选择 DeepSeek 还是豆包(doubao), 后端根据 llm_type 参数决定走哪个 API。原理完全一致,只是前端传的不是 model 而是 llm_type。