File size: 6,834 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""Shared config for Shaktimaan: the agent pool, prompts, capabilities and graders."""
import json, os, re

# agent -> served model id, max concurrent requests we send, relative cost per token
POOL = {
    "gemma-3-1b":         dict(model="google/gemma-3-1b-it", concurrency=32, cost=0.02),
    "kimi-vl":            dict(model="Kimi-VL-A3B-Thinking", concurrency=64, cost=0.05),
    "nemotron-lightning": dict(model="nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4", concurrency=64, cost=0.10),
    "glm-5.3-flash":      dict(model="zai-org/GLM-5.3-Flash", concurrency=32, cost=0.20),
    "qwen3.8-27b":        dict(model="Qwen/Qwen3.8-27B", concurrency=12, cost=0.50),
    "nemotron-ultra":     dict(model="nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4", concurrency=64, cost=1.00),
}
AGENTS = list(POOL)  # ordered cheapest -> most expensive
STRONGEST = "nemotron-ultra"
CONTEXT = {"gemma-3-1b": 32768, "kimi-vl": 8192, "nemotron-lightning": 131072, "glm-5.3-flash": 32768,
           "qwen3.8-27b": 32768, "nemotron-ultra": 262144}
VISION = {"kimi-vl"}  # only agent served with image input enabled
MAX_OUT = 8192


def endpoints() -> dict:
    """Agent -> OpenAI-compatible base URL (".../v1"), from $SHAKTIMAAN_ENDPOINTS: inline JSON or a path to a JSON file."""
    raw = os.environ["SHAKTIMAAN_ENDPOINTS"]
    return json.load(open(raw)) if os.path.exists(raw) else json.loads(raw)

SOLVER_SYSTEM = ("Solve the task. Be concise. End your response with a final line exactly of the form:\n"
                 "Final answer: <answer>\nFor multiple-choice questions, the answer is the option letter only.")
CODE_SYSTEM = ("Write a correct, self-contained Python solution. Put the complete code (imports, required function "
               "names and signatures) in a single ```python code block. Do not include tests or example usage.")

ROUTER_SYSTEM = (
    "You are Shaktimaan, an orchestrator. You do not answer the task yourself. You read the task and decide which "
    "agent(s) from the pool should handle it, minimising cost while maximising the chance of a correct answer.\n"
    "Agents (cheapest first): " + ", ".join(AGENTS) + ".\n"
    "Capabilities: only kimi-vl can see images; context limits: nemotron-ultra 262k tokens, nemotron-lightning 131k, "
    "kimi-vl 8k, all others 32k.\n"
    "Strategies: single = one agent answers; ensemble = several agents answer independently and the last one "
    "aggregates.\n"
    "Reply with JSON only: {\"domain\": ..., \"difficulty\": \"easy|medium|hard\", \"strategy\": \"single|ensemble\", "
    "\"agents\": [...]}"
)


def est_tokens(text: str) -> int:
    """Conservative token estimate (real tokenizers average ~3.5-4 chars/token on English)."""
    return len(text) // 3 + 1


def max_out(agent: str, prompt_tokens: int) -> int:
    return min(MAX_OUT, CONTEXT[agent] - prompt_tokens - 256)


def capable(agent: str, prompt: str, has_image: bool) -> bool:
    if has_image and agent not in VISION:
        return False
    return max_out(agent, est_tokens(prompt) + 1500 * has_image) >= 1024


def truncate(prompt: str, head: int = 5000, tail: int = 1500) -> str:
    return prompt if len(prompt) <= head + tail + 7 else prompt[:head] + "\n[...]\n" + prompt[-tail:]


def router_view(prompt: str, has_image: bool = False, n_tokens: int | None = None) -> str:
    """What the router reads: a size/attachment header plus the start and end of the task."""
    n = est_tokens(prompt) if n_tokens is None else n_tokens
    return f"[input ~{n // 1000}k tokens{'; 1 image attached' if has_image else ''}]\n" + truncate(prompt)


def strip_think(text: str) -> str:
    text = text or ""
    if "</think>" in text:
        text = text.rsplit("</think>", 1)[1]
    text = re.sub(r"◁think▷.*?◁/think▷", "", text, flags=re.S)  # Kimi-VL thinking markers
    return text.strip()


def extract_final(text: str) -> str:
    text = strip_think(text)
    m = re.findall(r"final answer\s*[::]\s*(.+)", text, flags=re.I)
    if m:
        return m[-1].strip()
    m = re.findall(r"^\s*answer\s*[::]\s*(.+)", text, flags=re.I | re.M)
    if m:
        return m[-1].strip()
    boxed = last_boxed(text)
    if boxed is not None:
        return boxed
    lines = [l for l in text.splitlines() if l.strip()]
    return lines[-1].strip() if lines else ""


def extract_code(text: str) -> str:
    text = strip_think(text)
    blocks = re.findall(r"```(?:python|py)?\s*\n(.*?)```", text, flags=re.S)
    return blocks[-1] if blocks else ""


def last_boxed(s: str):
    i = s.rfind("\\boxed{")
    if i < 0:
        return None
    i += len("\\boxed{")
    depth, j = 1, i
    while j < len(s) and depth:
        depth += {"{": 1, "}": -1}.get(s[j], 0)
        j += 1
    return s[i:j - 1] if depth == 0 else None


def _norm_math(s: str) -> str:
    s = s.strip().strip("$").strip()
    b = last_boxed(s)
    if b is not None:
        s = b
    for a, b2 in [("\\left", ""), ("\\right", ""), ("\\!", ""), ("\\,", ""), ("\\;", ""), ("dfrac", "frac"),
                  ("tfrac", "frac"), ("^\\circ", ""), ("^{\\circ}", ""), ("\\%", ""), ("%", ""), ("\\text{", "{"),
                  ("\\mbox{", "{"), (" ", ""), ("\\$", "")]:
        s = s.replace(a, b2)
    s = re.sub(r"\\frac(\d)(\d)", r"\\frac{\1}{\2}", s)
    s = s.rstrip(".")
    if s.startswith("{") and s.endswith("}") and s.count("{") == 1:
        s = s[1:-1]
    return s


def _num(s: str):
    s = s.replace(",", "").replace("$", "").strip()
    m = re.findall(r"-?\d+(?:\.\d+)?", s)
    return float(m[-1]) if m else None


def grade(kind: str, pred: str, gold: str) -> bool:
    """Grades extracted answers. Code is graded by execution (see gen.run_tests), not here."""
    if not pred:
        return False
    if kind == "mc":
        m = re.search(r"\b([A-J])\b", pred.upper().replace("(", " ").replace(")", " "))
        return bool(m) and m.group(1) == gold.upper()
    if kind == "num":
        p, g = _num(pred), _num(gold)
        return p is not None and g is not None and abs(p - g) < 1e-6 * max(1, abs(g))
    if kind == "relaxed":  # ChartQA convention: numbers within 5%, otherwise case-insensitive exact match
        pred = last_boxed(pred) or pred
        pred = re.sub(r"\\(?:mathbf|textbf|text|mathrm)\{([^}]*)\}", r"\1", pred).replace("\\(", "").replace("\\)", "")
        p, g = _num(pred), _num(gold)
        if g is not None and p is not None and re.fullmatch(r"[-\d.,%$ ]+", gold.strip()):
            return abs(p - g) <= 0.05 * abs(g) if g else p == 0
        return pred.strip().strip(".").lower() == gold.strip().lower()
    if kind == "math":
        p, g = _norm_math(pred), _norm_math(gold)
        if p == g:
            return True
        try:
            return abs(float(p) - float(g)) < 1e-6
        except ValueError:
            return False
    raise ValueError(kind)