「AIにコードを書いてもらうのはできた。でも、実際に天気APIを叩いてきてほしい」
Antigravityで開発していると、こういう一歩先の要求が自然と出てきます。AIアシスタントがコードを「提案する」だけでなく、「実際のデータを使って動く」ところまで自律して動いてほしい。そのための仕組みがFunction Callingです。
PythonからGeminiのFunction Callingを実装するとき、最初は「モデルが自分でAPIを呼ぶのか?」と思いがちです。実際は少し違います。ここではその仕組みの正確な理解と、Antigravity IDE上で実際に動かすコードを一緒に確認していきます。
Function Callingの往復を理解する
コードを書く前に、仕組みを整理しておきます。
Function Callingは「モデルが自分で関数を実行する」ではなく、「モデルが呼び出しを指示し、実行はアプリ側が行う」という設計です。この往復を把握しておくと、デバッグ時に何が起きているか見通せるようになります。
- アプリ側が「使えるツール(関数定義)」をモデルに渡す
- モデルが「この関数を呼んでほしい」というレスポンスを返す(実際には実行しない)
- アプリ側でその関数を実行し、結果をモデルに送る
- モデルが結果を踏まえて最終的な回答を生成する
セキュリティ面でも、この設計は優れています。アプリ側がどの関数を公開するかを完全に制御できるため、モデルが任意のコードを実行することはありません。
環境の準備
まずパッケージをインストールします:
pip install google-generativeai python-dotenvAPIキーはGoogle AI Studioから取得します。コードに直接書かず、環境変数で管理する習慣をつけておきましょう。プロジェクトルートに .env ファイルを作成して:
GOOGLE_API_KEY=YOUR_GEMINI_API_KEY
.gitignore に .env を追加するのも忘れずに。Antigravityのファイルツリーで .gitignore を開くと、インライン補完が候補を出してくれます。
最初のFunction Callingを動かす
シンプルな例として、「都市名を受け取って天気を返すツール」を定義してみます。
import google.generativeai as genai
import os
from dotenv import load_dotenv
load_dotenv()
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
# ① 実際に呼ばれる関数(ここを本物のAPIに差し替える)
def get_current_weather(city: str) -> dict:
"""指定した都市の現在の天気情報を返す(デモ用モック)"""
mock_data = {
"東京": {"weather": "晴れ", "temp": 22, "humidity": 45},
"大阪": {"weather": "曇り", "temp": 20, "humidity": 58},
"福岡": {"weather": "雨", "temp": 18, "humidity": 72},
}
return mock_data.get(city, {"weather": "情報なし", "temp": 0, "humidity": 0})
# ② モデルに渡すツールのスキーマ定義
weather_tool = genai.protos.Tool(
function_declarations=[
genai.protos.FunctionDeclaration(
name="get_current_weather",
description="指定した都市の現在の天気情報を取得する",
parameters=genai.protos.Schema(
type=genai.protos.Type.OBJECT,
properties={
"city": genai.protos.Schema(
type=genai.protos.Type.STRING,
description="天気を調べたい都市名(例: 東京、大阪、福岡)"
)
},
required=["city"]
)
)
]
)
# ③ モデルとチャットセッションの初期化
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
tools=[weather_tool]
)
chat = model.start_chat()
# ④ ユーザーメッセージを送る
response = chat.send_message("東京の今日の天気を教えてください")
# ⑤ Function Callレスポンスを処理する
part = response.candidates[0].content.parts[0]
if hasattr(part, "function_call") and part.function_call.name:
fc = part.function_call
print(f"モデルが要求した関数: {fc.name}")
print(f"引数: {dict(fc.args)}")
# 関数を実行
result = get_current_weather(**dict(fc.args))
print(f"実行結果: {result}")
# 結果をモデルに返す
final_response = chat.send_message(
genai.protos.Content(
parts=[genai.protos.Part(
function_response=genai.protos.FunctionResponse(
name=fc.name,
response={"result": result}
)
)]
)
)
print(f"\nモデルの最終回答:\n{final_response.text}")実行すると、次のような出力が得られます:
モデルが要求した関数: get_current_weather
引数: {'city': '東京'}
実行結果: {'weather': '晴れ', 'temp': 22, 'humidity': 45}
モデルの最終回答:
東京の現在の天気は晴れで、気温22℃、湿度45%です。...
複数ツールを組み合わせたループパターン
実用的なアプリでは、複数のツールをモデルが順番に呼び出すケースが多くなります。「天気を取得して、スケジュールと照合して、メモに保存する」のような一連の処理です。
import google.generativeai as genai
import os
from dotenv import load_dotenv
from datetime import datetime
load_dotenv()
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
# 複数のツール関数
def get_current_weather(city: str) -> dict:
"""都市の天気を返す"""
return {"city": city, "weather": "晴れ", "temp": 22}
def get_today_schedule() -> list:
"""今日のスケジュールを返す(モック)"""
return [
{"time": "10:00", "event": "チームMTG"},
{"time": "14:00", "event": "コードレビュー"},
]
def save_memo(title: str, content: str) -> dict:
"""メモをローカルファイルに保存する"""
filename = f"memo_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(filename, "w", encoding="utf-8") as f:
f.write(f"# {title}\n\n{content}")
return {"saved": True, "filename": filename}
except IOError as e:
return {"saved": False, "error": str(e)}
# ツールディスパッチャー(関数名 → 実装の対応表)
TOOL_FUNCTIONS = {
"get_current_weather": get_current_weather,
"get_today_schedule": get_today_schedule,
"save_memo": save_memo,
}
tools = [genai.protos.Tool(function_declarations=[
genai.protos.FunctionDeclaration(
name="get_current_weather",
description="指定した都市の天気を取得する",
parameters=genai.protos.Schema(
type=genai.protos.Type.OBJECT,
properties={"city": genai.protos.Schema(
type=genai.protos.Type.STRING, description="都市名"
)},
required=["city"]
)
),
genai.protos.FunctionDeclaration(
name="get_today_schedule",
description="今日の予定一覧を取得する",
parameters=genai.protos.Schema(
type=genai.protos.Type.OBJECT,
properties={}
)
),
genai.protos.FunctionDeclaration(
name="save_memo",
description="タイトルと内容を受け取ってメモを保存する",
parameters=genai.protos.Schema(
type=genai.protos.Type.OBJECT,
properties={
"title": genai.protos.Schema(type=genai.protos.Type.STRING, description="メモのタイトル"),
"content": genai.protos.Schema(type=genai.protos.Type.STRING, description="メモの本文")
},
required=["title", "content"]
)
),
])]
model = genai.GenerativeModel(model_name="gemini-2.0-flash", tools=tools)
chat = model.start_chat()
def run_agent(user_message: str) -> str:
"""Function Callingのループを処理して最終テキストを返す"""
response = chat.send_message(user_message)
# モデルがFunction Callを返す間ループし続ける
for _ in range(10): # 無限ループ防止のため最大10回
parts = response.candidates[0].content.parts
fc_part = next(
(p for p in parts if hasattr(p, "function_call") and p.function_call.name),
None
)
if fc_part is None:
break # テキスト回答が来たのでループ終了
fc = fc_part.function_call
func = TOOL_FUNCTIONS.get(fc.name)
if func is None:
print(f"⚠️ 未定義のツール: {fc.name}")
break
result = func(**dict(fc.args))
# 結果をモデルにフィードバック
response = chat.send_message(
genai.protos.Content(parts=[
genai.protos.Part(function_response=genai.protos.FunctionResponse(
name=fc.name,
response={"result": result}
))
])
)
return response.text
# 実行
result = run_agent(
"東京の天気と今日の予定を確認して、「今日のブリーフィング」というタイトルでメモに保存してください"
)
print(result)run_agent() でFunction Callingのループを一箇所に集約しているのがポイントです。モデルが複数の関数を順次呼び出すケースでも、この構造で自然に対応できます。
Antigravity IDEでデバッグするコツ
Function Callingの実装で詰まりやすいのは「モデルが関数を呼ばない」ケースです。Antigravityのインラインチャット(Cmd+I)で print(response.candidates[0].content.parts) の出力を解析すると速く解決できます。
まずレスポンス構造を丸ごと出力して確認するのが早道です:
# デバッグ用:レスポンスの構造を確認する
response = chat.send_message("東京の天気は?")
for i, part in enumerate(response.candidates[0].content.parts):
print(f"[part {i}] type={type(part).__name__}")
if hasattr(part, "function_call") and part.function_call.name:
print(f" function_call: {part.function_call.name}")
print(f" args: {dict(part.function_call.args)}")
elif hasattr(part, "text"):
print(f" text: {part.text[:100]}")モデルが関数を呼ばない原因は、だいたい次のどれかです。description が曖昧でモデルが「このツールを使うべき場面だ」と判断できていないか、ユーザーメッセージに関数を呼ぶべきシグナルが薄いかのどちらかです。たとえば「天気を教えて」より「天気APIを使って東京の天気を取得して」のほうが、モデルはツールを呼びやすくなります。
Antigravityのインライン補完は genai.protos.Schema の引数候補を出してくれるため、スキーマ定義時のタイポを防ぐのにも役立ちます。
次のステップ
Function Callingを一度動かしてみると、「AIに何かをさせる」ための視界が広がる感覚があります。まず手元のプロジェクトにある既存のユーティリティ関数を1つだけツールとして定義するところから始めてみてください。
Pythonでの本番環境向けのエラーハンドリングやレート制限対策については、Antigravity Python SDK 本番運用マスターガイドが参考になります。より複雑な自律エージェントに発展させたい場合は、AgentKit 2.0 完全ガイドで複数エージェントのオーケストレーションパターンを確認してみましょう。
Function Callingに慣れると、AIが「提案する」だけのツールから、「実際に動く」ツールへと使い方が変わってきます。