LLMs + 翻訳サービスとしての構造化出力
他の言語: English Español 한국어 Português 中文
以前の記事 では、AIを使ったアプリのローカライズについて、特にUI文字列の翻訳の自動化について議論しました。そのタスクでは、以下のような理由から、LLMではなく翻訳ツールを選びました。
…私はOpenAIのGPT3.5 Turboを使用する予定でしたが、それは厳密には翻訳モデルではないため、プロンプトを設定するのに余分な労力が必要です。また、その結果は安定していない傾向があるので、私が思いついた最初の専用の翻訳サービスを選びました…
その後、数社のLLM提供者が構造化出力機能を導入し、討論したユースケースに対してLLMを大幅に適用しました。 今日は、その問題とこの機能がそれをどのように解決するかについて、詳しく見ていきましょう。

問題点
LLMは本質的に非決定論的で、同じ入力に対して異なる出力を生成する可能性があります。このランダム性はLLMを「創造的」にしますが、特定の出力フォーマットに従うことが求められる場合には信頼性を低下させます。
たとえば、翻訳ユースケースを例にとると、直感的な解決策は、必要なフォーマットをプロンプトで説明することでしょう。
> Translate 'cat' to Dutch. ONLY GIVE A TRANSLATION AND NOTHING ELSE.
これはうまく機能しますが、時折、LLMが指示を間違えます。
> Certainly, allow me to provide the translation for you.
The word 'cat' is translated as 'kat' in Dutch. Sometimes, female cats may also be referred to as 'poes'.
アプリケーションはレスポンスとして単一の単語を期待しているので、単一の単語以外のものはエラーやデータの破損を引き起こす可能性があります。 さらなるチェックとリトライを追加しても、このような不一致はワークフローの信頼性を妨げます。
構造化出力とは
構造化出力は、レスポンスのスキーマを定義して、不一致のあるフォーマットという問題に対処します。たとえば、このような構造をリクエストすることができます。
{
"word": "cat",
"target_locale": "nl",
"translation_1": "kat",
"translation_2": "poes"
}
構造化出力の実装は、モデルによって異なることがあります。 一般的なアプローチには以下のようなものがあります。
- トークン間の遷移を追跡し、提供されたスキーマに違反する生成パスをプルーニングするために有限状態マシンを使用する
- 非遵守のオプションを選択する可能性を減らすために、候補トークンの確率を変更する
- JSONスキーマや他の構造化データフォーマットを認識することに焦点を当てたデータセットを使用して、モデルを微調整する
これらの追加メカニズムは、フォーマットの指示をプロンプトに追加するだけよりも、高い信頼性を達成するのに役立ちます。
コード
翻訳スクリプトを更新しましょう。 まず、スキーマを表現するオブジェクトを定義します。
response_format={
"type": "json_schema",
"json_schema": {
"name": "translation_service",
"schema": {
"type": "object",
"required": [
"word",
"translation"
],
"properties": {
"word": {
"type": "string",
"description": "The word that needs to be translated."
},
"translation": {
"type": "string",
"description": "The translation of the word in the target language."
}
},
"additionalProperties": False
},
"strict": True
}
}
次に、リクエストを行う関数でスキーマオブジェクトを使用します。
def translate_property_llm(value, target_lang):
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {openai_key}',
}
url = 'https://api.openai.com/v1/chat/completions'
data = {
'model': 'gpt-4o-mini',
'response_format': response_format,
'messages': [
{'role': 'system',
'content': f'Translate "{value}" to {target_lang}. '
f'Only provide the translation without any additional text or explanation.'},
{'role': 'user', 'content': value}
],
}
response = requests.post(url, headers=headers, data=json.dumps(data))
try:
content_json = json.loads(response.json()["choices"][0]["message"]["content"])
return content_json["translation"]
except (json.JSONDecodeError, KeyError) as e:
raise ValueError(f"Failed to parse translation response: {str(e)}")
それでコーディングは終了です! 詳しくは、GitHubのリポジトリでプロジェクトを探索し、実験してみてください。
前回のアプローチとの比較
更新されたバージョンでは、翻訳サービスをベースにしたものに比べて翻訳プロセスに少し時間がかかりました。とはいえ、提供したコードは最もシンプルな動作する例であり、リクエストを非同期で送信することでさらに高速化することが可能です。
費用の面では、5つのロケールに翻訳するのに0.01ドル未満となり、前回のバージョンと比べて10倍以上安くなりました。

結果として得られたメッセージバンドルをざっと見てみると、何も怪しいものは見つかりませんでした。新たに追加した言語はいずれも読むことができませんが、フォーマットは良好であり、‘Sure, I’ll translate that for you’のようなフレーズは一つも見つけられませんでした🙂。
なぜ重要なのか
上記の実験で説明した問題はニッチなものであるものの、LLMのより広範で重要な側面を示しています。個々のタスクの解決において特化したサービスを上回るかどうかはともかくとして、LLMの真に注目すべき点は、短時間で実用的な解決策を提供し、より特化したツールに対する汎用的な代替としての能力です。
私自身、機械学習についての知識は基本的なものしかないため、かつては中程度の解決策であっても専門知識を必要とするタスクのクラス全体を避けていました。しかし今では、分類器、翻訳サービス、LLMをジャッジとして使用するなど、無数のアプリケーションを組み立てるのが非常に容易になりました。
もちろん、品質は依然として考慮するべき事項ですが、実際には、多くの問題に対して100%の完璧さは必要ないのです。ハッキングをしない理由が一つ少なくなりました!
まとめ
この記事を適切にまとめる方法が思い浮かばなかったので、今日会った猫の写真を載せておきます(AI生成ではありません!)。
