TECH BLOG
メタサイト
メタサイト

GPT-4 TurboのAPI検証: json形式の注意点

目次

    はじめに

    こんにちは、メタサイトのYNです。GPT-4 Turboを業務で利用し、大量のテキストを出力するようなリクエストをしたところ、 ChatGPTが同じような返答を何度も繰り返す現象 に遭遇した。

    まるで何度も同じ話をする酔っ払いオヤジのような人間臭さを感じる。AIなのに。。。

    少しイメージしづらいと思うので、具体的な例を交えて説明する。 例えば、ChatGPTを使って、日々の献立を作成するアプリを開発することを想像してみて欲しい。メニューや摂取できる栄養素、アレルギー物質などを見やすいUIデザインで表現したい。そのため、ChatGPTの出力をベタで見せるのではなく、出力から要素を抽出し、アプリの画面に自動反映するシステムを設計する。

    ChatGPTの出力としては、

    2024/1/1(朝:○○、昼:××、夜:△△)、2024/1/2(朝:○○、昼:××、夜:△△)、2024/1/3(朝:○○、昼:××、夜:△△)、

    ・・・

    2024/XX/XX(朝:○○、昼:××、夜:△△)

    というような内容をJSON形式で構造化されたデータであれば抽出しやすい。

    しかし、実際にChatGPTで出力してみると、

    2024/1/1(朝:○○、昼:××、夜:△△)、2024/1/2(朝:○○、昼:××、夜:△△)、2024/1/3(朝:○○、昼:××、夜:△△)、

    ・・・

    2024/1/11(朝:○○、

    2024/1/1(朝:○○、昼:××、夜:△△)、2024/1/2(朝:○○、昼:××、夜:△△)、2024/1/3(朝:○○、昼:××、夜:△△)、

    ・・・

    2024/1/11(朝:○○、

    2024/1/1(朝:○○、昼:××、夜:△△)、2024/1/2(朝:○○、昼:××、夜:△△)、2024/1/3(朝:○○、昼:××、夜:△△)、

    ・・・

    というような出力となる現象が発生していた。

    この記事では、その現象を再現する検証と対処法を紹介する。

    なお、執筆時点でリリースされているgpt-4-0125-preview(GPT-4 Turbo)と過去のモデルを使った検証になるので、 将来的に解消されている可能性がある点には留意していただきたい。


    前置き

    本題に入る前に、まずはGPT-4 TurboとJSONについて簡単に触れる。

    GPT-4 Turbo

    GPT-4 Turboは、以前のモデルと比較してAI技術の進化と応用範囲の広がりを象徴するいくつかの重要な特徴と進化を遂げている。詳細は公式アナウンスや他で開設されているブログを参照して欲しいが、私は以下の点が特に影響が大きいと感じている。 (公式リファレンス)https://platform.openai.com/docs/models/overview

    コンテキストウィンドウの拡大

    GPT-4 Turboは、128kのコンテキストウィンドウを持つ。これは、単一のプロンプトで300ページのテキストに相当する。この拡大されたコンテキストウィンドウにより、より長い対話や文脈を理解できるようになった。

    費用対効果を重視した料金設定

    GPT-4 Turboは、入力トークンの価格が従来のGPT-4に比べて3分の1、出力トークンの価格が2分の1になった。これにより、ユーザーはコンテキストウィンドウの拡大をフルに活用できるようになった。

    高速化

    GPT-4 Turboは、以前のモデルに比べて高速化された。長文の処理や大規模なコンテキストにおいても、より効率的なパフォーマンスを実現できるようになった。

    JSONモード

    このモードでは、返答を必ずJSON形式で出力する。APIからの返答がJSONであれば、プログラムとの連携が容易になる。※GPT-3 Turboでも利用可能。 (公式リファレンス)https://platform.openai.com/docs/guides/text-generation/json-mode

    JSON形式出力の利点

    JSONは、データを階層的な構造で表現するためのフォーマット。例えば1日の食事のメニューの提案をChatGPTにリクエストすると、

    以下は1週間分の3食の献立の一例です:
    
    月曜日:
    朝食:トースト、スクランブルエッグ
    昼食:チキンカツ丼
    夕食:鮭のホイル焼きと野菜サラダ
    

    というような出力がされる。

    これをJSON形式で表現してみよう。

    {
      "breakfast": "トーストとスクランブルエッグ",
      "lunch": "チキンカツ丼",
      "dinner": "鮭のホイル焼きと野菜サラダ"
    }
    

    では、1日ではなく1週間の食事メニューを表現してみよう。

    {
      "monday": {
        "breakfast": "トーストと卵",
        "lunch": "チキンサラダ",
        "dinner": "野菜カレー"
      },
     "tuesday": {
        "breakfast": "ヨーグルトとフルーツ",
        "lunch": "そば",
        "dinner": "鶏の照り焼きと炒め野菜"
      }, 
    ...
    
     "friday": {
        "breakfast": "シリアルと牛乳",
        "lunch": "おにぎりと味噌汁",
        "dinner": "グリルチキンとサラダ"
      }
    }
    

    さらに複雑にしてみよう。

    品目ごとに配列にして、摂取できる栄養素加えると

    {
      "monday": {
        "breakfast": {
           "menu": ["トースト","卵"],
           "nutrients": ["炭水化物", "タンパク質", "ビタミンB群", "鉄分"]
        },
        "lunch": {
           "menu": ["チキンサラダ"],
           "nutrients": ["タンパク質", "ビタミンA", "ビタミンC", "食物繊維"]
         },
        "dinner": {
           "menu": ["野菜カレー"],
           "nutrients": ["タンパク質", "炭水化物", "ビタミンC", "食物繊維", "鉄分"]
        }
      },
    
    ...
    
     "friday": {
        "breakfast": {
          "menu": ["シリアル","牛乳"],
          "nutrients": ["炭水化物", "カルシウム", "ビタミンB群", "鉄分"]
        },
        "lunch": {
          "menu": ["おにぎり","味噌汁"],
          "nutrients": ["炭水化物", "タンパク質", "ビタミンB群", "食物繊維"]
        },
        "dinner": {
          "menu": ["グリルチキン","サラダ"],
          "nutrients": ["タンパク質", "ビタミンA", "ビタミンC", "鉄分"]
        }
      }
    }
    

    このようにいくらでも複雑にできるし、それでも可読性が高い。

    また、APIの応答をJSON形式で受け取ることで、データをより簡潔に整理でき、プログラムで特定の情報を抽出しやすくなる。ただのJSON形式でない文字列だと正規表現を使った手間のかかるプログラムを作成する必要があるが、JSON形式で上記例の金曜の昼食のメニューを抽出する場合には、下記のような簡潔なコードで抽出が可能。

    import json
    
    # JSONデータを文字列として表現
    json_string = '''
    ここはJSONデータの文字列を入力
    '''
    
    # 文字列からJSONオブジェクトにパース
    result = json.loads(json_string)
    
    # 金曜日の夕食のメニューを取得
    friday_lunch = result["friday"]["lunch"]["menu"]
    print(friday_lunch)
    # ["おにぎり","味噌汁"]と表示
    

    JSONは、多くのプログラミング言語でサポートされており、Python、JavaScript、Java、Rubyなど、さまざまな言語でJSONを簡単にパース可能。APIからのデータをJSON形式で受け取ることで、アプリケーションやウェブサイトと連携しやすくなる。

    また、他の階層的な構造で表現する形式としてはXML等が存在するが、JSONは扱いやすさ、可読性、拡張性、に優れている。 そして何よりコストに影響する文字数は少なければ少ないほど良く、簡潔に表現できるJSON形式はChatGPTと相性が良い。

    ChatGPTのAPIでJSON形式の出力を得るには

    ChatGPTでプロンプトに「出力はJSON形式で提供して」と指定してリクエストするだけでJSON形式の回答を得ることが出来る。gpt-4-turbo以前でも可能。前述のJSONモードでなくても良い。JSONモードにする場合はプロンプトでの指定に加え、response_format={"type": "json_object"}とパラメータを設定する必要がある。

    非JSONモードでのリクエスト例

    from openai import OpenAI
    
    # OpenAIのAPIキーを設定
    client = OpenAI(api_key = 'your-api-key')
    
    def gpt(content):
    # プロンプト、JSON形式の出力を指定
    messages = [
      {"role": "system", "content": "あなたは返答をすべてJSON形式で出力します。"},
      {"role": "user", "content": content},
    ]
    
    response = client.chat.completions.create(
      model="gpt-4-turbo-preview",
      messages=messages,
      temperature=0,
    )
    
    return response.choices[0].message.content
    
    result = gpt("1日分の朝昼夕食の献立を提案してください。")
    print(result)
    

    JSONモードでのリクエスト例

    from openai import OpenAI
    
    # OpenAIのAPIキーを設定
    client = OpenAI(api_key = 'your-api-key')
    
    def gpt(content):
    # プロンプト、JSON形式の出力を指定
    messages = [
      {"role": "system", "content": "あなたは返答をすべてJSON形式で出力します。"},
      {"role": "user", "content": content},
    ]
    
    response = client.chat.completions.create(
      model="gpt-4-turbo-preview",
      response_format={"type": "json_object"},  # ここでJSONモードを指定
      messages=messages,
      temperature=0,
    )
    
    return response.choices[0].message.content
    
    result = gpt("1日分の朝昼夕食の献立を提案してください。")
    print(result)
    

    ※JSONモードはプロンプトで「JSON」と明確に指定する必要がある。

    下記はそれぞれの出力。

    非JSONモードでの出力

    ```json
    {
      "朝食": {
        "主食": "全粒粉トースト",
        "副菜": "スクランブルエッグ",
        "サイド": "ヨーグルトとフルーツ",
        "飲み物": "オレンジジュース"
      },
      "昼食": {
        "主食": "チキンと野菜のラップサンド",
        "副菜": "カリカリベーコンのシーザーサラダ",
        "サイド": "スイートポテトフライ",
        "飲み物": "アイスティー"
      },
      "夕食": {
        "主食": "サーモンのハーブ焼き",
        "副菜": "キノコとほうれん草のガーリックソテー",
        "サイド": "クスクス",
        "飲み物": "白ワイン"
      }
    }
    ```
    

    JSONモードでの出力

    {
      "朝食": {
        "主食": "全粒粉トースト",
        "副菜": "スクランブルエッグ",
        "サイド": "ヨーグルトとフルーツ",
        "飲み物": "オレンジジュース"
      },
      "昼食": {
        "主食": "チキンと野菜のラップサンド",
        "副菜": "カリカリベーコンのシーザーサラダ",
        "サイド": "スイートポテトフライ",
        "飲み物": "アイスティー"
      },
      "夕食": {
        "主食": "サーモンのハーブ焼き",
        "副菜": "キノコとほうれん草のガーリックソテー",
        "サイド": "クスクス",
        "飲み物": "白ワイン"
      }
    }
    

    非JSONモードでは純粋にJSONの箇所のみが提供されるのではなく、「```json」のような不要な文字列が含まれた回答になっている。この例はまだ不要な文字列が少ない方で、プロンプトが複雑になり、指示も仔細になるほどJSON以外の文字列を含んだ回答になる傾向が強い。そのため、返答された文中からJSON部分のテキストを抽出する必要があった。またJSONの頭とお尻のみを記述し、途中を省略されることもある。 JSONモード実装によりその心配をする必要がなくなり、パース時のエラーも回避しやすくなった。


    検証:何度も同じ回答を繰り返す現象の再現

    さて、前置きが長くなったが、GPT-4 Turboで遭遇した現象について詳述する。 出力トークン数の上限に達した際、この新しいモデルは以前のモデルと異なり、同じ回答を繰り返すという現象だ。

    元々、APIの仕様として、出力トークン数の上限値(デフォルト値の4096トークン、あるいは、max_tokensパラメータで指定した出力トークン数)を超えるようなリクエストをした場合に、途中で文字切れした中途半端な出力になる。この仕様自体はGPT-4 Turbo以前のモデルでも同様だった。ただ、既存のモデルでは、プロンプトに会話の出力を沿えて再度リクエストすることで、最初の出力の続きを出力することが出来ていた。しかし、GPT-4 Turboになってから、今回紹介した現象が起きるようになった。

    以下では、max_tokensパラメータを100(出力文字数の制限で日本語のおよそ90文字に相当)に設定し、この制限を超える出力を求めるプロンプトを新旧のモデルに投げた際の違いを示し、問題の現象を確認する。

    ※あくまでプログラムからAPIリクエストを送った比較。PlayGroudではまた違った結果になることも確認できている。

    GPT-4(既存のモデル)は問題なし

    まずは既存モデルの出力から。本来はこうなるはず、という例。

    1回目出力

    {
      "Breakfast": {
          "飲み物": "ミルク",
          "主菜": "ベーコンと卵のフライ",
          "副菜1": "フレッシュフルーツ(バナナ、ブルーベリー)",
          "副菜2": "全粒粉トースト",
          "その他": "ヨーグル"
    

    見てわかる通り、中途半端な所で終わっている。

    2回目出力

     "ト(無糖)"
      },
      "Lunch": {
          "飲み物": "無糖緑茶",
          "主菜": "鶏胸肉のグリル",
          "副菜1": "もずくの酢の物",
          "副菜2": "茹でブロッコリー",
          "ごはん": "十"
    

    2回目の出力が1回目の出力の続きになっていることが確認できる。

    リクエストのpayloadのプロンプト部分は以下に示す。2回目のプロンプトは1回目の出力を追加した内容になる。

    1回目プロンプト

    [
    	{
    	  "role": "system",
    	  "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
    	},
    	{
    	  "role": "user",
    	  "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
    	}
    ]
    

    2回目プロンプト

    1回目のプロンプトに1回目の出力を追加した形になる。

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      },
      {
        "role": "assistant",
        "content": "{\n  \"Breakfast\": {\n    \"飲み物\": \"ミルク\",\n    \"主菜\": \"ベーコンと卵のフライ\",\n    \"副菜1\": \"フレッシュフルーツ(バナナ、ブルーベリー)\",\n    \"副菜2\": \"全粒粉トースト\",\n    \"その他\": \"ヨーグル"
      }
    ]
    

    GPT-4 Turbo(新しいモデル)は再現した

    次に問題の現象が観測された新しいモデル。

    1回目出力

    ```json
    {
      "朝食": {
        "主食": "全粒粉トースト (2枚)",
        "主菜": "スクランブルエッグ",
        "副菜": "サラダ(レタス、トマト、きゅうり)",
        "スープ": "野菜スープ",
        "飲み物": "オレ"
    

    2回目出力

    ```json{
      "breakfast": {
        "main_dish": "Whole wheat toast (2 slices)",
        "side_dish": "Scrambled eggs",
        "salad": "Lettuce, tomato, and cucumber salad",
        "soup": "Vegetable soup",
        "drink": "Orange juice"
      },
      "lunch": {
        "main_dish": "Grilled chicken breast with herbs",
        "side_dish": "Qu"
    

    明らかに2回目の出力は1回目の続きではない。 1回目の内容をなぜか英訳しているが、同じ出力を繰り返している。 以下にプロンプトを示すが、2回目に追加した1回目の出力部分以外は既存のプロンプトと同じことが分かるだろう。

    1回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      }
    ]
    

    2回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      },
      {
        "role": "assistant",
        "content": "```json\n{\n  \"朝食\": {\n    \"主食\": \"全粒粉トースト (2枚)\",\n    \"主菜\": \"スクランブルエッグ\",\n    \"副菜\": \"サラダ(レタス、トマト、きゅうり)\",\n    \"スープ\": \"野菜スープ\",\n    \"飲み物\": \"オレ"
      }
    ]
    

    GPT-3.5 Turbo(新しいモデル)は再現した

    さて、gpt-3.5-turboにもgpt-4-turboと同時期にjsonモードが実装されたため、こちらも検証する。

    1回目出力

    {
      "breakfast": [
        "オートミール",
        "ヨーグルト",
        "フルーツ",
        "卵"
      ],
      "lunch": [
        "サラダ",
        "鶏胸肉のグリル",
        "茶碗蒸し",
        "ごはん",
        "みそ汁"
      ],
      "dinner": [
        "サ
    

    2回目出力

    {
      "breakfast": [
        "オートミール",
        "ヨーグルト",
        "フルーツ",
        "卵"
      ],
      "lunch": [
        "サラダ",
        "鶏胸肉のグリル",
        "茶碗蒸し",
        "ごはん",
        "みそ汁"
      ],
      "dinner": [
        "サ
    

    2回目の出力は1回目の続きではない、というより全く同じになった。

    1回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      }
    ]
    

    2回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      },
      {
        "role": "assistant",
        "content": "{\n  \"breakfast\": [\n    \"オートミール\",\n    \"ヨーグルト\",\n    \"フルーツ\",\n    \"卵\"\n  ],\n  \"lunch\": [\n    \"サラダ\",\n    \"鶏胸肉のグリル\",\n    \"茶碗蒸し\",\n    \"ごはん\",\n    \"みそ汁\"\n  ],\n  \"dinner\": [\n    \"サ"
      }
    ]
    

    結果

    gpt-4-turboとgpt3.5-turboでは、既存モデルとは異なり、2回目の出力で1回目の出力の続きを出力せず、 1回目の回答とほぼ同じ内容を繰り返し出力する現象を再現できた。JSONモードでも検証したが、json部分以外の余計な文字列が無くなる以外は結果はほぼ同じだった。


    考察

    この現象から判ることは、現状ではjson形式の出力のトークン数は上限値である4096トークン以上は出力出来ないということだ。4096トークン以内に絶対に収まる用途なら問題ないとも言える。

    具体的なユースケースを考えてみよう。

    例えばディレクターからの指示書を基にしたWEB画面の制作業務での利用を想定すると、90%のところで中途半端に終わろうものなら、残り10%だとしても台無しで、修正に余計な手間がかかり、全て手作業のほうがいいよね、となりかねない。 出力の上限がある以上、そのままの運用は難しく、何等かの工夫が必要となる。

    ChatGPTの技術的背景は詳しくないが、おそらくJSONモードの実装による影響であることは容易に想像できる。リファレンスを見ると、JSONモード時の仕様として、モードの指定の他に、プロンプト文中に「json」という文字列が必要になっているのだが、JSONモード自体はjson形式の出力のリクエストではなく、json部以外の文字をtrimしたり、解析不能な文字を省く程度の機能で、LLMの演算自体に影響するものではないのだろう。

    そもそも構造化データたるJSONは、それ単体で簡潔していないと構造化データと呼べないので、「続き」という考え方自体がそぐわないのかもしれない。

    となると、将来的にもこの出力トークン数の上限が影響する現象は解消されない可能性も大きいと考える。


    対処法

    出力で4096トークンの上限を運用でカバーする方法を検討する。

    案①バッチ処理前提のリクエスト

    出力トークン数の上限値以内に収まるように、一定文字数ごと、あるいは単位ごとにリクエストするようなバッチ処理を行う。jsonモードを使用し、gpt-4-turboで検証する。

    バッチ1出力

    {
      "朝食": {
        "メイン": "ホットオートミール フレッシュフルーツとナッツトッピング",
        "サイド": "ヨーグルトとハチミツ",
        "ドリンク": "グリーンティー"
      }
    }
    

    バッチ2出力

    {
      "昼食": {
        "メイン": "鶏胸肉のグリル、アボカドとトマトのサルサ添え",
        "サイド": "キヌアと野菜のサラダ",
        "ドリンク": "フレッシュレモネード"
      }
    }
    

    バッチ3出力

    {
      "夕食": {
        "メイン": "サーモンのハーブ焼き、レモンバターソース",
        "サイド": "ロースト野菜(にんじん、じゃがいも、ブロッコリー)",
        "ドリンク": "赤ワイン"
      }
    }
    

    バッチ1プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。JSONで結果を出力します。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。1食ごとに、メイン、サイド、ドリンクの要素が必要です。まず朝食のみを提案してください。"
      }
    ]
    

    バッチ2プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。JSONで結果を出力します。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。1食ごとに、メイン、サイド、ドリンクの要素が必要です。まず朝食のみを提案してください。"
      },
      {
        "role": "assistant",
        "content": "{\n  \"朝食\": {\n    \"メイン\": \"ホットオートミール フレッシュフルーツとナッツトッピング\",\n    \"サイド\": \"ヨーグルトとハチミツ\",\n    \"ドリンク\": \"グリーンティー\"\n  }\n}"
      },
      {
        "role": "user",
        "content": "次に昼食のみを提案してください。"
      }
    ]
    

    バッチ3プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。JSONで結果を出力します。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。1食ごとに、メイン、サイド、ドリンクの要素が必要です。まず朝食のみを提案してください。"
      },
      {
        "role": "assistant",
        "content": "{\n  \"朝食\": {\n    \"メイン\": \"ホットオートミール フレッシュフルーツとナッツトッピング\",\n    \"サイド\": \"ヨーグルトとハチミツ\",\n    \"ドリンク\": \"グリーンティー\"\n  }\n}"
      },
      {
        "role": "user",
        "content": "次に昼食のみを提案してください。"
      },
      {
        "role": "assistant",
        "content": "{\n  \"昼食\": {\n    \"メイン\": \"鶏胸肉のグリル、アボカドとトマトのサルサ添え\",\n    \"サイド\": \"キヌアと野菜のサラダ\",\n    \"ドリンク\": \"フレッシュレモネード\"\n  }\n}"
      },
      {
        "role": "user",
        "content": "次に夕食のみを提案してください。"
      }
    ]
    

    判定

    成功。ただし、バッチごとのjsonが統一されるように、細かく指示が必要。

    メリットとして、ステップバイステップの処理がしやすく、途中でエラーが出たとしても、その時点から復帰すればよく、安定しやすい。セクション分割しやすいデータの処理に有効。

    デメリットとして、バッチごとにリクエストする必要があるので、処理時間やコストが大きくなる。

    案② 2回目以降はJSON形式を指定せずリクエスト

    1回目のプロンプトではJSON形式を指定したプロンプトにするが、2回目はリクエストしない。要はJSON形式を指定せずに1回目の出力からJSON形式であることを推測させ、1回目を踏襲した形で続きを出力してもらう。jsonモードは使用せず、gpt-4-turboで検証する。

    1回目出力

    ```json
    {
      "朝食": {
        "メイン": "玄米ごはん",
        "サイド": [
          "みそ汁(豆腐とわかめ)",
          "焼き魚(サバ)",
          "小松菜のおひたし"
        ],
        "ドリンク": "緑茶"
      },
      "昼
    

    2回目出力

    もちろんです。バランスの良い食事は健康の礎です。以下に1日分のバランスの良い献立を提案します。この献立は、適切な栄養素を含みながらも、季節の食材を取り入れることに注目しています。
    
    ### **
    

    1回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。結果はJSON形式で提供してください。"
      }
    ]
    

    2回目プロンプト

    [
      {
        "role": "system",
        "content": "あなたは知識豊富な栄養管理士のアシスタントです。"
      },
      {
        "role": "user",
        "content": "1日分の朝昼夕食の献立を提案してください。"
      },
      {
        "role": "assistant",
        "content": "```json\n{\n  \"朝食\": {\n    \"メイン\": \"全粒粉トースト\",\n    \"サイド\": \"スクランブルエッグ\",\n    \"フルーツ\": \"バナナ\",\n    \"飲み物\": \"カフェオレ\"\n  },\n  \"昼食\": {\n    \"メイン\": \"鶏胸肉のサラダラップ\",\n"
      }
    ]
    

    判定

    失敗。流石に都合が良すぎたか・・・?

    1回目の形式に沿って2回目の出力するのはJSON形式に関係なく一般的な方法なので、もしかしたらプロンプトを工夫すれば行けるのかもしれない。


    結論

    GPT-4 Turbo特有の挙動によりJSON形式の出力では、出力トークン数の上限値4096トークン、あるいはmax_tokensで指定したトークン数に収まるように注意が必要。超える場合はバッチ処理等の工夫が必要。

    指示書からの構造化データの書き起こしは業種問わず普遍的なニーズなので引き続きトライしていきたい。

    まだLLM自体は黎明期で明日にでも何かが変わるかもしれない環境なので、OpenAIのアップデートを期待しつつ(特に出力トークン数の上限の拡大を!)、臨機応変に最適な運用方法を考えていきたい。

    それでは良いChatGPTライフを!!


    あとがき

    ちなみにサムネのちょっと気味の悪い画像は、ChatGPTに搭載されているDALL-Eに

    「壊れたレコードのように酔っぱらって同じ話を何度も繰り返すオヤジのようなAIのイラストを描いて。同じ話をする表現は文字を使わず吹き出しの量で表現して。」

    というプロンプトでリクエストして生成されたもの、に、少し手を加えたもの。

    画像生成は初めてだったので、このプロンプトに行きつくまで、人間臭さがうまく表現できず結構苦労した。

    うーーん、「酔っ払い」「オヤジ」と「AI」が相性悪い???

    私たちは積極的に採用活動をしております。
    https://www.metasite.co.jp/recruit

    Companies

    エクスクリエ
    メタサイト
    クロス・マーケティンググループ
    クロス・コミュニケーション

    Tags