CoolFace
Datasetpublic

DCAgent2/bfcl-parity

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes188downloads
evaluate.py199 linesDownload Raw Back to tests
1"""2BFCL evaluation script for task: simple_python_3253 4This script evaluates the agent's function calling output against ground truth.5"""6import json7import sys8from pathlib import Path9 10 11def load_result():12    """Load the result.json file generated by the agent."""13    result_path = Path("/app/result.json")14    if not result_path.exists():15        raise FileNotFoundError("result.json not found. Agent must write output to /app/result.json")16    17    with open(result_path, 'r') as f:18        return json.load(f)19 20 21def normalize_function_name(name: str) -> str:22    """Normalize function name by replacing dots with underscores."""23    return name.replace('.', '_')24 25 26def compare_function_calls(predicted, ground_truth):27    """28    Compare predicted function calls against ground truth.29 30    Args:31        predicted: List of function call dicts from agent32        ground_truth: List of acceptable function call dicts33    34    Returns:35        bool: True if prediction matches any acceptable ground truth36    """37    if not isinstance(predicted, list):38        return False39    40    if len(predicted) == 0 and len(ground_truth) == 0:41        return True42    43    if len(predicted) != len(ground_truth):44        return False45    46    # Precompute ground truth entries47    gt_entries = []48    for gt_call in ground_truth:49        if not isinstance(gt_call, dict) or len(gt_call) != 1:50            return False51        gt_func_name = list(gt_call.keys())[0]52        gt_entries.append((normalize_function_name(gt_func_name), gt_call[gt_func_name]))53 54    # Order-independent matching: each predicted call must match55    # exactly one ground truth call, but in any order.56    # Matches upstream BFCL ast_checker.py parallel_function_checker_no_order.57    matched_gt = set()58    for pred_call in predicted:59        if not isinstance(pred_call, dict) or len(pred_call) != 1:60            return False61 62        pred_func_name = list(pred_call.keys())[0]63        pred_params = pred_call[pred_func_name]64        pred_func_name_norm = normalize_function_name(pred_func_name)65 66        found = False67        for j, (gt_func_name_norm, gt_params) in enumerate(gt_entries):68            if j in matched_gt:69                continue70            if pred_func_name_norm == gt_func_name_norm and compare_parameters(pred_params, gt_params):71                matched_gt.add(j)72                found = True73                break74 75        if not found:76            return False77 78    return True79 80 81def compare_parameters(pred_params, gt_params):82    """83    Compare predicted parameters against ground truth parameters.84    85    Ground truth may contain multiple acceptable values for each parameter.86    Rejects unexpected parameters not in ground truth (matches upstream BFCL behavior).87    """88    if not isinstance(pred_params, dict) or not isinstance(gt_params, dict):89        return False90    91    # Reject unexpected parameters not in ground truth92    for param_name in pred_params:93        if param_name not in gt_params:94            return False95    96    # Check all ground truth parameters97    for param_name, acceptable_values in gt_params.items():98        if param_name not in pred_params:99            # Parameter missing - check if it's in acceptable values (empty string means optional)100            if "" not in acceptable_values and None not in acceptable_values:101                return False102            continue103        104        pred_value = pred_params[param_name]105        106        # Check if predicted value matches any acceptable value107        if not isinstance(acceptable_values, list):108            acceptable_values = [acceptable_values]109        110        # Special case: if acceptable_values is empty list, check if pred_value is also empty list111        if len(acceptable_values) == 0:112            if pred_value != []:113                return False114            continue115        116        matched = False117        for acceptable_value in acceptable_values:118            if values_equal(pred_value, acceptable_value):119                matched = True120                break121        122        if not matched:123            return False124    125    return True126 127 128def standardize_string(s):129    """Standardize string by removing punctuation/whitespace and lowercasing.130    Matches upstream BFCL ast_checker.py standardize_string behavior."""131    import re132    return re.sub(r"[ ,./\-_*^]", "", s).lower().replace("'", '"')133 134 135def values_equal(v1, v2):136    """Check if two values are equal, handling type conversions."""137    # Handle empty string as "not provided" or default138    if v2 == "" or v2 is None:139        return True140 141    # Direct equality142    if v1 == v2:143        return True144 145    # Try numeric comparison146    try:147        if float(v1) == float(v2):148            return True149    except (ValueError, TypeError):150        pass151 152    # Try string comparison with standardization153    # Matches upstream BFCL ast_checker.py string_checker behavior:154    # strips spaces, commas, periods, slashes, hyphens, underscores, asterisks, carets155    if isinstance(v1, str) and isinstance(v2, str):156        if standardize_string(v1) == standardize_string(v2):157            return True158    elif str(v1).lower() == str(v2).lower():159        return True160    161    # Handle list/array comparison162    if isinstance(v1, list) and isinstance(v2, list):163        if len(v1) != len(v2):164            return False165        return all(values_equal(a, b) for a, b in zip(v1, v2))166    167    return False168 169 170def main():171    """Main evaluation function."""172    # Load ground truth173    ground_truth = [{'sports.match_results': {'team1': ['Chicago Bulls'], 'team2': ['Los Angeles Lakers'], 'season': ['']}}]174    175    try:176        # Load agent's result177        result = load_result()178        179        # Compare against ground truth180        if compare_function_calls(result, ground_truth):181            print("✓ Test passed: Function call matches ground truth")182            return 0183        else:184            print("✗ Test failed: Function call does not match ground truth")185            print(f"Predicted: {result}")186            print(f"Expected one of: {ground_truth}")187            return 1188    189    except Exception as e:190        print(f"✗ Test failed with error: {e}")191        import traceback192        traceback.print_exc()193        return 1194 195 196if __name__ == "__main__":197    exit_code = main()198    sys.exit(exit_code)199