目次
この記事について
本記事は、Agent Builder を使用したデータストア参照型対話エージェントの作成に関する調査記録です。
やりたいこと
Agent Builderを用いたデータストア参照型の対話エージェントを作成する際に、PlaybookのInstructionsやToolの Schemaの設定が手間であるため、必要な条件だけを入力すればAIが自動で作成してくれるようにしたい。
前回の調査日記
あわせて読みたい


AI Agent開発日記 2025/05/10
この記事について 本記事は、Agent Builder を使用したデータストア参照型対話エージェントの作成に関する調査記録です。 やりたいこと Agent Builderを用いたデータス...
PlaybookのInstructionsやToolの Schemaの設定が手間であるため、必要な条件だけを入力すればAIが自動で作成してくれるようにしたい。
- やりたいこと
- 毎回データに合わせてPlaybookやToolのスキーマ設定を作成するのが手間なので、自分用にWebのGeminiに投げて使えるプロンプトを作成したい 。
- 現状
- webのgeminiだけではなく、下記コードでpythonでも実行できるようにした。
playbook/Instructions作成プロンプトPython code ver
import google.generativeai as genai
import os
API_KEY = os.getenv("GEMINI_API_KEY")
if not API_KEY:
print("エラー: 環境変数 'GEMINI_API_KEY' が設定されていません。")
print("Jupyter Notebookを実行する環境でAPIキーを環境変数に設定するか、")
print("上記のコメントアウトされた行(非推奨)で一時的に設定してください。")
raise ValueError("APIキーが設定されていません")
genai.configure(api_key=API_KEY)
model = genai.GenerativeModel('gemini-1.5-flash')
try:
prompt = """
**【プロンプト本文】**
あなたはAgent BuilderのPlaybook設定(特にGoalとInstructions)を生成する専門家です。
以下に、Elasticsearch検索Toolを使用するPlaybookの既存設定(テンプレート)と、新しいインデックスの情報を提供します。提供された情報に基づいて、テンプレート設定の**特定の部分**を修正・拡張し、新しいインデックスに対応したPlaybookの「Goal」および「Instructions」セクションの内容(Markdown形式)を生成してください。
**生成対象:**
Agent Builder Playbook設定の「Goal」および「Instructions」セクションの内容。
**生成ルール:**
1. **提供されたPlaybookテンプレートの内容をベースとします。**
2. 以下のプレースホルダー部分を、提供する「新しいElasticsearchインデックス名」で置き換えてください。
* Instructions内の「利用可能なツール」の機能説明でインデックス名に言及している箇所。
* Instructions内の「ツール名」として使用する場合(例: 「[新しいインデックス名]検索ツール」とする)。
* Instructions内の「Tool呼び出し」で `index=<index_name>` を指定している箇所の値。
* Instructions内の「参照可能なインデックススキーマ」の説明でインデックス名に言及している箇所。
* Playbookルールの「条件 (When)」でインデックス名に言及している箇所(厳密な形式のルールなど)。
* Playbookルールの「処理 (Then)」のThoughtでインデックス名に言及している箇所。
* Playbookルールの「出力 (Output)」のThoughtや「ユーザーへの応答」でインデックス名に言及している箇所。
* Playbookルールの「Tool呼び出し」でツール名を指定している箇所(ツール名を変更する場合)。
* Playbookルールの「Tool実行エラーが発生した場合」の条件でツール名を指定している箇所(ツール名を変更する場合)。
* Playbookルールの「検索指示が不明確な場合」の「ユーザーへの応答」例でインデックス名に言及している箇所。
3. **提供された「新しいインデックスのドキュメントJSONデータ例」を解析し、以下の内容を生成または修正してください。**
* Instructions内の「参照可能なインデックススキーマ」の説明リストを、JSON例から推測されるフィールド名とデータ型(例: id (integer), famous_song (string), categories (array of string) など)のリストに更新してください。データ型は、JSON例のデータ型に基づいて推測します。
* Playbookルールの「処理 (Then)」にある「LLMによるパラメータ特定プロセス」の説明や例で、言及されているフィールド名や例示クエリを、新しいインデックスのフィールド名に合うように修正してください。
* Playbookルールの「出力 (Output)」の「ユーザーへの応答」にある、各曲の情報をリスト形式で表示する部分のフォーマット文字列(例: `- ID: \`{{tool_output.results[0].id}}\` 曲名: \`{{tool_output.results[0].famous_song}}\` ...`)を、新しいインデックスのフィールド名を使って適切に更新してください。提供されたJSON例に含まれる全てのトップレベルフィールドをリストアップする形で記述してください。
* Playbookルールの「検索指示が不明確な場合」の「ユーザーへの応答」例で、例示している検索クエリのフィールド名を新しいインデックスのフィールド名に合うように修正してください。
4. Tool名 (`${TOOL:ランダム曲検索}`) は、必要に応じて新しいインデックス名が反映された名称(例: `${TOOL:新しいインデックス名検索ツール}`)に変更してください。変更する場合は、Tool呼び出しやエラー条件など、Playbook全体で一貫性を持たせてください。テンプレートでは「ランダム曲検索」のままとして、Instructionsの冒頭で「利用可能なツール」のツール名定義を「[新しいインデックス名]検索ツール」のように変更する指示を加える方が柔軟かもしれません。ここではツール名自体は変更せず、その説明部分で新しいインデックス名に言及する形でテンプレートを維持し、Tool名の修正はオプションとして指示に含めます。
5. 出力は**Markdown形式**で、以下の構造で生成してください。「Goal」セクションの内容と、「Instructions」セクションの内容を明確に区別してください。
**提供情報:**
---
**新しいElasticsearchインデックス名:**
```
<!-- ここに新しいインデックス名を記入してください(例: my_data_index) -->
```
---
**新しいインデックスのドキュメントJSONデータ例 (JSON配列形式):**
```json
[
<!-- ここに、そのインデックスに格納されている実際のドキュメントのJSON例をいくつか貼り付けてください。 -->
<!-- ToolのOpenAPIスキーマ生成に使用したものと同じJSON例を推奨します。 -->
]
```
---
**ベースとなるPlaybook設定テンプレート:**
```markdown
**Goal**
toolを駆使し、ユーザーからのクエリを元にElasticsearchで検索し、出力フォーマットに沿った形式で検索結果を返します。
**Instructions**
- メイン目標: ユーザーからの楽曲検索リクエストに応答する。自然言語での曖昧なリクエストも理解し、適切な検索を実行する。
- 利用可能なツール: Elasticsearch検索ツール(ランダム曲検索)
- ツール名: ${TOOL:ランダム曲検索}
- 機能: Elasticsearchインデックス random_song_100000 に対して検索を実行する。
- 主な引数:
- index=<index_name>: 検索対象のインデックス名を指定。常に random_song_100000 を使用。
- query=<query_dsl>: Elasticsearch Query DSL形式のクエリを指定。(※このテンプレートではfield/keywordに分解していますが、query DSLを直接Toolに渡す形式にすることも可能です。ここではfield/keyword形式を維持します)
- field=<field_name>: 特定フィールドでキーワード検索する場合に指定。
- keyword=<keyword>: 特定フィールドでキーワード検索する場合に指定するキーワード。
- search_all=true: インデックス全体を検索する場合に指定。(※query DSLで {"match_all": {}} を使う方が一般的ですが、テンプレートに合わせて維持)
- size=<number>: 取得件数を指定。
- from=<number>: 取得開始位置を指定。(※OpenAPIスキーマに追加したのでInstructionsにも追加を指示)
- sort=<sort_param>: ソート条件を指定。(※OpenAPIスキーマに追加したのでInstructionsにも追加を指示)
- 戻り値: JSON形式で検索結果リストや件数を返す。例: {"count": 1, "results": [{"id": "5", "famous_song": "曲名", ...}]} (Tool定義に合わせて修正)
- 参照可能なインデックススキーマ (random_song_100000): id (string/number), famous_song (text), name (text/keyword, 作曲家名), categories (keyword), year (number), duration (text/keyword) など (Tool定義や別Instructionでスキーマ情報を提示)
- Playbook (処理フロー): ユーザー入力に対して以下のルール群を順番に評価し、最初にマッチしたルールを実行する。
- ルール1: インデックス全体の検索 (厳密な形式)
- 条件 (When): ユーザー入力が「`random_song_100000` インデックス全体を検索して」という厳密な形式に一致する場合。
- user_input に "^`random_song_100000` インデックス全体を検索して$" がマッチ。
- 処理 (Then): random_song_100000 インデックス全体を検索するToolを呼び出す。
- Thought: ユーザーからの明確な指示に基づき、random_song_100000 インデックス全体を検索するツールを呼び出します。
- Tool呼び出し: ${TOOL:ランダム曲検索} index=random_song_100000 search_all=true
- 出力 (Output): Toolの実行結果を整形して表示する。
- Thought: Toolの実行結果({{tool_output}})を整形してユーザーに表示します。
- ユーザーへの応答:
- 「`random_song_100000` インデックス全体を検索した結果、`{{tool_output.count}}` 件の曲が見つかりました。」
- 「上位10件を表示:」
- 各曲の情報をリスト形式で表示 (例: 「- ID: `{{tool_output.results[0].id}}` 曲名: `{{tool_output.results[0].famous_song}}` ...」) (tool_output構造に合わせて修正)
- 「全件表示が必要な場合は、お知らせください。」
- ルール2: フィールド指定検索 (厳密な形式)
- 条件 (When): ユーザー入力が「`random_song_100000` インデックスで `[フィールド名]:[検索キーワード]` を検索して」という厳密な形式に一致する場合。
- user_input に "^`random_song_100000` インデックスで `(.*):(.*)` を検索して$" がマッチ。
- マッチした内容からフィールド名とキーワードを抽出する (例: extract: field="$1", keyword="$2").
- 処理 (Then): 抽出したフィールドとキーワードでElasticsearch検索ツールを呼び出す。
- Thought: ユーザーからの明確な指示に基づき、指定されたフィールド({{field}})とキーワード({{keyword}})でElasticsearch検索ツールを呼び出します。
- Tool呼び出し: ${TOOL:ランダム曲検索} index=random_song_100000 field={{field}} keyword={{keyword}}
- 出力 (Output): Toolの実行結果を整形して表示する。
- Thought: Toolの実行結果({{tool_output}})を整形してユーザーに表示します。
- ユーザーへの応答:
- 「`random_song_100000` インデックスで `{{field}}:{{keyword}}` を検索した結果、`{{tool_output.count}}` 件の曲が見つかりました。」
- 「**検索結果:**」
- 各曲の情報をリスト形式で表示 (例: 「- ID: `{{tool_output.results[0].id}}` 曲名: `{{tool_output.results[0].famous_song}}` ...」) (tool_output構造に合わせて修正)
- 「他に条件があれば教えてください。」
- ルール3: 自然言語による検索リクエストの解釈と実行
- 条件 (When): 上記の厳密な検索フォーマットにマッチしない、その他のユーザー入力の場合。
- 他のルールにマッチしなかった場合のデフォルト処理として設定する。 (例: When: true)
- 処理 (Then): ユーザーの自由な入力から検索意図を解釈し、Tool呼び出しに必要なパラメータ(インデックス、フィールド、キーワード)を特定し、Toolを実行する。
- Thought: ユーザーの自由な入力({{user_input}})から楽曲検索の意図を解釈し、検索対象のインデックス、フィールド、キーワードを推測します。利用可能なインデックスは `random_song_100000` のみです。主な検索可能なフィールドは `id`, `famous_song`, `name` (作曲家名), `categories`, `year`, `duration` です。
- LLMによるパラメータ特定プロセス:
- 例1: 入力「idが5の曲を検索して」 → 推測結果: index=`random_song_100000`, field=`id`, keyword=`5`
- 例2: 入力「モーツァルトの曲を探して」 → 推測結果: index=`random_song_100000`, field=`name`, keyword=`モーツァルト`
- 例3: 入力「有名なクラシック曲を教えて」 → 推測結果: index=`random_song_100000`, field=`categories`, keyword=`クラシック`
- LLMは推測した結果に基づいて、Tool呼び出しの引数を決定します。
- Tool呼び出し: 推測したパラメータを使ってToolを呼び出す。
- 例: ${TOOL:ランダム曲検索} index={{inferred_index}} field={{inferred_field}} keyword={{inferred_keyword}} (AgentBuilderの構文に合わせて、LLMの推論結果をTool引数にマッピングする記述が必要)
- もし検索意図が不明確でパラメータを特定できない場合は、Tool呼び出しはスキップする。
- 出力 (Output): Toolが正常に実行された場合、その結果を整形して表示する。Tool呼び出しがスキップされた場合は別の応答をする。
- Thought: Toolが実行された場合、その結果({{tool_output}})を整形してユーザーに表示します。Toolがスキップされた場合は、意図が不明確だったことを伝えます。
- ユーザーへの応答 (条件分岐例):
- Toolが実行され、結果が得られた場合:
- 「`random_song_100000` インデックスで検索した結果、`{{tool_output.count}}` 件の曲が見つかりました。」
- 「**検索結果:**」
- 各曲の情報をリスト形式で表示 (例: 「- ID: `{{tool_output.results[0].id}}` 曲名: `{{tool_output.results[0].famous_song}}` ...」)
- 「他に条件があれば教えてください。」
- Toolが実行されなかった場合(パラメータ特定失敗など):
- 「検索の意図がうまく読み取れませんでした。もう少し詳しく教えていただけますか? 例:「idが5の曲を探して」または「モーツーァルトの曲名を教えて」のように、具体的なフィールド名や情報を指定してください。」 (この応答は次のフォールバックルールに任せることも可能)
- ルール4: 検索エラーが発生した場合
- 条件 (When): 前のステップでTool実行エラー(特に`${TOOL:ランダム曲検索}`に関連するエラー)が発生した場合。
- last_tool_status が "error" で、かつ last_tool_name が "ランダム曲検索" の場合。 (変数名はAgentBuilderに合わせる)
- 処理 (Then): 発生したエラー内容をユーザーにわかりやすい形で伝える。
- Thought: 発生したエラー内容({{last_error_message}})を解析し、ユーザーにわかりやすい形で伝えます。
- ユーザーへの応答:
- 「検索中にエラーが発生しました。[エラー内容]」 (エラー内容は{{last_error_message}}を参照)
- 「もう一度検索するか、内容をご確認ください。それでも解決しない場合は、管理者にご連絡ください。」
- ルール5: 検索指示が不明確な場合 (最終フォールバック)
- 条件 (When): 上記のどの検索関連ルール(厳密な形式、自然言語解釈ルールでのTool実行成功)にもマッチしなかった場合、または自然言語解釈ルールでTool実行がスキップされ、かつそのルール自身で適切なOutputが出されなかった場合。
- どのルールにもマッチしなかった場合の最終的なデフォルト処理として設定する。 (例: When: true を最後に配置)
- 処理 (Then): ユーザーの入力が検索の意図として不明確であることを伝え、適切な入力形式の例を提示して再入力を促す。
- Thought: ユーザーの入力が検索の意図として不明確か、処理できなかったため、適切な入力形式の例を提示して再入力を促します。
- ユーザーへの応答:
- 「検索の指定方法が不明確です。次のように入力してください:」
- 「- `random_song_100000` インデックスで `name:モーツァルト` を検索して」
- 「- `random_song_100000` インデックスで `famous_song:交響曲` を検索して」
- 「もう一度ご入力をお願いします。」
```
"""
print("AIへのプロンプトを準備しました。")
print("\nAIからの回答を生成中...")
response = model.generate_content(prompt)
print("\n--- 回答 ---")
response_text = response.text.strip()
if response_text.startswith("```markdown"):
response_text = response_text[len("```markdown"):].strip()
if response_text.endswith("```"):
response_text = response_text[:-len("```")].strip()
elif response_text.startswith("```"):
# 他の言語のコードブロック形式も簡易的に除去
response_text = response_text[response_text.find('\n') + 1:].strip() # ヘッダー行の除去
if response_text.endswith("```"):
response_text = response_text[:-len("```")].strip()
print(response_text)
except Exception as e:
print(f"\nエラーが発生しました: {e}")
Tool Schema 作成プロンプト python ver
import google.generativeai as genai
import os
import json
API_KEY = os.getenv("GEMINI_API_KEY")
if not API_KEY:
print("エラー: 環境変数 'GEMINI_API_KEY' が設定されていません。")
print("Jupyter Notebookを実行する環境でAPIキーを環境変数に設定するか、")
raise ValueError("APIキーが設定されていません")
genai.configure(api_key=API_KEY)
model = genai.GenerativeModel('gemini-1.5-flash')
try:
prompt = """
このプロンプトは、提供されたJSONデータ例を解析し、含まれるフィールド名とデータ型を推測して、適切なOpenAPIスキーマのプロパティ定義を生成するようGeminiに指示します。
---
**【プロンプト本文】**
あなたはOpenAPIスキーマを生成する専門家です。
以下に、Elasticsearch検索ToolとしてAgent Builderで使用するためのOpenAPI 3.0スキーマのテンプレートと、新しいインデックスの情報(インデックス名とドキュメントのJSONデータ例)を提供します。提供された情報に基づいて、テンプレートスキーマの**特定の部分**を修正・拡張し、新しいインデックスに対応した完全なOpenAPIスキーマ(**JSON標準に完全に準拠し、コメントを一切含まない形式**)を生成してください。
**生成対象:**
Elasticsearchの特定インデックスに対する `_search` API (POSTメソッド) を呼び出すためのOpenAPI 3.0スキーマ。
**生成ルール:**
1. **テンプレートスキーマ全体を使用します。** 生成されるJSON出力には、JSON標準で許可されていないいかなる形式のコメント(`//`, `/* ... */` など)も**一切含めないでください**。
2. 以下のプレースホルダー部分を、提供する「新しいElasticsearchインデックス名」で置き換えてください。
* `paths: /新しいインデックス名/_search` の `新しいインデックス名` 部分
* `operationId: search[新しいインデックス名PascalCase]Post` の `新しいインデックス名PascalCase` 部分(例: `my_data_index` -> `MyDataIndex`)。インデックス名がPascalCaseに変換できない文字を含む場合は、適切な識別子に変換してください。
* `description` 内でインデックス名に言及している箇所(あれば)
3. 最も重要かつ変更が大きい箇所は、**`responses.'200'.content.'application/json'.schema.properties.hits.properties.hits.items.properties._source.properties`** の内容です。
* この `_source.properties` の中身を、提供する「新しいインデックスのドキュメントJSONデータ例」を解析して生成してください。
* JSONデータ例に含まれる各ドキュメントのフィールド名とその値のデータ型を推測し、それらを元にOpenAPIスキーマの `properties` を定義します。
* **JSONデータ型の推測ルール:**
* JSONの文字列 (`"..."`) -> `type: string`
* JSONの整数 (`123`) -> `type: integer`
* JSONの浮動小数点数 (`123.45`) -> `type: number`
* JSONの真偽値 (`true`, `false`) -> `type: boolean`
* JSONのオブジェクト (`{...}`) -> `type: object` (その中のフィールドも再帰的に推測して `properties` に定義)
* JSONの配列 (`[...]`) -> `type: array` (配列の要素の型を推測し `items` に定義。要素の型が混在する場合は、可能な限り最も一般的な型または `{}` (任意の型) を `items` に使用してください。)
* JSONのnull (`null`) -> そのフィールドが存在する場合、スキーマに `nullable: true` を含めることを検討してください(必須ではありません)。
* JSON例からフィールドの必須(`required`)や説明(`description`)は判断できないため、`required`リストや各プロパティの`description`は含めなくて構いません。プロパティ名と `type` の定義のみでOKです。
4. 上記以外のテンプレートスキーマの箇所(`info`, `servers`, `requestBody`, `components`, `security` など)は、**一切変更しないでください**。テンプレートの内容をそのまま維持してください。
5. 出力は**JSON形式**で、コードブロックで囲んでください。生成されるJSON文字列全体に、JSON標準で許可されていないコメントを**絶対に含めないでください**。
**提供情報:**
---
**新しいElasticsearchインデックス名:**
```
<!-- ここに新しいインデックス名を記入してください(例: my_data_index) -->
```
---
**新しいインデックスのドキュメントJSONデータ例 (JSON配列形式):**
```json
[
<!-- ここに、そのインデックスに格納されている実際のドキュメントのJSON例をいくつか貼り付けてください。 -->
<!-- 理想的には、異なる構造を持つ可能性のあるドキュメントを複数含めると、より正確なスキーマが生成できます。 -->
<!-- 提供された例をそのまま貼り付けてもOKです。 -->
]
```
---
**ベースとなるOpenAPIスキーマテンプレート (コメントなし):**
```json
{
"openapi": "3.0.0",
"info": {
"title": "Elasticsearch Search API",
"version": "v1",
"description": "API to search documents in a specific Elasticsearch index via POST."
},
"servers": [
{
"url": "https://your-elasticsearch-endpoint:443"
}
],
"paths": {
"/新しいインデックス名/_search": {
"post": {
"summary": "Search documents in Elasticsearch using POST",
"description": "Executes a search query against the '新しいインデックス名' index using POST request with query in the body. Requires ApiKey authentication via Header.",
"operationId": "search新しいインデックス名PascalCasePost",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "object",
"description": "Query in Elasticsearch Query DSL format. Example - {\"match_all\":{}}",
"nullable": true
},
"size": {
"type": "integer",
"description": "Number of hits to return.",
"default": 10
},
"from": {
"type": "integer",
"description": "Starting document offset for pagination.",
"default": 0
},
"_source": {
"description": "Controls what fields are returned in _source. Set to true to return all, false to return none, or an array of specific field names.",
"oneOf": [
{ "type": "boolean" },
{ "type": "array", "items": { "type": "string" } }
],
"nullable": true
},
"sort": {
"description": "Sort order for search results.",
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "oneOf": [{ "type": "string" }, { "type": "object" }] } },
{ "type": "object" }
],
"nullable": true
}
},
"required": [
"query"
]
}
}
}
},
"responses": {
"200": {
"description": "Successful response with search results.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"took": { "type": "integer" },
"timed_out": { "type": "boolean" },
"_shards": {
"type": "object",
"properties": {
"total": { "type": "integer" },
"successful": { "type": "integer" },
"skipped": { "type": "integer" },
"failed": { "type": "integer" }
},
"additionalProperties": true
},
"hits": {
"type": "object",
"properties": {
"total": {
"type": "object",
"properties": {
"value": { "type": "integer" },
"relation": { "type": "string" }
},
"additionalProperties": true
},
"max_score": { "type": "number", "nullable": true },
"hits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"_index": { "type": "string" },
"_id": { "type": "string" },
"_score": { "type": "number", "nullable": true },
"_source": {
"type": "object",
"properties": {
// ↓↓↓ --- ここが提供されたJSONデータ例に基づいて生成されます --- ↓↓↓
// ↑↑↑ --- ここが提供されたJSONデータ例に基づいて生成されます --- ↑↑↑
},
"additionalProperties": true
},
"fields": {
"type": "object",
"additionalProperties": {
"oneOf": [
{ "type": "string" },
{ "type": "integer" },
{ "type": "number" },
{ "type": "boolean" },
{ "type": "array", "items": {} },
{ "type": "object" }
]
},
"nullable": true
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"aggregations": {
"type": "object",
"additionalProperties": true,
"nullable": true
},
"suggest": {
"type": "object",
"additionalProperties": true,
"nullable": true
}
},
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request. Invalid request body or query syntax.",
"content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
},
"401": {
"description": "Unauthorized. Invalid API Key or authentication setup."
},
"403": {
"description": "Forbidden. API Key lacks necessary permissions."
},
"404": {
"description": "Not Found. The specified index or resource was not found."
},
"500": {
"description": "Internal Server Error. Elasticsearch error.",
"content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"description": "Elasticsearch API Key authentication. Value should be 'ApiKey YOUR_BASE64_ENCODED_ID:API_KEY'. Provide this full value in Agent Builder auth settings."
}
}
},
"security": [
{
"ApiKeyAuth": []
}
]
}
"""
print("AIへのプロンプトを準備しました。")
print("\nAIからの回答を生成中...")
response = model.generate_content(prompt)
print("\n--- 回答 ---")
try:
response_text = response.text.strip()
if response_text.startswith("```json"):
response_text = response_text[len("```json"):].strip()
if response_text.endswith("```"):
response_text = response_text[:-len("```")].strip()
json_output = json.loads(response_text)
print(json.dumps(json_output, indent=2))
except json.JSONDecodeError:
print("AIからの回答は有効なJSON形式ではないようです。元のテキストを表示します。")
print(response.text)
except Exception as e:
print(f"回答の処理中にエラーが発生しました: {e}")
print("元のテキストを表示します。")
print(response.text)
except Exception as e:
print(f"\nエラーが発生しました: {e}")