PyTorch
Safetensors
action-model
function-calling
tool-calling
tool-use
non-autoregressive
masked-diffusion
modernbert
File size: 2,846 Bytes
6fd3740
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""BFCL-style AST matching (re-implemented, simplified) for predicted calls vs acceptable-value gold.

gold: [{func_name: {arg: [acceptable values]}}]; "" in the acceptable list means the arg may be omitted.
"""
from __future__ import annotations

import itertools
import re
from typing import Any

from .schema import Call


def _std(s: str) -> str:
    return re.sub(r"[ ,./\-_*^]", "", str(s)).lower().replace("'", '"')


def _num(x):
    if isinstance(x, bool):
        return None
    if isinstance(x, (int, float)):
        return float(x)
    if isinstance(x, str):
        try:
            return float(x)
        except ValueError:
            return None
    return None


def value_match(pred: Any, gold: Any) -> bool:
    if isinstance(gold, bool) or isinstance(pred, bool):
        if isinstance(pred, str):
            return pred.strip().lower() == str(gold).lower()
        return pred == gold
    gn, pn = _num(gold), _num(pred)
    if gn is not None and not isinstance(gold, str):
        return pn is not None and abs(pn - gn) < 1e-6
    if isinstance(gold, str):
        return _std(pred) == _std(gold) if not isinstance(pred, (list, dict)) else False
    if isinstance(gold, (list, tuple)):
        return isinstance(pred, (list, tuple)) and len(pred) == len(gold) and all(
            value_match(p, g) for p, g in zip(pred, gold))
    if isinstance(gold, dict):
        if not isinstance(pred, dict):
            return False
        for k, gv in gold.items():
            accept = gv if isinstance(gv, list) else [gv]
            if k not in pred:
                if "" not in accept:
                    return False
                continue
            if not any(value_match(pred[k], a) for a in accept if a != ""):
                return False
        return set(pred) <= set(gold)
    return pred == gold


def call_match(pred: Call, gold_call: dict) -> bool:
    (name, accept), = gold_call.items()
    if pred.name != name:
        return False
    for k, v in pred.args.items():
        if k not in accept:
            return False
        if not any(value_match(v, a) for a in accept[k] if not (isinstance(a, str) and a == "")):
            if not ("" in accept[k] and v in ("", None)):
                return False
    for k, vals in accept.items():
        if k not in pred.args and "" not in vals:
            return False
    return True


def match_calls(pred: list[Call], gold: list[dict]) -> bool:
    if len(pred) != len(gold):
        return False
    if not gold:
        return True
    for perm in itertools.permutations(range(len(pred))):
        if all(call_match(pred[perm[g]], gold[g]) for g in range(len(gold))):
            return True
    return False


def names_match(pred: list[Call], gold: list[dict]) -> bool:
    return sorted(c.name for c in pred) == sorted(next(iter(g)) for g in gold)