「東京の天気を教えて」のような質問に、AIが最新の天気データを取得して回答するアシスタントを作ります。
今回のポイントは、AI自身に天気APIを直接呼ばせるのではなく、AIには「どの関数を、どの引数で呼ぶか」を判断させ、実際のAPI呼び出しはアプリケーション側で行うことです。
OpenAI APIのFunction Calling(現在のドキュメントではTool Callingとも表記されます)は、このような外部データ連携に利用できます。
Function Callingの流れ
Function Callingを使った天気検索処理の流れは次の通りです。
処理は次の4段階です。
- AIに利用可能な関数の名前・説明・引数を渡す
- AIが質問に応じて関数呼び出しを返す
- アプリケーションが実際の天気APIを呼び出す
- 結果をAIに渡し、自然な文章を生成する
重要なのは、AIが関数の実装そのものを実行するわけではない点です。AIは構造化された呼び出し情報を返すだけで、APIアクセスや認証、エラー処理はアプリケーションが担当します。
環境を準備する
今回はPythonとOpenAI公式SDKを使います。天気データの取得には、サンプルとしてOpen-MeteoのAPIを利用します。
python -m venv .venv
source .venv/bin/activate
python -m pip install -U openai requests
Windows PowerShellの場合は、仮想環境の有効化を次のように行います。
.venv\Scripts\Activate.ps1
python -m pip install -U openai requests
APIキーはソースコードに直接書かず、環境変数で設定します。
export OPENAI_API_KEY="YOUR_API_KEY"
export OPENAI_MODEL="gpt-5"
Windows PowerShellでは次の形式です。
$env:OPENAI_API_KEY="YOUR_API_KEY"
$env:OPENAI_MODEL="gpt-5"
OPENAI_MODELには、利用している環境で使用可能なモデルIDを指定してください。
天気検索関数を実装する
まず、都市名から緯度・経度を取得し、その座標を使って現在の天気を取得します。
import requests
def weather_description(code: int) -> str:
if code == 0:
return "快晴"
if code in {1, 2, 3}:
return "晴れまたは曇り"
if code in {45, 48}:
return "霧"
if code in {51, 53, 55, 56, 57}:
return "霧雨"
if code in {61, 63, 65, 66, 67}:
return "雨"
if code in {71, 73, 75, 77}:
return "雪"
if code in {80, 81, 82}:
return "にわか雨"
if code in {95, 96, 99}:
return "雷雨"
return "不明"
def get_weather(location: str) -> dict:
geocoding = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={
"name": location,
"count": 1,
"language": "ja",
"format": "json",
},
timeout=10,
)
geocoding.raise_for_status()
places = geocoding.json().get("results", [])
if not places:
return {"error": f"{location}の位置情報が見つかりません"}
place = places[0]
forecast = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": place["latitude"],
"longitude": place["longitude"],
"current": (
"temperature_2m,apparent_temperature,"
"precipitation,weather_code"
),
"timezone": "auto",
},
timeout=10,
)
forecast.raise_for_status()
current = forecast.json()["current"]
return {
"location": place["name"],
"country": place.get("country", ""),
"temperature_c": current["temperature_2m"],
"apparent_temperature_c": current["apparent_temperature"],
"precipitation_mm": current["precipitation"],
"description": weather_description(current["weather_code"]),
"timezone": forecast.json().get("timezone"),
}
Function Callingで返してほしい値は、できるだけ小さなJSONにします。今回なら都市名、気温、体感温度、降水量、天気の説明だけで十分です。
AIに天気ツールを渡す
次に、AIが利用できる関数をJSON Schemaで定義します。
import json
import os
from openai import OpenAI
client = OpenAI()
model = os.getenv("OPENAI_MODEL", "gpt-5")
weather_tool = {
"type": "function",
"name": "get_weather",
"description": "指定された都市の現在の天気を取得します。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名。例: 東京、大阪、札幌",
}
},
"required": ["location"],
"additionalProperties": False,
},
"strict": True,
}
strictを有効にする場合、requiredにすべてのプロパティを含め、additionalPropertiesをfalseにする必要があります。ここを忘れると、リクエスト時にスキーマエラーになりやすいです。
Function Callingの結果を処理する
以下がアシスタント本体です。
from weather import get_weather
def run_assistant(question: str) -> str:
input_items = [
{
"role": "user",
"content": question,
}
]
for _ in range(3):
response = client.responses.create(
model=model,
input=input_items,
tools=[weather_tool],
parallel_tool_calls=False,
)
function_calls = [
item
for item in response.output
if item.type == "function_call"
]
# 関数呼び出しがなければ、通常の回答が完成している
if not function_calls:
return response.output_text
# AIの関数呼び出し情報を次の入力に引き継ぐ
input_items.extend(response.output)
for call in function_calls:
arguments = json.loads(call.arguments)
if call.name == "get_weather":
result = get_weather(**arguments)
else:
result = {"error": f"未対応の関数です: {call.name}"}
input_items.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(
result,
ensure_ascii=False,
),
}
)
return "ツール呼び出しが繰り返されたため、処理を中断しました。"
if __name__ == "__main__":
question = input("質問: ").strip()
if not question:
question = "東京の現在の天気を教えてください"
print(run_assistant(question))
Function Callingでは、AIが返したcall_idと同じ値をfunction_call_outputに設定します。この対応付けがないと、どの関数呼び出しに対する結果なのかAIが判断できません。
また、関数名をそのまま実行するのではなく、if call.name == "get_weather"のように呼び出し可能な関数を明示的に分岐させています。任意の関数名を実行する設計は避けたほうが安全です。
実行します。
python main.py
入力例です。
質問: 札幌の現在の天気と気温を教えて
実行時には、AIが内部でおおむね次のような呼び出しを生成します。
{
"type": "function_call",
"name": "get_weather",
"arguments": "{\"location\":\"札幌\"}"
}
その後、アプリケーションが天気APIを呼び出し、取得結果をAIへ返します。最終的には「札幌は現在○℃で、雨です。体感温度は○℃、降水量は○mmです」のような自然な回答になります。気温や天気は実行時のデータに応じて変わります。
実装時に気を付けること
まず、天気APIの失敗を考慮します。都市名が見つからない、外部APIがタイムアウトする、レスポンス形式が変わる、といったケースがあります。今回の例ではtimeoutとraise_for_status()を設定していますが、本番では例外を捕捉し、AIには安全なエラーメッセージだけを返す設計にします。
次に、AIの回答をそのまま信じないことです。気温や降水量などの事実は、必ずツールの戻り値を根拠に回答させます。システムプロンプトを追加する場合は、次のような指示が有効です。
instructions = """
あなたは天気案内アシスタントです。
天気に関する質問では必ず get_weather を利用してください。
ツールの結果にない気象情報を推測してはいけません。
"""
そして、APIキーは絶対にクライアント側へ埋め込まないようにします。Webアプリケーションにする場合も、Function Callingと外部APIの呼び出しはサーバー側で実行します。
まとめ
Function Callingを使うと、AIの自然言語理解と、通常のプログラムによる外部API連携を組み合わせられます。
今回の実装で押さえるポイントは次の通りです。
- AIには関数名・説明・JSON Schemaを渡す
- 実際のAPI呼び出しはアプリケーション側で行う
- 結果は
function_call_outputとしてAIへ返す -
call_idで関数呼び出しと結果を対応付ける - スキーマ検証、タイムアウト、APIキー管理を実装する
天気検索をニュース検索、在庫照会、社内データ検索などに置き換えれば、同じ構成で実用的なAIアシスタントへ拡張できます。詳しい仕様はOpenAI公式のFunction Callingガイドも参照してください。