Incorrect value of “finish_reason” when streaming

当通过 Ollama 的 OpenAI 兼容流式接口调用时,finish_reason 字段被错误地返回为空字符串("")而非 OpenAI 规范要求的 "stop" 或 "length"(截断时)。优先排查 Ollama 版本是否低于修复该问题的 commit。

快速结论:当通过 Ollama 的 OpenAI 兼容流式接口调用时,finish_reason 字段被错误地返回为空字符串(””)而非 OpenAI 规范要求的 “stop” 或 “length”(截断时)。优先排查 Ollama 版本是否低于修复该问题的 commit。

适用环境:Ollama(任何版本,只要低于包含修复的提交)。涉及 HTTP 流式接口(/v1/chat/completions?stream=true)。

最快修复方案:将 Ollama 升级至包含 commit 61273a96 的版本(例如 0.1.45 之后的版本)。如果无法升级,可手动修改源码中 finish_reason 的处理逻辑(将空字符串改为 nil 或有效值)。

注意事项:该修复仅解决了 finish_reason 为空字符串的问题。后续讨论指出,在响应被 max_tokens/num_predict 截断时,finish_reason 仍可能错误地返回 “stop” 而非 “length”,该问题在此 Issue 中未完全解决,下游项目(如 LiteLLM、LangChain)仍需额外处理。

问题场景

用户使用 Ollama 的 OpenAI 兼容 API(例如 /v1/chat/completions)并开启流式(stream=true)调用,观察到每个数据块中的 finish_reason 字段始终为空字符串。这导致依赖 OpenAI 规范进行流式结束判断的客户端(如 LangChain、LiteLLM、Hermes Agent)无法正确区分正常结束(”stop”)和截断结束(”length”),引发无限续写或停止时机错误。

报错原文

data: {"id":"chatcmpl-693","object":"chat.completion.chunk","created":1715427619,"model":"mistral:latest","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Hello"},"finish_reason":""}]}

data: {"id":"chatcmpl-693","object":"chat.completion.chunk","created":1715427619,"model":"mistral:latest","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"!"},"finish_reason":""}]}
...

原因分析

Ollama 在流式输出时,对 finish_reason 字段的处理不正确:当生成还未结束时,finish_reason 应为 null(即不发送该字段或发送 null),但 Ollama 错误地将其设置为空字符串 ""。当生成结束时,finish_reason 应返回 "stop""length",但 Ollama 可能在所有 chunk 中都返回空字符串,或者在最后仍返回空字符串。

环境排查

  • 确认 Ollama 版本。运行 ollama --version 查看。
  • 确认调用方式:是否使用了流式接口(stream=True),并且是通过 /v1/chat/completions 端点(OpenAI 兼容 API)。
  • 检查响应中的 finish_reason 字段内容:"""stop" 还是 "length"
  • 确认无其他中间件(如代理)修改响应格式。

解决步骤

  1. 方案一(推荐):升级 Ollama

    将 Ollama 更新到包含修复 commit 的版本(例如 0.1.45 之后)。升级命令:

    # 如果使用官方安装脚本
    curl -fsSL https://ollama.com/install.sh | sh
    # 或手动替换二进制
  2. 方案二(开发者):手动修改源码

    在 Ollama 源码的 server/routes.go 或对应处理流式响应的位置,将 finish_reason 字段处理逻辑改为:

    FinishReason: func(reason string) *string {
        if len(reason) > 0 {
            return &reason
        }
        return nil
    }(r.DoneReason),

    然后重新编译并替换二进制。

  3. 方案三(客户端临时绕过)

    如果无法升级或修改服务端,可在客户端接收流式数据时,忽略 finish_reason 字段,转而依赖 choices[0].delta.content 是否为空以及是否有 stop/length 等信号进行判断(此方案不准确,推荐升级后使用标准判断)。

验证方法

使用 curl 或其他 HTTP 客户端发送流式请求,观察最后一次数据块的 finish_reason 是否不再为空:

curl -N -X POST http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral:latest",
    "messages": [{"role": "user", "content": "Hello"}],
    "stream": true,
    "max_tokens": 10
  }'

预期:非最后一个

参考来源

ollama/ollama #4357

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 15872

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注