Files
zk-data-agent/benchmarks/suites/bfcl.py
T

342 lines
14 KiB
Python

"""
BFCL (Berkeley Function Calling Leaderboard) benchmark suite.
BFCL tests whether an agent can correctly identify and call functions/tools
based on natural language instructions. This is directly relevant for
coding agents that use tool calling.
Reference: https://gorilla.cs.berkeley.edu/leaderboard.html
"""
from __future__ import annotations
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
from .base import BenchmarkResult, BenchmarkSuite
# ---------------------------------------------------------------------------
# Built-in mini dataset — function-calling tasks
# ---------------------------------------------------------------------------
_BUILTIN_PROBLEMS: list[dict[str, Any]] = [
{
"id": "bfcl-001",
"instruction": (
"You have access to a function `get_weather(city: str, unit: str = 'celsius') -> dict` "
"that returns weather info. Write a Python script called solution.py that calls "
"get_weather for 'San Francisco' with unit 'fahrenheit' and prints the result."
),
"expected_function": "get_weather",
"expected_args": {"city": "San Francisco", "unit": "fahrenheit"},
"setup_code": (
"cat > weather_api.py << 'PYEOF'\n"
"def get_weather(city: str, unit: str = 'celsius') -> dict:\n"
" return {'city': city, 'unit': unit, 'temp': 72 if unit == 'fahrenheit' else 22}\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"# Check that solution.py exists and calls get_weather correctly\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'get_weather' in code, 'Must call get_weather'\n"
"assert 'San Francisco' in code, 'Must use San Francisco'\n"
"assert 'fahrenheit' in code, 'Must use fahrenheit'\n"
"exec(open('solution.py').read())\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-002",
"instruction": (
"You have access to these functions:\n"
"- `search_files(directory: str, pattern: str) -> list[str]`\n"
"- `read_file(path: str) -> str`\n"
"- `write_file(path: str, content: str) -> None`\n\n"
"Write a Python script called solution.py that:\n"
"1. Calls search_files('.', '*.txt') to find all text files\n"
"2. Reads each file using read_file\n"
"3. Writes a combined output using write_file to 'combined.txt'"
),
"expected_function": "search_files",
"expected_args": {"directory": ".", "pattern": "*.txt"},
"setup_code": (
"cat > file_api.py << 'PYEOF'\n"
"import glob\n"
"def search_files(directory: str, pattern: str) -> list:\n"
" import os\n"
" return [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.txt') and f != 'combined.txt']\n"
"def read_file(path: str) -> str:\n"
" with open(path) as f:\n"
" return f.read()\n"
"def write_file(path: str, content: str) -> None:\n"
" with open(path, 'w') as f:\n"
" f.write(content)\n"
"PYEOF\n"
"echo 'hello' > a.txt\n"
"echo 'world' > b.txt\n"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'search_files' in code, 'Must call search_files'\n"
"assert 'read_file' in code, 'Must call read_file'\n"
"assert 'write_file' in code, 'Must call write_file'\n"
"exec(open('solution.py').read())\n"
"import os\n"
"assert os.path.exists('combined.txt'), 'combined.txt must exist'\n"
"content = open('combined.txt').read()\n"
"assert 'hello' in content and 'world' in content\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-003",
"instruction": (
"You have a function `calculate(expression: str) -> float` that evaluates "
"a mathematical expression string. Write solution.py that:\n"
"1. Calculates '(15 + 25) * 3'\n"
"2. Calculates '100 / 4 - 5'\n"
"3. Prints both results"
),
"expected_function": "calculate",
"expected_args": {"expression": "(15 + 25) * 3"},
"setup_code": (
"cat > calc_api.py << 'PYEOF'\n"
"def calculate(expression: str) -> float:\n"
" return float(eval(expression))\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'calculate' in code, 'Must call calculate'\n"
"exec(open('solution.py').read())\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-004",
"instruction": (
"You have these functions:\n"
"- `create_user(name: str, email: str, role: str = 'user') -> dict`\n"
"- `delete_user(user_id: int) -> bool`\n"
"- `list_users() -> list[dict]`\n\n"
"Write solution.py that creates a user named 'Alice' with email "
"'alice@example.com' and role 'admin', then lists all users."
),
"expected_function": "create_user",
"expected_args": {"name": "Alice", "email": "alice@example.com", "role": "admin"},
"setup_code": (
"cat > user_api.py << 'PYEOF'\n"
"_users = []\n"
"_next_id = 1\n"
"def create_user(name: str, email: str, role: str = 'user') -> dict:\n"
" global _next_id\n"
" user = {'id': _next_id, 'name': name, 'email': email, 'role': role}\n"
" _users.append(user)\n"
" _next_id += 1\n"
" return user\n"
"def delete_user(user_id: int) -> bool:\n"
" global _users\n"
" _users = [u for u in _users if u['id'] != user_id]\n"
" return True\n"
"def list_users() -> list:\n"
" return list(_users)\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'create_user' in code, 'Must call create_user'\n"
"assert 'Alice' in code, 'Must use Alice'\n"
"assert 'alice@example.com' in code, 'Must use correct email'\n"
"assert 'admin' in code, 'Must use admin role'\n"
"assert 'list_users' in code, 'Must call list_users'\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-005",
"instruction": (
"You have a function `send_email(to: str, subject: str, body: str, "
"cc: list[str] = None) -> bool`. Write solution.py that sends an email "
"to 'boss@company.com' with subject 'Weekly Report', body 'Please see attached report.', "
"and cc=['team@company.com']."
),
"expected_function": "send_email",
"expected_args": {
"to": "boss@company.com",
"subject": "Weekly Report",
"body": "Please see attached report.",
"cc": ["team@company.com"],
},
"setup_code": (
"cat > email_api.py << 'PYEOF'\n"
"def send_email(to: str, subject: str, body: str, cc: list = None) -> bool:\n"
" print(f'Email sent to {to}, subject: {subject}')\n"
" return True\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'send_email' in code, 'Must call send_email'\n"
"assert 'boss@company.com' in code, 'Must use correct recipient'\n"
"assert 'Weekly Report' in code, 'Must use correct subject'\n"
"assert 'team@company.com' in code, 'Must include cc'\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-006",
"instruction": (
"You have these functions:\n"
"- `connect_db(host: str, port: int, database: str) -> object`\n"
"- `execute_query(connection: object, query: str) -> list`\n"
"- `close_db(connection: object) -> None`\n\n"
"Write solution.py that connects to host='localhost', port=5432, database='mydb', "
"executes 'SELECT * FROM users', and closes the connection."
),
"expected_function": "connect_db",
"expected_args": {"host": "localhost", "port": 5432, "database": "mydb"},
"setup_code": (
"cat > db_api.py << 'PYEOF'\n"
"class FakeConnection:\n"
" def __init__(self, host, port, db):\n"
" self.host = host\n"
" self.port = port\n"
" self.db = db\n"
" self.closed = False\n"
"def connect_db(host: str, port: int, database: str) -> object:\n"
" return FakeConnection(host, port, database)\n"
"def execute_query(connection: object, query: str) -> list:\n"
" return [{'id': 1, 'name': 'test'}]\n"
"def close_db(connection: object) -> None:\n"
" connection.closed = True\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'connect_db' in code, 'Must call connect_db'\n"
"assert 'execute_query' in code, 'Must call execute_query'\n"
"assert 'close_db' in code, 'Must call close_db'\n"
"assert 'localhost' in code, 'Must use localhost'\n"
"assert '5432' in code, 'Must use port 5432'\n"
"assert 'mydb' in code, 'Must use mydb database'\n"
"print('ALL_TESTS_PASSED')\n"
),
},
{
"id": "bfcl-007",
"instruction": (
"You have a function `sort_data(data: list, key: str, reverse: bool = False) -> list`. "
"Write solution.py that sorts the list [{'name': 'Charlie', 'age': 30}, "
"{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 35}] by 'age' in "
"descending order (reverse=True) and prints the result."
),
"expected_function": "sort_data",
"expected_args": {"key": "age", "reverse": True},
"setup_code": (
"cat > sort_api.py << 'PYEOF'\n"
"def sort_data(data: list, key: str, reverse: bool = False) -> list:\n"
" return sorted(data, key=lambda x: x[key], reverse=reverse)\n"
"PYEOF"
),
"test_code": (
"import sys\n"
"sys.path.insert(0, '.')\n"
"with open('solution.py') as f:\n"
" code = f.read()\n"
"assert 'sort_data' in code, 'Must call sort_data'\n"
"assert 'age' in code, 'Must sort by age'\n"
"assert 'reverse' in code or 'True' in code, 'Must use reverse order'\n"
"print('ALL_TESTS_PASSED')\n"
),
},
]
class BFCLBenchmark(BenchmarkSuite):
"""BFCL: Berkeley Function Calling Leaderboard."""
name = "BFCL"
description = "Function/tool calling evaluation"
category = "instruction-following"
def load_dataset(self) -> list[dict[str, Any]]:
jsonl_path = Path(self.data_dir) / "bfcl.jsonl"
if jsonl_path.exists():
problems: list[dict[str, Any]] = []
with open(jsonl_path) as fh:
for line in fh:
line = line.strip()
if line:
problems.append(json.loads(line))
if self.verbose:
print(f" Loaded {len(problems)} problems from {jsonl_path}")
return problems
if self.verbose:
print(f" {jsonl_path} not found — using built-in 7-problem subset")
return list(_BUILTIN_PROBLEMS)
def build_prompt(self, problem: dict[str, Any]) -> str:
return problem["instruction"]
def setup_workspace(self, problem: dict[str, Any], workspace: str) -> None:
setup = problem.get("setup_code", "")
if setup:
self._run_shell(setup, cwd=workspace, timeout=30.0)
test_code = problem.get("test_code", "")
if test_code:
with open(os.path.join(workspace, "test_harness.py"), "w") as fh:
fh.write(test_code)
def evaluate(self, problem: dict[str, Any], workspace: str) -> BenchmarkResult:
pid = problem.get("id", "unknown")
sol = os.path.join(workspace, "solution.py")
if not os.path.exists(sol):
return BenchmarkResult(
problem_id=pid, passed=False, error="solution.py not found"
)
output = ""
test_harness = os.path.join(workspace, "test_harness.py")
if os.path.exists(test_harness):
code, output = self._run_shell(
f"{sys.executable} test_harness.py", cwd=workspace, timeout=30.0
)
passed = code == 0 and "ALL_TESTS_PASSED" in output
else:
# Basic check: verify the expected function is called
with open(sol) as fh:
code_content = fh.read()
expected_fn = problem.get("expected_function", "")
passed = expected_fn in code_content if expected_fn else True
return BenchmarkResult(
problem_id=pid, passed=passed,
actual=output[:500],
error="" if passed else (output[:500] if output else "function call not found"),
)