Python AI Agent Tool Calling:信頼性の高い関数呼び出しシステムを構築する5つのコアパターン
関数呼び出しはなぜいつも問題を起こすのか?
AI Agentに5つのツールを接続したのに、モデルが都市名を数値パラメータに渡し、3つのツール間を行き来し、タイムアウト後にクラッシュし、返されたJSONが解析できない——Tool Callingの痛点は想像以上に多い。2026年、OpenAI、Anthropic、Llamaの3大プラットフォームがネイティブTool Calling機能を提供しているが、「動く」と「信頼できる」の間には5つのコアパターンがある。
コア概念クイックリファレンス
| 概念 | 説明 | ポイント |
|---|---|---|
| Tool Calling | モデルが外部ツールを能動的に呼び出す仕組み | 純テキスト生成との違い |
| Function Calling | OpenAIの関数呼び出しプロトコル | toolsパラメータ + tool_choice |
| JSON Schemaパラメータ | Schemaでツール入力構造を定義 | type/required/enum制約 |
| ツール選択戦略 | auto/required/noneおよび指定ツール | モデルがいつツールを呼ぶか制御 |
| 並列呼び出し | モデルが一度に複数のtool_callを返す | 並行実行の処理が必要 |
| 呼び出しチェーン | 複数ステップのツール依存実行 | 前の出力を次の入力に |
| 冪等性 | 同じパラメータで繰り返し呼び出しても同じ結果 | リトライ安全の基盤 |
| タイムアウト&リトライ | 呼び出し失敗後の自動リトライ | 指数バックオフ + 最大試行回数 |
5つの課題を深く分析
| 課題 | 典型的な症状 | 根本原因 |
|---|---|---|
| パラメータ生成エラー | 文字列をintegerに渡す、必須パラメータ欠落、enum値の範囲外 | モデルのSchema理解のズレ |
| マルチツール選択戦略 | 間違ったツール選択、必要な時に使わない、不要な時に強制使用 | ツール記述が不正確 |
| タイムアウト&リトライ | ネットワークジッタによる失敗、リトライストーム、カスケード障害 | バックオフ戦略とサーキットブレーカーの欠如 |
| 結果解析&検証 | 異常な戻り形式、フィールド欠落、型不一致 | 出力Schema検証の欠如 |
| ツール権限&セキュリティ | 危険な操作の実行、悪意のあるパラメータ注入、権限超過アクセス | 権限チェックとサンドボックスの欠如 |
ステップバイステップ:5つのコアパターン
パターン1:OpenAI Function Calling基本統合
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Optional
import json
import os
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "指定された銘柄のリアルタイム価格情報を取得",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "銘柄コード、例:AAPL、GOOGL",
"pattern": "^[A-Z]{1,5}$",
},
"exchange": {
"type": "string",
"description": "取引所",
"enum": ["NASDAQ", "NYSE", "TSE", "HKEX"],
},
},
"required": ["symbol"],
},
},
}
]
class StockResult(BaseModel):
symbol: str
price: float
change: float
exchange: str
def execute_get_stock_price(symbol: str, exchange: str = "NASDAQ") -> dict:
mock_prices = {
"AAPL": {"price": 198.5, "change": 2.3},
"GOOGL": {"price": 175.2, "change": -1.1},
"TSLA": {"price": 245.8, "change": 5.7},
}
data = mock_prices.get(symbol, {"price": 0, "change": 0})
return {"symbol": symbol, "price": data["price"], "change": data["change"], "exchange": exchange}
def basic_tool_calling(user_query: str) -> str:
messages = [
{"role": "system", "content": "あなたは株式分析アシスタントです。ツールを使ってリアルタイムデータを取得してください。"},
{"role": "user", "content": user_query},
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto",
temperature=0,
)
message = response.choices[0].message
if not message.tool_calls:
return message.content or "回答できません"
tool_call = message.tool_calls[0]
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"ツール呼び出し: {func_name}({json.dumps(func_args)})")
if func_name == "get_stock_price":
result = execute_get_stock_price(**func_args)
else:
result = {"error": f"不明なツール: {func_name}"}
validated = StockResult(**result) if "error" not in result else result
messages.append(message.to_dict())
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(validated if isinstance(validated, dict) else validated.model_dump()),
})
final = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0,
)
return final.choices[0].message.content
if __name__ == "__main__":
answer = basic_tool_calling("Appleの現在の株価は?")
print(answer)
パターン2:マルチツール登録と自動選択
from typing import Callable
import datetime
class ToolRegistry:
def __init__(self):
self._tools: dict[str, dict] = {}
def register(self, name: str, description: str, parameters: dict, executor: Callable):
self._tools[name] = {
"schema": {
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": parameters,
},
},
"executor": executor,
}
def get_tool_schemas(self) -> list[dict]:
return [t["schema"] for t in self._tools.values()]
def execute(self, name: str, arguments: dict) -> str:
if name not in self._tools:
return json.dumps({"error": f"ツール'{name}'が見つかりません", "available": list(self._tools.keys())})
try:
result = self._tools[name]["executor"](**arguments)
return json.dumps(result, default=str)
except TypeError as e:
return json.dumps({"error": f"パラメータエラー: {e}", "tool": name})
except Exception as e:
return json.dumps({"error": f"実行失敗: {e}", "tool": name})
registry = ToolRegistry()
registry.register(
name="get_weather",
description="指定された都市の天気情報を取得。温度、天気状況、湿度を含む",
parameters={
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名、例:東京、大阪"},
"unit": {"type": "string", "description": "温度単位", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
executor=lambda city, unit="celsius": {
"city": city, "temp": 26 if city == "東京" else 30,
"condition": "晴れ" if city == "東京" else "曇り", "unit": unit,
"updated_at": datetime.datetime.now().isoformat(),
},
)
registry.register(
name="search_web",
description="インターネット検索で最新情報を取得。ニュース、ドキュメント、技術資料に適している",
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "検索キーワード"},
"max_results": {"type": "integer", "description": "最大結果数", "minimum": 1, "maximum": 10},
},
"required": ["query"],
},
executor=lambda query, max_results=3: {
"results": [{"title": f"{query} - 結果{i}", "url": f"https://example.com/{i}"} for i in range(max_results)],
},
)
registry.register(
name="calculate",
description="数式の値を計算。四則演算をサポート",
parameters={
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数式、例:2+3*4"},
"precision": {"type": "integer", "description": "小数精度", "default": 2},
},
"required": ["expression"],
},
executor=lambda expression, precision=2: {"result": round(eval(expression, {"__builtins__": {}}, {}), precision), "expression": expression},
)
def multi_tool_agent(user_query: str, max_steps: int = 6) -> str:
messages = [
{"role": "system", "content": "あなたはインテリジェントアシスタントです。ユーザーの質問に基づいて適切なツールを選択してください。1ステップにつき1つのツールのみ呼び出し、結果を慎重に分析してください。"},
{"role": "user", "content": user_query},
]
for step in range(max_steps):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=registry.get_tool_schemas(),
tool_choice="auto",
temperature=0.1,
)
message = response.choices[0].message
messages.append(message.to_dict())
if not message.tool_calls:
return message.content or "回答を生成できません"
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
result = registry.execute(func_name, func_args)
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
return "最大ステップ数に達しました"
if __name__ == "__main__":
answer = multi_tool_agent("東京の天気は?あと (28+32)*1.5 を計算して")
print(answer)
パターン3:タイムアウトとリトライメカニズム
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class RetryConfig:
max_attempts: int = 3
base_delay: float = 1.0
max_delay: float = 30.0
timeout_seconds: float = 10.0
def exponential_backoff(attempt: int, base_delay: float = 1.0, max_delay: float = 30.0) -> float:
delay = min(base_delay * (2 ** attempt), max_delay)
return delay
def execute_with_retry(
executor: Callable,
arguments: dict,
config: RetryConfig = RetryConfig(),
) -> str:
last_error = None
for attempt in range(config.max_attempts):
try:
result = executor(**arguments)
parsed = json.dumps(result, default=str)
result_obj = json.loads(parsed)
if "error" in result_obj:
raise ValueError(result_obj["error"])
return parsed
except Exception as e:
last_error = e
delay = exponential_backoff(attempt, config.base_delay, config.max_delay)
logger.warning(f"試行{attempt + 1}失敗: {e}、{delay:.1f}s後にリトライ")
time.sleep(delay)
return json.dumps({"error": f"{config.max_attempts}回リトライ後も失敗: {last_error}"})
def tool_calling_with_retry(user_query: str) -> str:
messages = [
{"role": "system", "content": "あなたはインテリジェントアシスタントです。ツールを使って質問に答えてください。"},
{"role": "user", "content": user_query},
]
for step in range(6):
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=registry.get_tool_schemas(),
tool_choice="auto",
temperature=0.1,
timeout=30.0,
)
except Exception as e:
logger.error(f"API呼び出し例外: {e}")
continue
message = response.choices[0].message
messages.append(message.to_dict())
if not message.tool_calls:
return message.content or "回答できません"
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
result = execute_with_retry(
lambda **kwargs: json.loads(registry.execute(func_name, kwargs)),
func_args,
)
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
return "最大ステップ数に達しました"
if __name__ == "__main__":
answer = tool_calling_with_retry("大阪の天気を調べて")
print(answer)
パターン4:ツール結果検証とエラー回復
from pydantic import BaseModel, Field, ValidationError
class WeatherOutput(BaseModel):
city: str
temp: float
condition: str
unit: str = "celsius"
class CalculationOutput(BaseModel):
result: float
expression: str
OUTPUT_SCHEMAS: dict[str, type[BaseModel]] = {
"get_weather": WeatherOutput,
"calculate": CalculationOutput,
}
def validate_and_recover(tool_name: str, raw_result: str) -> dict:
try:
parsed = json.loads(raw_result)
except json.JSONDecodeError:
return {"error": "ツールが非JSON形式を返しました", "raw": raw_result[:200]}
if "error" in parsed:
return parsed
schema_class = OUTPUT_SCHEMAS.get(tool_name)
if not schema_class:
return parsed
try:
validated = schema_class(**parsed)
return validated.model_dump()
except ValidationError as e:
recovery = parsed.copy()
for err in e.errors():
field = err["loc"][0] if err["loc"] else None
if field and field not in recovery:
if err["type"] == "missing":
recovery[field] = None
recovery["_recovered"] = True
recovery["_recovery_note"] = f"フィールド{field}が欠落、デフォルト値で補完"
return recovery
def resilient_tool_agent(user_query: str) -> str:
messages = [
{"role": "system", "content": "あなたはインテリジェントアシスタントです。ツールがエラーを返した場合、原因を分析しパラメータを調整してリトライしてください。"},
{"role": "user", "content": user_query},
]
for step in range(8):
response = client.chat.completions.create(
model="gpt-4o", messages=messages,
tools=registry.get_tool_schemas(), tool_choice="auto", temperature=0.1,
)
message = response.choices[0].message
messages.append(message.to_dict())
if not message.tool_calls:
return message.content or "回答できません"
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
raw_result = registry.execute(func_name, func_args)
validated_result = validate_and_recover(func_name, raw_result)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(validated_result, default=str),
})
return "最大ステップ数に達しました"
if __name__ == "__main__":
answer = resilient_tool_agent("東京の天気は?100/3を計算して")
print(answer)
パターン5:本番級Tool Callingフレームワーク(モニタリング付き)
from dataclasses import dataclass, field
from typing import Any
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class ToolCallMetric:
tool_name: str
start_time: float
end_time: float = 0.0
success: bool = False
error: str = ""
retry_count: int = 0
@dataclass
class AgentRunMetric:
total_steps: int = 0
total_tool_calls: int = 0
total_retries: int = 0
total_duration: float = 0.0
tool_metrics: list[ToolCallMetric] = field(default_factory=list)
errors: list[str] = field(default_factory=list)
class ProductionToolCallingFramework:
def __init__(
self,
tool_registry: ToolRegistry,
model: str = "gpt-4o",
max_steps: int = 10,
max_retries: int = 3,
call_timeout: float = 15.0,
enable_validation: bool = True,
):
self.registry = tool_registry
self.model = model
self.max_steps = max_steps
self.max_retries = max_retries
self.call_timeout = call_timeout
self.enable_validation = enable_validation
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def _execute_tool_safely(self, name: str, arguments: dict) -> tuple[str, ToolCallMetric]:
metric = ToolCallMetric(tool_name=name, start_time=time.time())
last_result = ""
for attempt in range(self.max_retries):
try:
raw = self.registry.execute(name, arguments)
parsed = json.loads(raw)
if "error" in parsed:
metric.retry_count = attempt
if attempt < self.max_retries - 1:
time.sleep(min(1.0 * (2 ** attempt), 30.0))
continue
metric.error = parsed["error"]
last_result = raw
break
if self.enable_validation:
validated = validate_and_recover(name, raw)
last_result = json.dumps(validated, default=str)
else:
last_result = raw
metric.success = True
break
except Exception as e:
metric.retry_count = attempt + 1
metric.error = str(e)
last_result = json.dumps({"error": str(e), "tool": name})
if attempt < self.max_retries - 1:
time.sleep(min(1.0 * (2 ** attempt), 30.0))
metric.end_time = time.time()
return last_result, metric
def run(self, user_query: str, system_prompt: str = "") -> dict:
run_start = time.time()
metrics = AgentRunMetric()
messages = [
{"role": "system", "content": system_prompt or "あなたはプロフェッショナルなAIアシスタントです。ツールを使って質問に答えてください。エラー時は原因を分析しパラメータを調整してリトライしてください。"},
{"role": "user", "content": user_query},
]
tools = self.registry.get_tool_schemas()
for step in range(self.max_steps):
metrics.total_steps = step + 1
try:
response = self.client.chat.completions.create(
model=self.model, messages=messages, tools=tools,
tool_choice="auto", temperature=0.1, timeout=self.call_timeout,
)
except Exception as e:
metrics.errors.append(f"API例外: {e}")
logger.error(f"Step {step + 1} API例外: {e}")
break
message = response.choices[0].message
messages.append(message.to_dict())
if not message.tool_calls:
break
for tool_call in message.tool_calls:
metrics.total_tool_calls += 1
func_name = tool_call.function.name
try:
func_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError as e:
metrics.errors.append(f"パース失敗: {func_name}")
messages.append({"role": "tool", "tool_call_id": tool_call.id,
"content": json.dumps({"error": f"引数JSONパース失敗: {e}"})})
continue
result, tool_metric = self._execute_tool_safely(func_name, func_args)
metrics.tool_metrics.append(tool_metric)
metrics.total_retries += tool_metric.retry_count
if not tool_metric.success:
metrics.errors.append(f"{func_name}: {tool_metric.error}")
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
metrics.total_duration = round(time.time() - run_start, 3)
final_content = messages[-1].get("content", "") if messages else ""
return {
"answer": final_content,
"metrics": {
"steps": metrics.total_steps,
"tool_calls": metrics.total_tool_calls,
"retries": metrics.total_retries,
"duration_s": metrics.total_duration,
"errors": metrics.errors,
"tool_details": [
{"tool": m.tool_name, "success": m.success, "retries": m.retry_count,
"duration_ms": round((m.end_time - m.start_time) * 1000, 1) if m.end_time else 0}
for m in metrics.tool_metrics
],
},
}
if __name__ == "__main__":
framework = ProductionToolCallingFramework(registry, max_steps=8, max_retries=2)
result = framework.run("東京の天気は?Pythonの最新バージョンを検索、(28+32)*1.5を計算")
print(f"回答: {result['answer']}")
print(f"メトリクス: {json.dumps(result['metrics'], ensure_ascii=False, indent=2)}")
落とし穴ガイド
落とし穴1:Schema記述が曖昧
❌ "description": "データを取得" → モデルがいつ使うべきか判断できない
✅ "description": "指定された都市のリアルタイム天気を取得。温度と湿度を含む。天気関連の質問に適している" → 正確なトリガーシーンの記述
落とし穴2:tool_choiceのきめ細かい制御を無視
❌ すべてのシーンで tool_choice: "auto" → モデルがツールを使わない可能性
✅ ツールが必要な時は tool_choice: {"type": "function", "function": {"name": "xxx"}}、純粋な会話では tool_choice: "none"
落とし穴3:ツールが生の文字列を返す
❌ ツールが "28度晴れ" を直接返す → モデルが構造的に処理しにくい
✅ 常にJSONで返す {"temp": 28, "condition": "晴れ", "unit": "celsius"} → 構造化結果が推論に有利
落とし穴4:バックオフなしでリトライ
❌ 失敗後すぐに3回リトライ → サーバー負荷増大、レート制限トリガー ✅ 指数バックオフ(1s → 2s → 4s)+ 最大遅延上限 → 下流サービスを保護
落とし穴5:呼び出しチェーンの終了条件がない
❌ Agentが無限ループでツールを呼び出し → Token枯渇、コスト超過 ✅ max_stepsハードリミット + 重複呼び出し検出 + Token予算制御 → 異常ループを強制終了
エラートラブルシューティング
| # | エラーメッセージ | 原因 | 解決方法 |
|---|---|---|---|
| 1 | Invalid function name not found |
モデルが未登録のツールを呼び出した | ツールホワイトリスト検証を追加、利用可能ツールリストを返す |
| 2 | JSON decode error in function arguments |
モデルが無効なJSON引数を生成 | JSONパースエラーハンドリングを追加、DecodeErrorをキャッチ |
| 3 | Rate limit exceeded: 429 |
APIレート制限超過 | 指数バックオフリトライを実装、並行度を下げる |
| 4 | Context length exceeded |
履歴+ツール結果が長すぎる | 履歴メッセージを切り詰め、ツール戻り値を圧縮 |
| 5 | Tool execution timeout |
ツール実行がタイムアウト | timeoutパラメータを設定、非同期実行 |
| 6 | Missing required parameter |
必須パラメータの欠落 | Schemaでrequiredをマーク、Pydantic検証 |
| 7 | Circular tool call detected |
ツールループ呼び出しを検出 | max_steps制限 + 重複呼び出し検出 |
| 8 | Model refused to use tools |
モデルがツール使用を拒否 | tool_choiceとsystem promptを確認 |
| 9 | Unexpected tool output format |
異常なツール戻り形式 | JSON戻りを標準化 + 出力Schema検証 |
| 10 | Token budget exceeded |
Token使用量が予算超過 | Tokenカウントと予算サーキットブレーカーを追加 |
高度な最適化
1. ツール結果キャッシュ
冪等ツールの同じパラメータ呼び出しをキャッシュし、重複Token消費とAPI呼び出しを回避:
import hashlib
class ToolResultCache:
def __init__(self, ttl_seconds: int = 300):
self._cache: dict[str, tuple[float, str]] = {}
self._ttl = ttl_seconds
def _cache_key(self, name: str, args: dict) -> str:
raw = json.dumps({"name": name, "args": args}, sort_keys=True)
return hashlib.sha256(raw.encode()).hexdigest()
def get(self, name: str, args: dict) -> str | None:
key = self._cache_key(name, args)
if key in self._cache:
ts, result = self._cache[key]
if time.time() - ts < self._ttl:
return result
del self._cache[key]
return None
def set(self, name: str, args: dict, result: str):
key = self._cache_key(name, args)
self._cache[key] = (time.time(), result)
2. 並列Tool Calling処理
OpenAIは一度に複数のtool_callを返すことをサポート。並行実行で効率向上:
import concurrent.futures
def handle_parallel_tool_calls(tool_calls: list, registry: ToolRegistry) -> list[dict]:
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor_pool:
futures = {}
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
future = executor_pool.submit(registry.execute, func_name, func_args)
futures[future] = tool_call.id
for future in concurrent.futures.as_completed(futures):
tool_call_id = futures[future]
try:
result = future.result(timeout=10.0)
except Exception as e:
result = json.dumps({"error": str(e)})
results.append({"role": "tool", "tool_call_id": tool_call_id, "content": result})
return results
3. ツール権限サンドボックス
危険なツールに権限制御を実装し、権限超過操作を防止:
class ToolPermission:
ALLOWED = "allowed"
CONFIRM_REQUIRED = "confirm_required"
DENIED = "denied"
TOOL_PERMISSIONS = {
"get_weather": ToolPermission.ALLOWED,
"search_web": ToolPermission.ALLOWED,
"query_database": ToolPermission.CONFIRM_REQUIRED,
"execute_command": ToolPermission.DENIED,
}
def check_permission(tool_name: str) -> str:
return TOOL_PERMISSIONS.get(tool_name, ToolPermission.DENIED)
4. 呼び出しチェーンのオブザーバビリティ
各Agent実行に対して完全な呼び出しチェーントレースを生成:
class CallTracer:
def __init__(self):
self.trace_id = hashlib.md5(str(time.time()).encode()).hexdigest()[:12]
self.spans: list[dict] = []
def record(self, tool_name: str, args: dict, result: str, duration_ms: float, success: bool):
self.spans.append({
"trace_id": self.trace_id,
"tool": tool_name,
"args_summary": str(args)[:100],
"result_summary": result[:100],
"duration_ms": duration_ms,
"success": success,
"timestamp": time.time(),
})
def export(self) -> dict:
return {"trace_id": self.trace_id, "span_count": len(self.spans), "spans": self.spans}
比較分析
| 次元 | OpenAI Function Calling | Anthropic Tool Use | Llama Tool Calling |
|---|---|---|---|
| プロトコル形式 | tools配列 + tool_choice | tools配列 + tool_choice | カスタム形式、実装依存 |
| パラメータSchema | JSON Schema完全サポート | JSON Schema + input_schema | 基本JSON Schema |
| 並列呼び出し | ネイティブマルチtool_call対応 | マルチtool_useブロック対応 | フレームワーク依存 |
| ストリーミング出力 | streaming tool_call対応 | streaming対応 | 一部フレームワーク対応 |
| 強制呼び出し | tool_choice: required | tool_choice: any | プロンプト誘導が必要 |
| ツール指定 | tool_choiceでfunction指定 | tool_choiceでtool指定 | プロンプトで指定が必要 |
| エラー処理 | 自前実装が必要 | 自前実装が必要 | 自前実装が必要 |
| キャッシュ | Prompt Caching対応 | Prompt Caching対応 | ネイティブサポートなし |
| コスト | 高め | 中程度 | 低い(自己デプロイ) |
| エコシステム成熟度 | 最も成熟 | 成熟 | 急速に発展中 |
まとめと展望:2026年に信頼性の高いAgent Tool Callingシステムを構築する鍵は、5つのパターンを段階的に積み上げること——基本統合からマルチツール選択、タイムアウトリトライから結果検証、そしてモニタリング付き本番級フレームワークへ。重要原則:Schema記述は正確に、リトライにはバックオフを、結果は検証し、呼び出しには上限を、権限は管理する。MCPプロトコルとAgent Protocolの標準化に伴い、Tool Callingは「手書き統合」から「プロトコルベースの相互運用」へと進化するが、信頼性エンジニアリングのコアパターンは色褪せない。
オンラインツールおすすめ
- JSONフォーマッター:/ja/json/format — Tool CallingのSchemaとツールレスポンスJSONをフォーマット
- Hash計算:/ja/encode/hash — ツールキャッシュKeyと呼び出しチェーンTrace IDを計算
- Curl→コード変換:/ja/dev/curl-to-code — AI APIデバッグcurlコマンドをPythonコードに変換
ブラウザローカルツールを無料で試す →