AI API活用において、Function Calling(関数呼び出し)は外部システムとシームレスに連携するための核心機能です。しかし、拙速な実装はConnectionErrorや401 Unauthorizedといった厄介なエラーを招きます。本稿ではHolySheep AI(今すぐ登録)での実装経験を基に、堅牢なツール定義と包括的なエラーハンドリングのベストプラクティスを詳細に解説します。
Function Callingとは:基礎概念のおさらい
Function Callingとは、LLMがユーザーの意図を理解し、事前に定義した関数を呼び出して外部データや機能を実行する仕組みです。例えばCalifornia州のリアルタイム天気を取得したり、CRMシステムの顧客情報を検索したりできます。
HolySheep AIは¥1=$1という業界最安水準のレート(公式¥7.3=$1比85%節約)を提供しており、Function Callingの高頻度利用においても経済的に運用可能です。また、WeChat PayやAlipayにも対応し、アジア圈的ユーザーにも優しい設計になっています。
ツール定義の構造とパラメータ設計
効果的なツール定義はname、description、parametersの3要素で構成されます。以下に実践的な例を示します。
import openai
from typing import Optional, List
from pydantic import BaseModel, Field
import json
HolySheep AI用のクライアント設定
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
=== ツール定義:天気情報取得 ===
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在天気を取得します。" +
"目的は旅行計画、衣服選択、活动準備です。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名(例:Tokyo, New York)",
"pattern": "^[A-Za-z\\s]+$"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度単位",
"default": "celsius"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "search_database",
"description": "企业内部データベースから顧客情報を検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索クエリ(顧客名、メール、ID)"
},
"limit": {
"type": "integer",
"description": "最大結果数",
"default": 10,
"minimum": 1,
"maximum": 100
}
},
"required": ["query"]
}
}
}
]
def execute_function_call(function_name: str, arguments: dict) -> dict:
"""関数呼び出しの実装"""
if function_name == "get_weather":
# 実際の天気API呼び出し(ダミー実装)
return {
"temperature": 22,
"condition": "sunny",
"humidity": 65,
"location": arguments.get("location")
}
elif function_name == "search_database":
# データベース検索(ダミー実装)
return {
"results": [
{"id": "C001", "name": "田中太郎", "email": "[email protected]"}
],
"count": 1
}
else:
raise ValueError(f"Unknown function: {function_name}")
=== メイン実行ループ ===
def chat_with_tools(messages: list) -> dict:
"""Function Callingを含むchat完了を処理"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
# 関数呼び出しがある場合
if assistant_message.tool_calls:
messages.append(assistant_message.model_dump())
for tool_call in assistant_message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
try:
result = execute_function_call(function_name, arguments)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": json.dumps(result, ensure_ascii=False)
})
except Exception as e:
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": json.dumps({"error": str(e)})
})
# 関数結果を踏まえて最終応答を取得
final_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
return final_response.choices[0].message
return assistant_message
使用例
messages = [{"role": "user", "content": "東京今日の天気教えて?"}]
result = chat_with_tools(messages)
print(result.content)
エラーハンドリング戦略:4層アーキテクチャ
私は本番環境での実装を通じて、エラーハンドリングは4つの層で設計することが最も効果的だと結論づけました。
- 第1層:入力バリデーション — LLMからの引数を厳密に検証
- 第2層:関数実行エラー — try-exceptで例外を捕捉
- 第3層:API通信エラー — ネットワーク層でのリトライ
- 第4層:フォールバック応答 — 最終手段としての代替回答
import time
import logging
from functools import wraps
from typing import Callable, Any
from openai import APIError, RateLimitError, APITimeoutError
import openai
ロガーの設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
class FunctionCallingError(Exception):
"""Function Calling関連の基底例外"""
def __init__(self, function_name: str, message: str, recoverable: bool = True):
self.function_name = function_name
self.recoverable = recoverable
super().__init__(f"[{function_name}] {message}")
class ValidationError(FunctionCallingError):
"""入力バリデーションエラー"""
def __init__(self, function_name: str, param: str, reason: str):
super().__init__(
function_name,
f"パラメータ '{param}' の検証失敗: {reason}",
recoverable=False
)
class ExecutionError(FunctionCallingError):
"""関数実行エラー"""
def __init__(self, function_name: str, original_error: Exception):
super().__init__(
function_name,
f"実行失敗: {type(original_error).__name__}: {str(original_error)}",
recoverable=True
)
def retry_with_backoff(max_retries: int = 3, base_delay: float = 1.0):
"""指数バックオフ付きリトライデコレータ"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
last_exception = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except RateLimitError as e:
last_exception = e
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
logger.warning(
f"Rate limit exceeded. Retry {attempt + 1}/{max_retries} "
f"after {delay}s"
)
time.sleep(delay)
except APITimeoutError as e:
last_exception = e
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
logger.warning(
f"Timeout. Retry {attempt + 1}/{max_retries} "
f"after {delay}s"
)
time.sleep(delay)
except APIError as e:
last_exception = e
if e.status_code >= 500 and attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
logger.warning(
f"Server error {e.status_code}. Retry {attempt + 1}/"
f"{max_retries} after {delay}s"
)
time.sleep(delay)
else:
raise
raise last_exception
return wrapper
return decorator
def validate_arguments(function_name: str, params: dict, schema: dict) -> bool:
"""引数のバリデーション"""
required = schema.get("required", [])
# 必須パラメータのチェック
for field in required:
if field not in params:
raise ValidationError(function_name, field, "必須パラメータが不足")
# 型チェックと追加検証
properties = schema.get("properties", {})
for key, value in params.items():
if key in properties:
expected_type = properties[key].get("type")
if expected_type == "string" and not isinstance(value, str):
raise ValidationError(
function_name, key,
f"string型 expected, got {type(value).__name__}"
)
elif expected_type == "integer":
if not isinstance(value, int):
raise ValidationError(
function_name, key,
f"integer型 expected, got {type(value).__name__}"
)
if "minimum" in properties[key] and value < properties[key]["minimum"]:
raise ValidationError(
function_name, key,
f"minimum {properties[key]['minimum']} 未満"
)
return True
@retry_with_backoff(max_retries=3, base_delay=1.0)
def execute_with_retry(function_name: str, params: dict, executor: Callable) -> Any:
"""リトライ機構付きで関数を実行"""
try:
result = executor(params)
logger.info(f"[{function_name}] 実行成功")
return result
except Exception as e:
logger.error(f"[{function_name}] 実行エラー: {str(e)}")
raise ExecutionError(function_name, e)
def create_safe_executor(
function_name: str,
schema: dict,
executor: Callable
) -> Callable:
"""バリデーション+リトライ+エラーハンドリング付きの安全なエグゼキュータ"""
def safe_execute(params: dict) -> dict:
try:
# 第1層: バリデーション
validate_arguments(function_name, params, schema)
# 第2層: 実行(リトライ付き)
result = execute_with_retry(function_name, params, executor)
return {"status": "success", "data": result}
except ValidationError as e:
logger.error(f"Validation failed: {e}")
return {
"status": "error",
"error_type": "validation",
"message": str(e),
"recoverable": False
}
except ExecutionError as e:
logger.error(f"Execution failed: {e}")
return {
"status": "error",
"error_type": "execution",
"message": str(e),
"recoverable": True
}
except Exception as e:
logger.critical(f"Unexpected error: {e}")
return {
"status": "error",
"error_type": "unknown",
"message": str(e),
"recoverable": False
}
return safe_execute
=== 實際的な関数定義と安全的実行 ===
def get_weather_executor(params: dict) -> dict:
"""天気取得の實際的な実装"""
# 実際の外部API呼び出し
location = params["location"]
unit = params.get("unit", "celsius")
# ダミー実装
weather_data = {
"Tokyo": {"temp": 22, "condition": "sunny", "humidity": 65},
"Osaka": {"temp": 24, "condition": "cloudy", "humidity": 70},
"Nagoya": {"temp": 23, "condition": "rainy", "humidity": 85}
}
if location not in weather_data:
raise ValueError(f"Unsupported location: {location}")
return weather_data[location]
スキーマ定義
weather_schema = {
"required": ["location"],
"properties": {
"location": {
"type": "string",
"description": "都市名"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"default": "celsius"
}
}
}
安全なエグゼキュータを生成
safe_weather_executor = create_safe_executor(
"get_weather",
weather_schema,
get_weather_executor
)
使用テスト
if __name__ == "__main__":
# 正常系
result = safe_weather_executor({"location": "Tokyo"})
print(f"Result: {result}")
# 異常系:必須パラメータ欠如
result = safe_weather_executor({})
print(f"Validation Error: {result}")
# 異常系:型エラー
result = safe_weather_executor({"location": 123})
print(f"Type Error: {result}")
よくあるエラーと対処法
HolySheep AI含むFunction Calling実装で遭遇する典型的なエラーとその解決策をまとめます。
1. ConnectionError: timeout — ネットワーク接続のタイムアウト
原因:リクエストが長時間応答なく、既定のタイムアウト(通常10秒)を超過
解決方法:
import openai
from openai import APITimeoutError
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1",
timeout=30.0 # タイムアウトを30秒に設定
)
または httpx を使用してより詳細な設定
from httpx import Timeout
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1",
timeout=Timeout(60.0, connect=10.0) # (全体タイムアウト, 接続タイムアウト)
)
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "hello"}],
tools=tools
)
except APITimeoutError:
print("リクエストがタイムアウトしました。ネットワークまたはサーバーを確認してください。")
# フォールバック処理
fallback_response = "申し訳ありません。現在システムを不安定しているため、" +
"再度お試しください。"
2. 401 Unauthorized — APIキーの認証エラー
原因:無効なAPIキー、有効期限切れ、またはベースURLの誤り
解決方法:
import os
from openai import AuthenticationError, OpenAIError
def get_client():
"""認証情報を検証してクライアントを生成"""
api_key = os.environ.get("HOLYSHEEP_API_KEY") or "YOUR_HOLYSHEEP_API_KEY"
if not api_key or api_key == "YOUR_HOLYSHEEP_API_KEY":
raise ValueError(
"APIキーが設定されていません。"
"https://www.holysheep.ai/register でAPIキーを取得してください。"
)
# キーの長さで基本的な検証
if len(api_key) < 20:
raise ValueError("APIキーの形式が不正です。")
return openai.OpenAI(
api_key=api_key,
base_url="https://api.holysheep.ai/v1" # 正しいエンドポイント
)
def call_api_with_auth():
"""認証エラー対応付きのAPI呼び出し"""
try:
client = get_client()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}]
)
return response
except AuthenticationError as e:
print(f"認証エラー: {e}")
print("APIキーが無効です。ダッシュボードで確認してください:")
print("https://www.holysheep.ai/register")
return None
except OpenAIError as e:
print(f"APIエラー: {e}")
return None
3. RateLimitError — レート制限の超過
原因:短時間kapi.jpの多数のリクエスト送信,或者超过账户的配额限制。HolySheep AIではリアルタイムモニタリングで管理できます。
解決方法:
from openai import RateLimitError
import time
def handle_rate_limit(max_retries=5):
"""レート制限の指数バックオフ処理"""
base_delay = 2.0
max_delay = 60.0
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "test"}]
)
return response
except RateLimitError as e:
# HolySheep AIからのRetry-Afterヘッダーを確認
retry_after = e.response.headers.get("retry-after")
if retry_after:
delay = float(retry_after)
else:
delay = min(base_delay * (2 ** attempt), max_delay)
print(f"レート制限超過。{delay}秒後に再試行します...")
time.sleep(delay)
if attempt == max_retries - 1:
print("最大リトライ回数に達しました。")
raise e
return None
同時リクエスト制御
import threading
from collections import deque
import time
class RateLimiter:
"""トークンレートリミッター(スレッドセーフ)"""
def __init__(self, max_calls: int, period: float):
self.max_calls = max_calls
self.period = period
self.calls = deque()
self.lock = threading.Lock()
def __enter__(self):
with self.lock:
now = time.time()
# 期間外の呼び出し履歴をクリア
while self.calls and self.calls[0] < now - self.period:
self.calls.popleft()
if len(self.calls) >= self.max_calls:
sleep_time = self.calls[0] + self.period - now
if sleep_time > 0:
time.sleep(sleep_time)
self.calls.append(time.time())
def __exit__(self, *args):
pass
使用例:每秒5リクエストまでに制限
limiter = RateLimiter(max_calls=5, period=1.0)
with limiter:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "test"}]
)
4. InvalidRequestError — ツールパラメータの不正
原因:LLMが生成した引数の型がスキーマと一致しない、または必須パラメータが不足
解決方法:
import json
from openai import BadRequestError
def parse_and_validate_tool_args(
function_name: str,
raw_args: str,
schema: dict
) -> dict:
"""JSON引数をパースしてバリデーション"""
try:
# JSONパース
args = json.loads(raw_args)
except json.JSONDecodeError as e:
raise BadRequestError(
f"Invalid JSON in {function_name} arguments: {e}",
body={"function": function_name, "raw": raw_args}
)
# 必須パラメータチェック
required = schema.get("required", [])
missing = [p for p in required if p not in args]
if missing:
raise BadRequestError(
f"Missing required parameters for {function_name}: {missing}",
body={"function": function_name, "missing": missing}
)
# 型チェックと変換
properties = schema.get("properties", {})
for key, value in args.items():
if key in properties:
prop_schema = properties[key]
expected_type = prop_schema.get("type")
# 型不一致の محاولة 自動変換
if expected_type == "integer" and isinstance(value, str):
try:
args[key] = int(value)
except ValueError:
raise BadRequestError(
f"Cannot convert '{value}' to integer for parameter '{key}'"
)
elif expected_type == "string" and not isinstance(value, str):
args[key] = str(value)
# enum値の検証
if "enum" in prop_schema and value not in prop_schema["enum"]:
raise BadRequestError(
f"Invalid value '{value}' for parameter '{key}'. "
f"Must be one of: {prop_schema['enum']}"
)
return args
使用例
schema = {
"required": ["location"],
"properties": {
"location": {"type": "string"},
"unit": {"type": "