- Published on
BFCL 벤치마크 완전 가이드 2025: Tool Calling 성능 평가, 리더보드 분석, 모델 비교
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 도입: Tool Calling 벤치마크가 왜 중요한가?
- 1. Tool Calling 벤치마크가 필요한 이유
- 2. BFCL 개요
- 3. BFCL 카테고리 심층 분석
- 4. BFCL 평가 메트릭
- 5. 모델 성능 비교 (2025)
- 6. BFCL 직접 실행하기
- 7. 다른 Tool Calling 벤치마크
- 8. Tool Calling 성능 개선 전략
- 9. 현실 세계 vs 벤치마크 격차
- 10. 퀴즈
- 11. 참고 자료
도입: Tool Calling 벤치마크가 왜 중요한가?
AI Agent 시대의 핵심은 Tool Calling(Function Calling) 능력입니다. LLM이 아무리 뛰어난 추론 능력을 갖추고 있어도, 외부 도구를 정확하게 호출하지 못하면 실용적인 Agent를 만들 수 없습니다.
그런데 문제가 있습니다. MMLU는 일반 지식을, HumanEval은 코딩 능력을 측정하지만, Tool Calling 능력을 체계적으로 측정하는 벤치마크는 부족했습니다. 이 공백을 채운 것이 바로 UC Berkeley의 **BFCL (Berkeley Function Calling Leaderboard)**입니다.
이 가이드에서는 BFCL의 구조부터 평가 메트릭, 모델 성능 비교, 자체 평가 방법, 그리고 Tool Calling 성능 개선 전략까지 모든 것을 다룹니다.
1. Tool Calling 벤치마크가 필요한 이유
1.1 Tool Calling은 AI Agent의 기반
AI Agent 능력 스택:
┌─────────────────────┐
│ Multi-Agent 협업 │ ← Tool Calling 없이 불가능
├─────────────────────┤
│ 다단계 계획 실행 │ ← 각 단계에서 도구 호출
├─────────────────────┤
│ ★ Tool Calling ★ │ ← 핵심 능력
├─────────────────────┤
│ 추론 (CoT) │ ← 어떤 도구를 쓸지 결정
├─────────────────────┤
│ 텍스트 생성 │ ← 기초 능력
└─────────────────────┘
Tool Calling이 중요한 이유:
- 정확한 파라미터 추출: "내일 서울 날씨" →
get_weather(location="Seoul", date="2025-03-26") - 올바른 도구 선택: 유사한 10개 도구 중 정확한 것 선택
- 불필요한 호출 방지: 도구가 필요 없을 때는 호출하지 않는 판단
- 복합 호출: 여러 도구를 올바른 순서로 조합
1.2 벤치마크 없이는 체계적 개선 불가
개선 사이클:
┌──────────┐
│ 벤치마크 │
│ 로 평가 │
└────┬─────┘
│
┌────▼─────┐ ┌─────────────┐ ┌─────────────┐
│ 약점 발견 │───►│ 개선 조치 │───►│ 재평가 │
│ │ │ (프롬프트, │ │ (벤치마크) │
│ │ │ 파인튜닝) │ │ │
└──────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────────┘
▼
개선 확인 → 반복
1.3 BFCL이 채운 공백
| 벤치마크 | 측정 영역 | Tool Calling 평가 |
|---|---|---|
| MMLU | 일반 지식 | 불가 |
| HumanEval | 코딩 능력 | 불가 |
| MT-Bench | 대화 품질 | 불가 |
| GSM8K | 수학 추론 | 불가 |
| BFCL | Tool Calling | 전문 벤치마크 |
2. BFCL 개요
2.1 프로젝트 배경
BFCL은 UC Berkeley의 Gorilla 프로젝트 팀이 만든 Tool Calling 전문 벤치마크입니다. Gorilla 프로젝트는 LLM이 API를 정확하게 호출할 수 있도록 연구하는 프로젝트이며, 2023년 논문 "Gorilla: Large Language Model Connected with Massive APIs"로 시작되었습니다.
2.2 핵심 수치
BFCL 핵심 정보:
─────────────────────────────────────────
테스트 케이스: 2,000+ (v3 기준)
카테고리: 7개 주요 카테고리
지원 언어: Python, Java, JavaScript
평가 방식: AST + Executable
리더보드: gorilla.cs.berkeley.edu
최신 버전: BFCL v3 (2025)
업데이트 주기: 분기별
참여 모델: 60+ (상용 + 오픈소스)
─────────────────────────────────────────
2.3 버전 진화
| 버전 | 시기 | 주요 변화 |
|---|---|---|
| BFCL v1 | 2024 초 | 초기 버전. Simple/Multiple/Parallel 기본 카테고리 |
| BFCL v2 | 2024 중반 | Live 테스트 추가, Multi-turn 시나리오, 실행 기반 평가 강화 |
| BFCL v3 | 2025 | Multi-step 시나리오, 복합 호출 체인, 현실 시나리오 확대 |
3. BFCL 카테고리 심층 분석
3.1 Simple Function Calling (단순 함수 호출)
단일 함수, 단일 호출. 자연어에서 올바른 파라미터를 추출하는 기본 능력을 측정합니다.
테스트 예시:
# 사용자 입력
"What is the weather in San Francisco today?"
# 사용 가능한 함수
def get_weather(location: str, date: str = "today") -> dict:
"""Get weather information for a specific location and date."""
pass
# 기대 출력
get_weather(location="San Francisco", date="today")
평가 포인트:
- 올바른 함수 선택
- 필수 파라미터 정확한 추출
- 선택적 파라미터의 적절한 처리
- 파라미터 타입 일치 (string, int, float, boolean)
까다로운 케이스:
# 입력: "Find me flights from NYC to LA next Friday under $500"
# 사용 가능한 함수:
def search_flights(
origin: str, # 공항 코드나 도시명?
destination: str,
date: str, # "next Friday" → 실제 날짜 변환?
max_price: float, # "$500" → 500.0
currency: str = "USD"
) -> list:
pass
# 기대: search_flights(origin="NYC", destination="LA",
# date="2025-03-28", max_price=500.0, currency="USD")
3.2 Multiple Function Calling (다중 함수 선택)
여러 유사한 함수 중에서 올바른 것을 선택하는 능력을 측정합니다.
# 사용 가능한 함수들 (유사하지만 다름)
def get_current_weather(location: str, unit: str = "celsius") -> dict:
"""Get CURRENT weather conditions for a location."""
pass
def get_weather_forecast(location: str, days: int = 7) -> dict:
"""Get weather FORECAST for upcoming days."""
pass
def get_historical_weather(location: str, date: str) -> dict:
"""Get HISTORICAL weather data for a past date."""
pass
def check_severe_weather_alerts(region: str) -> list:
"""Check for severe weather ALERTS in a region."""
pass
# 테스트 1: "What will the weather be like in Tokyo next week?"
# 정답: get_weather_forecast(location="Tokyo", days=7)
# (현재 날씨가 아닌 예보)
# 테스트 2: "Were there any storms in Florida last month?"
# 정답: get_historical_weather(location="Florida", date="2025-02")
# (과거 날씨, 경보가 아님)
# 테스트 3: "Is it raining in Seoul right now?"
# 정답: get_current_weather(location="Seoul")
# (현재 날씨, 예보가 아님)
3.3 Parallel Function Calling (병렬 함수 호출)
하나의 요청에서 독립적인 여러 호출을 동시에 수행하는 능력을 측정합니다.
# 입력: "What's the weather in Seoul, Tokyo, and New York?"
# 기대: 3개의 독립적인 병렬 호출
[
get_weather(location="Seoul"),
get_weather(location="Tokyo"),
get_weather(location="New York")
]
# 더 복잡한 케이스:
# "Send a greeting email to Alice and Bob, and also check my calendar for tomorrow"
[
send_email(to="alice@example.com", subject="Greeting", body="Hello Alice!"),
send_email(to="bob@example.com", subject="Greeting", body="Hello Bob!"),
get_calendar(date="2025-03-26") # 다른 함수지만 병렬 가능
]
핵심 평가 포인트:
- 병렬화 가능한 호출 식별
- 올바른 수의 호출 생성
- 각 호출의 파라미터 정확성
- 의존 관계가 있는 호출은 병렬화하지 않는 판단
3.4 Nested/Composite Function Calling (중첩/복합 호출)
한 함수의 결과를 다른 함수의 입력으로 사용하는 다단계 추론을 측정합니다.
# 입력: "Book a flight to the cheapest destination from the list"
# 1단계: 목적지 가격 조회
destinations = get_destination_prices(origin="Seoul")
# 결과: [{"city": "Tokyo", "price": 300}, {"city": "Osaka", "price": 250}, ...]
# 2단계: 최저가 목적지로 예약
cheapest = min(destinations, key=lambda x: x["price"])
book_flight(origin="Seoul", destination=cheapest["city"], price=cheapest["price"])
다른 예시:
# 입력: "Get the manager's email of the employee who sold the most last quarter"
# 1단계: 최다 판매 직원 조회
top_seller = get_top_seller(period="Q4-2024")
# 결과: {"employee_id": "EMP-123", "name": "John"}
# 2단계: 해당 직원의 매니저 조회
manager = get_manager(employee_id="EMP-123")
# 결과: {"manager_id": "MGR-456", "name": "Jane"}
# 3단계: 매니저 이메일 조회
email = get_employee_email(employee_id="MGR-456")
# 결과: "jane@company.com"
3.5 Relevance Detection (관련성 탐지)
가장 중요한 카테고리 중 하나. 주어진 함수가 사용자 요청과 관련 없을 때, 호출하지 않는 능력을 측정합니다.
# 시나리오 1: 관련 없는 함수만 존재
# 사용자: "What is the meaning of life?"
# 사용 가능한 함수: get_weather(), search_products(), book_flight()
# 기대: 아무 함수도 호출하지 않고 직접 답변
# 시나리오 2: 부분적으로 관련 있지만 충분하지 않음
# 사용자: "How many calories are in a Big Mac?"
# 사용 가능한 함수: search_restaurants(cuisine, location)
# 기대: 함수 호출하지 않음 (레스토랑 검색이지, 칼로리 정보가 아님)
# 시나리오 3: 유혹적이지만 오용
# 사용자: "Tell me a joke about programming"
# 사용 가능한 함수: search_web(query)
# 기대: 함수 호출하지 않음 (LLM이 직접 농담 생성 가능)
왜 중요한가:
관련성 탐지 실패의 결과:
─────────────────────────────────
1. 불필요한 API 비용 발생
2. 사용자 경험 저하 (느린 응답)
3. 잘못된 결과로 인한 환각(hallucination)
4. 보안 위험 (불필요한 데이터 접근)
─────────────────────────────────
3.6 AST Evaluation (AST 평가)
생성된 함수 호출의 구조적 정확성을 Abstract Syntax Tree 기반으로 평가합니다.
# 평가 대상
generated_call = 'get_weather(location="Seoul", unit="celsius")'
# AST 파싱
import ast
tree = ast.parse(generated_call)
# 검증 항목:
# 1. 함수 이름이 올바른가?
# 2. 파라미터 이름이 올바른가?
# 3. 파라미터 타입이 올바른가? (string이 string인가?)
# 4. 필수 파라미터가 모두 포함되어 있는가?
# 5. 존재하지 않는 파라미터가 포함되어 있지 않은가?
AST 평가의 한계:
# AST는 통과하지만 실행 시 실패할 수 있는 케이스
get_weather(location="Seoull") # 오타지만 구문적으로 올바름
get_weather(location="서울") # 영어 도시명이 필요한 API에 한국어 전달
3.7 Executable Evaluation (실행 가능성 평가)
생성된 함수 호출을 실제로 실행하여 정확성을 검증합니다.
# 실행 기반 평가 프로세스
def evaluate_executable(generated_call, expected_result):
"""
1. 생성된 코드를 실행
2. 결과와 기대값 비교
3. 예외 발생 여부 확인
"""
try:
actual_result = eval(generated_call)
return compare_results(actual_result, expected_result)
except TypeError as e:
return {"status": "fail", "reason": f"Type error: {e}"}
except Exception as e:
return {"status": "fail", "reason": f"Execution error: {e}"}
지원 언어:
- Python: 가장 포괄적인 지원
- Java: 정적 타입 검증 포함
- JavaScript: 웹 API 시나리오
4. BFCL 평가 메트릭
4.1 메트릭 체계
BFCL 메트릭 구조:
─────────────────────────────────────────────────────
Overall Accuracy (종합 정확도)
├── AST Accuracy (구문적 정확도)
│ ├── Simple AST
│ ├── Multiple AST
│ ├── Parallel AST
│ └── Nested AST
├── Exec Accuracy (실행 정확도)
│ ├── Simple Exec
│ ├── Multiple Exec
│ ├── Parallel Exec
│ └── Nested Exec
├── Relevance Accuracy (관련성 정확도)
│ └── 불필요 호출 거부율
└── Live Test Accuracy (실시간 테스트)
└── 실제 API 대상 정확도
─────────────────────────────────────────────────────
4.2 세부 메트릭 설명
| 메트릭 | 설명 | 중요도 |
|---|---|---|
| Overall Accuracy | 전체 테스트 케이스 정확도 | 종합 지표 |
| AST Simple | 단순 호출의 구문 정확도 | 기본 능력 |
| AST Multiple | 다중 함수 선택 정확도 | 판별력 |
| AST Parallel | 병렬 호출 정확도 | 효율성 |
| Exec Accuracy | 실행 성공률 | 실용성 |
| Relevance | 불필요 호출 거부율 | 안전성 |
| Latency | 응답 시간 | 사용성 |
| Cost per call | 호출당 비용 | 경제성 |
4.3 정확도 계산 방식
# AST 정확도 계산
def calculate_ast_accuracy(predictions, ground_truth):
correct = 0
total = len(predictions)
for pred, truth in zip(predictions, ground_truth):
pred_ast = parse_function_call(pred)
truth_ast = parse_function_call(truth)
if (pred_ast.function_name == truth_ast.function_name and
match_parameters(pred_ast.params, truth_ast.params)):
correct += 1
return correct / total
# 파라미터 매칭 (순서 무관, 타입 일치)
def match_parameters(pred_params, truth_params):
"""
- 필수 파라미터가 모두 존재하는가?
- 파라미터 값이 일치하는가?
- 타입이 일치하는가?
- 추가 파라미터가 없는가?
"""
for key in truth_params:
if key not in pred_params:
return False
if not type_match(pred_params[key], truth_params[key]):
return False
return True
# Relevance 정확도 계산
def calculate_relevance_accuracy(predictions, labels):
"""
True Positive: 관련 없을 때 호출 안 함 (정답)
False Positive: 관련 없는데 호출함 (오답 - 환각)
False Negative: 관련 있는데 호출 안 함 (오답 - 누락)
"""
tp = sum(1 for p, l in zip(predictions, labels)
if p == "no_call" and l == "irrelevant")
fp = sum(1 for p, l in zip(predictions, labels)
if p != "no_call" and l == "irrelevant")
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
return precision
5. 모델 성능 비교 (2025)
5.1 종합 리더보드 (2025년 3월 기준)
| 순위 | 모델 | Overall | AST Simple | AST Multiple | AST Parallel | Relevance | Exec |
|---|---|---|---|---|---|---|---|
| 1 | Claude 3.5 Sonnet (v2) | 92.4% | 95.1% | 91.2% | 90.8% | 94.5% | 91.0% |
| 2 | GPT-4o (2025-01) | 91.8% | 94.8% | 90.5% | 91.2% | 93.0% | 90.2% |
| 3 | Gemini 2.0 Flash | 90.1% | 93.2% | 89.8% | 88.5% | 92.0% | 89.5% |
| 4 | Claude 3.5 Haiku | 88.5% | 92.0% | 87.5% | 86.2% | 91.5% | 87.0% |
| 5 | GPT-4 Turbo | 87.2% | 91.5% | 86.0% | 85.5% | 90.0% | 86.8% |
| 6 | Llama 3.1 405B | 85.5% | 90.0% | 84.5% | 83.0% | 88.5% | 84.0% |
| 7 | Qwen 2.5 72B | 84.2% | 89.0% | 83.0% | 82.5% | 87.0% | 83.5% |
| 8 | Mistral Large | 83.0% | 88.5% | 82.0% | 81.0% | 86.0% | 82.0% |
| 9 | Llama 3.1 70B | 81.5% | 87.0% | 80.0% | 79.5% | 84.5% | 80.5% |
| 10 | GPT-4o-mini | 80.8% | 86.5% | 79.0% | 78.5% | 83.0% | 79.5% |
5.2 카테고리별 강약점 분석
Claude 3.5 Sonnet
강점:
✅ Relevance Detection 최고 성능 (94.5%)
✅ 복잡한 파라미터 추출 정확도 높음
✅ 중첩 호출 체인에서 안정적
약점:
⚠️ 일부 병렬 호출에서 순차적 호출로 전환
⚠️ 매우 많은 도구(20+) 제공 시 선택 정확도 하락
GPT-4o
강점:
✅ Parallel 호출에서 최고 성능 (91.2%)
✅ JSON 스키마 준수율이 매우 높음
✅ 스트리밍 도구 호출 안정성
약점:
⚠️ Relevance에서 Claude보다 낮음
⚠️ 간혹 불필요한 도구 호출 발생
Gemini 2.0 Flash
강점:
✅ 빠른 응답 속도
✅ 비용 효율적
✅ 멀티모달 입력과 결합한 도구 호출
약점:
⚠️ 복잡한 중첩 호출에서 정확도 하락
⚠️ 일부 엣지 케이스에서 파라미터 타입 오류
오픈소스 모델 (Llama 3.1, Qwen 2.5)
강점:
✅ 자체 호스팅 가능 (데이터 프라이버시)
✅ Fine-tuning으로 특정 도메인 최적화 가능
✅ 비용 절감 (대규모 운영 시)
약점:
⚠️ 전반적으로 상용 모델 대비 5-10% 낮은 정확도
⚠️ Relevance Detection 능력 부족
⚠️ 복잡한 스키마 처리에 취약
5.3 비용 대비 성능 분석
비용 효율성 (정확도 / 비용):
─────────────────────────────────────────
모델 | 정확도 | 비용(/1M tok) | 효율성
GPT-4o-mini | 80.8% | ~$0.30 | ★★★★★
Claude 3.5 Haiku | 88.5% | ~$2.40 | ★★★★
Gemini 2.0 Flash | 90.1% | ~$0.40 | ★★★★★
Claude 3.5 Sonnet | 92.4% | ~$9.00 | ★★★
GPT-4o | 91.8% | ~$7.50 | ★★★
Llama 3.1 70B (self) | 81.5% | ~$0.10* | ★★★★★
─────────────────────────────────────────
* 자체 호스팅 기준 추정
6. BFCL 직접 실행하기
6.1 설치
# BFCL 저장소 클론
git clone https://github.com/ShishirPatil/gorilla.git
cd gorilla/berkeley-function-call-leaderboard
# 의존성 설치
pip install -r requirements.txt
# 또는 pip으로 직접 설치
pip install bfcl
6.2 평가 실행
# 기본 평가 실행
from bfcl import evaluate
# OpenAI 모델 평가
results = evaluate(
model="gpt-4o",
categories=["simple", "multiple", "parallel", "relevance"],
api_key="your-openai-api-key"
)
print(f"Overall Accuracy: {results['overall']:.2%}")
print(f"Simple: {results['simple']:.2%}")
print(f"Multiple: {results['multiple']:.2%}")
print(f"Parallel: {results['parallel']:.2%}")
print(f"Relevance: {results['relevance']:.2%}")
# CLI로 실행
python eval.py \
--model gpt-4o \
--categories simple multiple parallel relevance \
--output-dir ./results
# Anthropic 모델
python eval.py \
--model claude-3-5-sonnet \
--categories all \
--output-dir ./results
# 로컬 모델 (vLLM 서버)
python eval.py \
--model local \
--api-base http://localhost:8000/v1 \
--categories all
6.3 커스텀 모델 평가
from bfcl import BFCLEvaluator
class MyModelHandler:
"""커스텀 모델 핸들러"""
def __init__(self, model_path):
self.model = load_my_model(model_path)
def generate(self, prompt, tools, **kwargs):
"""
BFCL이 호출하는 인터페이스.
prompt: 사용자 입력
tools: 사용 가능한 도구 정의 목록
반환: 함수 호출 문자열 또는 "NO_CALL"
"""
formatted_prompt = self.format_prompt(prompt, tools)
response = self.model.generate(formatted_prompt)
return self.parse_tool_call(response)
def format_prompt(self, prompt, tools):
tool_descriptions = "\n".join([
f"Function: {t['name']}\n"
f"Description: {t['description']}\n"
f"Parameters: {json.dumps(t['parameters'])}"
for t in tools
])
return f"""Available functions:
{tool_descriptions}
User query: {prompt}
Respond with a function call or "NO_CALL" if no function is relevant."""
# 평가 실행
evaluator = BFCLEvaluator()
handler = MyModelHandler("/path/to/model")
results = evaluator.evaluate(
handler=handler,
categories=["simple", "multiple", "parallel", "relevance"],
output_dir="./my_model_results"
)
# 결과 리포트 생성
evaluator.generate_report(results, "./report.html")
6.4 커스텀 테스트 케이스 추가
# 커스텀 테스트 케이스 형식
custom_test = {
"id": "custom_001",
"category": "simple",
"prompt": "우리 팀 Slack 채널에 '회의 시작합니다' 메시지를 보내줘",
"available_functions": [
{
"name": "send_slack_message",
"description": "Send a message to a Slack channel",
"parameters": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Slack channel name"
},
"message": {
"type": "string",
"description": "Message text"
}
},
"required": ["channel", "message"]
}
}
],
"ground_truth": 'send_slack_message(channel="team", message="회의 시작합니다")',
"acceptable_variants": [
'send_slack_message(channel="team", message="회의 시작합니다")',
'send_slack_message(message="회의 시작합니다", channel="team")',
]
}
# 커스텀 테스트 실행
evaluator.evaluate_custom(
handler=handler,
test_cases=[custom_test],
output_dir="./custom_results"
)
6.5 결과 해석
# 결과 분석 예시
import json
with open("./results/evaluation_results.json") as f:
results = json.load(f)
# 카테고리별 정확도
for category, accuracy in results["categories"].items():
print(f"{category}: {accuracy:.2%}")
# 실패 케이스 분석
failures = results["failures"]
for failure in failures[:5]:
print(f"\nTest ID: {failure['id']}")
print(f"Category: {failure['category']}")
print(f"Prompt: {failure['prompt']}")
print(f"Expected: {failure['expected']}")
print(f"Got: {failure['predicted']}")
print(f"Error Type: {failure['error_type']}")
# error_type: wrong_function, wrong_params, unnecessary_call, missing_call
7. 다른 Tool Calling 벤치마크
7.1 벤치마크 비교
| 벤치마크 | 제작자 | 테스트 수 | 특징 | 강점 |
|---|---|---|---|---|
| BFCL | UC Berkeley | 2,000+ | 가장 포괄적, 라이브 리더보드 | 업계 표준 |
| API-Bank | Li et al. | 264 | API 호출 계획 + 실행 | 다단계 평가 |
| ToolBench | Qin et al. | 16,000+ | 대규모, RapidAPI 기반 | 규모와 다양성 |
| Nexus | Srinivasan | 1,500 | NexusRaven 모델과 함께 | 함수 호출 특화 |
| T-Eval | Chen et al. | 553 | 단계별 평가 (계획/선택/실행) | 세밀한 분석 |
| Seal-Tools | Various | 1,000+ | 다국어 지원 | 국제화 |
7.2 API-Bank
# API-Bank 특징: 3단계 평가
# Level 1: API 호출 능력 (단일)
# Level 2: API 검색 + 호출 (올바른 API 찾기)
# Level 3: API 조합 + 계획 (다단계)
# 예시 (Level 3):
# "내일 오전 회의가 있는지 확인하고, 있으면 참석자에게 알림을 보내줘"
# → Step 1: check_calendar(date="tomorrow", time="morning")
# → Step 2: if meeting exists, get_attendees(meeting_id=...)
# → Step 3: send_notification(recipients=..., message=...)
7.3 ToolBench
# ToolBench 특징: RapidAPI의 실제 16,000+ API 기반
# 실제 API 문서를 사용하여 더 현실적인 시나리오
# 카테고리:
# - Single Tool: 단일 API 사용
# - Intra-Category: 같은 카테고리 내 여러 API
# - Inter-Category: 다른 카테고리의 API 조합
# 평가 메트릭:
# - Pass Rate: 실행 성공률
# - Win Rate: 다른 모델 대비 선호도 (GPT-4 평가)
7.4 T-Eval
# T-Eval 특징: 도구 사용의 각 단계를 세밀하게 평가
# 6가지 하위 능력 측정:
# 1. Instruct Following: 지시 이해
# 2. Plan: 작업 계획 수립
# 3. Reason: 올바른 도구 추론
# 4. Retrieve: 적절한 도구 검색
# 5. Understand: 도구 문서 이해
# 6. Review: 결과 검증 및 수정
7.5 벤치마크 선택 가이드
어떤 벤치마크를 사용해야 하나?
─────────────────────────────────────────────────
목적 | 추천 벤치마크
종합 Tool Calling 평가 | BFCL
대규모 실제 API 테스트 | ToolBench
단계별 세밀 분석 | T-Eval
다단계 API 계획 평가 | API-Bank
빠른 기본 평가 | BFCL (Simple만)
자체 모델 비교 | BFCL + 커스텀 테스트
─────────────────────────────────────────────────
8. Tool Calling 성능 개선 전략
8.1 Fine-tuning 데이터셋 생성
# Tool Calling Fine-tuning 데이터 생성 파이프라인
import json
from openai import OpenAI
def generate_training_data(tools, num_examples=1000):
"""GPT-4o를 사용하여 학습 데이터 생성"""
client = OpenAI()
training_data = []
tool_descriptions = json.dumps(tools, indent=2)
for i in range(num_examples):
# 1. 자연어 쿼리 생성
query_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"""Generate a natural language user query
that would require calling one of these tools:
{tool_descriptions}
Generate diverse, realistic queries. Include edge cases.
Respond with ONLY the query text."""},
{"role": "user", "content": f"Generate query #{i+1}"}
]
)
query = query_response.choices[0].message.content
# 2. 올바른 함수 호출 생성
call_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": query}
],
tools=[{"type": "function", "function": t} for t in tools],
tool_choice="auto"
)
if call_response.choices[0].message.tool_calls:
tc = call_response.choices[0].message.tool_calls[0]
training_data.append({
"messages": [
{"role": "system", "content": f"You have access to: {tool_descriptions}"},
{"role": "user", "content": query},
{"role": "assistant", "content": None, "tool_calls": [
{
"type": "function",
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments
}
}
]}
]
})
return training_data
# 데이터 저장
data = generate_training_data(my_tools, num_examples=5000)
with open("tool_calling_train.jsonl", "w") as f:
for item in data:
f.write(json.dumps(item) + "\n")
8.2 Tool Description 최적화
# 단계적 최적화 프로세스
# Step 1: 초기 설명 작성
v1 = {
"name": "search_products",
"description": "Search for products" # 너무 간단
}
# Step 2: 명확한 용도 설명 추가
v2 = {
"name": "search_products",
"description": "Search for products in the catalog by name, category, or keywords. Returns matching products with price and availability."
}
# Step 3: 사용/비사용 조건 추가
v3 = {
"name": "search_products",
"description": """Search for products in the e-commerce catalog.
USE WHEN: User wants to find, browse, or compare products.
DO NOT USE: For order status (use get_order), account info (use get_account), or returns (use create_return).
Returns: List of products with name, price, rating, availability."""
}
# Step 4: 예시 추가 (최종)
v4 = {
"name": "search_products",
"description": """Search for products in the e-commerce catalog.
USE WHEN: User wants to find, browse, or compare products.
DO NOT USE: For order status, account info, or returns.
EXAMPLES:
- "wireless headphones" -> query="wireless headphones"
- "cheap laptops under $500" -> query="laptops", max_price=500
- "best rated phones" -> query="phones", sort_by="rating"
Returns: List of products with name, price, rating, availability."""
}
8.3 시스템 프롬프트 엔지니어링
# Tool Calling 최적화 시스템 프롬프트
system_prompt = """You are an AI assistant with access to tools.
IMPORTANT RULES:
1. ONLY call a function when the user's request CLEARLY requires it.
2. If you can answer directly from your knowledge, do NOT call any function.
3. When calling functions, ensure ALL required parameters are provided.
4. Use the EXACT parameter names and types defined in the function schema.
5. If a user's request is ambiguous, ask for clarification BEFORE calling a function.
PARAMETER GUIDELINES:
- Dates: Use ISO 8601 format (YYYY-MM-DD)
- Locations: Use the most common English name
- Numbers: Use numeric type, not string
- Booleans: Use true/false, not "yes"/"no"
WHEN NOT TO CALL FUNCTIONS:
- General knowledge questions
- Opinions or advice
- Greetings or small talk
- Math that you can calculate yourself
"""
8.4 에러 분석 방법론
# 체계적 에러 분석
def analyze_errors(results):
"""BFCL 결과에서 에러 패턴을 분석"""
error_categories = {
"wrong_function": [], # 잘못된 함수 선택
"missing_params": [], # 필수 파라미터 누락
"wrong_param_type": [], # 파라미터 타입 오류
"extra_params": [], # 불필요한 파라미터 추가
"unnecessary_call": [], # 불필요한 함수 호출
"missing_call": [], # 필요한 함수 미호출
"wrong_value": [], # 파라미터 값 오류
}
for failure in results["failures"]:
error_type = classify_error(failure)
error_categories[error_type].append(failure)
# 통계 출력
print("Error Distribution:")
print("=" * 50)
total = sum(len(v) for v in error_categories.values())
for category, errors in sorted(
error_categories.items(),
key=lambda x: len(x[1]),
reverse=True
):
pct = len(errors) / total * 100 if total > 0 else 0
print(f" {category}: {len(errors)} ({pct:.1f}%)")
# 가장 흔한 에러 패턴 식별
print("\nTop Error Patterns:")
for category, errors in error_categories.items():
if errors:
print(f"\n{category}:")
patterns = find_common_patterns(errors)
for pattern, count in patterns[:3]:
print(f" - {pattern} ({count} occurrences)")
return error_categories
8.5 반복 개선 사이클
Tool Calling 개선 사이클:
─────────────────────────────────────────────────
1단계: 현재 성능 측정
└─ BFCL 전체 카테고리 실행
└─ 카테고리별 정확도 기록
2단계: 약점 식별
└─ 에러 분석 실행
└─ 가장 빈번한 에러 유형 파악
└─ 실패 케이스 패턴 분석
3단계: 개선 조치
├─ 프롬프트 개선 (빠른 효과)
│ └─ Tool Description 개선
│ └─ System Prompt 최적화
│ └─ Few-shot 예시 추가
├─ 도구 설계 개선 (중간)
│ └─ 스키마 단순화
│ └─ 관련 도구 통합
│ └─ 파라미터 이름 명확화
└─ Fine-tuning (장기)
└─ 실패 케이스 기반 학습 데이터 생성
└─ LoRA/QLoRA Fine-tuning
└─ 평가 + 반복
4단계: 재평가
└─ 같은 벤치마크로 재측정
└─ 개선율 확인
└─ 새로운 약점 식별
→ 2단계로 반복
9. 현실 세계 vs 벤치마크 격차
9.1 벤치마크가 다루지 못하는 것
벤치마크 한계:
─────────────────────────────────────────────────
1. 모호한 사용자 입력
벤치마크: "Seoul의 날씨" (명확)
현실: "날씨 어때?" (위치 없음, 언제인지 불명)
2. 대화 컨텍스트 의존
벤치마크: 단일 턴 테스트
현실: 이전 대화에서 "거기"가 어디인지 추론
3. 에러 복구
벤치마크: 정상 응답만 테스트
현실: API 실패, 타임아웃, 잘못된 응답 처리
4. 도구 조합 폭발
벤치마크: 5-10개 도구
현실: 50-100개 도구 동시 제공
5. 실시간 성능
벤치마크: 정확도만 측정
현실: 속도, 비용, 안정성 모두 중요
─────────────────────────────────────────────────
9.2 자체 평가 스위트 구축
# 프로덕션 시나리오 기반 평가 스위트
class ProductionEvalSuite:
def __init__(self, tools, model):
self.tools = tools
self.model = model
self.test_cases = []
def add_test_case(self, category, prompt, expected, context=None):
self.test_cases.append({
"category": category,
"prompt": prompt,
"expected": expected,
"context": context or []
})
def build_standard_suite(self):
"""표준 평가 세트 구축"""
# 1. 기본 기능 테스트
self.add_test_case(
"basic", "서울 날씨 알려줘",
"get_weather(location='Seoul')"
)
# 2. 모호한 입력 테스트
self.add_test_case(
"ambiguous", "날씨 어때?",
"ASK_CLARIFICATION" # 위치 물어봐야 함
)
# 3. 다중 턴 컨텍스트 테스트
self.add_test_case(
"multi_turn",
"거기 내일 날씨는?",
"get_weather(location='Seoul', date='tomorrow')",
context=[
{"role": "user", "content": "서울 날씨 알려줘"},
{"role": "assistant", "content": "서울은 현재 15도입니다."}
]
)
# 4. 관련성 테스트
self.add_test_case(
"relevance", "인생의 의미가 뭐야?",
"NO_CALL"
)
# 5. 에러 복구 테스트
self.add_test_case(
"error_recovery",
"서울 날씨 알려줘",
"RETRY_OR_FALLBACK",
context=[
{"role": "tool", "content": "ERROR: API timeout"}
]
)
def run(self):
results = {"total": 0, "correct": 0, "by_category": {}}
for test in self.test_cases:
result = self.evaluate_single(test)
results["total"] += 1
if result["correct"]:
results["correct"] += 1
cat = test["category"]
if cat not in results["by_category"]:
results["by_category"][cat] = {"total": 0, "correct": 0}
results["by_category"][cat]["total"] += 1
if result["correct"]:
results["by_category"][cat]["correct"] += 1
results["accuracy"] = results["correct"] / results["total"]
return results
9.3 프로덕션 모니터링
# Tool Calling 프로덕션 모니터링
class ToolCallingMonitor:
def __init__(self):
self.metrics = {
"total_calls": 0,
"successful_calls": 0,
"failed_calls": 0,
"unnecessary_calls": 0,
"latency_sum": 0,
"cost_sum": 0,
"by_tool": {},
}
def record_call(self, tool_name, success, latency, cost,
was_necessary=True):
self.metrics["total_calls"] += 1
if success:
self.metrics["successful_calls"] += 1
else:
self.metrics["failed_calls"] += 1
if not was_necessary:
self.metrics["unnecessary_calls"] += 1
self.metrics["latency_sum"] += latency
self.metrics["cost_sum"] += cost
if tool_name not in self.metrics["by_tool"]:
self.metrics["by_tool"][tool_name] = {
"calls": 0, "successes": 0, "failures": 0
}
self.metrics["by_tool"][tool_name]["calls"] += 1
if success:
self.metrics["by_tool"][tool_name]["successes"] += 1
else:
self.metrics["by_tool"][tool_name]["failures"] += 1
def get_dashboard_data(self):
total = self.metrics["total_calls"]
if total == 0:
return {}
return {
"success_rate": self.metrics["successful_calls"] / total,
"failure_rate": self.metrics["failed_calls"] / total,
"unnecessary_rate": self.metrics["unnecessary_calls"] / total,
"avg_latency": self.metrics["latency_sum"] / total,
"total_cost": self.metrics["cost_sum"],
"tool_breakdown": self.metrics["by_tool"],
}
def alert_on_anomaly(self):
data = self.get_dashboard_data()
alerts = []
if data.get("failure_rate", 0) > 0.1:
alerts.append("HIGH: Tool call failure rate above 10%")
if data.get("unnecessary_rate", 0) > 0.2:
alerts.append("MEDIUM: Unnecessary tool calls above 20%")
if data.get("avg_latency", 0) > 5.0:
alerts.append("MEDIUM: Average latency above 5 seconds")
return alerts
10. 퀴즈
Q1: BFCL의 7가지 주요 평가 카테고리는 무엇인가요?
정답: Simple Function Calling, Multiple Function Calling, Parallel Function Calling, Nested/Composite Function Calling, Relevance Detection, AST Evaluation, Executable Evaluation.
Simple은 단일 함수 단일 호출, Multiple은 여러 유사 함수 중 선택, Parallel은 독립적 병렬 호출, Nested는 결과 체이닝, Relevance는 불필요 호출 거부, AST는 구문 정확성, Executable은 실행 정확성을 평가합니다.
Q2: Relevance Detection이 왜 Tool Calling에서 가장 중요한 카테고리 중 하나인가요?
정답: Relevance Detection은 LLM이 도구가 필요하지 않을 때 호출하지 않는 능력을 측정합니다. 이것이 부족하면: 1) 불필요한 API 비용 발생, 2) 응답 지연, 3) 잘못된 결과로 인한 환각(hallucination), 4) 보안 위험(불필요한 데이터 접근)이 발생합니다. 실제 프로덕션에서 사용자 질문의 상당수는 도구 없이도 답변 가능하므로, 이 능력이 부족하면 비용과 사용자 경험 모두 악화됩니다.
Q3: AST Evaluation과 Executable Evaluation의 차이점은 무엇인가요?
정답: AST Evaluation은 생성된 함수 호출의 구문적 구조만 검증합니다(함수명, 파라미터명, 타입 일치). Executable Evaluation은 생성된 코드를 실제로 실행하여 결과를 검증합니다. AST는 get_weather(location="Seoull")을 통과시키지만(구문적으로 올바름), Executable은 실제 API가 "Seoull"을 인식하지 못해 실패로 판정합니다.
Q4: 2025년 기준, BFCL에서 Tool Calling 성능이 가장 좋은 모델과 비용 효율이 가장 좋은 모델은?
정답: 성능 최고는 Claude 3.5 Sonnet (Overall 약 92.4%)과 GPT-4o (약 91.8%)입니다. 비용 효율 최고는 Gemini 2.0 Flash (90.1% / 저비용)와 GPT-4o-mini (80.8% / 최저 비용)입니다. 자체 호스팅이 가능하다면 Llama 3.1 70B도 비용 효율적입니다.
Q5: BFCL 외에 어떤 Tool Calling 벤치마크가 있으며, 각각의 특징은?
정답: 1) API-Bank - 3단계 평가(호출/검색/계획), 다단계 API 사용, 2) ToolBench - RapidAPI 기반 16,000+ 실제 API로 대규모 테스트, 3) T-Eval - 6가지 하위 능력(지시이해/계획/추론/검색/이해/검증)을 세밀하게 평가, 4) Nexus - NexusRaven 모델과 함께 함수 호출 특화 평가. 종합 평가는 BFCL, 대규모 테스트는 ToolBench, 세밀 분석은 T-Eval이 적합합니다.
11. 참고 자료
- BFCL Official Website - gorilla.cs.berkeley.edu/leaderboard
- Gorilla: Large Language Model Connected with Massive APIs - Patil et al., 2023
- Berkeley Function-Calling Leaderboard Paper - Yan et al., 2024
- API-Bank: A Comprehensive Benchmark for Tool-Augmented LLMs - Li et al., 2023
- ToolBench: An Open Platform for Tool-Augmented LLMs - Qin et al., 2023
- T-Eval: Evaluating Tool Utilization Capability of LLMs - Chen et al., 2024
- Nexus Function Calling Benchmark - Srinivasan et al., 2024
- OpenAI Function Calling Best Practices - OpenAI 공식 문서
- Anthropic Tool Use Documentation - Anthropic 공식 문서
- Gorilla GitHub Repository - github.com/ShishirPatil/gorilla
- Unsloth Fine-tuning Guide - Tool Calling 파인튜닝 가이드
- LangSmith Evaluation Documentation - LangSmith 평가 프레임워크
- Seal-Tools: Multilingual Tool Calling Benchmark - 다국어 벤치마크
- HuggingFace Open LLM Leaderboard - 오픈소스 모델 비교