LLM SDK

temperature を下げても答えは合いません ── LLM のつまみを読む

生成AIが書いたコードには、なんとなく temperature=0.7 が入っています。このつまみが実際に何を変えて何を変えないのかを、同じ問いを20回ずつ通した実測で並べました。答えが間違っているときに温度を下げても、同じ間違いが20回そろって出てくるだけです。

  • LLM SDK
  • temperature
  • top_p
  • max_tokens
  • finish_reason

ここを見れば気づける

  • temperature=0 を「正しい」の意味で使っている(実測: 誤答が20回中20回そろって出る)
  • temperature と top_p を同時にいじり、片方が片方を打ち消している
  • max_tokens で切られても例外は出ない。壊れた JSON が正常応答として返る
  • 思考する設定では max_tokens が本文の前に消え、空文字が成功として返る

証拠の出し方:同じ問いを temperature / top_p の組ごとに20回ずつ通し、正答数・出力の種類数・打ち切り率を数える。max_tokens は finish_reason と JSON のパース成否で見る

氷河期世代のクラウドエンジニア、terralien です。AIが書いたコードをレビューしていると、temperature=0.7 という行に一番よく出くわします。なぜ 0.7 なのかを聞いても、たぶん誰も答えられません。

そして厄介なことに、出力が間違っていたときの最初の一手も、だいたい「じゃあ temperature を下げよう」なんですよね。この記事は、それをやると何が起きるかを20回ずつ数えた記録です。

数字はすべて手元の実測です。MacBook Pro(Apple M5 Pro / 64GB / macOS 26.6.1)+ Ollama 0.32.14 + gemma4:latest(8.0B・Q4_K_M)、クライアントは Python 3.11.2。2026-08-29 に計測しました。推測値は書いていません。

用途 ── つまみが決めるのは「品質」ではなく「ばらつき」

先に結論を出します。まったく同じ問いを、温度だけ変えて20回ずつ投げた結果です。問いは「税込合計はいくらか」で、正解は 14,168 円(1,280×7 + 980×4 = 12,880、×1.1)です。

設定正答(20回中)出てきた答えの種類いちばん多かった答え
temperature=00111888(20回とも)
temperature=0.701611704(3回)
temperature=1.5020ばらばら(全部違う)

温度をどう動かしても、正答は 0 のままです。動いたのは間違い方の散らばりだけでした。

ダーツの手ブレの話に似ています。温度は「腕のブレの大きさ」を決めるつまみで、的の位置は決めません。狙いがそもそも隣の的を向いているなら、腕を固めれば固めるほど、同じ外れ方が正確に再現されるだけです。temperature=0 で20回とも 11888 が出たのは、まさにこれです。

temperature は正解率のつまみではありません。ここを取り違えたまま「0 にしたので大丈夫です」と書かれたコードは、監査を通してはいけません。

代表要素 ── 3つのつまみ

引数何をするか既定値の罠
temperature(0〜2 程度)次の1トークンを選ぶときの確率分布を、平らにするか尖らせるか。0 は「いちばん確率の高いものを必ず選ぶ」0 は「正しい」ではなく「毎回同じ」。多くの SDK の既定は 1.0 で、AIが書くコードは 0.7 を置きがち
top_p(0〜1)確率の高い順に足していって、合計が top_p を超えたところで候補を打ち切るtemperature と効き目が重なる。両方いじると片方が打ち消される(後述)
max_tokens / num_predict出力トークンの上限超えても例外は出ない。finish_reason="length" が付いた、切れた文字列が正常応答として返る

AIが書く例 ── 実際に書かせたもの

手元の Qwen3.8:27b に「議事録を要約する関数を書いてください」と頼んだら、これが返ってきました(一字も直していません)。

from openai import OpenAI

def summarize_minutes(text: str, model: str = "gpt-4o") -> str:
    client = OpenAI()
    response = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "system",
                "content": "あなたは議事録の要約専門家です。提供された議事録テキストを簡潔かつ正確に要約してください。"
            },
            {
                "role": "user",
                "content": f"以下の議事録を要約してください:\n\n{text}"
            }
        ],
        temperature=0.3
    )
    return response.choices[0].message.content

つまみは temperature=0.3 の1つだけ。max_tokens は書かれていません。そして finish_reason を誰も見ていません。

max_tokens を書かないのは、一見すると「切り詰めないので安全」に見えます。実際は逆で、モデル側の既定上限に当たったときに、同じように黙って切れます。しかも上限が何トークンかはモデルを変えるたびに変わるので、切れる長さがコードのどこにも書いていない状態になります。長い議事録が来たら、この関数は途中で終わった要約を、成功として返します。

監査① ── temperature=0 は「正しい」ではなく「毎回同じ」

さっきの表の続きです。temperature=0 は再現性のためのつまみであって、正しさのつまみではありません。

では何が効いたのか。同じ問いに、思考(reasoning)を有効にして temperature=0 のまま投げ直しました。

設定正答
temperature=0・思考なし0 / 20
temperature=0.7・思考なし0 / 20
temperature=1.5・思考なし0 / 20
temperature=0・思考あり10 / 10(全部 14168)

温度は1ミリも動かしていません。動かしたのは途中計算をさせるかどうかでした。

つまり読むべき順序はこうです。出力が間違っているとき、まず疑うのは温度ではなく、タスクがそのモデルの1発では解けない形になっていないか。ここを飛ばして温度を下げると、間違いの再現性だけが上がります。

温度が効く場面もちゃんとあります。「ブログのキャッチコピーを1つ」を10回頼んだところ、temperature=0 では10回とも同じ1文、temperature=1.0 では10回とも別の文でした。候補が欲しい用途では、温度0は道具として壊れています。 抽出・分類・要約は下げる、案出しは上げる。分けるのはタスクであって、好みではありません。

監査② ── top_p を一緒にいじると、temperature が消えます

これが2つめの罠です。AIが書いたコードには、両方が並んで置かれていることがよくあります。

設定出てきた答えの種類(20回中)いちばん多かった答え
temperature=1.5 / top_p=1.020(全部違う)―
temperature=1.5 / top_p=0.1111888(20回とも)
temperature=0 / top_p=1.0111888(20回とも)

top_p=0.1 を足した瞬間、temperature=1.5 の出力が temperature=0 とまったく同じものに戻りました。候補を上位1割に絞ってしまうと、そこにはもう最有力候補しか残っていないので、温度で分布を平らにしても選ばれるものが変わらないんです。

読むときのルールは単純です。

コードの見た目判定
temperature だけ指定○ 意図が読める
top_p だけ指定○ 意図が読める
両方に既定値でない値が入っている❌ どちらが効いているか誰にも分からない。片方を消す
「両方下げたので確実です」というコメント付き❌ 二重に絞っているだけ。一方は無意味

監査③ ── max_tokens は静かに切ります

3つめは、例外が飛ばないので気づけないやつです。同じ抽出タスクを JSON で返させ、max_tokens だけを変えて10回ずつ通しました。

max_tokensJSON としてパースできたfinish_reason="length"切れた末尾
160 / 1010 / 10{ "date": "2026年8
320 / 1010 / 10{ "date": "...", "amount": "1,284
6410 / 100 / 10―
25610 / 100 / 10―

max_tokens=16 のとき、API は HTTP 200 を返しています。例外は飛びません。response.choices[0].message.content にはちゃんと文字列が入っていて、それが { "date": "2026年8 なだけです。この文字列を json.loads() に渡した側が、遠く離れた場所で JSONDecodeError を出します。

だから読むところは1つです。

resp = client.chat.completions.create(..., max_tokens=1000)

# ★ここが無いコードは、切れた出力を正常系として下流に流す
if resp.choices[0].finish_reason == "length":
    raise ValueError("出力が max_tokens で打ち切られました")

return resp.choices[0].message.content

監査④ ── 思考する設定では、max_tokens は本文の前に消えます

最近のモデル特有の罠です。思考(reasoning)が有効なとき、max_tokens は思考ぶんも含めた上限として効きます。

max_tokens消費トークンfinish_reason思考の長さ本文
200200length262文字空文字
600258stop332文字14168
1500258stop332文字14168

max_tokens=200 のとき、200トークンぶんきっちり課金されて、返ってきた本文は空文字です。エラーではありません。「成功したリクエスト」として集計されます。

思考ありのモデルに切り替えたとたん出力が空になる、という報告が来たら、まずここを見ます。上限を上げれば直りますが、上げすぎても意味はありません。600 と 1500 で結果が同じなのは、必要なだけ使って止まっているからです。上限は蛇口の太さであって、水の量ではありません。

隣で読むもの

記事ここと繋がるところ
JSON で返ってきたから正しい、ではありません切れた JSON をどこで止めるか。finish_reason の次に見る層の話です
ストリーミング応答を読むストリーミングでは finish_reason も usage も、意識しないと手に入りません

チートシート

見たもの疑うこと直し方
temperature=0.7(根拠なし)タスクに合っているか抽出・分類は 0 付近/案出しは 1.0 付近
「間違うので温度を下げた」正解率は上がらない(実測 0/20 のまま)手順を分ける・思考を有効にする・入力を整える
temperature と top_p の両方片方が打ち消しているどちらか一方だけ残す
top_p=0.1実質 temperature=0 と同じ出力意図がそれなら温度側で書く
max_tokens があるのに finish_reason を見ていない切れた出力が正常系で流れる== "length" で弾く
思考ありなのに max_tokens が小さい本文が空で成功する思考ぶんを上乗せする
案出しなのに temperature=010回頼んで1種類しか出ない上げる
読めるようになったか、ひとつだけ確認

Q. 抽出処理の答えが間違っています。temperature を 0.7 から 0 に下げるべきですか?

正解率は変わりません。実測では 0.7 でも 0 でも正答 0/20 で、下げたことで同じ誤答が20回そろって出るようになっただけでした。下げる価値があるのは「毎回結果が違って検証できない」という別の困りごとのほうです。答えを合わせにいくなら、つまみではなく手順を疑います。

……と書いておいてなんですが、こちらも去年まで、出力が安定しないと聞けば反射で温度を下げていました。「安定した」と報告して、実は同じ間違いが安定していただけ、という可能性を今になって思い出しています。当時の関係者の方、すみませんでした。

出典