はじめに
こんにちは。ニフティ株式会社の高垣と申します。
私が所属しているチームでは、会員様向けのお問い合わせに対応するコールセンターの業務改善にAIを活用しています。その中で、Amazon BedrockのLLMを呼び出すLambdaを実装する際に 「LLMの出力を安定してJSON形式で受け取りたい」 という課題にぶつかりました。
本記事では、この課題を Converse APIのTool Use で解決した方法をご紹介します。BedrockでLLMの出力を構造化データとして扱いたい方の参考になれば幸いです。
簡単な業務背景
弊社のコールセンターでは、お電話をいただいた際にお客様がニフティのご契約者かどうかを確認する「本人確認」のステップがあります。これにAIを活用することで、本人確認にかかる時間を短縮し、お客様がよりスムーズにお問い合わせできるのではないかというアイデアが生まれました。私はこの取り組みの中で、Amazon Bedrockを呼び出すLambdaの作成を担当しました。Bedrockから受け取った結果を後続の処理にJSON形式で渡す必要があったのですが、ここで課題が発生しました。

invoke_model の課題:LLMの出力が文字列になる
通常、Python(boto3)のLambdaからBedrockを呼び出す際は invoke_model がよく使われます。しかし、この方法ではLLMからの回答が「文字列(String)」として返ってきます。
LLMの特性上、出力は確率に基づいて生成されるため、たとえプロンプトでJSONを返すように指示しても、常に期待通りのデータ構造になるとは限りません。その結果、後続の処理でパースに失敗し、エラーを招くリスクがありました。
Converse APIのTool Useによる解決
そこで今回は、この不確実性を排除するために Converse API を採用しました。Converse APIを活用することで、LLMからの出力を安定して「JSON形式」で受け取れるようになり、後続処理への連携が非常にスムーズになりました。
具体的には、toolConfig の tools に期待するJSONの形式を定義し、Converse APIでBedrockを呼び出す際にtoolを指定します。
以下にサンプルコードを載せます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import boto3 bedrock_client = boto3.client("bedrock-runtime") #tool_listの定義 tool_list = [ { "toolSpec": { "name": "parse_json", "description": "JSONにパースする", "inputSchema": { "json": { "type": "object", "properties": { "A": {"type": "number", "description": "Aの説明"}, "B": {"type": "string", "description": "Bの説明"}, }, "required": ["A", "B"], # レスポンス時に必須なもの } }, } } ] # Bedrockを呼び出し response = bedrock_client.converse( modelId=MODEL_ID, #モデルIDを指定(anthropic.claude-3-sonnet-20240229-v1:0など) toolConfig={"tools": tool_list, "toolChoice": {"tool": {"name": "parse_json"}}}, messages=[ { "role": "user", # 送信者のロール "content": [{"text": user_prompt}], # ユーザーからの入力プロンプト } ], system=[{"text": system_prompt}], # システムプロンプトの設定 ) |
このサンプルコードでは{A: 1, B: "test"}のような形式のJSONレスポンスが返されます。tool_list の inputSchema で定義したスキーマに沿って、LLMの出力が構造化されます。
レスポンスからJSONデータを取り出すには、以下のようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# レスポンスのcontentリストからtoolUseブロックを探して取得する json_result = None for block in response["output"]["message"]["content"]: if "toolUse" in block: # toolUseのinputには、パース済みの辞書(dict)データが格納されている json_result = block["toolUse"]["input"] break # 見つけたらループを抜ける if json_result is not None: print(json_result) # {'A': 1, 'B': 'test'} else: print("レスポンスにtoolUseブロックが含まれていませんでした。") |
終わりに
invoke_model ではLLMの出力が文字列になるため、JSON構造を保証するのが難しいという課題がありましたが、Converse APIの Tool Use 機能を活用することで、この問題をシンプルに解決できました。
LLMの出力を構造化データとして扱いたい場面では、ぜひConverse APIを利用してみてください。
今後も、お客様の満足度向上のために、AIやクラウド技術を活用したコールセンター業務の改善に取り組んでいきます。
参考
https://catalog.workshops.aws/building-with-amazon-bedrock/en-US
https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-runtime/client/converse.html


