从零打通大模型:阿里云百炼 + FastAPI + Vue3 实现单轮与流式 AI 对话 本文以一个「AI 求职助手」为例完整演示如何调用通义千问大模型从阿里云百炼开通与配置到 Python 直连验证再到 FastAPI 封装接口、接口测试、最后用 Vue3 Element Plus 前端对接实现「单轮对话」与「流式输出」两种模式。所有代码均来自真实可运行项目。目录一、整体架构与最终效果二、阿里云百炼配置DASHSCOPE_API_KEY三、Python 直接调用大模型3.1 单轮对话case1.py3.2 流式输出case2.py四、FastAPI 封装 HTTP 接口4.1 请求体 Schema4.2 路由单轮 SSE 流式4.3 在 main.py 注册路由五、接口测试curl / Postman / Swagger六、前端对接Vue3 Element Plus6.1 接口封装 llm.js6.2 Vite 代理配置6.3 豆包风格聊天面板组件6.4 页面与路由七、常见问题与踩坑八、总结一、整体架构与最终效果我们要做的是一个模仿「豆包」的 AI 对话页面包含两种模式模式传输方式后端接口体验单轮对话普通POST等待完整响应后一次性渲染POST /llm-day01/case1发问 → 等几秒 → 整段出现流式输出POST SSEtext/event-stream逐段推送POST /llm-day01/case2发问 → 文字逐字「打字机」式出现整体链路如下浏览器(Vue3) │ /api/llm-day01/case1 (axios, 单轮) │ /api/llm-day01/case2 (fetchSSE, 流式) ▼ Vite 代理 (/api → http://127.0.0.1:8000) ▼ FastAPI 后端 (case1_api.py) ▼ 阿里云百炼 DashScope (通义千问 qwen-plus, OpenAI 兼容)二、阿里云百炼配置DASHSCOPE_API_KEY本项目通过阿里云百炼原 DashScope提供的OpenAI 兼容接口调用通义千问底层库使用官方openaiSDK。2.1 开通与获取 API Key登录 阿里云百炼控制台。开通「模型服务」并进入API-KEY 管理点击「创建 API-KEY」。复制生成的 Key格式形如sk-xxxxxxxxxxxxxxxx。2.2 配置环境变量推荐强烈建议用环境变量不要硬编码 Key 到代码里。# Linux / macOS export DASHSCOPE_API_KEYsk-你的key ​ # Windows (PowerShell) $env:DASHSCOPE_API_KEYsk-你的key ​ # 或写入 .env 文件不要提交到 git echo DASHSCOPE_API_KEYsk-你的key .env代码中直接读取import os api_key os.getenv(DASHSCOPE_API_KEY)2.3 base_url 与模型OpenAI 兼容模式下百炼的base_url为https://dashscope.aliyuncs.com/compatible-mode/v1注本文项目代码中使用的是专属 MaaS endpointhttps://ws-xxxxx.cn-beijing.maas.aliyuncs.com/compatible-mode/v1功能等价可按你自己的专属地址替换。常用模型通过model参数指定qwen-plus通义千问 plus性价比高本项目使用qwen-max效果更强qwen-turbo速度最快、最便宜三、Python 直接调用大模型先不碰 Web 框架用纯 Python 验证大模型调用是否跑通。项目把这两段放在llm/目录下。3.1 单轮对话case1.py# llm/case1.py import os from openai import OpenAI ​ client OpenAI( api_keyos.getenv(DASHSCOPE_API_KEY), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, ) ​ completion client.chat.completions.create( modelqwen-plus, messages[ {role: system, content: You are a helpful assistant.}, {role: user, content: 人为什么要睡觉?}, ], temperature0.75, max_completion_tokens100, ) ​ print(completion.choices[0].message.content)运行pip install openai python llm/case1.py3.2 流式输出case2.py关键参数是streamTrue此时返回的是一个可迭代的生成器每收到一个 chunk 就 yield 一次。# llm/case2.py import os from openai import OpenAI ​ client OpenAI( api_keyos.getenv(DASHSCOPE_API_KEY), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, ) ​ completion client.chat.completions.create( modelqwen-plus, messages[ {role: system, content: You are a helpful assistant.}, {role: user, content: 请介绍一下自己}, ], streamTrue, stream_options{include_usage: True}, # 返回 token 用量 ) ​ content_parts [] print(AI: , end, flushTrue) ​ for chunk in completion: if chunk.choices: content chunk.choices[0].delta.content or print(content, end, flushTrue) content_parts.append(content) elif chunk.usage: print(\n--- 请求用量 ---) print(f输入 Tokens: {chunk.usage.prompt_tokens}) print(f输出 Tokens: {chunk.usage.completion_tokens}) print(f总计 Tokens: {chunk.usage.total_tokens}) ​ print(f\n--- 完整回复 ---\n{.join(content_parts)})流式模式下内容通过chunk.choices[0].delta.content逐段获取用它来实现「打字机」效果是标准做法。四、FastAPI 封装 HTTP 接口验证通了之后把调用逻辑封装成 Web 接口供前端调用。4.1 请求体 Schema使用 Pydantic 定义请求体只接收一个question字段# app/schemas/llm_case1.py from pydantic import BaseModel, Field ​ ​ class LLMCase1(BaseModel): question: str Field(..., title问题, description用户问题)4.2 路由单轮 SSE 流式# app/llm/case1_api.py import os from fastapi import APIRouter from openai import OpenAI from starlette.responses import StreamingResponse from app.schemas.llm_case1 import LLMCase1 BASE_URL https://dashscope.aliyuncs.com/compatible-mode/v1 router APIRouter(prefix/llm-day01, tags[LLM-DAY01]) # 单轮对话等待完整回复后返回 JSON router.post(/case1, summary单轮对话) async def case1_api(body: LLMCase1): client OpenAI( api_keyos.getenv(DASHSCOPE_API_KEY), base_urlBASE_URL, ) completion client.chat.completions.create( modelqwen-plus, messages[ {role: system, content: 你是一个智能助手}, {role: user, content: body.question}, ], temperature0.75, ) ai_reply completion.choices[0].message.content return { code: 1, message: 请求成功, data: {ai_reply: ai_reply}, } # 流式对话SSE 逐段推送 def stream_chunk(user_question: str): client OpenAI( api_keyos.environ[DASHSCOPE_API_KEY], base_urlBASE_URL, ) completion client.chat.completions.create( modelqwen-plus, messages[ {role: system, content: You are a helpful assistant.}, {role: user, content: user_question}, ], streamTrue, stream_options{include_usage: True}, ) for chunk in completion: if chunk.choices: delta chunk.choices[0].delta if delta.content: yield fdata: {delta.content}\n\n # SSE 格式data: 内容 两个换行 yield data: [done]\n\n # 自定义结束标志 router.post(/case2, summary流式对话(SSE)) async def case2_api(body: LLMCase1): return StreamingResponse( contentstream_chunk(body.question), media_typetext/event-stream, )SSE 格式要点每个事件以data: 内容\n\ndata: 内容 两个换行分隔结束用data: [done]\n\n。前端据此切分。4.3 在 main.py 注册路由# main.py节选 from app.llm.case1_api import llm_day01_router app.include_router(llm_day01_router) # 别忘了跨域方便前端本地联调 from starlette.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[*], allow_methods[*], allow_headers[*], allow_credentialsTrue, )启动后端pip install fastapi uvicorn uvicorn main:app --host 127.0.0.1 --port 8000 --reload五、接口测试curl / Postman / Swagger5.1 curl 测试单轮curl -X POST http://127.0.0.1:8000/llm-day01/case1 \ -H Content-Type: application/json \ -d {question:你好}返回{ code: 1, message: 请求成功, data: { ai_reply: 你好很高兴见到你 有什么可以帮你的吗 } }5.2 curl 测试流式curl -N -X POST http://127.0.0.1:8000/llm-day01/case2 \ -H Content-Type: application/json \ -d {question:介绍一下你自己}你会看到内容被逐行推出来最后一行是data: [done]。5.3 Swagger 在线文档FastAPI 自带交互式文档浏览器打开即可填参数直接测试http://127.0.0.1:8000/docs如果接口文档里测试通过、但前端报错问题几乎一定在前端超时 / 代理 / SSE 解析见第七节。六、前端对接Vue3 Element Plus前端是标准的 Vue3 Vite Element Plus 项目。核心难点是流式接口是 POST不能用浏览器的原生EventSource它只支持 GET因此要手写fetch ReadableStream解析 SSE。6.1 接口封装 llm.js// src/api/llm.js import request from /utils/request // 单轮对话 export function askOnce(question) { return request({ url: /llm-day01/case1, method: post, data: { question }, timeout: 120000, // 大模型单轮完整生成可能较慢务必放宽超时 }) } // 流式对话POST SSE不能用 EventSource export async function askStream(question, { onChunk, onDone, onError } {}) { try { const token localStorage.getItem(candidateToken) const resp await fetch(/api/llm-day01/case2, { method: POST, headers: { Content-Type: application/json, ...(token ? { Authorization: Bearer ${token} } : {}), }, body: JSON.stringify({ question }), }) if (!resp.ok || !resp.body) { const text await resp.text().catch(() ) throw new Error(text || 请求失败状态码 ${resp.status}) } const reader resp.body.getReader() const decoder new TextDecoder(utf-8) let buffer while (true) { const { done, value } await reader.read() if (done) break buffer decoder.decode(value, { stream: true }) // SSE 以空行(\n\n)分隔事件 let sep while ((sep buffer.indexOf(\n\n)) ! -1) { const event buffer.slice(0, sep) buffer buffer.slice(sep 2) const dataLine event .split(\n) .find((line) line.startsWith(data:)) if (!dataLine) continue const data dataLine.slice(5).trim() if (data [DONE] || data [done]) { onDone onDone() return } onChunk onChunk(data) } } onDone onDone() } catch (err) { console.error([askStream] 流式请求异常:, err) onError onError(err) } }6.2 Vite 代理配置把/api代理到后端 8000并去掉/api前缀// vite.config.js export default defineConfig({ server: { port: 3003, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ), }, }, }, })这样前端访问/api/llm-day01/case1实际打到http://127.0.0.1:8000/llm-day01/case1跨域问题交给代理解决。6.3 豆包风格聊天面板组件核心是一个可复用组件AiChatPanel.vue通过mode区分单轮 / 流式!-- src/components/ai/AiChatPanel.vue核心逻辑节选 -- script setup import { ref, reactive } from vue import { askOnce, askStream } from /api/llm const props defineProps({ mode: { type: String, default: single } }) const messages ref([]) const inputText ref() const loading ref(false) const sendSingle async (question) { loading.value true const aiMsg reactive({ role: ai, content: , streaming: true }) messages.value.push(aiMsg) try { const res await askOnce(question) aiMsg.content res?.data?.ai_reply || 暂无回复 } catch (e) { console.error([sendSingle] 单轮请求失败:, e) aiMsg.content 请求失败请稍后重试。 } finally { aiMsg.streaming false loading.value false } } const sendStream (question) { loading.value true const aiMsg reactive({ role: ai, content: , streaming: true }) messages.value.push(aiMsg) askStream(question, { onChunk: (chunk) { aiMsg.content chunk }, // 逐字追加 onDone: () { aiMsg.streaming false; loading.value false }, onError: (err) { aiMsg.streaming false aiMsg.content aiMsg.content || 请求失败请稍后重试。 loading.value false }, }) } /script6.4 页面与路由两个页面分别复用面板再注册路由// router/index.js import AiSingleChat from /pages/ai/AiSingleChat.vue import AiStreamChat from /pages/ai/AiStreamChat.vue // 在 /candidate 子路由 children 中增加 { path: ai-single, name: AiSingleChat, component: AiSingleChat }, { path: ai-stream, name: AiStreamChat, component: AiStreamChat },!-- src/pages/ai/AiSingleChat.vue -- template AiChatPanel modesingle / /template script setup import AiChatPanel from /components/ai/AiChatPanel.vue /script !-- src/pages/ai/AiStreamChat.vue -- template AiChatPanel modestream / /template script setup import AiChatPanel from /components/ai/AiChatPanel.vue /script启动前端npm install npm run dev # 默认 http://127.0.0.1:3003七、常见问题与踩坑❌ 坑 1单轮对话「请求超时」axios 默认timeout: 1500015 秒。大模型在并发高或首字延迟大时单轮完整响应很容易超过 15 秒触发超时。解决在askOnce中单独设置timeout: 1200002 分钟见 6.1。流式模式不会超时因为首字节很快返回连接一直「活跃」。这也解释了为什么「流式正常、单轮超时」。❌ 坑 2流式接口是 POSTEventSource 用不了new EventSource(url)只能发 GET。我们的流式接口是POST /llm-day01/case2必须带 body。解决用fetchresponse.body.getReader()手动读取流按\n\n切分 SSE 事件见 6.1 的askStream。❌ 坑 3SSE 结束标志要自己约定标准 SSE 用data: [DONE]结束。本项目后端约定data: [done]小写。前端解析时两者都兼容即可。❌ 坑 4跨域 / 代理本地开发若前端直接请求http://127.0.0.1:8000会触发 CORS。统一走 Vite 的/api代理见 6.2即可无需在前端写完整域名。❌ 坑 5本地联调 405用浏览器或 curl 以GET方式访问/llm-day01/case1会返回405 Method Not Allowed——这是正常的因为接口只接受 POST。测试请用curl -X POST或 Swagger。八、总结本文从配置到落地打通了「阿里云百炼 → FastAPI → Vue3」的完整链路配置在百炼控制台拿到DASHSCOPE_API_KEY用环境变量注入base_url 走 OpenAI 兼容模式。Python 直连streamTrue实现流式delta.content取片段。FastAPI 封装单轮返回 JSON流式用StreamingResponse SSEdata: 片段/data: [done]。测试curl、Postman、Swagger/docs三件套验证接口。前端 axios 单轮注意超时fetch ReadableStream解析 POST 流式Vite 代理解决跨域。踩坑超时、EventSource 不支持 POST、SSE 结束标志、代理与 405。照着这套你也能快速给自己的系统加上一个「会打字」的 AI 助手。环境依赖# 后端 pip install fastapi uvicorn openai ​ # 前端 npm install axios element-plus完整项目结构节选fastApiProject4/ ├── llm/ │ ├── case1.py # Python 单轮直连示例 │ └── case2.py # Python 流式直连示例 ├── app/ │ ├── llm/case1_api.py # FastAPI 单轮 SSE 流式接口 │ ├── schemas/llm_case1.py │ └── main.py # 注册路由 CORS └── main.py ​ new_boss_vue-main/boss-candidate-ui/ ├── src/api/llm.js # 接口封装单轮 流式 ├── src/components/ai/AiChatPanel.vue ├── src/pages/ai/AiSingleChat.vue ├── src/pages/ai/AiStreamChat.vue ├── src/router/index.js └── vite.config.js # /api 代理到 8000