一、什么是流式输出?为什么大模型都在用它

在日常使用 ChatGPT 或智谱 GLM 等大语言模型时,你会发现答案并不是瞬间全部弹出来的,而是像打字机一样一个字一个字地蹦出来。这种机制在技术上被称为流式输出

如果不用流式输出,采用传统的“一问一答”模式,由于大模型生成几百上千字的内容往往需要十几秒甚至更久,用户在等待期间只能对着一个空白屏幕发呆,体验极其糟糕。而流式输出的核心逻辑是“边生成边返回”:模型每计算出一个 Token(最小语义单元),就立刻通过网络推送给前端。这样极大地降低了用户的首字响应时间(TTFT),有效缓解了等待焦虑。

提示:从底层原理来看,大模型本质上是一个“下一个词预测器”。它本来就是在循环推测下一个 Token,所以流式输出不仅优化了体验,也完美契合了模型底层的生成逻辑。

二、背后的功臣:深入理解 SSE 协议

大模型是怎么做到“边想边说”的?这就不得不提背后的通信协议了。虽然 WebSocket 也能实现双向实时通信,但大模型 API 普遍选择了更轻量的 SSE(Server-Sent Events,服务器发送事件) 协议。

SSE 是基于 HTTP 协议的一种轻量级推送技术。与普通的 HTTP 请求一次性断开不同,在使用 SSE 时,服务器会在响应头中设置 Content-Type: text/event-stream。这意味着这条 HTTP 连接建立后不会被立刻关闭,服务器可以持续地通过这条长连接向客户端发送数据。

在数据格式上,SSE 有着严格的纯文本规范。每个推送的消息一般由若干个字段组成,最常见的是 data:。服务器发送的每条消息必须以两个换行符 \n\n 结尾,这样浏览器或客户端解析器就知道一个完整的文本块已经结束了。

三、GLM 模型的流式返回机制解析

智谱 GLM 系列模型(如 GLM-3-TurboGLM-4)的 API 设计非常贴近开发者习惯。当你把请求参数中的 stream 设置为 true 时,GLM 服务器就会开启流式响应模式。

此时,接口返回的不再是单个完整的 JSON 对象,而是一连串的 Chunk(数据块)。每一个 Chunk 都包含着模型刚刚生成的增量内容。你会观察到这类特殊的响应头信息,标志着数据流的开启:

在 GLM 的流式数据流中,除了包含每次吐出的文本片段,还会在最后几个 Chunk 里带上本次对话消耗的 Token 统计信息。当模型完全生成完毕后,服务器会发送一个特殊的结束标志(通常是 data: [DONE]),随后关闭这次 HTTP 连接。

四、实战演练:用 Python 处理 GLM 流数据

理论说完了,我们来看看在实际代码中怎么消费 GLM 的流式接口。由于大模型厂商基本都兼容 OpenAI 的 SDK 格式,智谱也提供了非常便捷的调用方式。核心在于使用循环去不断读取数据块,并捕获结束信号

下面是一段使用 Python 和 SSE 机制解析大模型流式输出的通用伪代码/示例:

import requests
import json

# GLM 流式请求示例 (伪接口地址,请替换为实际的智谱 API 地址)
url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
headers = {
    "Authorization": "Bearer YOUR_ZHIPU_API_KEY",
    "Content-Type": "application/json"
}
payload = {
    "model": "glm-4",
    "messages": [{"role": "user", "content": "请用100字解释什么是量子纠缠"}],
    "stream": True  # 关键:开启流式输出
}

# 发送流式请求
response = requests.post(url, headers=headers, json=payload, stream=True)

print("GLM 正在回复:", end="", flush=True)

# 逐行读取 SSE 数据流
for line in response.iter_lines():
    if line:
        # GLM 返回的格式通常是 b'data: {...}'
        decoded_line = line.decode('utf-8')
        
        # 确保是 SSE 的 data 字段
        if decoded_line.startswith('data:'):
            data_str = decoded_line.split('data:', 1)[1].strip()
            
            # 捕获结束信号
            if data_str == "[DONE]":
                print("\n[流式输出结束]")
                break
                
            # 解析 JSON 并提取增量文本
            chunk = json.loads(data_str)
            delta_content = chunk['choices'][0]['delta'].get('content', '')
            
            # 实时打印到控制台,并强制刷新缓冲区
            print(delta_content, end="", flush=True)

这段代码的核心逻辑有三步:首先在请求体中设置 stream=True;其次利用 response.iter_lines() 按行迭代读取 TCP 连接中的数据流;最后过滤出 data: 字段并解析 JSON,遇到 [DONE] 则主动跳出循环。

五、避坑指南:流式开发的细节与心得

在把玩 GLM 流式输出时,有一些细节如果没处理好,很容易导致程序崩溃或体验下降。结合我个人的踩坑经验,总结了以下几点注意事项:

提示:在处理增量数据时,大模型流式返回的字段往往和全量返回不同,一定要仔细阅读官方文档中的字段差异!

大模型的流式输出虽然让前后端的处理逻辑稍微复杂了一些,但它带来的是交互体验质的飞跃。真正理解 SSE 协议和流式数据的拼接处理,是每个 AI 应用开发者走向进阶的必经之路。