Jedi / jedi.py
podstack's picture
Jedi v1.0
7a74082 verified
Raw History Blame Contribute Delete
4.76 kB
"""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)