Ollama 流式响应让 response.json() 报 Extra data
Ollama 原生聊天默认返回 NDJSON。需要完整结果时设置 stream=false;保留流时逐行解析,检查错误对象及 done,区别 Requests 的 stream 参数。
Python 调用 Ollama 的 /api/chat,HTTP 请求已经返回,response.json() 却抛出:
Extra data: line 2 column 1
如果响应是多行 JSON 对象,通常是客户端把流式 NDJSON 当成了一个 JSON 文档。换行并不会把多个独立对象合成数组。
下面针对 Ollama 原生 HTTP API,以服务端 0.11.4 和 Requests 2.32.5 的接口核对。Ollama Python SDK 的 chat() 默认参数与原生 HTTP 请求不同,不能直接按 SDK 的习惯猜测 HTTP 默认值。
请求与读取方式要对应
原生 /api/chat 和 /api/generate 默认流式返回。聊天片段的正文在 message.content,生成接口则使用 response。每一行有自己的 JSON 对象,最后的完成对象带 done 等信息。
先核对实际出站请求中有没有 "stream": false,尤其检查中间代理是否丢掉了这个字段。响应媒体类型及实际正文也应一起看;HTML 错误页、空正文和截断数据不能因为解码失败就统称为流式问题。
Requests 的 stream=True 只控制客户端如何读取响应,不会向 Ollama 请求关闭或开启模型流式输出。服务端协议由请求 JSON 中的 stream 控制。
只需要完整结果,显式关闭服务端流
下面的示例会向本机 Ollama 发送推理请求,模型名须换成已经准备好的聊天模型:
import requests
response = requests.post(
"http://localhost:11434/api/chat",
json={
"model": "your-installed-chat-model",
"messages": [{"role": "user", "content": "用一句话解释向量检索"}],
"stream": False,
},
timeout=(5, 120),
)
response.raise_for_status()
data = response.json()
if "error" in data:
raise RuntimeError(data["error"])
print(data["message"]["content"])
模型名是占位值,连接和读取超时也应按服务条件调整。关闭流式后响应为单个 JSON 对象;这只是读取协议的选择,不保证工具调用或回答内容正确。SDK、代理和 UI 都应遵循同一约定。
要逐段显示,就按行消费
需要保留流式输出时,逐行解析,不对整个响应调用 json():
import json
import requests
done = False
with requests.post(
"http://localhost:11434/api/chat",
json={
"model": "your-installed-chat-model",
"messages": [{"role": "user", "content": "用一句话解释向量检索"}],
"stream": True,
},
stream=True,
timeout=(5, 120),
) as response:
response.raise_for_status()
for line in response.iter_lines():
if not line:
continue
part = json.loads(line)
if "error" in part:
raise RuntimeError(part["error"])
print(part.get("message", {}).get("content", ""), end="", flush=True)
if part.get("done") is True:
done = True
break
if not done:
raise RuntimeError("stream ended before done=true")
HTTP 200 之后流内仍可能出现错误对象,因此代码同时检查 HTTP 状态和每个对象的 error。读到连接结束却没有完成标记,应按中断处理;不能把已显示的部分文本作为完整答案。
这个例子只显示普通聊天的 content,不执行 tool_calls,也不处理 thinking 字段。使用这些功能时应分别保留它们的结构,不能把任意片段拼成一段文本后直接执行。iter_lines() 也不应由多个消费者同时遍历。
如果响应正文末尾多出的其实是 curl 的状态码,处理路径不同,见 curl write-out 混入 JSON 的问题。先确认多出来的数据来自哪里,再选择关闭流或调整读取器。


