mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
115 lines
5.3 KiB
Python
115 lines
5.3 KiB
Python
"""Report a fine label when Kai is sure of it, its parent group when only the group is clear, else review."""
|
|
|
|
import gzip
|
|
import json
|
|
import random
|
|
import pathlib
|
|
import tempfile
|
|
import urllib.request
|
|
from collections import Counter
|
|
|
|
from hanzo_kai import Kai, RetryPolicy
|
|
|
|
HARNESS = "https://raw.githubusercontent.com/hanzoai/benchmarks/039fbfe5fa5dcb1483407594d829fa822d78974a/decision/results/states.json.gz"
|
|
|
|
# Banking77's 77 intents in nine groups of our own, each group's labels separated by semicolons
|
|
GROUPS = {
|
|
"card": (
|
|
"activate my card; card about to expire; card arrival; card delivery estimate; card linking; "
|
|
"disposable card limits; get disposable virtual card; get physical card; getting spare card; "
|
|
"getting virtual card; order physical card; visa or mastercard"
|
|
),
|
|
"broken card": (
|
|
"card acceptance; card not working; card swallowed; contactless not working; "
|
|
"declined card payment; virtual card not working"
|
|
),
|
|
"security": (
|
|
"change pin; compromised card; lost or stolen card; lost or stolen phone; passcode forgotten; pin blocked"
|
|
),
|
|
"payment": (
|
|
"Refund not showing up; apple pay or google pay; card payment fee charged; "
|
|
"card payment not recognised; direct debit payment not recognised; extra charge on statement; "
|
|
"pending card payment; request refund; reverted card payment?; transaction charged twice"
|
|
),
|
|
"cash": (
|
|
"atm support; cash withdrawal charge; cash withdrawal not recognised; declined cash withdrawal; "
|
|
"pending cash withdrawal; wrong amount of cash received; wrong exchange rate for cash withdrawal"
|
|
),
|
|
"transfer": (
|
|
"balance not updated after bank transfer; beneficiary not allowed; cancel transfer; "
|
|
"declined transfer; failed transfer; pending transfer; receiving money; transfer fee charged; "
|
|
"transfer into account; transfer not received by recipient; transfer timing"
|
|
),
|
|
"top up": (
|
|
"automatic top up; balance not updated after cheque or cash deposit; pending top up; "
|
|
"top up by bank transfer charge; top up by card charge; top up by cash or cheque; top up failed; "
|
|
"top up limits; top up reverted; topping up by card; verify top up"
|
|
),
|
|
"currency": (
|
|
"card payment wrong exchange rate; exchange charge; exchange rate; exchange via app; "
|
|
"fiat currency support; supported cards and currencies"
|
|
),
|
|
"account": (
|
|
"age limit; country support; edit personal details; terminate account; unable to verify identity; "
|
|
"verify my identity; verify source of funds; why verify identity"
|
|
),
|
|
}
|
|
PARENT = {label: group for group, labels in GROUPS.items() for label in labels.split("; ")}
|
|
|
|
|
|
def harness(suite: str) -> list:
|
|
"""The pinned harness's cases for one suite, each [state, questions, gold]."""
|
|
path = pathlib.Path(tempfile.gettempdir()) / "hanzo-kai-cookbook" / "states.json.gz"
|
|
if not path.exists():
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
urllib.request.urlretrieve(HARNESS, path)
|
|
with gzip.open(path) as file:
|
|
return json.load(file)[suite]
|
|
|
|
|
|
def place(answer, fine: float, coarse: float = 0.8) -> tuple[str, str | None]:
|
|
"""The level Kai can answer at, and the answer: a label, its group, or nothing for review."""
|
|
if answer.confidence >= fine:
|
|
return "label", answer.choice
|
|
mass = Counter()
|
|
for label, p in answer.probabilities.items():
|
|
mass[PARENT[label]] += p
|
|
group, p = mass.most_common(1)[0]
|
|
return ("group", group) if p >= coarse else ("review", None)
|
|
|
|
|
|
cases = harness("jev.banking77_full")
|
|
if set(cases[0][1]["intent"]["criteria"]) != set(PARENT):
|
|
raise SystemExit("GROUPS must cover every Banking77 label exactly once")
|
|
by_label = {}
|
|
for case in cases:
|
|
by_label.setdefault(case[2]["intent"]["idx"], []).append(case)
|
|
rng = random.Random(7)
|
|
drawn = [case for idx in sorted(by_label) for case in rng.sample(by_label[idx], 6)]
|
|
|
|
rows = []
|
|
with Kai(retry=RetryPolicy(max_retries=10)) as kai:
|
|
for state, questions, gold in drawn:
|
|
answer = kai.decide(state=state, questions=questions).choices["intent"] # the harness's own question
|
|
rows.append((state["message"], answer, list(questions["intent"]["criteria"])[gold["intent"]["idx"]]))
|
|
|
|
print(f"{len(rows)} messages, {len(by_label)} gold labels, {len(PARENT)} labels in {len(GROUPS)} groups\n")
|
|
print("label at label: n right group: n right review (group when its probability is at least 0.8)")
|
|
for fine in (0.0, 0.9, 0.99, 0.999, 0.9999):
|
|
levels, right = Counter(), Counter()
|
|
for _, answer, truth in rows:
|
|
level, value = place(answer, fine)
|
|
levels[level] += 1
|
|
right[level] += value == (truth if level == "label" else PARENT[truth])
|
|
print(
|
|
f"{fine:8.4f} {levels['label']:8} {right['label']:6} {levels['group']:8} {right['group']:6} {levels['review']:7}"
|
|
)
|
|
grouped = sum(PARENT[answer.choice] == PARENT[truth] for _, answer, truth in rows)
|
|
print(f"the group of the top label, always: {grouped}/{len(rows)} right")
|
|
|
|
print(f"\n{'wrong label':40} {'gold':32} kai, confidence, same group")
|
|
for message, answer, truth in rows:
|
|
if answer.choice != truth:
|
|
same = "yes" if PARENT[answer.choice] == PARENT[truth] else "no"
|
|
print(f"{message[:40]:40} {truth:32} {answer.choice}, {answer.confidence:.4f}, {same}")
|