File size: 4,763 Bytes
7a74082
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Jedi runtime: answer with the cheapest agent, let the 0.5B verifier check it, escalate up the ladder if needed.

Usage:
  JEDI_ENDPOINTS='{"glm-5.3-flash": "http://host:8000/v1", ...}' python jedi.py [--threshold 0.7] [--code] "task"

The threshold is the single cost-vs-quality dial: raise it for accuracy (more escalations), lower it for savings.
Prints the final answer; the per-rung trace goes to stderr as JSON lines like
  {"agent": "glm-5.3-flash", "verdict": "escalate", "confidence": 0.87, "escalate_to": "qwen3.8-27b"}
"""
import argparse, json, math, os, re, sys
import httpx
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from common import CODE_SYSTEM, LADDER, POOL, JEDI_SYSTEM, SOLVER_SYSTEM, endpoints, est_tokens, judge_view, max_out

MODEL = os.environ.get("JEDI_MODEL", "podstack/Jedi")


class Jedi:
    def __init__(self, path=MODEL):
        self.tok = AutoTokenizer.from_pretrained(path)
        self.model = AutoModelForCausalLM.from_pretrained(path, dtype=torch.bfloat16).eval()
        if torch.cuda.is_available():
            self.model.cuda()
        try:
            from huggingface_hub import hf_hub_download
            cal_file = os.path.join(path, "calibration.json") if os.path.isdir(path) else \
                hf_hub_download(path, "calibration.json")
            cal = json.load(open(cal_file))
        except Exception:  # noqa: BLE001
            cal = {"a": 1.0, "b": 0.0}
        self.a, self.b = cal["a"], cal["b"]
        self.ids = [self.tok.encode(w, add_special_tokens=False)[0] for w in ("accept", "escalate")]

    def p_correct(self, task, answer, agent, kind, finish=None):
        """Calibrated probability that `answer` (written by `agent`) is correct."""
        enc = self.tok.apply_chat_template(
            [{"role": "system", "content": JEDI_SYSTEM},
             {"role": "user", "content": judge_view(task, answer, agent, kind, finish)}],
            add_generation_prompt=True, return_tensors="pt", return_dict=True).to(self.model.device)
        with torch.no_grad():
            logits = self.model(**enc).logits[0, -1].float()
        gap = (logits[self.ids[0]] - logits[self.ids[1]]).item()
        return 1 / (1 + math.exp(-(self.a * gap + self.b)))


def ask(agent, task, code):
    base = endpoints()[agent].rstrip("/")
    msgs = [{"role": "system", "content": CODE_SYSTEM if code else SOLVER_SYSTEM}, {"role": "user", "content": task}]
    r = httpx.post(f"{base}/chat/completions", timeout=1800, json={
        "model": POOL[agent]["model"], "messages": msgs, "max_tokens": max_out(agent, est_tokens(task))})
    r.raise_for_status()
    c = r.json()["choices"][0]
    return c["message"].get("content") or "", c.get("finish_reason")


def infer_kind(task, code):
    """Task type shown to Jedi (its training data used: code, mc, num, math, exact)."""
    if code:
        return "code"
    return "mc" if re.search(r"^\(?[A-J][.)]\s", task, flags=re.M) else "math"


def solve(task, threshold=0.7, code=False, kind=None, judge=None):
    """Run the ladder. Returns (final answer text, trace)."""
    judge = judge or Jedi()
    kind = kind or infer_kind(task, code)
    rungs = [a for a in LADDER if a in endpoints()]
    trace, text = [], ""
    for i, agent in enumerate(rungs):
        try:
            text, finish = ask(agent, task, code)
        except httpx.HTTPError as e:
            if i == 0:
                raise
            trace.append({"agent": agent, "verdict": "unavailable", "error": repr(e)[:120]})
            return text, trace  # keep the best answer so far
        if i == len(rungs) - 1:
            trace.append({"agent": agent, "verdict": "final", "confidence": None, "escalate_to": None})
            return text, trace
        p = judge.p_correct(task, text, agent, kind, finish)
        verdict = "accept" if p >= threshold else "escalate"
        trace.append({"agent": agent, "verdict": verdict, "confidence": round(p if verdict == "accept" else 1 - p, 3),
                      "escalate_to": None if verdict == "accept" else rungs[i + 1]})
        if verdict == "accept":
            return text, trace


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("task", nargs="+")
    ap.add_argument("--threshold", type=float, default=0.7, help="min P(correct) to accept an answer")
    ap.add_argument("--code", action="store_true", help="ask agents for a Python code solution")
    ap.add_argument("--kind", choices=["math", "num", "mc", "code", "exact"], help="task type (default: inferred)")
    args = ap.parse_args()
    answer, trace = solve(" ".join(args.task), args.threshold, args.code, args.kind)
    for step in trace:
        print(json.dumps(step), file=sys.stderr)
    print(answer)