pip install openai
from openai import OpenAI
client = OpenAI(
api_key="你的_API_KEY", # 替换为 ThisToken.AI 控制台中的密钥
base_url="https://api.thistoken.ai/v1",
)
stream = client.chat.completions.create(
model="gpt-4o-mini", # 按网关支持的模型名填写
messages=[
{"role": "system", "content": "你是一个简洁的中文助手。"},
{"role": "user", "content": "用三句话解释什么是 SSE 流式输出。"},
],
stream=True,
)
full = ""
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
full += delta
print(delta, end="", flush=True) # flush 很关键,别攒缓冲
print("\n--- 完成,共收到", len(full), "个字符 ---")
Two details are worth noting: `flush=True` ensures you're seeing the real arrival rhythm rather than Python's own buffering; printing `len(full)` gives you something to check against when debugging—if the final character count doesn't match the complete answer, chunks were dropped along the way.
## Step Three: Use ReadableStream on the Frontend for Incremental Rendering
Once the baseline is confirmed, return to the browser. The correct approach is to manually read the stream with `response.body.getReader()` and **only append increments, never rewrite the whole thing**:
async function streamChat(prompt) {
const res = await fetch("/api/chat", { // 走自己的后端转发,密钥不进浏览器
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
const bubble = document.querySelector("#answer"); // 对话气泡节点
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
// 关键:append 而不是 innerHTML 整体重写
bubble.append(text);
bubble.scrollTop = bubble.scrollHeight;
}
}
Note that the code requests `/api/chat`—your own backend route, which holds the key and forwards to `https://api.thistoken.ai/v1`. The key is never exposed in the browser; that's the bottom line, even if it's just a debugging demo.
The "append only" principle solves the flickering problem: DOM nodes only get new text added, never destroyed and rebuilt, so the browser doesn't need to reflow entire sections. If you later want Markdown rendering, you can format the complete text once the stream ends, while keeping it plain-text appending during streaming.
## Step Four: Add a "Dashboard" for Debugging
Finally, address the solution to scenario three: make the stream's state visible. Three low-cost techniques:
1. **Timestamp every chunk**: `console.log(Date.now(), delta)`—at a glance you can tell whether the stream is arriving at a steady pace or stalled mid-way.
2. **Frontend counter**: display "received N chunks / M characters" on the page—when things stall, you immediately know it stopped after which chunk.
3. **Check intermediate-layer buffering**: if you're using Nginx or Serverless functions to forward, make sure response buffering is disabled (add `X-Accel-Buffering: no` for Nginx); otherwise the gateway is streaming output while the middle layer is hoarding packets, and the frontend still gets one big dump.
## Summary
Nine times out of ten, streaming output experience problems stem from the habit of "treating the stream as a whole." The path is actually clear: first do a baseline verification with a unified gateway (server-side token-by-token printing), then use ReadableStream for incremental rendering (append only, no rewriting), and finally add timestamps and counters to make the pipeline transparent. Once this workflow is set up—whether you're switching models, switching frontend frameworks, or troubleshooting production lag—you'll have a reliable debugging foundation.
Don't have a gateway account to run the baseline yet? Take two minutes to register one and get that Python snippet above running: https://api.thistoken.ai/register
---
Tired of juggling provider integrations? Register at https://api.thistoken.ai/register and call every model through one base_url.Ready to try Token.AI?
Create a project-level API Key, enable channels in the console, and configure routing, budgets, and audit logs.
注册 ThisToken.AI 并获取 API Key