File size: 6,785 Bytes
c1f5d12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Stage 4: score the router on held-out tasks.

Single-agent routes are scored from the recorded agent outcomes (an agent that cannot take a task - no image input,
context too small - counts as a failure at zero cost). Shaktimaan's ensemble routes on text/code tasks are executed
live (agents + aggregator via their OpenAI-compatible endpoints) and graded; oracle ensemble labels are scored conservatively as
the aggregator's recorded outcome.

Baselines: every always-one-agent policy, plus capability rules ("rule:X" = image -> kimi-vl, too long for X ->
the cheapest agent with enough context, otherwise X). A useful router has to beat the rules, not just fixed agents.
"""
import asyncio, json, sys
from collections import Counter, defaultdict
import httpx
import torch
from datasets import load_from_disk
from transformers import AutoModelForCausalLM, AutoTokenizer
from common import (AGENTS, CODE_SYSTEM, POOL, ROUTER_SYSTEM, SOLVER_SYSTEM, STRONGEST, capable, endpoints,
                    extract_code, extract_final, grade, strip_think)
from gen import call, run_tests

path = sys.argv[1] if len(sys.argv) > 1 else "shaktimaan-0.5b"
tok = AutoTokenizer.from_pretrained(path)
tok.padding_side = "left"
model = AutoModelForCausalLM.from_pretrained(path, dtype=torch.bfloat16).cuda().eval()
test = load_from_disk("data/shaktimaan_routing")["test"]
BY_CONTEXT = ["kimi-vl", "glm-5.3-flash", "nemotron-lightning", STRONGEST]  # cheapest first, growing context


def parse(text):
    try:
        r = json.loads(text.strip())
        agents = [a for a in r.get("agents", []) if a in POOL]
        return r, agents or [STRONGEST], True
    except Exception:  # noqa: BLE001
        return {}, [STRONGEST], False


def score(row, agents):
    res = json.loads(row["agent_results"])
    return bool(res[agents[-1]]["correct"]), sum(POOL[a]["cost"] * res[a]["completion_tokens"] for a in agents)


def rule(row, default):
    res = json.loads(row["agent_results"])
    if row["has_image"]:
        return ["kimi-vl"]
    if not res[default]["skipped"]:
        return [default]
    return [next((a for a in BY_CONTEXT if not res[a]["skipped"]), STRONGEST)]


async def run_ensembles(jobs):
    """jobs: list of (row, agents). Returns [(correct, cost)] from real agent + aggregator calls."""
    urls = {a: f"{base.rstrip('/')}/chat/completions" for a, base in endpoints().items()}
    sems = {a: asyncio.Semaphore(POOL[a]["concurrency"]) for a in POOL}

    async def one(client, row, agents):
        code = row["kind"] == "code"
        system = CODE_SYSTEM if code else SOLVER_SYSTEM
        msgs = [{"role": "system", "content": system}, {"role": "user", "content": row["prompt"]}]
        drafts = await asyncio.gather(*[call(client, urls[a], POOL[a]["model"], sems[a], msgs) for a in agents])
        joined = "\n\n".join(f"### Candidate {i + 1}\n{strip_think(d['text'])}" for i, d in enumerate(drafts))
        agg = await call(client, urls[agents[-1]], POOL[agents[-1]]["model"], sems[agents[-1]], [
            {"role": "system", "content": system},
            {"role": "user", "content": f"{row['prompt']}\n\nIndependent candidate solutions:\n{joined}\n\n"
                                        "Check them, resolve disagreements, and give the best final answer."}])
        cost = sum(POOL[a]["cost"] * d["completion_tokens"] for a, d in zip(agents, drafts))
        cost += POOL[agents[-1]]["cost"] * agg["completion_tokens"]
        ok = await run_tests(extract_code(agg["text"]), row["gold"]) if code else \
            grade(row["kind"], extract_final(agg["text"]), row["gold"])
        return ok, cost

    async with httpx.AsyncClient(timeout=httpx.Timeout(1800)) as client:
        return await asyncio.gather(*[one(client, r, a) for r, a in jobs])


preds = []
for i in range(0, len(test), 32):
    batch = test[i:i + 32]
    msgs = [[{"role": "system", "content": ROUTER_SYSTEM}, {"role": "user", "content": v}]
            for v in batch["router_input"]]
    enc = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt", padding=True,
                                  return_dict=True).to("cuda")
    with torch.no_grad():
        out = model.generate(**enc, max_new_tokens=96, do_sample=False)
    preds += tok.batch_decode(out[:, enc["input_ids"].shape[1]:], skip_special_tokens=True)

parsed = [parse(p) for p in preds]
ens = [(row, [a for a in ag if capable(a, row["prompt"], False)])
       for row, (r, ag, ok) in zip(test, parsed)
       if r.get("strategy") == "ensemble" and len(ag) > 1 and not row["has_image"] and not row["prompt_truncated"]]
ens = [(row, ag) for row, ag in ens if len(ag) > 1]
ens_results = dict(zip([row["id"] for row, _ in ens], asyncio.run(run_ensembles(ens))))
print(f"ran {len(ens)} live ensembles, solved {sum(c for c, _ in ens_results.values())}", flush=True)

policies = ["shaktimaan", "oracle"] + [f"always:{a}" for a in AGENTS] + \
           ["rule:glm-5.3-flash", "rule:qwen3.8-27b", f"rule:{STRONGEST}"]
stats = {k: [0, 0.0] for k in policies}
by_src = defaultdict(lambda: defaultdict(list))
valid = exact = 0
for row, (r, agents, ok) in zip(test, parsed):
    valid += ok
    exact += ok and agents == json.loads(row["route"])["agents"]
    choices = {"shaktimaan": None, "oracle": json.loads(row["route"])["agents"],
               **{f"always:{a}": [a] for a in AGENTS},
               **{f"rule:{a}": rule(row, a) for a in ["glm-5.3-flash", "qwen3.8-27b", STRONGEST]}}
    for key, ag in choices.items():
        if key == "shaktimaan":
            c, cost = ens_results[row["id"]] if row["id"] in ens_results else score(row, agents)
        else:
            c, cost = score(row, ag)
        stats[key][0] += c
        stats[key][1] += cost
        by_src[row["source"]][key].append(c)

n = len(test)
base_cost = stats[f"always:{STRONGEST}"][1]
report = {"n_test": n, "valid_json": valid / n, "route_match": exact / n, "live_ensembles": len(ens),
          "live_ensembles_solved": sum(c for c, _ in ens_results.values()),
          "route_counts": dict(Counter(",".join(ag) for _, ag, _ in parsed).most_common()),
          "policies": {k: {"accuracy": round(v[0] / n, 4), "relative_cost": round(v[1] / base_cost, 4)}
                       for k, v in stats.items()},
          "accuracy_by_source": {s: {"n": len(d["shaktimaan"]),
                                     **{k: round(sum(v) / len(v), 3) for k, v in d.items()
                                        if k in ("shaktimaan", "oracle", "rule:qwen3.8-27b", "rule:glm-5.3-flash",
                                                 f"always:{STRONGEST}")}}
                                 for s, d in sorted(by_src.items())}}
print(json.dumps(report, indent=2))
json.dump(report, open(f"{path}/eval_results.json", "w"), indent=2)