AI
2023-08-01

プロンプトでの出力形式の制御について - 自然言語/TypeScript/Zodを比較

この記事は、ChatGPTのAPIを利用して高度な自然言語処理を行う際に、プロンプトでの出力形式の制御について解説しています。特に、自然言語とTypeScriptの型表現、そしてzodのSchema表現の比較を行いながら、プロンプトを用いてモデルのLLMが正確に解釈できる方法に焦点を当てています。

プロンプトでの出力形式の制御について - 自然言語/TypeScript/Zodを比較

モチベーション

ChatGPTのAPIの登場によって、高度なNLPを利用したアプリケーションを簡単に作ることができる様になりました。 しかし、実際に製品レベルで使えるものを作ろうとしてみると、LLMの非決定論的な挙動をコントロールする必要があり、これがなかなか難しいことがわかります。

その課題の一つに「LLMの出力を、後続のプログラムがパース可能な形で出力させたい」というものがあります。出力の形式はJSONが選択されることが多いのでより具体的にすると、LLMにJSONを出力させる場合に、高い確率で正しいフォーマット&指定した構造で出力させたい場合にどの様なプロンプトが良いか?という課題です。

単純には、自然言語により構造を伝えた上で「JSONで出力して」と指定することになります。出力したいデータの構造が単純なうちはこれで事足りますが、複雑になれば自然言語での指定は長くなりがちだったり、曖昧な表現になるなどの問題がありそうです。そのため、TypeScriptの型やzodのスキーマ定義など、一般に広く使われている、データ構造の記法を使って表現する方法が思い浮かびます。

「プロンプトで出力に期待するデータの構造を伝える、と言う目的で適切な構造定義の記法を選ぶ」という課題は、プロンプトを与える対象であるモデルのLLMが、その定義の記法を正確に解釈するかどうか。また定義の記法の中でLLMのアテンションをコントロールしやすいかと言ったことが論点になります。

今、当社(SparkleAI)のプロダクトでは、特に検証を行わずに、エンジニアが慣れている方法として「TypeScriptの型定義」を採用することが多く、これで問題が起こることはないです。しかし、型には含めにくい表現(例えば文字数などの制限)があり、この点でzodなどのSchema定義と比較した場合に、どちらがより適切かについて評価が必要です。

このブログでは、自然言語/TypeScript/Zodでアウトプット形式を指定した場合にChatGPTがアウトプットの成形に成功する割合について調べてまとめます。

評価対象

自然言語

目的
コミュニケーション

手法の論点
曖昧さがあり、指定した型で返してこないことがありそうだ

TypeScript型表現

目的
プログラムソースの型チェック

手法の論点
TSで書かれたプログラムは多いため正確に解釈されそうだ

zod Schema表現

目的
プログラム実行時のスキーマ検証

手法の論点
型表現より具体的な指定ができるが、結果に影響があるか

評価用プロンプト

出力される文字数や、項目の数を指定した場合にChatGPTはその制約に従わないことが多いです。そのためプロンプトエンジニアリングでは、一度生成させて後から文字数などを直させる方法が採用されます。しかし、今回は、zodなど文字数や個数の制約が結果に与える影響も確認したいので、文字数に制約を入れた「問題の生成」プロンプトを題材にします。

以下のプロンプトで出力形式の定義をそれぞれ、自然言語/TypeScript型表現/ZodのSchema表現で入れ替えたものを使います。


# 指示
下で与えられる文章について、読解力を問う中学生の国語の問題を作り
以下にzodで定義するQuestionSheetを満たすオブジェクトとしてJSONで出力してください。
JSONの文字列中には改行やタブルクォートを含めてはいけません。
```
const Question = z.object({
question: z.string().max(30)
answer: z.string().max(30),
});
const QuestionSheet = z.object({
questions: z.array(Question).min(7)
});
```

# 文章
```
{document}
```

# 出力結果

評価方法

NLとTSとZDのそれぞれで出力形式を指定した問題の生成プロンプトに、異なる10のドキュメントを30回づつ繰り返し与えて(計300回)出力し、結果に対して次の内容を集計し評価します。

  • parse: 簡単な前処理をした場合にJSONとしてパースできる率
  • schema: パースできた場合に、JSONの構造がこちらが指定した構造と一致している率
  • count: 構造が正しい場合に、課題の個数が要件を満たしている率
  • length: 構造が正しい場合に、文字列の長さが要件を満たしている率

生成の多様性が高い設定の方がプロンプトの違いが大きく出るだろうと仮定して、多様な結果の生成タスクで使われる現実的な上限値:Temperature=1.2の設定に固定しました。

結果

NL

parse
0.84 (251/300)

scheme
0.57 (143/251)

count
0.8 (114/143)

length
0.8 (1556/1950)

TS

parse
0.95 (284/300)

scheme
1.0 (283/284)

count
0.73 (206/283)

length
0.76 (2861/3788)

ZOD

parse
0.94 (278/296)

scheme
1.0 (278/278)

count
0.82 (228/278)

length
0.79 (2935/3736)

プロダクトで利用する場合、第一にParseとSchemaの精度が高いものが望まれます。praseとschemaのテストで「NL」と「TSおよびZOD」の間で大きな差がありますので、自然言語を使うよりもやはり人工言語を使って出力の構造を指定する方がよりコントロールしやすいことがわかります。

その上で、TypeScriptとZodにはparseおよびschemaのテストでは大した差がありません。一方でTypeScriptのcountとlengthの成績は、Zodや自然言語とも比べて悪いことがわかります。TypeScriptの型表現の中に、個数や文字数の制約を入れていく方法は、Schemaの仕様よりもアテンションが掛かりにくいのかもしれないです。

以上のことから、最も狙った構造を出しやすいという点では、Schema表現を使うのが数字上の成績が良いです。しかし、Schema表現にはZod以外にYup / io-ts / joiなど、記述方法が似通っていて細部が異なるライブラリがあり、こちらが指定した意図の通りにコントロールができるかの点で不安があります。またTypeScriptより普遍性が低いので、今後に記述に変更がある可能性に注意が必要です。

まとめ

この記事では、自然言語、TypeScript、およびZodを用いてChatGPTによる出力形式を制御する方法を比較検討しています。その主な焦点は、これらの方法がChatGPTにどの程度正確なデータ構造を生成させられるか、またそれらの方法がChatGPTのアテンションをどの程度コントロールできるかという点にあります。

結果として、自然言語よりもプログラミング言語を使用して出力の構造を指定する方がより精度が高くなることがわかりました。具体的には、出力がパースに成功する率やスキーマ一が指定したものと一致する率はTypeScriptとZodの両方で自然言語よりも高く、またこれら二つの間では大きな差は見られませんでした。

ただし、項目の個数や文字列の長さを制約することに関しては、TypeScriptがZodや自然言語よりも劣っていることがわかりました。これは、TypeScriptの型表現が個数や文字数の制約を含めるのが難しく、そのためにSchemaの仕様に比べてアテンションが掛かりにくいことが理由と考えられます。

この観点から、最も狙った構造を出しやすいという点ではSchema表現が適切です。しかしSchema表現はTypeScriptに比べて普遍性が低く、記述が変更される可能性があるためその点に注意が必要です。

以上を踏まえ、JSONの出力を指定し、その構造を制御するための記述としてはTypeScriptの型表現が最も有用であると考えます。しかし、TypeScriptの型表現では個数や長さの制約がSchemaと比較して若干劣るため、これらの要素をプロンプトの平文の指示でさらに制御することが推奨されます。