首页 / 文章 / curl 调用流式接口:SSE 返回怎么读

curl 调用流式接口:SSE 返回怎么读

用 curl 调二五云API 的流式接口:-N 为什么要加、data: 行和 [DONE] 怎么处理、用 sed 加 jq 抽出正文,以及代理缓冲、PowerShell 别名这些常见坑。

一条能跑的 curl

bash
curl -N -sS https://ai.idcidiot.com/v1/chat/completions \
  -H "Authorization: Bearer $YUN25_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "model": "glm-5.3-flash",
    "stream": true,
    "messages": [{"role": "user", "content": "写一个 bash 函数,列出目录里最大的 5 个文件"}]
  }'

-N 等于 --no-buffer,关掉 curl 自己的输出缓冲。不加这个参数,返回会被攒在缓冲区里,看起来像卡住,其实数据早就到了。-sS 隐藏进度条但保留错误信息,脚本里比默认输出干净。Accept 头不是必须的,写上便于中间层判断这是流式请求。Windows 的 PowerShell 里 curl 是 Invoke-WebRequest 的别名,参数对不上,要显式写 curl.exe。

返回长什么样

正常输出是一串以 data: 开头的行,事件之间用空行分隔:

text
data: {"id":"chatcmpl-8f2","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"可"},"finish_reason":null}]}

data: {"id":"chatcmpl-8f2","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"以"},"finish_reason":null}]}

data: [DONE]

每一行是一块增量,delta.content 拼起来才是完整回答。第一块通常只带 delta.role,没有正文,取值时允许空。data: 后面有一个空格,解析要连空格一起去掉;[DONE] 不是 JSON,按字符串比较。中间可能夹着以 : 开头的注释行,那是心跳,跳过即可。

每块都带 id、object、created、model 和 choices。object 固定是 chat.completion.chunk,可以用它确认对面返回的确实是流式响应,而不是一段普通 JSON。choices[0].index 在 n 大于 1 时区分候选分支,单候选时永远是 0。

函数调用的流式块里 delta.tool_calls 会分片到达,要按 index 把参数拼起来,比纯文本流麻烦。只想验证连通性的话,先用纯文本提示词,别一上来就测工具调用。

把 content 抽出来

bash
curl -sN https://ai.idcidiot.com/v1/chat/completions \
  -H "Authorization: Bearer $YUN25_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.3-flash","stream":true,"messages":[{"role":"user","content":"用一句话解释 TCP 三次握手"}]}' \
| sed -n 's/^data: //p' \
| while IFS= read -r line; do
    [ "$line" = "[DONE]" ] && break
    printf '%s' "$line" | jq -r '.choices[0].delta.content // empty'
  done

sed 去掉前缀,jq 取字段,// empty 让 null 和缺失字段都输出空字符串。没有 jq 就装一个,用 cut 或字符串截取很容易被正文里的引号打乱。

想同时拿 token 统计,在请求体里加 "stream_options": {"include_usage": true},末尾一块会带 usage,前面所有块的统计都是 null。按输出逐字计费的场景,这个字段比事后估算准。

自己写客户端要注意什么

用程序消费 SSE 时,三件事容易做错。按行读取而不是按字节读,一个网络包里可能只有半行,缓冲区要一直读到换行为止。每个事件以空行结束,也就是连续两个换行;只读到一行就开始解析,JSON 会被截断。连接要保持长,不少 HTTP 客户端默认在首包后关闭,需要显式关掉空闲超时。

如果返回头是 Content-Type: application/json 而不是 text/event-stream,说明请求没被当成流式,多半是 body 里少了 stream 字段,或者打到了非流式端点。先看响应头,再看内容,比逐行猜快。

断线重连时服务端不会从断点续传,只能整段重发。长回答在客户端做超时兜底,比反复重连划算,重连几次的 token 就够再生成一遍了。

常见坑

  • 输出一次性刷出来:代理或 CDN 缓冲了响应,关掉缓冲或换直连。
  • 只收到 [DONE]:响应里没有正文,检查 max_tokens 是不是 0,或者模型直接返回了空内容。
  • 400 且提示 JSON 解析失败:-d 里的引号被 shell 吃掉,用单引号包住整个 JSON。
  • 中文乱码:终端不是 UTF-8,export LANG=C.UTF-8 或换终端。
  • 请求挂住不返回:加 --max-time 120 和 --connect-timeout 10。
  • 管道里 jq 报错:某一行不是合法 JSON,先打印原始行看看是不是心跳混进来了。
  • 加了 -N 还是卡:换 --http1.1 试一次,个别代理对 HTTP/2 的流式转发支持不好。

成本

glm-5.3-flash 的 model_ratio 是 0.4,官渠国模分组 0.2 倍,输入价 = 0.4 × 2 × 0.2 = $0.16/百万 token,输出按 completion_ratio 3.5 折算为 $0.56/百万 token。100 万输入加 20 万输出,合计 $0.16 + $0.112 = $0.27。

写脚本批量跑时,把 model 和提示词抽成变量,别在循环里拼长 JSON,引号转义出错很难查。偶尔手测用 curl 最快,接进业务代码还是交给 SDK 处理分帧和重连更省事。倍率表在 价格页,模型清单在 /model/。把上面那条命令存成 shell 函数,下次换提示词直接调用。

用 二五云API 跑通这些模型

OpenAI 兼容接口,一个 Key 调用 GPT、Claude、Gemini、DeepSeek、Kimi 等模型,按量计费。

免费注册查看模型价格

相关文章