LINE予約「LUCA」の機能と料金はこちら

n8nでmax_tokens is not supportedが出る原因と直し方

n8n 実務ノート 公開

n8nでOpenAIのモデル名を新しいものに差し替えたら、それまで動いていたワークフローが400で止まる。エラー文に max_tokens is not supported with this model と出ている場面を想定して書いています。変えたのはモデル名の1か所だけなので、どこを直せばいいのか見当がつきにくい不具合です。

やっかいなのは、同じ「トークン数の上限」がノードとその設定によって3通りの名前で送られることです。どれになるかはn8n本体の実装を読めば確定できます。この記事では、400になる場所を条件つきで絞り込み、弊社で実際に踏んだ別の壊れ方も書きます。

この記事の結論

  • 自分でAPIを叩いている箇所は、max_tokens を max_completion_tokens に変えます。OpenAIの公式リファレンスは max_tokens を「This value is now deprecated in favor of max_completion_tokens, and is not compatible with o-series models.」としています。
  • 400になりうる場所は3つです。①CodeノードやHTTP Requestノードで max_tokens を書いている箇所。②OpenAIノード 1.x 系の「Analyze Image」(上限を設定していなくても max_tokens を送ります)。③OpenAI Chat Modelノードのうち、「Use Responses API」がOFFかトグルの無い古いノードで、上限を設定していて、モデル名が先頭一致の判定から外れるもの。
  • ③の判定では、gpt-5 か o+数字で始まる名前は max_completion_tokens で送られ、400になりません。トグルがONのノードは max_output_tokens を送るので、名前に関係なく起きません。
  • モデル名の一括置換はやりません。400が出ないケースでも、出力の文字列が変わって後続の書き込みが静かに落ちることがあります。

実際に起きたこと

弊社で動かしているワークフローのうち、gpt-4o を使っているものが24本、gpt-4o-mini を使っているものが32本ありました。2026年9月6日に、新しい安価なモデル gpt-5.6-luna へ移せるかを実際のワークフローで検証しました。

本番の判定プロンプト(7,971字)を5ケース流すと、gpt-4o も新しいモデル(reasoning_effort=none)も5件中5件が正解でした。API利用料は5件で $0.0813 から $0.0067 です。検証の結論は「移行する価値はある。ただしモデル名の一括置換はしない」でした。なお2026年9月7日に同じ5ケースで比べ直したときは、3回中1回が5件中4件でした。5件中5件は安定した値ではありません。

一括置換をしない理由は、max_tokens を送っている箇所が、新しいモデル名にした瞬間に400になるからです。弊社でも、送信ノードの式の中で max_tokens と temperature を名指しで送っている作りがあり、モデル名だけでなく式も書き換えました。

400にならない壊れ方も実際に起きました。Airtableへ書き込むワークフローの1本(trend-content-injector)を差し替えたところ、業種を入れる industry 欄に、選択肢に無い新しい語を出力しました。返ってきたのは INVALID_MULTIPLE_CHOICE_OPTIONS / Insufficient permissions to create new select option で、2件とも書き込みに失敗しています。それでもn8nの実行ステータスは success、ノードも success でした。ログの saved:0 を過去の実行(毎回 saved:1)と並べて、ようやく気づきました。

こうした点を1本ずつ潰し、2026年9月21日に、稼働中で gpt-4o をモデルに使うワークフローは0本になりました。

原因

API側:max_tokens はもう新しいモデル向けではない

公式リファレンスは max_completion_tokens を「An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens.」と説明しています。推論トークン(モデルが内部で使い、本文には出てこないトークン)を含めた上限がこちらです。max_tokens は前の世代の名前です。

n8n側:同じ設定が送られ方で分かれている

ここからは n8n 2.19.2(package.json に "version": "2.19.2")の実装です。同梱の @langchain/openai は "version": "1.1.3" でした。

ノード/操作 実際に送られるパラメータ 400になる条件
OpenAI Chat Model(「Use Responses API」がON) max_output_tokens なし
OpenAI Chat Model(同OFF、またはトグルの無い 1.2 以下) max_completion_tokens か max_tokens 上限を設定済みで、モデル名が先頭一致から外れるとき
OpenAIノード 1.x の「Message a Model」 max_completion_tokens なし(常に付け替え)
OpenAIノード 1.x の「Analyze Image」 max_tokens で固定 上限の設定に関係なく
OpenAIノード 2.x(既定は 2.3) max_output_tokens なし
Codeノード/HTTP Requestノード 書いたとおり max_tokens と書いていれば、新旧どのワークフローでも

Chat Modelノードは「Use Responses API」で経路が2つに分かれる

まず見るのはこのトグルです。ノードの実装にこうあります。

// LmChatOpenAi.node.js
displayName: 'Use Responses API',
name: 'responsesApiEnabled',
type: 'boolean',
default: true,
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.3 } }] } },

ノードのバージョンは version: [1, 1.1, 1.2, 1.3] で、いま画面に置けば 1.3 になります。トグルは 1.3 以上でだけ表示され、既定はONです。1.2 以下のまま残っている古いノードにはこの項目が無く、OFF として扱われます。

ONのとき、ノードはLangChainに useResponsesApi を立てて渡します。

// LmChatOpenAi.node.js(supplyData)
if (responsesApiEnabled) {
    fields.useResponsesApi = true;
}

受け取った側は、この値を見て送信先を切り替えます。

// @langchain/openai/dist/chat_models/index.js
if (this._useResponsesApi(options)) return this.responses.invocationParams(optionsWithDefaults);
return this.completions.invocationParams(optionsWithDefaults);

Responses API側の組み立てはこうです。

// @langchain/openai/dist/chat_models/responses.js
max_output_tokens: this.maxTokens === -1 ? void 0 : this.maxTokens,

ONのノードは max_tokens も max_completion_tokens も送りません。画面の「Maximum Number of Tokens」は max_output_tokens になります。モデル名は見ていないので、この経路にいる限り名前で壊れることはありません。

なお、Web検索などの組み込みツール(Built-in Tools)の項目もトグルがONのときだけ表示され、ノードがそれをLangChainへ渡すのもONのときだけです。n8nのノードとして使う限り、どちらの経路になるかはこのトグルで決まると考えて構いません。

// LmChatOpenAi.node.js(supplyData)
if (responsesApiEnabled) {
    const tools = (0, common_1.formatBuiltInTools)(this.getNodeParameter('builtInTools', itemIndex, {}));

モデル名の先頭一致はChat Completions側だけの話

トグルがOFF(または 1.2 以下)のノードは、従来どおり Chat Completions へ行きます。付け替えの判断はLangChain側です。

// @langchain/openai/dist/chat_models/completions.js
if (isReasoningModel(params.model)) params.max_completion_tokens = this.maxTokens === -1 ? void 0 : this.maxTokens;
else params.max_tokens = this.maxTokens === -1 ? void 0 : this.maxTokens;

// @langchain/openai/dist/utils/misc.js
function isReasoningModel(model) {
	if (!model) return false;
	if (/^o\d/.test(model ?? "")) return true;
	if (model.startsWith("gpt-5") && !model.startsWith("gpt-5-chat")) return true;
	return false;
}

やっているのは名前の先頭一致だけです。gpt-5 で始まる名前は max_completion_tokens、gpt-5-chat で始まる名前は除外、o+数字で始まる名前も max_completion_tokens。それ以外の名前は max_tokens で送られます。

弊社が移した gpt-5.6-luna は gpt-5 で始まるので、OFFのノードでも max_completion_tokens で送られ、400になりません。一方で gpt-6-astra のように判定から外れる名前は max_tokens で送られます。

弊社は同じ間違いを自前の検証スクリプトでもやりました。gpt-5 で始まるときだけ max_completion_tokens にしていたため、gpt-6-astra を max_tokens で叩いて400にしています。名前の先頭で分岐させる書き方は、次の世代のモデル名で外れます。

Chat Modelノードは「上限を設定したノード」だけが対象

「Maximum Number of Tokens」は default: -1 で、画面では collection 型の「Options」の中にあります。collection は「Add Option」で足さないとキー自体が存在しません。LangChain側も this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens; で、無ければ未定義のままです。値が -1 のときも未定義に落ちます。

つまりChat Modelノードで400になるのは、OFF(または古いノード)で、上限を設定していて、モデル名が判定から外れるという3つがそろったときだけです。「似たノードなのに一部だけ落ちる」ときは、この3つを順に見ます。

Analyze Image だけは設定で直せない

OpenAIノードの 1.x 系には、付け替えを無条件でやってくれる操作があります。画面のラベルは「Message a Model」です。

// vendors/OpenAi/v1/actions/text/message.operation.js
if (options.maxTokens !== undefined) {
    options.max_completion_tokens = options.maxTokens;
    delete options.maxTokens;
}

モデル名を見ていないので、どのモデルでも通ります。ところが同じ 1.x 系の「Analyze Image」は、max_tokens が焼き込まれています。

// vendors/OpenAi/v1/actions/image/analyze.operation.js
const body = {
    model,
    messages: [ ... ],
    max_tokens: options.maxTokens || 300,
};

|| 300 があるため、画面で何も設定していなくても必ず max_tokens: 300 が送られます。2.x 系は Responses API を使う実装に変わり、max_output_tokens が送られます。

OpenAIノードはバージョンで実装が切り替わり、OpenAi.node.js の defaultVersion: 2.3 が現在の既定です。1 から 1.8 までが 1.x 系、2 から 2.3 までが 2.x 系です。ノードは置いた時のバージョンのまま動き続けるので、古いワークフローに残っているものは 1.x 系で動いています。

直し方

手順1:400になりうる箇所を数える

画面を1本ずつ開いて探すのは現実的ではありません。APIで全ワークフローを取り出し、上の3つの条件で拾います。n8nのPublic APIは limit の上限が 250(既定は 100)で、それを超える分は nextCursor を辿らないと返ってきません。nextCursor はbase64の文字列で + や / が混ざることがあるので、URLエンコードしてから付けます。

KEY="(n8nのAPIキー)"
python3 - "$KEY" <<'PY'
import json, subprocess, sys, urllib.parse
key, cursor = sys.argv[1], ""
while True:
    url = "http://localhost:5678/api/v1/workflows?limit=250"
    if cursor:
        url += "&cursor=" + urllib.parse.quote(cursor, safe="")
    res = json.loads(subprocess.run(
        ["curl", "-s", url, "-H", "X-N8N-API-KEY: " + key],
        capture_output=True, text=True).stdout)
    for w in res["data"]:
        for n in w["nodes"]:
            p, v = n.get("parameters", {}), n.get("typeVersion", 1)
            opt = p.get("options", {}) if isinstance(p.get("options"), dict) else {}
            hit = ""
            if "max_tokens" in json.dumps(p, ensure_ascii=False):
                hit = "max_tokens を書いている"
            elif (n["type"].endswith(".openAi") and v < 2
                  and p.get("resource") == "image" and p.get("operation") == "analyze"):
                hit = "1.x の Analyze Image"
            elif (n["type"].endswith(".lmChatOpenAi")
                  and opt.get("maxTokens", -1) != -1
                  and (v < 1.3 or p.get("responsesApiEnabled") is False)):
                hit = "Chat Model:上限あり・OFF(モデル名を確認)"
            if hit:
                print(w["active"], w["id"], w["name"], n["name"], hit, sep="\t")
    cursor = res.get("nextCursor") or ""
    if not cursor:
        break
PY

Analyze Image は上限を設定していないと保存データに maxTokens が残りません。文字列の検索だけでは漏れるので、ノードの種類とバージョンで拾っています。Chat Modelノードの行は、モデル名が先頭一致の判定に入るかを目で確かめます。

手順2:自分で書いた箇所を書き換える

// 変更前
const body = { model: 'gpt-4o', messages, max_tokens: 800 };

// 変更後
const body = { model: '(新しいモデル名)', messages, max_completion_tokens: 800 };

ここでモデル名を見て分岐させる書き方にはしません。公式リファレンスが max_tokens を「deprecated in favor of max_completion_tokens」としているので、置き換え先は1つに決まります。HTTP Requestノードの本文を式で組み立てている場合は、式の中も同じように直します。

手順3:上限の値を小さくしない

max_completion_tokens は推論トークンを含んだ上限です。小さいと推論だけで使い切って本文が空文字で返ります。400は出ず、ステータスも成功のままです。移行のついでに絞るのは避け、前と同じ数字を置きます。

手順4:ノード側は「Use Responses API」を見る

  • Chat Modelノードで「Maximum Number of Tokens」を設定していないなら、そもそも起きません。
  • トグルがONなら max_output_tokens で送られます。モデル名を新しくしてもこの400は出ません。
  • OFFにしているノードで400が出たなら、ONに戻すのが一番短い直し方です。Codeノードへの作り直しは要りません。
  • 1.2 以下の古いノードにはトグルがありません。ノードを置き直すと 1.3 になり、既定でONになります。設定は入れ直しです。
  • 1.x 系の「Analyze Image」は固定なので、ノードを削除して置き直します。既定バージョンで作られ、max_output_tokens を送る実装になります。

トグルを切り替えると送信先のAPIそのものが変わります。400は消えますが、応答の作られ方も変わるので、切り替えたら次の節のとおり前後を比べます。

手順5:出力の文字列が変わる前提で後続を見る

400を消しただけでは終わりません。モデルが変われば出力の言葉遣いも変わります。弊社が踏んだAirtableの422は、書き込み側のオプションでは直りませんでした。typecast: true を付けても、トークンに選択肢を作る権限がないので同じです。直し方は既存の選択肢をプロンプトに列挙して、その中から選ばせることです。

Airtable側の「静かに落ちる」挙動は、Airtable filterByFormulaでリンク先のレコードIDが0件になる原因と回避策にも別のパターンを書いています。

直ったかを確かめる

実行が success になったことを合格の証拠にはできません。弊社の例では、実行ステータスもノードのステータスも success のまま、書き込みが2件とも失敗していました。モデル交代の合否は、前回実行との出力比較で測ります。

  1. 同じ入力で、前後の出力を並べる。弊社は、本番のCodeノードを直近の成功実行の入力でそのまま再生し、OpenAIへの要求だけを旧モデルと新モデルの両方へ投げて並べました。書き込みの要求は全部止めておきます。
  2. 書き込み系は「何件保存されたか」を見る。弊社が気づけたのは saved:0 と saved:1 の差だけでした。件数をログに出していないなら、先に出すようにします。
  3. 本文が空文字になっていないか見る。長さ0を弾く判定を1つ入れておきます。

差し替えは客先に出ていないワークフローから1本ずつ進め、JSONスキーマや単一選択に依存しているものは最後に回します。

それでも直らないとき

エラー文が max_tokens ではなく temperature の話になった場合。弊社が移したモデルでは、temperature を既定以外にすると400でした。

"Unsupported value: 'temperature' does not support 0.1 with this model. Only the default (1) value is supported."

弊社では、稼働中のワークフローのうち39本が temperature を渡していました。Responses API へ送る経路でも同じく400です。n8nの公式OpenAIノード(2.x)も画面の temperature をそのまま送るので、その設定を外します。

設定を直したのに送信内容が変わらない場合。Chat Modelノードの Chat Completions 側では、reasoningEffort が low / medium / high 以外だと黙って落とされます。

// LmChatOpenAi.node.js(Chat Completions 側)
if (options.reasoningEffort && ['low', 'medium', 'high'].includes(options.reasoningEffort)) {
    modelKwargs.reasoning_effort = options.reasoningEffort;
}

式で none を入れても送られません。受け付ける値はモデルによって違い、公式ガイドも「Supported values are model-dependent」としています。実際に何が送られたかは実行データで確認します。

モデル名を差し替える場所がn8nの外にある場合。外部サービスの管理APIで差し替えると、その操作で別の設定が消えることがあります。弊社ではAI電話の設定で、モデルだけを更新したつもりが中の指示文(システムプロンプト)が丸ごと消えました。APIは200を返し、モデル名も正しく更新されるので成功に見えます。差し替え後に指示文の文字数を実測してから使い始めます。

APIキーの読み込みで別のエラーが出る場合。Codeノードから環境変数を読む構成なら、n8nのCodeノードで「access to env vars denied」が出る原因と直し方のほうが原因かもしれません。

まとめ

  • 自分で書いた max_tokens は max_completion_tokens に置き換えます。公式リファレンスが置き換え先を明記しています。
  • OpenAI Chat Modelノードは「Use Responses API」で経路が2つに分かれます。ONなら max_output_tokens、OFFならモデル名の先頭一致で max_completion_tokens か max_tokens です。
  • 400になりうるのは、CodeやHTTP Requestで max_tokens を書いた箇所、1.x の Analyze Image、そして「OFFまたは古いChat Model・上限を設定済み・モデル名が判定から外れる」の3つがそろったノードです。
  • 探すときは文字列の検索だけでなく、ノードの種類とバージョンでも拾います。
  • 400が消えても終わりではありません。出力の文字列が変わって書き込みが失敗しても、n8nの実行は success のままです。前回実行との出力比較と保存件数で測ります。

参考にした公式ドキュメント

n8nの点検と構築のご相談

「モデルを新しくしたいが、どのワークフローが壊れるか分からない」といった移行前の洗い出しや、止まったまま気づかれていないワークフローの点検をお引き受けしています。

お問い合わせはこちら

この記事を書いた人

貫名 孝夫(マルタマーケティング株式会社 代表取締役)。n8nで140本を超えるワークフローを本番で動かしながら、実際に起きた不具合と直し方を記録しています。会社概要