AI活用

OllamaのStructured OutputsでComfyUI設定JSONを作る|Schema検証付き

OllamaのStructured Outputsはformatにスキーマを渡し応答の形を保証しますが、値域や意味は保証しません。ComfyUI向けの最小Schema設計、受信後の再検証、ノードIDを隠すMapping層、Preset方式、自然文20件の検証手順を解説します。

この記事の目次
  1. 結論:Schemaで「形」を保証し、値域と意味はこちらで検証し、ノードには自分で差す
  2. なぜSchemaが必要か
  3. 最小Schemaをどう設計するか
  4. Schema付きでどう依頼するか
  5. 受信後に何を再検証するか
  6. 自然文20件で検証する
  7. Workflowへどう安全に差し替えるか
  8. Schemaを大きくしすぎないためにどうするか
  9. よくある質問
  10. まとめ

OllamaのStructured Outputsは、/api/chatformatにJSON Schemaを渡すと、応答をそのSchemaに沿ったJSONに強制する機能です。自然文から壊れにくいComfyUI設定JSONを作るには、Schemaをprompt・seed・width・height・presetなど数項目に絞り、受信後に値域と意味を再検証し、workflowのノードIDには触れさせないMapping層を置きます。

情報確認日:2026年8月22日(日本時間)

結論:Schemaで「形」を保証し、値域と意味はこちらで検証し、ノードには自分で差す

責任範囲

  • 「JSONらしい文字列」と「Schemaで構造が保証されたJSON」の違いを説明する
  • ComfyUI用の最小Schema(5項目)を設計し、formatに渡す現行の書き方を示す
  • 型が合っていても必要な値域・意味の再検証をJavaScriptで示す
  • LLMの出力をノードIDに変換するMapping層と、Preset方式を提案する
  • 自然文20件で「parseは通るが検証で落ちる」件数を記録する手順を載せる
  • Tool Callingの実行フロー、Ollamaの基本操作、RAGは扱わない

基本の連携はOllamaとComfyUIを連携する方法、LLMに「呼ぶかどうか」を判断させる構成はOllamaのTool CallingでComfyUIを実行するで扱っています。この記事は「必ずJSONを作らせる」場面のSchema設計に絞ります。

スポンサーリンク

なぜSchemaが必要か

「JSONで答えて」とpromptに書くだけでも、多くの場合はJSONらしい文字列が返りますが、構造の保証ではありません。前後に説明文が付く、キー名が揺れる、数値が文字列になるといった崩れは頻度が低くても起き、アプリはその「たまに」で落ちます。

観点 promptで「JSONで」と頼む formatにSchemaを渡す
構造 モデルの善意に依存 Schemaに沿った形に制約される
キー名 揺れる(Promptprompt_text Schemaのpropertiesに固定
"1024"1024が混在し得る integerなら数値
値域・意味 保証なし 保証なし。別に検証が要る

表の最後の行が要点です。Schemaが守るのは「形」までで、「promptが空文字でないか」「存在しないpresetを指していないか」は守りません。形の保証と意味の検証を分けて考えます。

最小Schemaをどう設計するか

Schemaは「workflowの中で差し替える値」だけにします。ComfyUIのworkflow JSONをまるごと表すSchemaは作りません。5項目あれば大半の用途は足ります。

{
  "type": "object",
  "required": ["prompt", "preset"],
  "properties": {
    "prompt":   { "type": "string",  "description": "English positive prompt" },
    "negative": { "type": "string",  "description": "English negative prompt. Empty string if none" },
    "preset":   { "type": "string",  "enum": ["portrait", "landscape", "square"] },
    "seed":     { "type": "integer", "description": "-1 for random" },
    "style":    { "type": "string",  "enum": ["photo", "illustration", "watercolor"] }
  }
}

width・heightを数値で持たせずpresetの列挙にしています。数値だと「1023」や「8192」が返る余地が残りますが、列挙なら選択肢の外が構造上ありません。styleも同様に、checkpoint名を直接返させず、アプリ側で対応表を引く名前だけを返させます。

列挙にできるものは列挙にする:自由値を返させるのはpromptとseedだけに留めると、次の章の再検証が短く済みます。

Schema付きでどう依頼するか

公式ドキュメント(2026年8月時点)では、/api/chatformatパラメータにJSON Schemaオブジェクトを渡します。文字列"json"を渡す簡易モードもありますが、構造を固定したいならSchemaを渡します。推奨事項として、temperatureを下げる(例:0)、Schemaをpromptにも文字列で入れる、の2点が挙げられています。

// 確認日: 2026-08-22 / Ollama Structured Outputs の format パラメータに基づく
const SCHEMA = { /* 上のSchema */ };

async function toSettings(userText) {
  const res = await fetch("http://127.0.0.1:11434/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "qwen3",
      stream: false,
      format: SCHEMA,
      options: { temperature: 0 },
      messages: [
        { role: "system", content: "Convert the request into image settings. Return JSON matching this schema: " + JSON.stringify(SCHEMA) },
        { role: "user", content: userText },
      ],
    }),
  });
  if (!res.ok) throw new Error(`ollama ${res.status}`);
  const { message } = await res.json();
  return JSON.parse(message.content);   // 構造はSchemaで保証。意味はまだ未検証
}

message.contentは文字列なのでJSON.parseが必要です。例外は稀ですがゼロではないため、try-catchで「parse失敗」として記録してください。JavaScriptではZod、PythonではPydanticでSchemaを定義し、同じ定義を検証にも使う方法が公式で案内されています。

受信後に何を再検証するか

parseが通った時点で保証されているのは「キーと型」だけです。次の検証を必ず挟みます。

const PRESETS = { portrait: [832, 1216], landscape: [1216, 832], square: [1024, 1024] };
const STYLES  = { photo: "base.safetensors", illustration: "illust.safetensors", watercolor: "base.safetensors" };
const BANNED  = [/\bnsfw\b/i, /\bnude\b/i];           // 用途に応じて管理

function validateSettings(s) {
  const errors = [];
  if (typeof s.prompt !== "string" || !s.prompt.trim()) errors.push("prompt empty");
  if (s.prompt && s.prompt.length > 400) errors.push("prompt too long");
  if (BANNED.some((re) => re.test(s.prompt ?? ""))) errors.push("banned word");
  if (!(s.preset in PRESETS)) errors.push(`preset ${s.preset}`);
  if (s.style !== undefined && !(s.style in STYLES)) errors.push(`style ${s.style}`);
  const seed = Number.isInteger(s.seed) ? s.seed : -1;
  if (seed < -1 || seed > 2 ** 32 - 1) errors.push("seed range");
  return { ok: errors.length === 0, errors, value: { ...s, seed, negative: s.negative ?? "" } };
}

型が合っていても落とすべき例は、空文字のprompt、負の大きなseedenumにないはずの値(制約が効かないモデルで起こり得る)、禁止語を含むpromptです。検証を省くと、これらがそのままworkflowに入ります。

自然文20件で検証する

設計の良し悪しは、自分の用途に近い日本語の依頼を20件用意し、「parseが通ったか」「検証が通ったか」「落ちた理由」を1件ずつ記録すると分かります。結果は環境とモデルで変わるため、この記事では表を空欄のまま載せます。

No. 依頼文 parse 検証 落ちた理由 対処(Schema/prompt/検証)
1 「夕焼けの海辺を写真っぽく、縦長で」
2 「さっきと同じ構図で色違い」(文脈なし)
3 「8Kで超高解像度に」
20

見るべきは「parse成功なのに検証で落ちた」件数です。これが多ければSchemaの形は正しく、意味の指示(systemメッセージやdescription)が足りないことを示します。逆にparse自体が落ちるなら、モデルの対応状況かSchemaの複雑さを疑います。

Workflowへどう安全に差し替えるか

LLMにノードIDを扱わせないために、検証済みの設定をworkflow JSONに書き込む層を1か所に分離します。ノードIDを知っているのはこの層だけです。

// ノードIDは自分のworkflow(Export (API))に合わせる
const NODE = { positive: "6", negative: "7", latent: "5", sampler: "3", checkpoint: "4" };

function applyToWorkflow(base, v) {
  const wf = structuredClone(base);
  wf[NODE.positive].inputs.text = v.prompt;
  wf[NODE.negative].inputs.text = v.negative;
  [wf[NODE.latent].inputs.width, wf[NODE.latent].inputs.height] = PRESETS[v.preset];
  wf[NODE.sampler].inputs.seed = v.seed >= 0 ? v.seed : Math.floor(Math.random() * 2 ** 32);
  if (v.style) wf[NODE.checkpoint].inputs.ckpt_name = STYLES[v.style];
  return wf;   // これを POST /prompt の body.prompt に入れる
}

この層があると、workflowを作り直してノードIDが変わっても、直すのはNODEの対応表だけで、LLM側のSchemaとpromptは変わりません。LLMにノードIDを返させる設計では、workflowを変えるたびにSchemaとモデルの挙動を再確認することになります。

書き込む先を限定する:Mapping層が触るのはtextwidthheightseedckpt_nameのような値だけにし、ノードの追加・削除・接続の変更はしません。ckpt_nameも対応表にある名前しか入らないため、存在しないファイル名がworkflowに入る経路はありません。

Schemaを大きくしすぎないためにどうするか

「samplerも」「stepsも」「LoRAの強度も」と足していくと、検証が追いつかなくなり、出力も不安定になります。代わりにPreset方式を使います。

Workflow全構造をSchemaにする

  • ノード・接続・全パラメータを表す巨大なSchema
  • モデルが存在しないノードや無効な接続を作り得る
  • 検証がworkflowの再実装に近づく

Preset方式(推奨)

  • 「誰が見ても意味が分かる名前」の列挙だけ返させる
  • 名前→実値の対応表はアプリが持ち、検証はin判定で済む
  • 新しい組み合わせが欲しければPresetを1つ足す

目安として、自由値が3つ、列挙が3つを超えたら、別Schemaに分けるかPresetに畳めないかを考えてください。数値は仮置きです。スタイル規約をRAGで参照させる方法や、n8nで同じ変換を組む方法は別記事で扱います。

よくある質問

format: "json"とSchema指定はどう違いますか?

"json"は「有効なJSONであること」だけを求める簡易モードで、キー名や型は固定されません。アプリで使うならSchemaオブジェクトを渡し、その上で再検証してください。

Schemaにenumを書いても外れた値が返ることはありますか?

構造の制約はモデルとバージョンに依存します。公式は制約を強制すると説明していますが、アプリ側の検証を省く根拠にはしないでください。検証があれば、外れた値が返ってもworkflowには入りません。

日本語のpromptをそのままComfyUIに入れてよいですか?

多くのcheckpointは英語promptを前提に学習されています。Schemaのdescriptionで「English」と指定し、モデルに翻訳させる形にすると、ComfyUI側の変更なしで済みます。

まとめ

Structured OutputsはformatにSchemaを渡して「形」を保証する機能であり、値域と意味は保証しません。Schemaはprompt・negative・preset・seed・styleの5項目に絞り、列挙にできるものは列挙にし、受信後に空文字・値域・禁止語・対応表の有無を検証します。

検証済みの値をworkflowに書き込むMapping層を1か所に置けば、ノードIDはLLMから完全に隠せます。まず自然文20件で「parse成功・検証失敗」の件数を記録し、Schemaと指示のどちらを直すべきかを決めてください。

スポンサーリンク