OpenAIのResponses APIを使用したRAGアプローチによるマルチツールオーケストレーション

こちらのノートブックをDatabricksでウォークスルーします。

https://cookbook.openai.com/examples/responses_api/responses_api_tool_orchestration

このクックブックは、OpenAIのResponses APIを使用して動的なマルチツールワークフローを構築する方法をガイドします。ユーザーのクエリを適切な組み込みツールや外部ツールにインテリジェントにルーティングする、Retrieval-Augmented Generation（RAG）アプローチの実装方法を示します。一般的な知識を求めるクエリでも、Pineconeのようなベクターデータベースから特定の内部コンテキストにアクセスする必要があるクエリでも、このガイドは関数呼び出し、組み込みツールでのウェブ検索、ドキュメント検索を活用して正確でコンテキストに基づいた応答を生成する方法を紹介します。

Responses APIのファイル検索機能を使用してPDF上でRAGを実行する実例については、[こちら](https://cookbook.openai.com/examples/file_search_responses)のノートブックを参照してください。

この例は、Responses APIの柔軟性を示しており、内部ベクトルストアに接続する内部`file_search`ツールを超えて、外部ベクトルデータベースに簡単に接続できる能力もあることを示しています。これにより、ホストされたツールと連携したRAGアプローチの実装が可能となり、さまざまな検索および生成タスクに対する多用途なソリューションを提供します。

```py
%pip install datasets tqdm pandas pinecone openai --quiet
%pip install -U mlflow
%restart_python
```

```py
import os
os.environ['OPENAI_API_KEY'] = dbutils.secrets.get("demo-token-takaaki.yayoi", "openai_api_key")
```

```py
# 必要なライブラリをインポート
import os
import time
from tqdm.auto import tqdm
from pandas import DataFrame
from datasets import load_dataset
import random
import string

# OpenAIクライアントをインポートし、APIキーで初期化
from openai import OpenAI

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Pineconeクライアントと関連仕様をインポート
from pinecone import Pinecone
from pinecone import ServerlessSpec
```

この例では、Hugging Faceからのサンプル医療推論データセットを使用します。データセットをPandas DataFrameに変換し、「Question」と「Response」列を1つの文字列にマージします。このマージされたテキストは埋め込みに使用され、後でメタデータとして保存されます。

```py
# データセットをロード（必要に応じてhuggingface-cliでログインしていることを確認）
ds = load_dataset("FreedomIntelligence/medical-o1-reasoning-SFT", "en", split='train[:100]', trust_remote_code=True, cache_dir="/tmp/hf_cache/")
ds_dataframe = DataFrame(ds)

# 質問と回答の列を1つの文字列にマージ
ds_dataframe['merged'] = ds_dataframe.apply(
    lambda row: f"Question: {row['Question']} Answer: {row['Response']}", axis=1
)
print("Example merged text:", ds_dataframe['merged'].iloc[0])
```
```py
ds_dataframe
```
![Screenshot 2025-04-07 at 10.59.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/a9c3564d-666e-44c6-a4f0-d7c71bc8064e.png)

# データセットに基づいてPineconeインデックスを作成する
データセット自体を使用して埋め込みの次元数を決定します。例えば、マージされた列から1つの埋め込みを計算し、それに応じてインデックスを作成します。

```py
MODEL = "text-embedding-3-small"  # 必要に応じて本番の埋め込みモデルに置き換えてください
# 最初のドキュメントの埋め込みを計算して埋め込み次元を取得します。
sample_embedding_resp = client.embeddings.create(
    input=[ds_dataframe['merged'].iloc[0]],
    model=MODEL
)
embed_dim = len(sample_embedding_resp.data[0].embedding)
print(f"Embedding dimension: {embed_dim}")
```
```
Embedding dimension: 1536
```

```py
os.environ['PINECONE_API_KEY'] = "<PINECONEのAPI KEY>"
```

```py
# PineconeをAPIキーで初期化します。
pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))

# Pineconeのサーバーレス仕様を定義します。
AWS_REGION = "us-east-1"
spec = ServerlessSpec(cloud="aws", region=AWS_REGION)

# 小文字の英数字と'-'を使用してランダムなインデックス名を作成します。
index_name = 'pinecone-index-' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))

# インデックスが存在しない場合は作成します。
if index_name not in pc.list_indexes().names():
    pc.create_index(
        index_name,
        dimension=embed_dim,
        metric='dotproduct',
        spec=spec
    )

# インデックスに接続します。
index = pc.Index(index_name)
time.sleep(1)
print("Index stats:", index.describe_index_stats())
```
```
Index stats: {'dimension': 1536,
 'index_fullness': 0.0,
 'metric': 'dotproduct',
 'namespaces': {},
 'total_vector_count': 0,
 'vector_type': 'dense'}
```

## データセットをPineconeインデックスにアップサートする

データセットをバッチ処理し、各マージされたテキストの埋め込みを生成し、メタデータ（個別の質問と回答フィールドを含む）を準備し、各バッチをインデックスにアップサートします。必要に応じて特定のエントリのメタデータを更新することもできます。

```py
batch_size = 32
for i in tqdm(range(0, len(ds_dataframe['merged']), batch_size), desc="Upserting to Pinecone"):
    i_end = min(i + batch_size, len(ds_dataframe['merged']))
    lines_batch = ds_dataframe['merged'][i: i_end]
    ids_batch = [str(n) for n in range(i, i_end)]
    
    # 現在のバッチの埋め込みを作成します。
    res = client.embeddings.create(input=[line for line in lines_batch], model=MODEL)
    embeds = [record.embedding for record in res.data]
    
    # メタデータを元の質問と回答から抽出して準備します。
    meta = []
    for record in ds_dataframe.iloc[i:i_end].to_dict('records'):
        q_text = record['Question']
        a_text = record['Response']
        # 特定のエントリのメタデータをオプションで更新します。
        meta.append({"Question": q_text, "Answer": a_text})
    
    # バッチをPineconeにアップサートします。
    vectors = list(zip(ids_batch, embeds, meta))
    index.upsert(vectors=vectors)
```

インデックスが作成されます。

![Screenshot 2025-04-07 at 11.01.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4875fbe3-b165-4d42-961d-08c1b2f5d1c8.png)

# Pineconeインデックスをクエリする

自然言語クエリを作成し、そのエンベディングを計算してPineconeインデックスで類似性検索を実行します。返される結果には、回答を生成するためのコンテキストを提供するメタデータが含まれています。

```py
def query_pinecone_index(client, index, model, query_text):
    # クエリのエンベディングを生成します。
    query_embedding = client.embeddings.create(input=query_text, model=model).data[0].embedding

    # インデックスをクエリし、上位5件の一致を返します。
    res = index.query(vector=[query_embedding], top_k=5, include_metadata=True)
    print("クエリ結果:")
    for match in res['matches']:
        print(f"{match['score']:.2f}: {match['metadata'].get('Question', 'N/A')} - {match['metadata'].get('Answer', 'N/A')}")
    return res
```

```py
# トレイン/テストセットからの異なるクエリを使用した例
query = (
    "アルコール使用の既往がある45歳の男性が、混乱、運動失調、眼筋麻痺を含む症状を呈しています。 "
    "最も可能性の高い診断と推奨される治療法は何ですか？"
)
query_pinecone_index(client, index, MODEL, query)
```
```
クエリ結果:
0.53: A 45-year-old man with a history of alcohol use, who has been abstinent for the past 10 years, presents with sudden onset dysarthria, shuffling gait, and intention tremors. Given this clinical presentation and history, what is the most likely diagnosis? - Considering the clinical presentation of sudden onset dysarthria, shuffling gait, and intention tremors in a 45-year-old man with a history of alcohol use who has been abstinent for the past 10 years, the most likely diagnosis is acquired hepatocerebral degeneration.

This condition is associated with chronic liver disease, which can often be a consequence of long-term alcohol use. Despite the patient's abstinence from alcohol for a decade, previous alcohol use may have led to underlying liver dysfunction. This dysfunction, even if subclinical, can cause encephalopathy due to the accumulation of neurotoxic substances that affect the brain. The sudden onset of these neurological symptoms aligns with how acquired hepatocerebral degeneration can manifest, making it a probable diagnosis in this scenario.
0.37: A 73-year-old man is evaluated for increasing forgetfulness, getting lost while walking, irritability, and difficulty recalling recent events while retaining detailed memories from over 20 years ago. On examination, he is oriented to person and place but disoriented to time, and an MRI of the brain reveals significant changes. Considering these symptoms and the imaging findings, what is the most likely underlying pathological process contributing to the patient's condition? - The symptoms and MRI findings of this 73-year-old man suggest the most likely underlying pathological process is the buildup of amyloid-beta plaques and tau protein tangles, which are characteristic of Alzheimer's disease. These changes often begin in brain regions involved in memory, such as the hippocampus and temporal lobes, leading to the gradual memory decline, disorientation, and personality changes observed in the patient.
0.36: A 38-year-old man presents with sudden onset abdominal pain and undergoes an emergent laparoscopic appendectomy. After the surgery, he experiences visual and auditory hallucinations. Given his lack of prior medical or psychiatric history, what mechanism of action of the anesthetic used in this case is most likely responsible for these symptoms? - The 38-year-old man's visual and auditory hallucinations following his appendectomy are likely associated with the use of ketamine as part of the anesthetic regimen. Ketamine is a dissociative anesthetic that works primarily by blocking NMDA receptors, which are involved in glutamate neurotransmission in the brain. This blockade can lead to alterations in sensory perception, resulting in hallucinations. While ketamine might not be the primary anesthetic used in typical procedures such as an appendectomy, it is sometimes included for its analgesic properties and rapid recovery profile. Given its distinctive mechanism of action and known side effects, ketamine is the most plausible cause of the hallucinations observed in this patient.
0.34: A 59-year-old man presents with abdominal pain, severe vomiting, a recent tonic-clonic seizure, and ventricular tachycardia after ingesting an unknown medication in a suicide attempt. He has a medical history of COPD, coronary artery disease, and chronic back pain. Laboratory tests show hyperglycemia and hypokalemia. Considering these clinical findings, which drug overdose is most likely responsible for his current condition? - Based on the clinical findings, the drug overdose most likely responsible for the man's condition is Theophylline. This medication, commonly used for COPD, can cause seizures, ventricular tachycardia, hypokalemia, and hyperglycemia when taken in excessive amounts. The symptoms and his medical history align with a Theophylline overdose.
0.33: A 45-year-old man presents with symptoms including a wide-based gait, a blank facial expression, hallucinations, memory issues, a resting tremor that resolves with movement, and bradykinesia. Based on these clinical findings, what is most likely to be observed in the histological specimen of his brain? - Based on the clinical findings presented—wide-based gait, blank facial expression, hallucinations, memory issues, resting tremor that resolves with movement, and bradykinesia—it is likely that the 45-year-old man is experiencing a condition related to Parkinsonism, possibly Parkinson's disease or dementia with Lewy bodies. Both of these conditions are associated with the presence of Lewy bodies in the brain. Lewy bodies are abnormal aggregates of protein, primarily alpha-synuclein, which can cause both the motor and cognitive symptoms observed in this patient. Therefore, in the histological specimen of his brain, you would most likely observe the presence of Lewy bodies.
```

# 取得したコンテキストを使用して応答を生成する

クエリ結果から最も一致する結果を選択し、取得したコンテキストと元の質問を組み合わせてOpenAI Responses APIを使用して最終的な回答を生成します。

```py
# 上位3件の一致コンテキストを取得して連結します。
matches = index.query(
    vector=[client.embeddings.create(input=query, model=MODEL).data[0].embedding],
    top_k=3,
    include_metadata=True
)['matches']

context = "\n\n".join(
    f"Question: {m['metadata'].get('Question', '')}\nAnswer: {m['metadata'].get('Answer', '')}"
    for m in matches
)
# コンテキストを使用して最終的な回答を生成します。
response = client.responses.create(
    model="gpt-4o",
    input=f"内部知識ベースに従って、コンテキスト: {context} と質問: {query} に基づいて回答を提供してください",
)
print("\n最終回答:")
print(response.output_text)
```
```

最終回答:
45歳の男性がアルコール使用の既往があり、混乱、運動失調、眼筋麻痺を呈している場合、最も可能性の高い診断はウェルニッケ脳症です。これは、チアミン（ビタミンB1）の欠乏が原因で起こる神経疾患で、特に長期間のアルコール使用者において発生することがあります。

### 推奨される治療法:
- **チアミン補充**: ウェルニッケ脳症を疑う場合、直ちに静脈内または筋肉内で高用量のチアミンを投与することが重要です。
- **栄養管理**: 確実に適切な栄養を摂取し、他のビタミンやミネラルの欠乏も補うようにします。

早期のチアミン投与が予後の改善に非常に重要です。
```

# マルチツール呼び出しのオーケストレーション

ここでは、Responses APIを通じて利用可能な組み込み関数を定義し、外部ベクトルストアであるPineconeを例として呼び出す機能を含めます。

*Web検索プレビューツール*: モデルがライブウェブ検索を実行し、結果をプレビューできるようにします。これは、インターネットからリアルタイムまたは最新情報を取得するのに理想的です。

*Pinecone検索ツール*: モデルがセマンティック検索を使用してベクトルデータベースをクエリできるようにします。これは、ベクトル化された形式で保存された医療文献やその他のドメイン固有のコンテンツなどの関連ドキュメントを取得するのに特に役立ちます。

```py
# ツールの定義: ツールのリストには以下が含まれます:
# - ウェブ検索プレビューツール。
# - 医療文書を取得するためのPinecone検索ツール。

# 利用可能なツールを定義します。
tools = [   
    {"type": "web_search_preview",
      "user_location": {
        "type": "approximate",
        "country": "US",
        "region": "California",
        "city": "SF"
      },
      "search_context_size": "medium"},
    {
        "type": "function",
        "name": "PineconeSearchDocuments",
        "description": "ユーザーが尋ねた医療質問に基づいて、ベクトルデータベース内に保存されている関連文書をセマンティッククエリを使用して検索します。",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "ベクトルデータベースを検索するための自然言語クエリ。"
                },
                "top_k": {
                    "type": "integer",
                    "description": "返すトップ結果の数。",
                    "default": 3
                }
            },
            "required": ["query"],
            "additionalProperties": False
        }
    }
]
```

```py
# モデルが適切にルーティングするべき例のクエリ。
queries = [
    {"query": "1983年のクリケットワールドカップで優勝したのは誰ですか？"},
    {"query": "インターネットによると、アメリカ合衆国で最も一般的な死因は何ですか？"},
    {"query": ("鎌状赤血球症の7歳の男の子が膝と股関節の痛みを訴え、過去に痛みの危機で入院したことがあり、"
               "現在は足を引きずって歩いています。彼の検査では正常で冷たい股関節が見られ、"
               "可動域が減少し、歩行時に痛みがあります。内部知識ベースによると、"
               "管理の次の最も適切なステップは何ですか？")}
]
```

```py
# 各クエリを動的に処理します。
for item in queries:
    input_messages = [{"role": "user", "content": item["query"]}]
    print("\n🌟--- クエリを処理中 ---🌟")
    print(f"🔍 **ユーザーのクエリ:** {item['query']}")
    
    # ツールを有効にしてResponses APIを呼び出し、並行ツール呼び出しを許可します。
    response = client.responses.create(
        model="gpt-4o",
        input=[
            {"role": "system", "content": "質問が提示されたときに、質問に基づいて使用する適切なツールを選択してください。"
            },
            {"role": "user", "content": item["query"]}
        ],
        tools=tools,
        parallel_tool_calls=True
    )
    
    print("\n✨ **初期のレスポンス出力:**")
    print(response.output)
    
    # ツール呼び出しが必要かどうかを判断し、適宜処理します。
    if response.output:
        tool_call = response.output[0]
        if tool_call.type in ["web_search_preview", "function_call"]:
            tool_name = tool_call.name if tool_call.type == "function_call" else "web_search_preview"
            print(f"\n🔧 **モデルがツール呼び出しをトリガーしました:** {tool_name}")
            
            if tool_name == "PineconeSearchDocuments":
                print("🔍 **PineconeSearchDocumentsツールを呼び出しています...**")
                res = query_pinecone_index(client, index, MODEL, item["query"])
                if res["matches"]:
                    best_match = res["matches"][0]["metadata"]
                    result = f"**質問:** {best_match.get('Question', 'N/A')}\n**回答:** {best_match.get('Answer', 'N/A')}"
                else:
                    result = "**インデックスに一致する文書が見つかりませんでした。**"
                print("✅ **PineconeSearchDocumentsツールが正常に呼び出されました。**")
            else:
                print("🔍 **シミュレートされたウェブ検索ツールを呼び出しています...**")
                result = "**シミュレートされたウェブ検索結果。**"
                print("✅ **シミュレートされたウェブ検索ツールが正常に呼び出されました。**")
            
            # ツール呼び出しとその出力を会話に追加します。
            input_messages.append(tool_call)
            input_messages.append({
                "type": "function_call_output",
                "call_id": tool_call.call_id,
                "output": str(result)
            })
            
            # ツールの結果を組み込んだ最終回答を取得します。
            final_response = client.responses.create(
                model="gpt-4o",
                input=input_messages,
                tools=tools,
                parallel_tool_calls=True
            )
            print("\n💡 **最終回答:**")
            print(final_response.output_text)
        else:
            # ツール呼び出しがトリガーされない場合、レスポンスを直接表示します。
            print("💡 **最終回答:**")
            print(response.output_text)
```
```

🌟--- クエリを処理中 ---🌟
🔍 **ユーザーのクエリ:** 1983年のクリケットワールドカップで優勝したのは誰ですか？

✨ **初期のレスポンス出力:**
[ResponseOutputMessage(id='msg_67f32e8f6e1881919026a86e743140e1021525310a92a2c4', content=[ResponseOutputText(annotations=[], text='1983年のクリケットワールドカップで優勝したのはインドです。', type='output_text')], role='assistant', status='completed', type='message')]
💡 **最終回答:**
1983年のクリケットワールドカップで優勝したのはインドです。

🌟--- クエリを処理中 ---🌟
🔍 **ユーザーのクエリ:** インターネットによると、アメリカ合衆国で最も一般的な死因は何ですか？

✨ **初期のレスポンス出力:**
[ResponseFunctionWebSearch(id='ws_67f32e903fdc81918e3e97e29bdf5fc20c56aa3270664fca', status='completed', type='web_search_call'), ResponseOutputMessage(id='msg_67f32e926360819188e7e32559dad0150c56aa3270664fca', content=[ResponseOutputText(annotations=[AnnotationURLCitation(end_index=159, start_index=87, title='新型コロナ、米で３番目の死因に\u3000ＣＤＣ暫定統計 - CNN.co.jp', type='url_citation', url='https://www.cnn.co.jp/usa/35168685.html?utm_source=openai'), AnnotationURLCitation(end_index=282, start_index=210, title='新型コロナ、米で３番目の死因に\u3000ＣＤＣ暫定統計 - CNN.co.jp', type='url_citation', url='https://www.cnn.co.jp/usa/35168685.html?utm_source=openai'), AnnotationURLCitation(end_index=487, start_index=341, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=697, start_index=551, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai')], text='アメリカ合衆国における最新の主要な死因は以下の通りです：\n\n1. **心疾患**：2023年には約69万8,882人が心疾患で亡くなり、依然として主要な死因となっています。 ([cnn.co.jp](https://www.cnn.co.jp/usa/35168685.html?utm_source=openai))\n\n2. **がん**：同年、約59万8,932人ががんで亡くなり、2番目に多い死因となっています。 ([cnn.co.jp](https://www.cnn.co.jp/usa/35168685.html?utm_source=openai))\n\n3. **不慮の傷害**：2023年には約22万2,518人が不慮の傷害で亡くなり、3番目の死因となっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n新型コロナウイルス感染症（COVID-19）は、2020年には3番目の死因でしたが、2023年には10番目に下がりました。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\nこれらの統計は、米国疾病予防管理センター（CDC）の報告に基づいています。 ', type='output_text')], role='assistant', status='completed', type='message')]
💡 **最終回答:**
アメリカ合衆国における最新の主要な死因は以下の通りです：

1. **心疾患**：2023年には約69万8,882人が心疾患で亡くなり、依然として主要な死因となっています。 ([cnn.co.jp](https://www.cnn.co.jp/usa/35168685.html?utm_source=openai))

2. **がん**：同年、約59万8,932人ががんで亡くなり、2番目に多い死因となっています。 ([cnn.co.jp](https://www.cnn.co.jp/usa/35168685.html?utm_source=openai))

3. **不慮の傷害**：2023年には約22万2,518人が不慮の傷害で亡くなり、3番目の死因となっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))

新型コロナウイルス感染症（COVID-19）は、2020年には3番目の死因でしたが、2023年には10番目に下がりました。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))

これらの統計は、米国疾病予防管理センター（CDC）の報告に基づいています。 

🌟--- クエリを処理中 ---🌟
🔍 **ユーザーのクエリ:** 鎌状赤血球症の7歳の男の子が膝と股関節の痛みを訴え、過去に痛みの危機で入院したことがあり、現在は足を引きずって歩いています。彼の検査では正常で冷たい股関節が見られ、可動域が減少し、歩行時に痛みがあります。内部知識ベースによると、管理の次の最も適切なステップは何ですか？

✨ **初期のレスポンス出力:**
[ResponseFunctionToolCall(arguments='{"query":"7歳 男の子 鎌状赤血球症 膝 股関節の痛み 股関節滑膜炎 疑い 痛み管理 ステップ","top_k":1}', call_id='call_uzKdOUCHVh83HgiofeaapGEr', name='PineconeSearchDocuments', type='function_call', id='fc_67f32e958aa08191848e96e56e8fb2eb07c4b8e4375ad72a', status='completed')]

🔧 **モデルがツール呼び出しをトリガーしました:** PineconeSearchDocuments
🔍 **PineconeSearchDocumentsツールを呼び出しています...**
クエリ結果:
0.59: A 7-year-old boy with sickle cell disease is experiencing knee and hip pain, has been admitted for pain crises in the past, and now walks with a limp. His physical exam shows a normal and cool hip to the touch, with decreased range of motion at the hip and pain with ambulation. Given these findings, what is the most appropriate next step in the management of this patient's hip pain? - In managing the hip pain of a 7-year-old boy with sickle cell disease, who presents with knee and hip pain, a limp, and decreased range of motion in the hip, the most appropriate next step is to obtain an X-ray of the hip. This will help evaluate the possibility of avascular necrosis (AVN) or other structural abnormalities. X-rays are typically the first-line imaging technique in such cases due to their accessibility and ability to reveal gross pathological changes. If the X-ray does not provide conclusive information and clinical suspicion of AVN remains high, an MRI may subsequently be considered for a more detailed assessment.
0.41: A 3-year-old boy presents with recurrent long bone fractures following minor trauma, accompanied by decreased bone density on X-ray and he is at the lower percentiles for height and weight. Given these symptoms, what is the most likely underlying genetic condition causing his symptoms? - The most likely underlying genetic condition causing this 3-year-old boy's symptoms is osteogenesis imperfecta. This condition is often due to mutations in the COL1A1 or COL1A2 genes, which are crucial for producing type I collagen. Type I collagen is an essential component of bone, and any defects in its production can lead to the weakened bone structure we see in osteogenesis imperfecta, resulting in the easy fractures and decreased bone density observed in the boy. The fact that he is also at lower percentiles for height and weight aligns with the impact this condition can have on overall growth and development.
0.41: What is the most likely diagnosis for a 2-year-old 70 kg child who presents with limitation of abduction and internal rotation, tenderness in Scarpa's triangle, and abduction of the limb upon flexing the hip? - Based on the described symptoms and the unusual weight for a 2-year-old child, the most likely diagnosis is Slipped Capital Femoral Epiphysis (SCFE). Even though SCFE typically occurs in older children, mainly adolescents, the combination of excessive weight, limited hip abduction and internal rotation, tenderness in the hip area, and the characteristic limb movement (abduction upon hip flexion) strongly points towards SCFE as the most plausible diagnosis in this scenario.
0.38: A 5-year-old boy has recurrent abdominal pain primarily occurring during school hours, with no significant findings on physical examination and normal stool characteristics. His symptoms resolve at home, and his laboratory tests and abdominal exam are unremarkable. Considering the psychological factors involved, what is the most appropriate next step in managing his condition? - Given the symptoms and the context you've provided, it seems quite possible that the boy's recurrent abdominal pain is linked to psychological stressors related to school. Since all medical tests and examinations have returned normal results, this suggests that the pain might be stress-induced, possibly due to anxiety or stress at school. 

The most appropriate next step is to focus on addressing any potential psychological or emotional factors. Consulting a psychologist or school counselor would be beneficial. They can work with the boy to explore any underlying emotional issues or anxieties about school. Through conversation, play, or other therapeutic techniques suitable for his age, they can help identify and manage any stressors he might be facing. This approach could not only help alleviate his abdominal pain but also improve his overall well-being by addressing the source of his anxiety.
0.38: In a patient who, five days post-open colectomy for colon cancer, develops severe pain and swelling of the left calf along with necrotic lesions, a fever, and thrombocytopenia while on unfractionated heparin, what is the most appropriate next step in management? - In this clinical scenario, the presentation of severe pain and swelling in the calf, necrotic skin lesions, fever, and thrombocytopenia in a patient receiving unfractionated heparin strongly suggests heparin-induced thrombocytopenia (HIT). HIT is a prothrombotic disorder caused by antibodies against heparin-platelet factor 4 complexes, leading to platelet activation, thrombocytopenia, and an increased risk of thrombosis.

The most appropriate next step in management is to immediately discontinue the unfractionated heparin to prevent further complications related to thrombosis. Simultaneously, it's crucial to initiate an alternative anticoagulant that does not cross-react with HIT antibodies to manage the thrombotic risk. Argatroban or fondaparinux are commonly used anticoagulants in this context as they are safe and effective for patients with HIT. Direct-acting oral anticoagulants (DOACs) are also potential options, but argatroban is often preferred initially due to its intravenous route and ability to be titrated easily in acute care settings. This dual approach addresses both the cause and the risk effectively.
✅ **PineconeSearchDocumentsツールが正常に呼び出されました。**

💡 **最終回答:**
この7歳の男の子の股関節の痛みを管理する次の最も適切なステップは、股関節のX線検査を行うことです。これは、無血管性壊死（AVN）や他の構造的異常の可能性を評価するために役立ちます。
```

上記のように、クエリに応じて適切なツールが呼び出され、最適な応答が決定されます。

例えば、3番目の例を見ると、モデルが「PineconeSearchDocuments」というツールをトリガーすると、コードは現在のクエリで`query_pinecone_index`を呼び出し、最適な一致（または適切なコンテキスト）を結果として抽出します。健康に関連しない問い合わせや明示的なインターネット検索が求められるクエリの場合、コードは`web_search_call`関数を呼び出し、その他のクエリについては、ツールを呼び出さずに質問に基づいて応答を提供することもあります。

最後に、ツールの呼び出しとその出力が会話に追加され、最終的な回答がResponses APIによって生成されます。

# マルチツール呼び出しのオーケストレーションフロー

次に、入力クエリとシステム指示をResponses APIに従ってツール呼び出しシーケンスを実行し、出力を生成するように変更してみましょう。

```py
# クエリを1つ処理して、レスポンス出力の一部としてツール呼び出しと関数呼び出しを理解します。
item = "アメリカ合衆国で最も一般的な死因は何ですか？"

# ユーザーのクエリで入力メッセージを初期化します。
input_messages = [{"role": "user", "content": item}]
print("\n🌟--- クエリを処理中 ---🌟")
print(f"🔍 **ユーザーのクエリ:** {item}")
    
# ツールを有効にしてResponses APIを呼び出し、並行ツール呼び出しを許可します。
print("\n🔧 **ツールを有効にしてResponses APIを呼び出し中**")
print("\n🕵️‍♂️ **ステップ1: ウェブ検索呼び出し**")
print("   - 初期情報を収集するためにウェブ検索を開始します。")
print("\n📚 **ステップ2: Pinecone検索呼び出し**")
print("   - 内部知識ベースから関連する例を見つけるためにPineconeをクエリします。")
    
response = client.responses.create(
        model="gpt-4o",
        input=[
            {"role": "system", "content": "質問が提示されるたびに、最初にウェブ検索ツールを呼び出して結果を取得し、その後、内部知識ベースで実際の例を見つけるために`PineconeSearchDocuments`を呼び出します。"},
            {"role": "user", "content": item}
        ],
        tools=tools,
        parallel_tool_calls=True
    )
    
# 初期のレスポンス出力を表示します。
print("input_messages", input_messages)

print("\n✨ **初期のレスポンス出力:**")
print(response.output)
```
```

🌟--- クエリを処理中 ---🌟
🔍 **ユーザーのクエリ:** アメリカ合衆国で最も一般的な死因は何ですか？

🔧 **ツールを有効にしてResponses APIを呼び出し中**

🕵️‍♂️ **ステップ1: ウェブ検索呼び出し**
   - 初期情報を収集するためにウェブ検索を開始します。

📚 **ステップ2: Pinecone検索呼び出し**
   - 内部知識ベースから関連する例を見つけるためにPineconeをクエリします。
input_messages [{'role': 'user', 'content': 'アメリカ合衆国で最も一般的な死因は何ですか？'}]

✨ **初期のレスポンス出力:**
[ResponseFunctionWebSearch(id='ws_67f32ed0739881919acd50307a65c4ef01542f478b305ba5', status='completed', type='web_search_call'), ResponseOutputMessage(id='msg_67f32ed2a134819194f84799e6d949a401542f478b305ba5', content=[ResponseOutputText(annotations=[AnnotationURLCitation(end_index=179, start_index=86, title='US deaths are down and life expectancy is up, but improvements are slowing', type='url_citation', url='https://apnews.com/article/be061f9f14c883178eea6dddc9550e60?utm_source=openai'), AnnotationURLCitation(end_index=380, start_index=234, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=597, start_index=451, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=786, start_index=640, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=983, start_index=837, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=1182, start_index=1036, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=1370, start_index=1224, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=1558, start_index=1412, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=1762, start_index=1616, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai'), AnnotationURLCitation(end_index=1989, start_index=1843, title='昨年の米国の死者数は6%減少、新型コロナウイルスが主要な死亡原因の3位から10位に下がった - ja', type='url_citation', url='https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai')], text='アメリカ合衆国における最新の主要な死因は以下のとおりです：\n\n1. **心疾患**：2023年には約300万人以上が心疾患で亡くなり、依然として主要な死因となっています。 ([apnews.com](https://apnews.com/article/be061f9f14c883178eea6dddc9550e60?utm_source=openai))\n\n2. **がん**：2019年から2023年にかけて、年間65万人から70万人ががんで亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n3. **不慮の事故**：2023年には22万2,518人が不慮の事故で亡くなり、これは主に薬物過剰摂取による死亡の増加によるものです。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n4. **脳卒中**：2023年には162,639人が脳卒中で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n5. **慢性下気道疾患**：2023年には145,350人が慢性下気道疾患で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n6. **アルツハイマー病**：2023年には114,034人がアルツハイマー病で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n7. **糖尿病**：2023年には95,181人が糖尿病で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n8. **腎臓病**：2023年には55,250人が腎臓病で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n9. **慢性肝疾患および肝硬変**：2023年には52,220人が慢性肝疾患および肝硬変で亡くなっています。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\n10. **新型コロナウイルス感染症（COVID-19）**：2023年には49,928人がCOVID-19で亡くなり、主要な死因の中で10位となりました。 ([drugslib.com](https://drugslib.com/news/us-deaths-dropped-6-last-year-as-covid-fell-from-3rd-to-10th-leading-killer-2573/ja/?utm_source=openai))\n\nこれらの統計は、2023年のデータに基づいており、最新の情報を反映しています。 ', type='output_text')], role='assistant', status='completed', type='message'), ResponseFunctionToolCall(arguments='{"query":"most common causes of death in the United States 2023"}', call_id='call_KIFl80zRai3cVsT0dr3oZ6Ar', name='PineconeSearchDocuments', type='function_call', id='fc_67f32ed75ac88191afdd1fce8a17a72c01542f478b305ba5', status='completed')]
```

```py
# ツール呼び出しと関数呼び出しをレスポンス出力の一部として理解します

# ツール呼び出しと関数呼び出しの詳細を格納するリストを作成します
tool_calls = []

# レスポンス出力を反復処理して詳細を収集します
for i in response.output:
    tool_calls.append({
        "Type": i.type,
        "Call ID": i.call_id if hasattr(i, 'call_id') else i.id if hasattr(i, 'id') else "N/A",
        "Output": str(i.output) if hasattr(i, 'output') else "N/A",
        "Name": i.name if hasattr(i, 'name') else "N/A"
    })

# リストをデータフレームに変換して表形式で表示します
df_tool_calls = spark.createDataFrame(tool_calls)

# データフレームを表示します
display(df_tool_calls)
```

クエリーに応じてツールが使い分けられていることがわかります。

|Call ID|Name|Output|Type|
|---|---|---|---|
|ws_67f32ed0739881919acd50307a65c4ef01542f478b305ba5|N/A|N/A|web_search_call|
|msg_67f32ed2a134819194f84799e6d949a401542f478b305ba5|N/A|N/A|message|
|call_KIFl80zRai3cVsT0dr3oZ6Ar|PineconeSearchDocuments|N/A|function_call|

```py
tool_call_1 = response.output[0]
print(tool_call_1)
print(tool_call_1.id)

tool_call_2 = response.output[2]
print(tool_call_2)
print(tool_call_2.call_id)
```
```
ResponseFunctionWebSearch(id='ws_67f32ed0739881919acd50307a65c4ef01542f478b305ba5', status='completed', type='web_search_call')
ws_67f32ed0739881919acd50307a65c4ef01542f478b305ba5
ResponseFunctionToolCall(arguments='{"query":"most common causes of death in the United States 2023"}', call_id='call_KIFl80zRai3cVsT0dr3oZ6Ar', name='PineconeSearchDocuments', type='function_call', id='fc_67f32ed75ac88191afdd1fce8a17a72c01542f478b305ba5', status='completed')
call_KIFl80zRai3cVsT0dr3oZ6Ar
```

```py
# ツール呼び出しとその出力を会話に追加します
input_messages.append(response.output[2])
input_messages.append({
    "type": "function_call_output",
    "call_id": tool_call_2.call_id,
    "output": str(result)
})
print(input_messages)
```
```
[{'role': 'user', 'content': 'アメリカ合衆国で最も一般的な死因は何ですか？'}, ResponseFunctionToolCall(arguments='{"query":"most common causes of death in the United States 2023"}', call_id='call_KIFl80zRai3cVsT0dr3oZ6Ar', name='PineconeSearchDocuments', type='function_call', id='fc_67f32ed75ac88191afdd1fce8a17a72c01542f478b305ba5', status='completed'), {'type': 'function_call_output', 'call_id': 'call_KIFl80zRai3cVsT0dr3oZ6Ar', 'output': "**質問:** A 7-year-old boy with sickle cell disease is experiencing knee and hip pain, has been admitted for pain crises in the past, and now walks with a limp. His physical exam shows a normal and cool hip to the touch, with decreased range of motion at the hip and pain with ambulation. Given these findings, what is the most appropriate next step in the management of this patient's hip pain?\n**回答:** In managing the hip pain of a 7-year-old boy with sickle cell disease, who presents with knee and hip pain, a limp, and decreased range of motion in the hip, the most appropriate next step is to obtain an X-ray of the hip. This will help evaluate the possibility of avascular necrosis (AVN) or other structural abnormalities. X-rays are typically the first-line imaging technique in such cases due to their accessibility and ability to reveal gross pathological changes. If the X-ray does not provide conclusive information and clinical suspicion of AVN remains high, an MRI may subsequently be considered for a more detailed assessment."}]
```

```py
# ツールの結果を取り入れて最終回答を取得します。
print("\n🔧 **最終回答のためのレスポンスAPI呼び出し**")

response_2 = client.responses.create(
    model="gpt-4o",
    input=input_messages,
)
print(response_2)
```
```
🔧 **最終回答のためのレスポンスAPI呼び出し**
Response(id='resp_67f32efeb3f481919c38d38869c7c29b01542f478b305ba5', created_at=1743990526.0, error=None, incomplete_details=None, instructions=None, metadata={}, model='gpt-4o-2024-08-06', object='response', output=[ResponseOutputMessage(id='msg_67f32eff133c81919c7a31f7f4e0327501542f478b305ba5', content=[ResponseOutputText(annotations=[], text='アメリカ合衆国で最も一般的な死因の一つは心臓病です。心血管疾患は長年にわたり主要な死因となっていますが、具体的な統計は年によって変動するため、最新のデータを確認することが重要です。', type='output_text')], role='assistant', status='completed', type='message')], parallel_tool_calls=True, temperature=1.0, tool_choice='auto', tools=[], top_p=1.0, max_output_tokens=None, previous_response_id=None, reasoning=Reasoning(effort=None, generate_summary=None), status='completed', text=ResponseTextConfig(format=ResponseFormatText(type='text')), truncation='disabled', usage=ResponseUsage(input_tokens=283, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=71, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=354), user=None, store=True)
```

```py
# 最終回答を表示
display(response_2.output_text)
```
```
'アメリカ合衆国で最も一般的な死因の一つは心臓病です。心血管疾患は長年にわたり主要な死因となっていますが、具体的な統計は年によって変動するため、最新のデータを確認することが重要です。'
```

ここでは、OpenAIのResponses APIを利用して、マルチツール呼び出し機能を備えた検索強化生成（RAG）アプローチを実装する方法を見てきました。モデルが入力クエリに基づいて適切なツールを選択する例を紹介しています。一般的な質問はweb検索などの組み込みツールで処理され、内部知識に関連する特定の医療問い合わせは、関数呼び出しを介してベクターデータベース（Pineconeなど）からコンテキストを取得して対応します。さらに、複数のツール呼び出しを順次組み合わせて、Responses APIに提供された指示に基づいて最終的な応答を生成する方法も紹介しました。

これらの概念を実験し構築し続ける中で、追加のリソースや例を探索して理解と応用をさらに深めることを検討してください。

ハッピーコーディング！

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)