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