本地 Ollama 0.3.12 跑 qwen2.5:7b,用 requests 调 /api/chat 且 stream 设成 true,返回是一行一个 JSON 对象,代码里 response.json() 直接抛 JSONDecodeError 提示 Extra data,改成 iter_lines 又在最后一行空串上报 Expecting value,关掉 stream 能拿到单个对象但首字要等六秒。

排查方向其实有限,先把可能性排一排。流式响应是 NDJSON 整体解析只能吃到第一个对象;iter_lines 末尾的空行与心跳行也被当成 JSON;结束标志是 done 为 true 的那一行 不是连接关闭;content 分片返回 需要自己按顺序累加。

动手时先用 curl 看原始返回是不是一行一个对象

curl -s http://127.0.0.1:11434/api/chat -d '{"model":"qwen2.5:7b","messages":[{"role":"user","content":"hi"}],"stream":true}'

接着按行解析并跳过空行

python -c "import json;print([json.loads(l)['message']['content'] for l in open('out.ndjson') if l.strip()])"

累加分片直到 done 为 true 再收尾

python ollama_stream.py --url http://127.0.0.1:11434/api/chat --model qwen2.5:7b

末了把首 token 延迟和总耗时写进日志

python ollama_stream.py --model qwen2.5:7b --metrics ttft

同事提醒我看一下最近一次发布,时间点确实对得上。

命令输出我都留了记录,免得改完记不清原来的值。

改完先别撤,确认这几项。流式内容逐字打印,拼接结果与非流式完全一致,日志里 TTFT 稳定在 400 毫秒左右。

容易忽略的是,Ollama 的流式是 NDJSON 不是 SSE,两个解析套路别混用,done 为 true 之后不会再有新行。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189