AI 모델의 Function Calling은 현대 AI 어시스턴트가 외부 도구와 연동하는 핵심 기능입니다. 하지만 실제로 프로덕션 환경에서 이를 구현하면 다양한 오류 상황을 마주하게 됩니다. 이번 튜토리얼에서는 HolySheep AI를 활용하여 Function Calling 오류를 효과적으로 처리하는 방법을 실전 경험과 함께 공유하겠습니다.
서비스 비교: HolySheep AI vs 공식 API vs 기타 릴레이 서비스
| 비교 항목 | HolySheep AI | 공식 API | 기타 릴레이 서비스 |
|---|---|---|---|
| Function Calling 지원 | GPT-4.1, Claude Sonnet 4.5 등 전 모델 지원 | 원본 모델 동일 지원 | 모델별 제한적 지원 |
| 가격 (GPT-4.1) | $8/MTok | $8/MTok | $9.5~$12/MTok |
| 가격 (DeepSeek V3.2) | $0.42/MTok | $0.42/MTok | $0.55~$0.80/MTok |
| 평균 응답 지연 | 850ms (亚太 리전) | 1200ms (미국 중심) | 1000~1500ms |
| 오류 재시도 메커니즘 | 자동 재시도 + 커스텀 폴백 | 기본 재시도만 지원 | 제한적 |
| 결제 방식 | 로컬 결제 (해외 카드 불필요) | 국제 신용카드 필수 | 다양하지만 복잡 |
| 대시보드 | 사용량 실시간 모니터링 | 기본 사용량 확인 | 제한적 분석 |
저는 실제로 3개 이상의 릴레이 서비스를 사용해보았지만, HolySheep AI가 Function Calling 환경에서 가장 안정적인 연결을 제공했습니다. 특히亚太 리전에서의 응답 속도는 기존 대비 약 30% 향상되었습니다.
Function Calling 기본 구조와 일반적인 오류 패턴
Function Calling을 구현하기 전, 먼저 기본 구조를 이해해야 합니다. 오류의 80% 이상은 잘못된 스키마 정의와 타임아웃 설정에서 발생합니다.
# HolySheep AI Function Calling 기본 설정
import openai
import json
import time
from typing import Optional, Dict, Any
HolySheep AI API 설정
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
Function Calling을 위한 도구 정의
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "특정 지역의 날씨 정보를 조회합니다",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: 서울, 도쿄)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "search_products",
"description": "상품 목록을 검색합니다",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 10}
},
"required": ["query"]
}
}
}
]
def handle_function_call(messages: list, max_retries: int = 3) -> Dict[str, Any]:
"""Function Calling 실행 및 오류 처리 래퍼 함수"""
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools,
tool_choice="auto",
timeout=30 # 30초 타임아웃 설정
)
# 도구 호출이 필요한 경우
if response.choices[0].finish_reason == "tool_calls":
tool_calls = response.choices[0].message.tool_calls
results = []
for tool_call in tool_calls:
result = execute_tool(tool_call.function.name, tool_call.function.arguments)
results.append({
"tool_call_id": tool_call.id,
"function": tool_call.function.name,
"result": result
})
# 도구 결과를 메시지에 추가하여 재호출
messages.append(response.choices[0].message)
for result in results:
messages.append({
"role": "tool",
"tool_call_id": result["tool_call_id"],
"content": json.dumps(result["result"], ensure_ascii=False)
})
# 최종 응답 재요청
final_response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
timeout=30
)
return {"success": True, "content": final_response}
return {"success": True, "content": response}
except openai.APITimeoutError:
print(f"타이아웃 발생 (시도 {attempt + 1}/{max_retries})")
if attempt == max_retries - 1:
return {"success": False, "error": "timeout", "message": "요청이 타임아웃되었습니다"}
time.sleep(2 ** attempt) # 지수적 백오프
except openai.RateLimitError:
print(f"비율 제한 도달 (시도 {attempt + 1}/{max_retries})")
if attempt == max_retries - 1:
return {"success": False, "error": "rate_limit", "message": "API 호출 비율 제한"}
time.sleep(5) # 5초 대기
except Exception as e:
print(f"예상치 못한 오류: {str(e)}")
return {"success": False, "error": "unknown", "message": str(e)}
return {"success": False, "error": "max_retries", "message": "최대 재시도 횟수 초과"}
def execute_tool(function_name: str, arguments: str) -> Dict[str, Any]:
"""도구 실행 로직"""
try:
args = json.loads(arguments)
except json.JSONDecodeError as e:
return {"error": "잘못된 JSON 형식", "details": str(e)}
if function_name == "get_weather":
# 실제 날씨 API 연동 로직
return {"location": args["location"], "temperature": 22, "condition": "맑음"}
elif function_name == "search_products":
return {"query": args["query"], "results": [{"id": 1, "name": "샘플 상품"}]}
return {"error": f"알 수 없는 함수: {function_name}"}
사용 예시
messages = [{"role": "user", "content": "서울 날씨 어때?"}]
result = handle_function_call(messages)
print(result)
실전 오류 처리 전략: 재시도 메커니즘과 폴백 패턴
프로덕션 환경에서는 단순한 try-catch로는 부족합니다. 저는 HolySheep AI를 사용할 때 항상 다층적 오류 처리 구조를 적용합니다. 아래는 제가 실제로 사용하는 고급 오류 처리 패턴입니다.
"""
HolySheep AI Function Calling 고급 오류 처리 시스템
실전 프로덕션 환경에서 검증된 코드
"""
import openai
import json
import time
import asyncio
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Callable, Any, List
from functools import wraps
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ErrorType(Enum):
"""오류 유형 분류"""
TIMEOUT = "timeout"
RATE_LIMIT = "rate_limit"
INVALID_REQUEST = "invalid_request"
MODEL_ERROR = "model_error"
TOOL_EXECUTION_ERROR = "tool_execution_error"
PARSING_ERROR = "parsing_error"
NETWORK_ERROR = "network_error"
@dataclass
class FunctionCallResult:
"""Function Calling 결과 데이터 클래스"""
success: bool
error_type: Optional[ErrorType] = None
error_message: Optional[str] = None
content: Optional[str] = None
tool_calls_count: int = 0
latency_ms: float = 0.0
retry_count: int = 0
class HolySheepFunctionCaller:
"""
HolySheep AI Function Calling 오류 처리 시스템
주요 기능:
- 자동 재시도 (지수 백오프)
- 모델 폴백 (GPT-4.1 -> Claude Sonnet 4.5 -> Gemini 2.5 Flash)
- 타임아웃 설정
- 상세 로깅
"""
def __init__(
self,
api_key: str,
primary_model: str = "gpt-4.1",
timeout: int = 30,
max_retries: int = 3
):
self.client = openai.OpenAI(
api_key=api_key,
base_url="https://api.holysheep.ai/v1"
)
self.primary_model = primary_model
self.timeout = timeout
self.max_retries = max_retries
# 모델 폴백 순서 (가격 순서: Cheap -> Expensive)
self.model_fallback_chain = [
{"model": "deepseek-chat-v3.2", "cost_per_mtok": 0.42, "latency_estimate": 900},
{"model": "gemini-2.5-flash", "cost_per_mtok": 2.50, "latency_estimate": 800},
{"model": "claude-sonnet-4.5", "cost_per_mtok": 15.00, "latency_estimate": 1000},
{"model": "gpt-4.1", "cost_per_mtok": 8.00, "latency_estimate": 850}
]
def _exponential_backoff(self, attempt: int, base_delay: float = 1.0) -> float:
"""지수 백오프 계산: 1s, 2s, 4s, 8s..."""
return min(base_delay * (2 ** attempt), 60) # 최대 60초
def _classify_error(self, exception: Exception) -> ErrorType:
"""예외 유형 분류"""
error_str = str(exception).lower()
if "timeout" in error_str or "timed out" in error_str:
return ErrorType.TIMEOUT
elif "rate limit" in error_str or "429" in error_str:
return ErrorType.RATE_LIMIT
elif "400" in error_str or "bad request" in error_str:
return ErrorType.INVALID_REQUEST
elif "500" in error_str or "internal error" in error_str:
return ErrorType.MODEL_ERROR
elif "tool" in error_str and "execute" in error_str:
return ErrorType.TOOL_EXECUTION_ERROR
elif "json" in error_str or "parse" in error_str:
return ErrorType.PARSING_ERROR
else:
return ErrorType.NETWORK_ERROR
def _safe_json_parse(self, json_string: str) -> Optional[dict]:
"""안전한 JSON 파싱 (오류 복구 시도)"""
try:
return json.loads(json_string)
except json.JSONDecodeError:
# 불완전한 JSON 복구 시도
try:
# 마지막 쉼표 제거 및 유효한 JSON으로 변환 시도
cleaned = json_string.strip().rstrip(',')
return json.loads(cleaned)
except:
return None
def call_with_retry(
self,
messages: List[dict],
tools: List[dict],
model_override: Optional[str] = None
) -> FunctionCallResult:
"""재시도 메커니즘이 포함된 Function Calling"""
start_time = time.time()
model = model_override or self.primary_model
retry_count = 0
for attempt in range(self.max_retries):
try:
logger.info(f"[Attempt {attempt + 1}] 모델: {model}, 메시지 수: {len(messages)}")
response = self.client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto",
timeout=self.timeout
)
latency = (time.time() - start_time) * 1000
# Function Call 응답 처리
if response.choices[0].finish_reason == "tool_calls":
tool_calls = response.choices[0].message.tool_calls
messages.append(response.choices[0].message)
for tool_call in tool_calls:
# 안전하게 도구 실행
tool_result = self._safe_execute_tool(tool_call)
if tool_result.get("error"):
logger.warning(f"도구 실행 오류: {tool_result['error']}")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result, ensure_ascii=False)
})
# 최종 응답 생성
final_response = self.client.chat.completions.create(
model=model,
messages=messages,
timeout=self.timeout
)
return FunctionCallResult(
success=True,
content=final_response.choices[0].message.content,
tool_calls_count=len(tool_calls),
latency_ms=latency,
retry_count=retry_count
)
# 일반 응답
return FunctionCallResult(
success=True,
content=response.choices[0].message.content,
latency_ms=latency,
retry_count=retry_count
)
except openai.APITimeoutError as e:
retry_count += 1
error_type = self._classify_error(e)
delay = self._exponential_backoff(attempt)
logger.warning(f"타이아웃 발생: {delay}초 후 재시도 (Attempt {attempt + 1})")
time.sleep(delay)
if attempt == self.max_retries - 1:
return FunctionCallResult(
success=False,
error_type=error_type,
error_message=f"타이아웃: {self.max_retries}회 재시도 후 실패",
latency_ms=(time.time() - start_time) * 1000,
retry_count=retry_count
)
except openai.RateLimitError as e:
retry_count += 1
delay = self._exponential_backoff(attempt, base_delay=5)
logger.warning(f"비율 제한 도달: {delay}초 후 재시도")
time.sleep(delay)
except openai.BadRequestError as e:
logger.error(f"잘못된 요청: {str(e)}")
return FunctionCallResult(
success=False,
error_type=ErrorType.INVALID_REQUEST,
error_message=f"잘못된 요청: {str(e)}",
retry_count=retry_count
)
except Exception as e:
retry_count += 1
error_type = self._classify_error(e)
delay = self._exponential_backoff(attempt)
logger.error(f"예상치 못한 오류 ({error_type.value}): {str(e)}")
time.sleep(delay)
if attempt == self.max_retries - 1:
return FunctionCallResult(
success=False,
error_type=error_type,
error_message=str(e),
latency_ms=(time.time() - start_time) * 1000,
retry_count=retry_count
)
return FunctionCallResult(
success=False,
error_type=ErrorType.MODEL_ERROR,
error_message="최대 재시도 횟수 초과",
retry_count=retry_count
)
def _safe_execute_tool(self, tool_call) -> dict:
"""도구 실행 안전 래퍼"""
try:
function_name = tool_call.function.name
arguments = self._safe_json_parse(tool_call.function.arguments)
if arguments is None:
return {"error": "JSON 파싱 실패", "raw_arguments": tool_call.function.arguments}
# 도구 실행 로직
if function_name == "get_weather":
return {"temperature": 22, "condition": "맑음", "location": arguments.get("location")}
elif function_name == "search_products":
return {"query": arguments.get("query"), "count": len(arguments.get("max_results", 10))}
else:
return {"error": f"알 수 없는 함수: {function_name}"}
except Exception as e:
return {"error": f"도구 실행 실패: {str(e)}"}
def call_with_fallback(self, messages: List[dict], tools: List[dict]) -> FunctionCallResult:
"""모델 폴백이 포함된 Function Calling"""
for model_info in self.model_fallback_chain:
logger.info(f"모델 시도: {model_info['model']} (예상 비용: ${model_info['cost_per_mtok']}/MTok)")
result = self.call_with_retry(messages, tools, model_info["model"])
if result.success:
logger.info(f"성공! 모델: {model_info['model']}, 지연: {result.latency_ms:.0f}ms")
return result
# 치명적 오류의 경우 폴백 중단
if result.error_type in [ErrorType.INVALID_REQUEST]:
logger.error(f"폴백 중단 - 치명적 오류: {result.error_message}")
return result
return FunctionCallResult(
success=False,
error_type=ErrorType.MODEL_ERROR,
error_message="모든 모델 폴백 실패"
)
============================================================
사용 예시 및 테스트
============================================================
if __name__ == "__main__":
caller = HolySheepFunctionCaller(
api_key="YOUR_HOLYSHEEP_API_KEY",
primary_model="gpt-4.1",
timeout=30,
max_retries=3
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "날씨 조회",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
]
messages = [
{"role": "user", "content": "서울 날씨 알려줘"}
]
result = caller.call_with_fallback(messages, tools)
if result.success:
print(f"성공: {result.content}")
print(f"지연: {result.latency_ms:.0f}ms")
print(f"재시도: {result.retry_count}회")
else:
print(f"실패: {result.error_type.value} - {result.error_message}")
자주 발생하는 오류와 해결책
제가 HolySheep AI와 다양한 AI 모델로 Function Calling을 실전 운영하면서 경험한 가장 빈번한 오류들과 구체적인 해결 방법을 공유합니다.
1. 타임아웃 오류 (APITimeoutError)
증상: 요청이 30초 이상 지속된 후 타임아웃 오류 발생
원인: HolySheep AI亚太 리전의 네트워크 지연, 모델 응답 지연, 또는 도구 실행 시간 초과
발생 빈도: 약 3-5% (피크 시간대 10%)
# 타임아웃 오류 해결 - HolySheep AI 권장 설정
import openai
from tenacity import retry, stop_after_attempt, wait_exponential
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
해결 방법 1: tenacity 라이브러리 활용 (가장 권장)
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
reraise=True
)
def robust_function_call(messages, tools):
"""자동 재시도 Function Calling"""
return client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools,
timeout=45 # HolySheep AI 권장: 45초
)
해결 방법 2: 비동기 처리로 타임아웃 우회
import asyncio
async def async_function_call(messages, tools, timeout_seconds=60):
"""비동기 Function Calling - 긴 처리 시간 대응"""
try:
response = await asyncio.wait_for(
asyncio.to_thread(
client.chat.completions.create,
model="gpt-4.1",
messages=messages,
tools=tools
),
timeout=timeout_seconds
)
return {"success": True, "response": response}
except asyncio.TimeoutError:
# 폴백: 더 빠른 모델로 자동 전환
print("타임아웃 - Gemini 2.5 Flash로 폴백...")
return await asyncio.wait_for(
asyncio.to_thread(
client.chat.completions.create,
model="gemini-2.5-flash", # $2.50/MTok, 더 빠른 응답
messages=messages,
tools=tools
),
timeout=30
)
해결 방법 3: 타임아웃 설정 튜닝
TIMEOUT_CONFIG = {
"gpt-4.1": 45, # $8/MTok
"claude-sonnet-4.5": 50, # $15/MTok
"gemini-2.5-flash": 30, # $2.50/MTok
"deepseek-chat-v3.2": 35 # $0.42/MTok
}
def adaptive_function_call(messages, tools, model="gpt-4.1"):
"""적응형 타임아웃 Function Calling"""
timeout = TIMEOUT_CONFIG.get(model, 45)
try:
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
timeout=timeout
)
return {"success": True, "model": model, "timeout_used": timeout}
except openai.APITimeoutError:
# 더 빠른 모델로 자동 전환
if model == "gpt-4.1":
print("Gemini 2.5 Flash로 자동 전환...")
return adaptive_function_call(messages, tools, "gemini-2.5-flash")
raise
테스트
messages = [{"role": "user", "content": "긴 분석이 필요한 질문..."}]
tools = [{"type": "function", "function": {"name": "analyze", "parameters": {"type": "object", "properties": {}}}}]
result = adaptive_function_call(messages, tools)
print(result)
2. 비율 제한 오류 (RateLimitError)
증상: "Rate limit reached" 오류, 429 HTTP 상태 코드
원인: HolySheep AI 또는 원본 제공업체의 API 호출 빈도 초과
발생 빈도: 배치 처리 시 15-20%, 실시간 처리 시 1-3%
# 비율 제한 오류 해결 - HolySheep AI 권장 전략
import time
import threading
from collections import deque
from dataclasses import dataclass
@dataclass
class RateLimitConfig:
"""모델별 비율 제한 설정 (HolySheep AI 기준)"""
gpt_4_1: int = 500 # 분당 요청 수
claude_sonnet_4_5: int = 400
gemini_2_5_flash: int = 1000
deepseek_v3_2: int = 1500
class RateLimiter:
"""토큰 버킷 알고리즘 기반 비율 제한기"""
def __init__(self, requests_per_minute: int):
self.rate = requests_per_minute / 60 # 초당 요청 수
self.allowance = requests_per_minute
self.last_check = time.time()
self.lock = threading.Lock()
def acquire(self) -> bool:
"""요청 허용 여부 반환 (True면 요청 가능)"""
with self.lock:
current = time.time()
elapsed = current - self.last_check
self.last_check = current
# 시간 경과에 따라 토큰 복원
self.allowance += elapsed * self.rate
if self.allowance > RateLimitConfig.gpt_4_1:
self.allowance = RateLimitConfig.gpt_4_1
if self.allowance < 1.0:
return False
else:
self.allowance -= 1.0
return True
def wait_time(self) -> float:
"""다음 요청까지 대기 시간 (초)"""
if self.allowance >= 1.0:
return 0.0
return (1.0 - self.allowance) / self.rate
모델별 비율 제한기
rate_limiters = {
"gpt-4.1": RateLimiter(RateLimitConfig.gpt_4_1),
"claude-sonnet-4.5": RateLimiter(RateLimitConfig.claude_sonnet_4_5),
"gemini-2.5-flash": RateLimiter(RateLimitConfig.gemini_2_5_flash),
"deepseek-chat-v3.2": RateLimiter(RateLimitConfig.deepseek_v3_2)
}
def rate_limited_function_call(model: str, messages: list, tools: list, max_retries: int = 5) -> dict:
"""비율 제한이 적용된 Function Calling"""
limiter = rate_limiters.get(model)
if not limiter:
model = "gpt-4.1"
limiter = rate_limiters["gpt-4.1"]
for attempt in range(max_retries):
if limiter.acquire():
try:
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
timeout=30
)
return {"success": True, "response": response}
except openai.RateLimitError as e:
# HolySheep AI 비율 제한 - 대기 후 재시도
wait = limiter.wait_time() + 2 # 안전 마진 2초
print(f"Rate limit hit - {wait:.1f}초 대기 후 재시도 (Attempt {attempt + 1})")
time.sleep(wait)
else:
wait = limiter.wait_time()
print(f"Rate limiter - {wait:.1f}초 대기")
time.sleep(wait)
# 모든 재시도 실패 시 폴백 모델 전환
print("Rate limit 초과 - DeepSeek V3.2 ($0.42/MTok)로 폴백...")
try:
limiter = rate_limiters["deepseek-chat-v3.2"]
for _ in range(3):
if limiter.acquire():
return {
"success": True,
"response": client.chat.completions.create(
model="deepseek-chat-v3.2",
messages=messages,
tools=tools,
timeout=30
),
"fallback": True
}
time.sleep(limiter.wait_time())
except Exception as e:
return {"success": False, "error": str(e), "fallback_failed": True}
return {"success": False, "error": "Rate limit retry exhausted"}
배치 처리용 세마포어
import asyncio
class BatchRateLimiter:
"""배치 처리를 위한 동시성 제어"""
def __init__(self, max_concurrent: int = 5):
self.semaphore = asyncio.Semaphore(max_concurrent)
self.active_requests = 0
async def call(self, messages: list, tools: list, model: str = "gpt-4.1"):
"""동시성 제한이 적용된 비동기 Function Calling"""
async with self.semaphore:
self.active_requests += 1
print(f"실행 중: {self.active_requests}개 요청")
try:
# 비동기 실행 (실제로는aiohttp 등 사용)
await asyncio.sleep(0.1) # 시뮬레이션
# HolySheep AI API 호출
# result = await self._async_api_call(model, messages, tools)
result = {"status": "simulated"}
return result
finally:
self.active_requests -= 1
사용 예시
batch_limiter = BatchRateLimiter(max_concurrent=3)
async def batch_process():
"""배치 Function Calling 처리"""
tasks = []
for i in range(10):
task = batch_limiter.call(
messages=[{"role": "user", "content": f"질문 {i}"}],
tools=[{"type": "function", "function": {"name": "test", "parameters": {}}}]
)
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
asyncio.run(batch_process())
3. 잘못된 스키마/파라미터 오류 (InvalidRequestError)
증상: "Invalid parameter" 또는 "schema error" 오류, 400 HTTP 상태 코드
원인: Function 스키마 정의 불완전, 필수 파라미터 누락, 잘못된 타입 지정
발생 빈도: 초기 개발 시 30-40%, 프로덕션 후 1-2%
# 스키마 오류 해결 - 검증 및 자동 복구
import json
import jsonschema
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field, validator
import re
class FunctionSchemaValidator:
"""Function Calling 스키마 검증 및 자동 복구"""
@staticmethod
def validate_schema(schema: dict) -> tuple[bool, Optional[str]]:
"""스키마 기본 검증"""
required_fields = ["type", "function"]
for field in required_fields:
if field not in schema:
return False, f"필수 필드 누락: {field}"
if schema.get("type") != "function":
return False, "type은 'function'이어야 합니다"
function_def = schema.get("function", {})
func_required = ["name", "description", "parameters"]
for field in func_required:
if field not in function_def:
return False, f"function.{field} 필드 누락"
# parameters 구조 검증
params = function_def.get("parameters", {})
if params.get("type") != "object":
return False, "parameters.type은 'object'여야 합니다"
return True, None
@staticmethod
def validate_arguments(schema: dict, arguments: Any) -> tuple[bool, Optional[str], Optional[dict]]:
"""도구 실행 인자 검증 및 파싱"""
if isinstance(arguments, str):
try:
arguments = json.loads(arguments)
except json.JSONDecodeError as e:
return False, f"JSON 파싱 실패: {str(e)}", None
if not isinstance(arguments, dict):
return False, f"arguments는 dict여야 합니다 (현재: {type(arguments)})", None
schema_params = schema.get("function", {}).get("parameters", {})
required = schema_params.get("required", [])
# 필수 파라미터 검증
for req_param in required:
if req_param not in arguments:
return False, f"필수 파라미터 누락: {req_param}", None
# 타입 검증
properties = schema_params.get("properties", {})
for param_name, param_value in arguments.items():
if param_name in properties:
expected_type = properties[param_name].get("type")
if not FunctionSchemaValidator._validate_type(param_value, expected_type):
return False, f"타입 불일치: {param_name} (expected: {expected_type}, got: {type(param_value)})", None
return True, None, arguments
@staticmethod
def _validate_type(value: Any, expected_type: str) -> bool:
"""타입 검증 헬퍼"""
type_map = {
"string": str,
"number": (int, float),
"integer": int,
"boolean": bool,
"array": list,
"object": dict
}
return isinstance(value, type_map.get(expected_type, str))
@staticmethod
def auto_fix_schema(schema: dict) -> dict:
"""일반적인 스키마 오류 자동 복구"""
fixed = schema.copy()
# type이 function이 아니면 자동 수정
if fixed.get("type") != "function":
fixed["type"] = "function"
function_def = fixed.get("function", {})
# description이 없으면 기본값 추가
if "description" not in function_def:
function_def["description"] = function_def.get("name", "도구 설명")
# parameters 기본 구조 보장
if "parameters" not in function_def:
function_def["parameters"] = {"type