尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

把 OpenAI SDK 的 base_url 改到 TaoToken 之后,chat.completions 的 stream=True 就能打出打字机效果

把 OpenAI SDK 的 base_url 改到 TaoToken 之后,chat.completions 的 stream=True 就能打出打字机效果 把 OpenAI SDK 的 base_url 改到 TaoToken官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content之后chat.completions 的 streamTrue 就能打出打字机效果。这篇从接入配置视角拆一份可以直接复制的 client 初始化、流式 for chunk、FastAPI SSE 推送示例重点解决 base_url 是否带 /v1、api_key 放哪、delta.content 判空、finish_reason 判断结束这几个高频问题。你不需要改前端渲染逻辑也不需要为 deepseek-v4-pro 单独写适配层把 OpenAI SDK 的 base_url 和 api_key 换成 TaoToken 的 OpenAI 兼容入口原有 streamTrue 代码路径就能继续跑。下面按原问题、前置准备、可复制配置、验证请求、常见错排查、CTA 六段展开。一、原问题与场景chat.completions 非流式等待太长默认调用 chat.completions 时如果不加 streamTrue请求会等模型把整段回答生成完再一次性把结果返回。用户侧的体感就是页面转圈输入框上方迟迟没有内容最后突然出现一整段文字。对于聊天、客服、写作辅助、代码补全这类交互场景这种等待非常影响体验。更关键的是首字延迟也就是 TTFT。非流式请求下TTFT 几乎等于整段生成时间流式请求下模型每生成一个 token就会通过分片返回一个 token客户端收到一个就渲染一个。同样是几秒完成流式会让用户感觉“系统一直在工作”非流式则容易让人以为卡住了。原文的接入方式是用 OpenAI SDK 直连某个网关地址在 client 初始化时把 base_url 和 api_key 写死。现在要把这一步改到 TaoToken打开官网注册并创建 Keyclient 的 base_url 填https://taotoken.net/api注意不带/v1也不带 UTMapi_key 填刚创建的 Key。其余代码保持原样streamTrue的 for chunk 循环即可跑通。这么做的价值在于TaoToken 在这里作为 OpenAI 兼容的统一入口解决“各家流式格式细节有差异”的痛点。你不需要为deepseek-v4-pro单独维护适配逻辑也不需要因为切换模型而重写一大套分片解析代码。读者拿到 Key 后配通 OpenAI SDK即可在 FastAPI SSE 的前端里看到逐字打字机效果。二、TaoToken 前置OpenAI SDK 的 base_url 与 api_key 从哪来先把接入参数确认清楚再动代码。TaoToken 的官网是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content打开后完成注册进入控制台创建 Key。Key 在代码里先写成占位符YOUR_API_KEYOpenAI SDK 需要的两个核心参数是base_url https://taotoken.net/api api_key YOUR_API_KEY这里有两个容易写错的地方。第一base_url不要写成https://taotoken.net/api/v1。OpenAI SDK 在发起 chat.completions 请求时会在 base_url 后面拼接/chat/completions。如果 base_url 多带了/v1最终请求路径就可能变成/api/v1/chat/completions这取决于服务端路由是否兼容。本篇场景明确要求使用https://taotoken.net/api所以直接按这个填。第二API 地址本身不带 UTM。UTM 只用于官网注册和 CTA 跳转统计不要把它拼进base_url否则 SDK 拼接路径时会出现奇怪的查询参数或路径错误。建议把 Key 放到环境变量或.env文件里不要提交到 Git。最小.env如下TAOTOKEN_API_KEYYOUR_API_KEY TAOTOKEN_BASE_URLhttps://taotoken.net/api然后在 Python 里用os.getenv读取。这样本地调试、测试环境、线上环境可以换不同 Key不需要改业务代码。如果你在创建 Key 或确认接入地址时遇到问题可以先去 TaoToken 控制台的 API Keys 页面检查 Key 状态https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入参数和端点说明可以看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc三、可复制配置client.py、main.py 与 SSE 的 streamTrue 写法下面给出一套最小可复制配置。依赖只需要 OpenAI SDK 和 FastAPIpip install openai fastapi uvicorn或者写进requirements.txtopenai fastapi uvicorn先写一个公共 client 文件client.py# client.py import os from openai import OpenAI client OpenAI( base_urlos.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api), api_keyos.getenv(TAOTOKEN_API_KEY, YOUR_API_KEY), )注意这里 base_url 是https://taotoken.net/api没有/v1也没有 UTM 参数。api_key 从环境变量读取没读到才退回占位符方便本地临时试验。接着写最小流式调用stream_demo.py# stream_demo.py from client import client stream client.chat.completions.create( modeldeepseek-v4-pro, messages[ {role: user, content: 用三句话介绍你自己} ], streamTrue, ) for chunk in stream: if not chunk.choices: continue choice chunk.choices[0] delta choice.delta if delta and delta.content: print(delta.content, end, flushTrue) if choice.finish_reason: print(f\nfinish_reason{choice.finish_reason})这段代码的关键是streamTrue。加上之后返回值不再是完整响应对象而是一个可迭代的流。每个chunk里通常包含choices[0].delta正文在delta.content。首个分片可能只有 role没有正文所以要用if delta and delta.content判空。最后一个分片的finish_reason会从None变成stop或length生产代码可用它判断“是否说完了”。如果要接到 Web 前端可以用 FastAPI 做 SSE 推送。写一个main.py# main.py from fastapi import FastAPI from fastapi.responses import StreamingResponse from client import client app FastAPI() app.get(/chat) def chat(q: str 你好): def event_gen(): stream client.chat.completions.create( modeldeepseek-v4-pro, messages[{role: user, content: q}], streamTrue, ) for chunk in stream: if not chunk.choices: continue choice chunk.choices[0] delta choice.delta if delta and delta.content: yield fdata: {delta.content}\n\n if choice.finish_reason: yield data: [DONE]\n\n return StreamingResponse( event_gen(), media_typetext/event-stream, headers{ Cache-Control: no-cache, X-Accel-Buffering: no, }, )前端可以用 EventSource 接收div idoutput/div script const output document.querySelector(#output); const es new EventSource(/chat?q用三句话介绍你自己); es.onmessage (event) { if (event.data [DONE]) { es.close(); return; } output.textContent event.data; }; es.onerror () { es.close(); }; /script这样服务端每收到一个delta.content就立刻推一段data: ...\n\n给浏览器浏览器边收边追加就形成逐字打字机效果。原来的前端渲染逻辑基本不用改只是把数据源从一次性响应换成 SSE 流。四、验证请求curl 和 Python 逐字打印确认 finish_reason 成功在接前端之前先用命令行验证配置是否正确。用 curl 直接请求 TaoToken 的 OpenAI 兼容端点curl -N https://taotoken.net/api/chat/completions \ -H Authorization: Bearer YOUR_API_KEY \ -H Content-Type: application/json \ -d { model: deepseek-v4-pro, messages: [ {role: user, content: 用三句话介绍你自己} ], stream: true }这里-N的作用是关闭 curl 的缓冲方便看到逐段输出。请求路径是/api/chat/completions对应 base_urlhttps://taotoken.net/api加 SDK 自动拼接的/chat/completions。如果你在代码里把 base_url 写成https://taotoken.net/api/v1这里的路径对照就会变得混乱所以验证阶段先按本文的 base_url 写。如果 Key、模型名、请求体都正确你会看到类似下面的流式分片。不同模型的字段细节可能略有差异但核心结构是choices[0].delta.contentdata: {id:...,choices:[{delta:{role:assistant},index:0,finish_reason:null}]} data: {id:...,choices:[{delta:{content:你},index:0,finish_reason:null}]} data: {id:...,choices:[{delta:{content:好},index:0,finish_reason:null}]} data: {id:...,choices:[{delta:{},index:0,finish_reason:stop}]}成功结果有两个判断点第一正文是分多次出现的每个delta.content只包含一小段文字。这说明流式已经生效。第二最后一个分片里finish_reason为stop表示模型正常结束如果是length表示输出被长度限制截断。你的生产代码可以根据这个字段决定是否继续追问、是否截断展示、是否记录日志。再用 Python 跑stream_demo.py。如果终端里不是整段突然出现而是“你”“好”“我”这样逐字往外蹦说明 OpenAI SDK 的 base_url、api_key、streamTrue 已经配通。接下来启动 FastAPIuvicorn main:app --reload浏览器打开包含 EventSource 的页面就能看到逐字渲染。如果只想先验证模型名和返回格式可以到 TaoToken 的模型对话页面直接试https://taotoken.net/console/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat五、本篇常见错排查base_url 带 /v1、delta.content 为空、SSE 被缓冲接入流式输出时问题通常集中在几个固定位置。下面按报错现象排查。第一401 或鉴权失败。检查YOUR_API_KEY是否替换成了真实 Key前后有没有空格或换行。如果用.env确认启动 Python 前已经加载环境变量。FastAPI 用uvicorn启动时环境变量要在同一个 shell 或同一个进程环境里生效。第二404 或路径不对。优先检查base_url是否写成了https://taotoken.net/api/v1。本篇场景要求用https://taotoken.net/api不要带/v1。另外不要把 UTM 参数拼进base_url。OpenAI SDK 会自动拼接/chat/completions你只需要提供干净的 base_url。第三AttributeError: NoneType object has no attribute content。这通常是因为首个分片只有 role没有正文delta.content是None。代码里要写if delta and delta.content不要直接print(delta.content)。部分模型在结束分片里也可能给出空 delta所以判空要保留。第四前端没有逐字效果而是最后一次性出现。先确认 FastAPI 返回的是StreamingResponsemedia_type是text/event-stream。再检查反向代理是否开启了缓冲。Nginx 下可以加proxy_buffering off;应用层可以加X-Accel-Buffering: no。有些云厂商的网关也会缓冲 SSE需要在对应控制台确认。第五EventSource 没有收到消息。EventSource 默认发起 GET 请求如果后端只写了 POST/chat前端就连不上。演示阶段可以像本文一样用 GET或者前端改用fetch加ReadableStream读取流式响应。SSE 的格式也要注意每条消息以data:开头以两个换行结束即\n\n。少一个换行浏览器可能不会触发onmessage。第六结束判断不要只看 content 是否为空。正确做法是同时看finish_reason。当它为stop或length时表示当前回答结束。你可以在 SSE 里发一个data: [DONE]作为业务层结束标记前端收到后主动关闭 EventSource。第七模型名写错。deepseek-v4-pro只是本文示例实际可用模型名以 TaoToken 控制台或文档为准。模型名错误时通常不会进入流式循环而是在 create 阶段就返回错误。遇到这种情况先用模型对话页面确认模型 ID再改代码。第八超时和连接中断。流式请求持续时间可能比非流式更长客户端的 timeout 设置不要过短。服务端也要处理客户端断开连接的情况避免生成已经取消但后端还在继续请求模型。六、语义一致 CTA把接入、验证和长期编码串起来回到标题把 OpenAI SDK 的 base_url 改到 TaoToken 之后chat.completions 的 streamTrue 就能打出打字机效果。核心改动只有两个参数base_url 用https://taotoken.net/apiapi_key 用 TaoToken 控制台创建的 Key。streamTrue和 for chunk 循环保持原样FastAPI SSE 前端即可逐字渲染。如果你还在排障先去 TaoToken 控制台 API Keys 页面确认 Key 和权限https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入参数、鉴权方式和端点说明看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc想快速验证模型名和流式返回格式用模型对话https://taotoken.net/console/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat如果你准备把流式输出接入长期编码助手、Agent 或团队工具链可以关注 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan按这套配置跑通后前端看到的就不再是转圈后的一整段文字而是从第一个 token 开始就往外冒的打字机效果。切换模型时也只需要改model参数不用为每个模型重写一套流式解析逻辑。
返回列表