Error [json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)]

Ollama で `think=false` を送ると `format`(JSON Schema)が無視される

FIX SUMMARY verified
Applies when
ollama/ollama:0.31.1ollama /api/chat with think=false + format (JSON Schema) on a thinking model (qwen3.5:9b), CPU

Verified: reproduced in ollama/ollama:0.31.1, then the json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) signature was gone after the fix (exit 0).

Ollama の構造化出力は、format に JSON Schema を渡すとモデルの出力をその形に制約します。ところがサーバの版によっては、think=false を一緒に送った瞬間にこの制約が外れます。モデルはプロンプトどおりの自然文を返し、受け取った側の json.loads() が落ちる、という壊れ方です。

同じスクリプトが、Ollama サーバの版を上げるだけで通ります。直すのはクライアントではなくサーバです。

出るエラー

Python から呼んでいるなら、多くはパース側の例外として現れます。

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

例外を捕まえて表示している場合、見えるのはメッセージ部分だけです。

Expecting value: line 1 column 1 (char 0)

例外にならず、「JSON のはずが英語の文章で返ってくる」という形で気づくこともあります。実際に返ってきたのは次の文字列でした。

The weather is okay today, with a total of seven sunny hours expected!

結論

Ollama サーバを 0.31.2 以上に上げてください。 クライアントのコードは変更不要です。

サーバをコンテナで動かしているなら、イメージのタグを上げて作り直す手順です。11434 番は既存のサーバが使っているので、先に止めないとポート衝突で失敗します。

docker rm -f ollama                       # 稼働中の旧サーバを止める(名前は docker ps で確認)
docker run -d --name ollama \
  -v ollama:/root/.ollama \
  -p 127.0.0.1:11434:11434 \
  ollama/ollama:0.31.2

-p にホスト側 IP を書かないと Docker は全インターフェース(0.0.0.0)へ公開します。ローカルで使うだけなら 127.0.0.1: を付けて loopback に閉じてください。モデルは名前付き volume ollama に残るので、再ダウンロードは要りません。

Docker Compose なら image: のタグを ollama/ollama:0.31.2 に書き換えて docker compose up -d で作り直します。上がったら curl http://127.0.0.1:11434/api/version で版を確かめてください。

再現するコード

think=falseformat を同時に送るだけで再現します。ここでは schema に反する出力をモデルに促すため、プロンプトで「JSON を使うな」と指示しました。format が守られていれば schema が勝ち、無視されればプロンプトが勝つ、という切り分けです。

import json

from ollama import Client

SCHEMA = {
    "type": "object",
    "properties": {
        "answer": {"type": "string", "enum": ["ok"]},
        "count": {"type": "integer", "enum": [7]},
    },
    "required": ["answer", "count"],
    "additionalProperties": False,
}

PROMPT = (
    "Do not use JSON. Answer as one friendly English sentence. "
    "The answer value should be ok and the count should be 7."
)

client = Client(host="http://127.0.0.1:11434")
resp = client.chat(
    model="qwen3.5:9b",
    messages=[{"role": "user", "content": PROMPT}],
    format=SCHEMA,
    think=False,
    options={"temperature": 0, "num_predict": 64, "num_ctx": 2048},
)

content = resp.message.content
print("raw content:", repr(content))

data = json.loads(content)
assert data == {"answer": "ok", "count": 7}, data
print("FORMAT_OK", data)

ollama/ollama:0.31.1 のサーバに対して実行すると、raw content に自然文が入り、次の行の json.loads()JSONDecodeError を送出して終了コード 1 になります。ollama/ollama:0.31.2 に向ければ、同じスクリプトが {"answer": "ok", "count": 7} を出して終了コード 0 で終わります。

プロンプトの敵対性は再現の条件ではありません。「JSON を使うな」を外した中立な指示(The answer value should be ok and the count should be 7.)でも、0.31.1 は schema を無視して文章を返しました(num_predict: 64 で打ち切られたため末尾は途中で切れています)。

Understood! I'll ensure:  
- The **answer value** is reasonable/acceptable ("ok").  
- The **count** equals exactly **7**.  

版の境界

errfix の検証ハーネスで実測した2点です。モデルもスクリプトも固定し、入れ替えたのはサーバの版だけです。

Ollama サーバthink=false + format返ってくる message.content
ollama/ollama:0.31.1schema が無視される自然文
ollama/ollama:0.31.2schema どおり{ "answer": "ok", "count": 7 }

上流でも境界がそのまま追えます。qwen3.5 系での報告が issue #14645(報告版は 0.17.6)、修正が PR #15901、そのコミット 892e7f6v0.31.1…v0.31.2 の差分に含まれます。v0.31.2 のリリースノートの1行が指しているのも、この修正です。

Fixed structured output for thinking models when thinking is disabled

つまり不具合は 0.31.1 で急に生えたものではなく、少なくとも 0.17.6 の報告まで遡ります。古いサーバを使い続けている環境ほど踏みます。

この不具合は、手元では出ないのに古い推論サーバに向けたときだけ出ます。クライアントライブラリ(ollama パッケージ)の版を上げても直りませんし、pip freeze にも痕跡が残りません。修正が入ったのはサーバの server/routes.go だからです。Docker Compose でタグを固定したまま放置している環境や、チームで共有している推論サーバだけが落ちる、という形になりやすいところです。

temperature: 0 で測っており、同じ環境の3回はすべて同じ出力でした。あらゆる入力・あらゆる環境で同じ文字列が返るという意味ではありません。判定に使うのは文面の一致ではなく、JSON としてパースでき schema に合うかどうかです。

なぜ起きるか

format の制約は、thinking 対応のモデルではすぐには適用されません。サーバは thinking の出力から通常の出力へ切り替わる瞬間を合図にして、そこから制約をかけ始めます。think=false を送るとモデルは thinking をまったく出さないので、その合図が一度も来ません。結果として format は最後まで適用されないままになります。

0.31.1 の server/routes.go には、この即時適用が gemma4 だけの暫定対応として書かれていました。

// TODO(parthsareen): temporary fix for https://github.com/ollama/ollama/issues/15260.
forceImmediate := m.Config.Parser == "gemma4" && req.Think != nil && !req.Think.Bool()

PR #15901 はこれを、thinking に対応したパーサ全般へ広げます。

forceImmediate := builtinParser != nil && builtinParser.HasThinkingSupport() && req.Think != nil && !req.Think.Bool()

qwen3.5 はこの一般化を待っていた側でした。チャットテンプレートを書き換えても直りません。 判定しているのはサーバであって、モデル側のテンプレートではないからです。

どのモデルで出るか

thinking に対応したパーサを持つモデルだけが、この経路に入ります。ハーネスで再検証したのは qwen3.5:9b の1点で、残りは GPU ホストでの参考実測です。

モデル0.31.1 での think=false + format理由
qwen3.5:9bschema を無視して自然文(ハーネスで再検証)一般化を待っていた側
qwen3.6:35b-a3bschema を無視して自然文(参考実測)同上
qwen3:4b / 8b / 14b正しい JSON(参考実測)thinking の経路に入らない
gemma4:26b正しい JSON(参考実測)上の暫定対応で先に直っていた

同じ Qwen でも qwen3 系では出ず、qwen3.5qwen3.6 で出ます。gemma4 が無事なのはモデルが優れているからではなく、gemma4 だけを名指しする暫定対応が先に入っていたからです。0.31.2 の一般化で、この差はなくなりました。

0.31.1 に留まる場合

think パラメータを送らないという回避策があります。false を明示しなければ format は適用されます。上流の issue #15260 にも同じことが書かれており、errfix でも qwen3.5:9b で1回だけ確認しました(num_predict の上限を外した状態で {"answer": "ok", "count": 7} が返り、done_reasonstop)。

引き換えは thinking のコストです。同じ観測で thinking は 31,234 文字まで伸び、CPU 推論では応答まで約20分かかりました。この長さはモデルとプロンプトに強く依存するので、相場ではなく最も膨らんだ一例として読んでください。think=false はまさにこのコストを避けるための指定なので、回避策として万能ではありません。

そして生成枠を絞ったままでは成立しませんnum_predict を 64 や 1024 にすると thinking がその枠を使い切り、content は空文字のまま done_reason: length で返ります。回避策を試したつもりが、別の壊れ方を見ているだけです。かといって num_predict: -1(無制限)を共有サーバで常用すると、1リクエストが長時間 CPU や GPU を占有します。採るなら、クライアント側のタイムアウトと同時実行数を決めたうえで、有限の上限をモデルごとに測って選んでください。本番や共有環境では、回避策よりサーバの版上げを優先するほうが安全です。

なお chat_template_kwargs{"think": false, "chat_template_kwargs": {"enable_thinking": false}})は回避策になりません。これは llama.cpp などのサーバ側オプションで、Ollama の /api/chat にはこのパラメータ自体が存在しません(0.31.1 の api/types.go にあるのは formatthink です)。送っても黙って無視されるので、think=false を単独で送ったときと結果は変わりません。

切り分け

content が空文字で返る。 thinking が生成枠を使い切っている可能性があります。num_predict を絞ったまま thinking を有効にすると、content に一文字も到達しないまま done_reason: length で返ります。これは 0.31.1 でも 0.31.2 でも同じように起きるので、この症状だけを見て版境界の不具合と判断しないでください。レスポンスの thinking の長さと done_reason を見れば切り分けられます。0.31.2 に上げても空文字が続くなら、見るべきは版ではなく生成枠です。

format を渡していないのに JSON が返ってこない。 それは構造化出力ではなくプロンプトの問題で、この記事の対象外です。

/v1/ の OpenAI 互換エンドポイントを使っている。 errfix はこちらを実測していません。ただし 0.31.1 の openai/openai.go を読むと、response_format の JSON Schema は内部の Format へ、reasoning_effort は内部の Think へ変換され、/api/chat と同じリクエストに合流します。同じ症状が出る可能性があります。その場合も直すのはサーバの版です。

クライアント側の版を上げた。 修正はサーバの server/routes.go に入りました(PR #15901)。クライアントを更新しても症状は変わりません。確認すべきなのはサーバの /api/version が返す値です。

検証環境

  • ollama/ollama:0.31.1(再現)/ollama/ollama:0.31.2(消滅)
  • モデル qwen3.5:9b、GPU 無し(CPU 推論)
  • Python クライアント ollama==0.6.2
  • リクエスト POST /api/chatstream: falseoptions: {"temperature": 0, "num_predict": 64, "num_ctx": 2048}

再現から修正までは errfix の検証ハーネスが機械的に確認しています。再現時にシグネチャが出ること、修正後に消えること、終了コードが 1 から 0 に変わることの3点です。上流の版・PR・機構の記述は harness の保証外で、GitHub の issue・PR・タグ差分・ソースで裏を取っています。

検証(machine-verified)

この修正は ollama/ollama:0.31.1 のバージョン固定コンテナ内で再現し、修正後に json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/ollama-think-format-ignored
● reproduce json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) present ✓
● apply fix exit 0
● re-run json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) gone ✓
PASS verified · ollama/ollama:0.31.1 · signature gone