Algomatic Tech Blog

Algomaticの開発チームによる Tech Blog です

LLMでJSON出力する際に気をつけていること3選

はじめに

こんにちは、Algomatic ネオセールスカンパニーで営業 AI エージェント 「アポドリ」 を開発している 末國 です。

apodori.ai

LLMを組み込んだアプリケーション開発において、JSON出力のコントロールは時に困難を伴う課題かと思います。特に複雑な処理を行おうとすると、思った通りの結果が得られないケースも少なくありません。

アポドリの開発においても、プロンプト設計には工夫を重ねてきました。

そこで本記事では、LLMアプリケーション開発で実際にプロンプトを作成する機会のある方々に向けて、アポドリ開発において個人的に気をつけていることを3つ紹介します。

(個人的お気に入りは2つ目です)

【其の一】 CoTの出力順を間違えないようにする

そもそもCoTとは?

CoT(Chain of Thought)とは、LLMに「思考過程を出力してから最終的な回答を出力する」手法です。これは、人間が問題を解く際に「まず〇〇を考えて、次に△△を考えて...」というように段階的に思考を進めるのと同様のプロセスです。

一時期流行っていた「ステップバイステップで考えて」という指示も、これに近いものです。

例えば、つるかめ算を解く際に、いきなり答えさせるのではなく、以下のような途中式を出力させることでより正確な答えを導き出すことができます:

+思考過程
+1. すべてがつるだとすると、足の数は 80 × 2 = 160本
+2. 実際の足の数は200本なので、200 - 160 = 40本多い
+3. つるとかめの足の数の差は2本なので、かめの数は 40 ÷ 2 = 20匹
+4. つるの数は 80 - 20 = 60羽

最終回答
つる:60羽
かめ:20匹

「CoTが機能する仕組み」は解明されていないようですが、私は勝手に「認知的足場を作ることで、より正確な答えを導き出す」ようなイメージで解釈しています。

複雑な問題を一度に解こうとするのではなく、段階的に情報を整理し、より正確な結論にたどり着くための道筋を作っていく感覚です。

なお、CoTのより詳細な説明や、Zero-shot CoTなどの発展的な手法については、以下の記事で詳しく解説しています。2年前の記事ですが、いまだに普遍的な内容だと思いますので合わせてご覧ください。

zenn.dev

JSON出力時にも順番を間違えないようにする

この CoTはJSON出力時にも有効な手法で、OpenAIのStructured Output機能の例としても紹介されています。

platform.openai.com

CoTにおいては、思考過程を先に、結論を後に出力させることが重要ですが、JSONスキーマを書いていると、ついこの順番を逆にしてしまうことがありました。

必ず思考過程を先に、結論を後に出力するようにしましょう。

NGな出力例

answerを先に出力しても、answerを正当化する途中式が生成されるだけです。

{
  "answer": {
    "crane": 60,
    "turtle": 20
  },
  "thought": "つるかめ算を解くために、以下の手順で考えます。\\n1. すべてがつるだとすると、足の数は 80 × 2 = 160本\\n2. 実際の足の数は200本なので、200 - 160 = 40本多い\\n3. つるとかめの足の数の差は2本なので、かめの数は 40 ÷ 2 = 20匹\\n4. つるの数は 80 - 20 = 60羽"
}

改善後の出力例

{
  "thought": "つるかめ算を解くために、以下の手順で考えます。\\n1. すべてがつるだとすると、足の数は 80 × 2 = 160本\\n2. 実際の足の数は200本なので、200 - 160 = 40本多い\\n3. つるとかめの足の数の差は2本なので、かめの数は 40 ÷ 2 = 20匹\\n4. つるの数は 80 - 20 = 60羽",
  "answer": {
    "crane": 60,
    "turtle": 20
  }
}

【其の二】 プロパティ名を詳細化して出力を制御する

JSON出力時、そのプロパティ名はある程度確実に出力させることができます。 さらに、GPT系およびGemini系のLLMは、別途JSONスキーマを与えることで、より確実なコントロールが可能です。

CoTは「認知的な足場を作っていくイメージ」と書きましたが、これを転用して「プロパティー名を認知的足場として活用する」ことができます。

具体的には、プロパティ名自体に詳細な指示を埋め込むという方法です。

例えば「簡潔な回答を求めているのに、どうしても冗長になってしまう」という状況を想定します。

出力例:Before

{
    "answer": "このホワイトペーパーでは、プロンプトエンジニアリングの全体像と実践手法を体系的に解説しています。まず、LLM(大規模言語モデル)の予測エンジンとしての仕組みを紹介し、出力トークン数やサンプリング(Temperature、Top-K/Top-P)といった出力設定の最適化方法を示します。その上で、以下の主要なプロンプト技法を順に解説しています:……"
}

こういう時、JSONのプロパティー名を変えることで、出力が安定することがあります。

出力例:Before

{
  "short_answer": "プロンプトエンジニアリングの総合ガイド"
}

上記の例では、answershort_answerに変更することで、LLMは自然と簡潔な回答を生成するようになります。

他にも応用例として

  • formal_answer: フォーマルな文体になる
  • detailed_answer: より詳細な回答になる
  • answer_in_markdown_format: マークダウン書式をより強制できる

このように、プロパティ名に詳細な指示を埋め込むことで、LLMは自然とその形式に沿った内容を生成しようとします。これは特に、プロンプトでは詳細にコントロールしきれない場合に有効な手法で、複雑な処理や、出力が長くなってきたときに使うことが多いです。

ただし、冗長なプロパティー名自体が出力品質の低下を招く可能性も否定できませんし、出力トークンの増加はコストの増加につながるため、あくまで「どうしてもコントロールできない」時に使うようにしています。

【其の三】 プロンプトが矛盾しないようにする

JSON出力を丁寧に制御しようとすると、以下の3箇所に指示を記述する必要があります:

  1. プロンプト
  2. スキーマ定義
  3. Few-shotとして与えた出力例の内容

しかし、これらの間で矛盾が生じると、LLMは混乱し、期待通りの出力が得られなくなります。

NG例(詳細度の矛盾)

  • プロンプト: 「回答は簡潔にしてください。」
  • JSON Schemaのdescription: 「内容の詳細な説明」

NG例(プロパティー不足)

  • プロンプト: 「入力を要約した後、返信を作成してください」
  • JSON Schema: replyしかプロパティーがなく、要約が出力されない

上記のNG例を改善すると、以下のようになります:

改善例(詳細度の一貫性を保つ)

  • プロンプト: 「回答は簡潔にしてください。」
  • JSON Schemaのdescription: 「内容の簡潔な説明」

改善例(必要なプロパティの追加)

  • プロンプト: 「入力を要約した後、返信を作成してください」
  • JSON Schema: 以下のようにsummaryreplyの両方を含める
{
  "summary": "ユーザー入力の要約",
  "reply": "ユーザーへの返信"
}

こういった矛盾は気づきにくく、「プロンプトを変えても出力が変わらないと思ったら、例示に引っ張られていた」という状況に陥りがちです。

まとめ

本記事では、LLMでJSON出力を行う際に気をつけていることを3つ紹介しました。

  1. CoTの出力順を間違えないようにする
  2. プロパティ名を詳細化して出力を制御する
  3. プロンプトが矛盾しないようにする

これらのテクニックを組み合わせることで、LLMからの安定した高品質なJSON出力を実現できます。ぜひ、ご自身のLLMアプリケーション開発にお役立てください。

ここまで読んでいただきありがとうございました! Algomatic では一緒に 営業 AI エージェントを育てる仲間を絶賛募集中です。

興味を持っていただいた方、ぜひお声掛けください!

jobs.algomatic.jp