Upload app_kimi.py
Browse files- app_kimi.py +203 -17
app_kimi.py
CHANGED
|
@@ -10,6 +10,18 @@ SCOPE: this collects data only. It does NOT train anything -- no DPO loop,
|
|
| 10 |
no optimizer, nothing that touches gradients. Output is training-ready JSONL
|
| 11 |
for someone else's training scripts to consume.
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
WHAT CHANGED IN v10 (code-enforced general agent-thinker trajectories, single-file-per-worker storage,
|
| 14 |
anonymization, Dataset Viewer fix)
|
| 15 |
----------------------------------------------------------------------------------------------------------------
|
|
@@ -2041,6 +2053,161 @@ class LlamaCppClient:
|
|
| 2041 |
return ok
|
| 2042 |
|
| 2043 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2044 |
def start_llama_backend():
|
| 2045 |
"""-> (client, server). Model download, binary build/download and tokenizer load run in parallel."""
|
| 2046 |
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -2062,7 +2229,7 @@ def start_llama_backend():
|
|
| 2062 |
if kind == "gpu" and not re.search(r"offloaded|CUDA\d", server.tail(600)):
|
| 2063 |
print("[llama][WARNING] the server log does not mention CUDA/offloading -- it may be running on CPU. "
|
| 2064 |
f"Check {server.log_path}", flush=True)
|
| 2065 |
-
client =
|
| 2066 |
print(f"[llama] context per request: {client.ctx} tokens | server adds BOS: {client.server_adds_bos}", flush=True)
|
| 2067 |
if not client.warmup():
|
| 2068 |
print("[llama][WARNING] warmup produced no text; continuing, but check llama-server.log", flush=True)
|
|
@@ -2100,18 +2267,20 @@ def _strip_atem_noise(text: str) -> str:
|
|
| 2100 |
return _channel_noise_re().sub(" ", text or "").strip()
|
| 2101 |
|
| 2102 |
|
| 2103 |
-
|
| 2104 |
-
|
|
|
|
|
|
|
|
|
|
| 2105 |
|
| 2106 |
|
| 2107 |
def _strip_reasoning_leak(text: str) -> str:
|
| 2108 |
-
"""v8:
|
| 2109 |
-
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
while another's stays clean -- a no-op for a model with no thinking mode, defense-in-depth for one whose
|
| 2113 |
-
thinking is suppressed by _CHAT_TEMPLATE_EXTRA_KWARGS below, the only backstop for one with no off-switch."""
|
| 2114 |
text = _THINK_BLOCK_RE.sub(" ", text or "")
|
|
|
|
| 2115 |
return _THINK_DANGLING_RE.sub(" ", text).strip()
|
| 2116 |
|
| 2117 |
|
|
@@ -3015,7 +3184,8 @@ def _validate_fields(fields: dict, language: str, limits: dict):
|
|
| 3015 |
if "<atem:" in val:
|
| 3016 |
return f"{name} contains tool-protocol markup"
|
| 3017 |
if ("<think" in val or "</think>" in val or "<tool_call" in val or "<minimax:tool_call" in val
|
| 3018 |
-
or "\uff5cDSML\uff5c" in val
|
|
|
|
| 3019 |
return f"{name} contains a model-native reasoning/tool-call leak" # v8: backstop, see WHAT CHANGED IN v8
|
| 3020 |
if len(val) < lo:
|
| 3021 |
return f"{name} too short ({len(val)}<{lo} chars)"
|
|
@@ -3381,7 +3551,7 @@ _TASK_TAG_RE = re.compile(r"<task>(.*?)</task>", re.DOTALL | re.IGNORECASE)
|
|
| 3381 |
_NEEDS_TOOLS_RE = re.compile(r"NEEDS_TOOLS\s*:\s*(yes|no)", re.IGNORECASE)
|
| 3382 |
_REVISION_RE = re.compile(r"REVISION\s*:\s*(.*)", re.DOTALL | re.IGNORECASE)
|
| 3383 |
_RATIONALE_RE = re.compile(r"RATIONALE:\s*(.*)")
|
| 3384 |
-
_PY_CODE_BLOCK_RE = re.compile(r"```(?:python|py)
|
| 3385 |
|
| 3386 |
EXECUTE_CODE_TIMEOUT_S = int(os.environ.get("GIJL_EXECUTE_CODE_TIMEOUT_S", "20"))
|
| 3387 |
EXECUTE_CODE_MAX_OUTPUT_CHARS = 4000
|
|
@@ -3454,6 +3624,18 @@ def _grounding_check(proposal: str, research_steps: list) -> dict:
|
|
| 3454 |
return {"passed": passed, "overlap_ratio": round(overlap, 3), "reason": reason}
|
| 3455 |
|
| 3456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3457 |
def _code_syntax_ok(text: str):
|
| 3458 |
"""Cheap, real, non-LLM pre-check (DeepMind's synthetic-data finding: verify with code execution where
|
| 3459 |
you can). A syntax check only -- used as a fast path so an unparseable block never reaches the heavier
|
|
@@ -3532,9 +3714,11 @@ Your own plan: {think}
|
|
| 3532 |
|
| 3533 |
{research_note}
|
| 3534 |
|
| 3535 |
-
If your answer includes
|
| 3536 |
-
|
| 3537 |
-
needed imports), not just a sketch.
|
|
|
|
|
|
|
| 3538 |
you found above, not guessed. Do not pad the answer with meta-commentary about your process -- just the
|
| 3539 |
answer a real person would receive.
|
| 3540 |
|
|
@@ -3571,7 +3755,8 @@ RECOVERY_FIX_TEMPLATE = """Based on what you just found, write ONE consolidated
|
|
| 3571 |
not conflict with your original approach -- reconcile the candidate fixes you found into a single coherent
|
| 3572 |
answer rather than patching in every suggestion. Before the fix itself, write one line starting "RATIONALE:"
|
| 3573 |
explaining briefly why this is the right fix given what you found. Then give the complete, corrected final
|
| 3574 |
-
answer (same format rules as before:
|
|
|
|
| 3575 |
|
| 3576 |
<task>{task_prompt}</task>
|
| 3577 |
|
|
@@ -3600,9 +3785,10 @@ def _run_verification(client, proposal: str, research_steps: list, task_prompt:
|
|
| 3600 |
"""Harness-selected, not model-selected: picks the verification method from what `proposal` actually
|
| 3601 |
contains, runs it for real, and returns {"method", "passed", "evidence"}. This is what makes
|
| 3602 |
"verify everything you produce" generalize across arbitrary task kinds instead of one fixed routine."""
|
| 3603 |
-
code_blocks = _PY_CODE_BLOCK_RE.findall(proposal)
|
| 3604 |
if code_blocks:
|
| 3605 |
-
|
|
|
|
| 3606 |
return {"method": "code_execution", "passed": False,
|
| 3607 |
"evidence": {"stderr": "SyntaxError: the proposed code does not even parse", "stdout": "",
|
| 3608 |
"exit_code": None, "timed_out": False}}
|
|
|
|
| 10 |
no optimizer, nothing that touches gradients. Output is training-ready JSONL
|
| 11 |
for someone else's training scripts to consume.
|
| 12 |
|
| 13 |
+
WHAT CHANGED IN v10.1 (field report on the live shards: reasoning-tag leak, 5x unclosed thinking, CSS run as Python)
|
| 14 |
+
----------------------------------------------------------------------------------------------------------------
|
| 15 |
+
* _strip_reasoning_leak() now also removes an ORPHAN close tag (a close with no open) in addition to complete
|
| 16 |
+
pairs and dangling opens, for both delimiter conventions (<think> and kimi's triangle-glyph tags);
|
| 17 |
+
_validate_fields() rejects any field still containing the glyph form. (All workers.)
|
| 18 |
+
* Code fences: only ```python / ```py / ```python3 blocks are executed (an untagged fence used to match, so
|
| 19 |
+
CSS/HTML/shell was run as Python and failed with a guaranteed SyntaxError); a block fenced as python that is
|
| 20 |
+
plainly HTML/CSS/shell is skipped for execution; PROPOSE and RECOVERY templates now say other languages must
|
| 21 |
+
use their own fence tag. (All workers.)
|
| 22 |
+
* kimi only: KimiThinkBudgetClient -- capped two-stage generation (GIJL_KIMI_THINK_BUDGET, default 1200), the
|
| 23 |
+
same cure MinimaxThinkBudgetClient applies to minimax, adapted to kimi writing its own opening tag.
|
| 24 |
+
|
| 25 |
WHAT CHANGED IN v10 (code-enforced general agent-thinker trajectories, single-file-per-worker storage,
|
| 26 |
anonymization, Dataset Viewer fix)
|
| 27 |
----------------------------------------------------------------------------------------------------------------
|
|
|
|
| 2053 |
return ok
|
| 2054 |
|
| 2055 |
|
| 2056 |
+
_KIMI_THINK_OPEN = "\u25c1think\u25b7"
|
| 2057 |
+
_KIMI_THINK_CLOSE = "\u25c1/think\u25b7"
|
| 2058 |
+
_KIMI_THINK_BUDGET = int(os.environ.get("GIJL_KIMI_THINK_BUDGET", "1200"))
|
| 2059 |
+
|
| 2060 |
+
|
| 2061 |
+
def _strip_special_keep_think(tok, text: str, keep_atem: bool) -> str:
|
| 2062 |
+
"""Like _strip_special_tokens, but never deletes a think-delimiter token: if kimi's glyph tags are
|
| 2063 |
+
registered as special tokens, the stock stripper would remove them and the stage logic (and the later
|
| 2064 |
+
stripper) could no longer see where reasoning starts and ends."""
|
| 2065 |
+
for t in set(getattr(tok, "all_special_tokens", []) or []):
|
| 2066 |
+
if not t:
|
| 2067 |
+
continue
|
| 2068 |
+
low = t.lower()
|
| 2069 |
+
if "think" in low or (keep_atem and "atem" in low):
|
| 2070 |
+
continue
|
| 2071 |
+
text = text.replace(t, "")
|
| 2072 |
+
return text
|
| 2073 |
+
|
| 2074 |
+
|
| 2075 |
+
class KimiThinkBudgetClient(LlamaCppClient):
|
| 2076 |
+
"""v10.1, this worker only -- field evidence from the live shard: kimi writes its own reasoning block
|
| 2077 |
+
delimited by the triangle-glyph think tags (29 opens vs 6 closes in one file), i.e. in roughly four of five
|
| 2078 |
+
turns it never closes the block inside the token budget, so the visible answer is empty or cut mid-thought.
|
| 2079 |
+
Same disease as minimax's (see MinimaxThinkBudgetClient), same cure: generation is split into two
|
| 2080 |
+
/completion calls against the same slot. Stage 1 runs with the close tag as a stop string and a capped
|
| 2081 |
+
budget (GIJL_KIMI_THINK_BUDGET, default 1200 tokens). If it stops on the close tag, or hits the cap while a
|
| 2082 |
+
block is open, the close tag is written (forced in the second case) and stage 2 continues from there with
|
| 2083 |
+
the worker's full max_new_tokens for the answer / tool call; the second call reuses the first one's KV
|
| 2084 |
+
cache (exact prompt-prefix match). If stage 1 ends on its own (no reasoning block at all), or hits the cap
|
| 2085 |
+
while writing a plain long answer, nothing is forced: the first case is returned as is, the second simply
|
| 2086 |
+
continues. Unlike minimax, no opening tag is added back: kimi emits its own, so the combined text is
|
| 2087 |
+
byte-for-byte what a single uncapped call would have produced. Best-effort: raise
|
| 2088 |
+
GIJL_KIMI_THINK_BUDGET if the [llama][kimi] forced-close line shows up on most turns."""
|
| 2089 |
+
|
| 2090 |
+
def chat(self, messages, max_new_tokens=GEN_MAX_NEW_TOKENS, temperature=0.4, tools=None):
|
| 2091 |
+
import requests
|
| 2092 |
+
tool_mode = bool(tools)
|
| 2093 |
+
self.server.ensure_running()
|
| 2094 |
+
base_prompt = self._prepare_prompt(_render_chat(self.tok, messages, tools))
|
| 2095 |
+
try:
|
| 2096 |
+
n_prompt = len(self._tokenize(base_prompt, True))
|
| 2097 |
+
except requests.RequestException:
|
| 2098 |
+
n_prompt = -1
|
| 2099 |
+
if n_prompt >= 0 and n_prompt + 256 > self.ctx:
|
| 2100 |
+
print(f"[llama][kimi] prompt is {n_prompt} tokens with ctx={self.ctx} -- refusing to generate", flush=True)
|
| 2101 |
+
return {"content": "", "error": "context_too_long", "timed_out": False, "prompt_tokens": n_prompt}
|
| 2102 |
+
|
| 2103 |
+
think_budget = _KIMI_THINK_BUDGET
|
| 2104 |
+
if n_prompt >= 0:
|
| 2105 |
+
think_budget = max(256, min(think_budget, self.ctx - n_prompt - max_new_tokens - 64))
|
| 2106 |
+
s1 = self._raw_completion(base_prompt, think_budget, temperature, [_KIMI_THINK_CLOSE], tool_mode)
|
| 2107 |
+
|
| 2108 |
+
def _result(content, s2=None):
|
| 2109 |
+
s2 = s2 or {"cached": 0, "gen": 0, "error": None, "timed_out": False}
|
| 2110 |
+
return {"content": content, "error": s1["error"] or s2["error"],
|
| 2111 |
+
"timed_out": s1["timed_out"] or s2["timed_out"], "prompt_tokens": n_prompt,
|
| 2112 |
+
"cached_tokens": s1["cached"] + s2["cached"], "gen_tokens": s1["gen"] + s2["gen"]}
|
| 2113 |
+
|
| 2114 |
+
body = s1["content"]
|
| 2115 |
+
if s1["error"] or s1["timed_out"]:
|
| 2116 |
+
return _result(body)
|
| 2117 |
+
stop_type = s1.get("stop_type")
|
| 2118 |
+
if stop_type == "word" or _KIMI_THINK_CLOSE in body:
|
| 2119 |
+
# the model closed its own reasoning; the stop string itself is not part of the streamed text
|
| 2120 |
+
if _KIMI_THINK_CLOSE not in body:
|
| 2121 |
+
body = body + _KIMI_THINK_CLOSE
|
| 2122 |
+
body = body.rstrip() + "\n"
|
| 2123 |
+
elif stop_type == "limit" and _KIMI_THINK_OPEN in body:
|
| 2124 |
+
body = body.rstrip() + "\n" + _KIMI_THINK_CLOSE + "\n"
|
| 2125 |
+
print(f"[llama][kimi] reasoning hit the {think_budget}-token think-budget before closing itself "
|
| 2126 |
+
f"-- forced the close tag and continuing to the answer stage", flush=True)
|
| 2127 |
+
elif stop_type != "limit":
|
| 2128 |
+
return _result(body) # finished on its own (EOS / other): a complete answer, nothing to continue
|
| 2129 |
+
# else: cap reached while writing a plain (non-reasoning) answer -> just continue it in stage 2
|
| 2130 |
+
|
| 2131 |
+
s2 = self._raw_completion(base_prompt + body, max(64, max_new_tokens), temperature, None, tool_mode)
|
| 2132 |
+
return _result(body + s2["content"], s2)
|
| 2133 |
+
|
| 2134 |
+
def _raw_completion(self, prompt, n_predict, temperature, stop, tool_mode):
|
| 2135 |
+
"""One /completion call -- same streaming loop, timeouts and SSE parsing as LlamaCppClient.chat();
|
| 2136 |
+
additionally reports llama-server's stop_type ('word' = a stop string matched, 'limit' = n_predict
|
| 2137 |
+
reached, 'eos' = the model finished)."""
|
| 2138 |
+
import requests
|
| 2139 |
+
payload = {"prompt": prompt, "n_predict": n_predict, "temperature": temperature, "top_p": TOP_P, "top_k": TOP_K,
|
| 2140 |
+
"stream": True, "cache_prompt": True}
|
| 2141 |
+
if stop:
|
| 2142 |
+
payload["stop"] = stop
|
| 2143 |
+
payload.update(self.extra_payload)
|
| 2144 |
+
|
| 2145 |
+
gap = GEN_PER_CHUNK_GAP_TIMEOUT_S.get(self.kind, 300)
|
| 2146 |
+
cap = GEN_MAX_TOTAL_S.get(self.kind, 900)
|
| 2147 |
+
chunks, final, err, timed_out, resp = [], None, None, False, None
|
| 2148 |
+
start = last_data = last_hb = time.time()
|
| 2149 |
+
try:
|
| 2150 |
+
resp = requests.post(self.server.base_url + "/completion", json=payload, stream=True, timeout=(15, gap))
|
| 2151 |
+
if resp.status_code != 200:
|
| 2152 |
+
body = resp.text[:400]
|
| 2153 |
+
err = "context_too_long" if "exceed_context_size" in body else f"HTTP {resp.status_code}: {_redact(body)}"
|
| 2154 |
+
else:
|
| 2155 |
+
for raw in resp.iter_lines(chunk_size=1):
|
| 2156 |
+
now = time.time()
|
| 2157 |
+
if now - start > cap:
|
| 2158 |
+
print(f"[gen][TIMEOUT][kimi] hit the {cap}s hard cap after {len(chunks)} chunks -- cancelling generation.", flush=True)
|
| 2159 |
+
timed_out = True
|
| 2160 |
+
break
|
| 2161 |
+
if now - last_data > gap:
|
| 2162 |
+
print(f"[gen][TIMEOUT][kimi] no generated text for {gap}s -- cancelling generation.", flush=True)
|
| 2163 |
+
timed_out = True
|
| 2164 |
+
break
|
| 2165 |
+
line = raw.decode("utf-8", errors="replace") if isinstance(raw, bytes) else raw
|
| 2166 |
+
if not line.startswith("data:"):
|
| 2167 |
+
continue
|
| 2168 |
+
try:
|
| 2169 |
+
d = json.loads(line[5:].strip())
|
| 2170 |
+
except ValueError:
|
| 2171 |
+
continue
|
| 2172 |
+
if "error" in d:
|
| 2173 |
+
e = d["error"]
|
| 2174 |
+
err = _redact(str(e.get("message", e)) if isinstance(e, dict) else str(e))
|
| 2175 |
+
break
|
| 2176 |
+
last_data = now
|
| 2177 |
+
if d.get("content"):
|
| 2178 |
+
chunks.append(d["content"])
|
| 2179 |
+
if d.get("stop"):
|
| 2180 |
+
final = d
|
| 2181 |
+
break
|
| 2182 |
+
if now - last_hb > 30:
|
| 2183 |
+
print(f"[gen][kimi] {sum(len(c) for c in chunks)} chars, {now - start:.0f}s elapsed ({self.kind})", flush=True)
|
| 2184 |
+
last_hb = now
|
| 2185 |
+
except requests.RequestException as e:
|
| 2186 |
+
if isinstance(e, requests.exceptions.Timeout) or "timed out" in str(e).lower():
|
| 2187 |
+
print(f"[gen][TIMEOUT][kimi] llama-server sent nothing for {gap}s -- treating as stuck.", flush=True)
|
| 2188 |
+
timed_out = True
|
| 2189 |
+
else:
|
| 2190 |
+
err = f"{type(e).__name__}: {_redact(str(e))[:200]}"
|
| 2191 |
+
finally:
|
| 2192 |
+
if resp is not None:
|
| 2193 |
+
resp.close()
|
| 2194 |
+
|
| 2195 |
+
cached = gen = 0
|
| 2196 |
+
stop_type = None
|
| 2197 |
+
if final:
|
| 2198 |
+
t = final.get("timings") or {}
|
| 2199 |
+
cached, gen = int(t.get("cache_n") or 0), int(final.get("tokens_predicted") or 0)
|
| 2200 |
+
stop_type = final.get("stop_type")
|
| 2201 |
+
print(f"[llama][kimi] prompt {final.get('tokens_evaluated')} tok (cached {cached}) prefill "
|
| 2202 |
+
f"{t.get('prompt_per_second') or 0:.0f} tok/s | generated {gen} tok @ "
|
| 2203 |
+
f"{t.get('predicted_per_second') or 0:.1f} tok/s | stop={stop_type}", flush=True)
|
| 2204 |
+
if err and err != "context_too_long":
|
| 2205 |
+
print(f"[llama][kimi] request failed: {err}", flush=True)
|
| 2206 |
+
text = _strip_special_keep_think(self.tok, "".join(chunks), keep_atem=tool_mode)
|
| 2207 |
+
return {"content": text, "error": err, "timed_out": timed_out, "cached": cached, "gen": gen,
|
| 2208 |
+
"stop_type": stop_type}
|
| 2209 |
+
|
| 2210 |
+
|
| 2211 |
def start_llama_backend():
|
| 2212 |
"""-> (client, server). Model download, binary build/download and tokenizer load run in parallel."""
|
| 2213 |
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
| 2229 |
if kind == "gpu" and not re.search(r"offloaded|CUDA\d", server.tail(600)):
|
| 2230 |
print("[llama][WARNING] the server log does not mention CUDA/offloading -- it may be running on CPU. "
|
| 2231 |
f"Check {server.log_path}", flush=True)
|
| 2232 |
+
client = KimiThinkBudgetClient(server, tok, kind)
|
| 2233 |
print(f"[llama] context per request: {client.ctx} tokens | server adds BOS: {client.server_adds_bos}", flush=True)
|
| 2234 |
if not client.warmup():
|
| 2235 |
print("[llama][WARNING] warmup produced no text; continuing, but check llama-server.log", flush=True)
|
|
|
|
| 2267 |
return _channel_noise_re().sub(" ", text or "").strip()
|
| 2268 |
|
| 2269 |
|
| 2270 |
+
_THINK_OPEN_RE = r"(?:<think>|\u25c1think\u25b7)"
|
| 2271 |
+
_THINK_CLOSE_RE = r"(?:</think>|\u25c1/think\u25b7)"
|
| 2272 |
+
_THINK_BLOCK_RE = re.compile(_THINK_OPEN_RE + r".*?" + _THINK_CLOSE_RE, re.DOTALL)
|
| 2273 |
+
_THINK_ORPHAN_CLOSE_RE = re.compile(r"\A.*" + _THINK_CLOSE_RE, re.DOTALL) # a close with no open: everything before it was reasoning
|
| 2274 |
+
_THINK_DANGLING_RE = re.compile(_THINK_OPEN_RE + r".*\Z", re.DOTALL) # closing tag never arrived (token cap / timeout)
|
| 2275 |
|
| 2276 |
|
| 2277 |
def _strip_reasoning_leak(text: str) -> str:
|
| 2278 |
+
"""v8/v10.1: strips a model's own native reasoning block in whichever delimiter convention its chat
|
| 2279 |
+
template uses (ASCII think tags, or kimi's triangle-glyph variant -- confirmed from live output, not from
|
| 2280 |
+
a model card). Order matters: complete pairs first, then an orphan close (reasoning whose opening tag was
|
| 2281 |
+
baked into the prompt or cut off), then a dangling open (reasoning that never closed)."""
|
|
|
|
|
|
|
| 2282 |
text = _THINK_BLOCK_RE.sub(" ", text or "")
|
| 2283 |
+
text = _THINK_ORPHAN_CLOSE_RE.sub(" ", text)
|
| 2284 |
return _THINK_DANGLING_RE.sub(" ", text).strip()
|
| 2285 |
|
| 2286 |
|
|
|
|
| 3184 |
if "<atem:" in val:
|
| 3185 |
return f"{name} contains tool-protocol markup"
|
| 3186 |
if ("<think" in val or "</think>" in val or "<tool_call" in val or "<minimax:tool_call" in val
|
| 3187 |
+
or "\uff5cDSML\uff5c" in val
|
| 3188 |
+
or "\u25c1think\u25b7" in val or "\u25c1/think\u25b7" in val):
|
| 3189 |
return f"{name} contains a model-native reasoning/tool-call leak" # v8: backstop, see WHAT CHANGED IN v8
|
| 3190 |
if len(val) < lo:
|
| 3191 |
return f"{name} too short ({len(val)}<{lo} chars)"
|
|
|
|
| 3551 |
_NEEDS_TOOLS_RE = re.compile(r"NEEDS_TOOLS\s*:\s*(yes|no)", re.IGNORECASE)
|
| 3552 |
_REVISION_RE = re.compile(r"REVISION\s*:\s*(.*)", re.DOTALL | re.IGNORECASE)
|
| 3553 |
_RATIONALE_RE = re.compile(r"RATIONALE:\s*(.*)")
|
| 3554 |
+
_PY_CODE_BLOCK_RE = re.compile(r"```(?:python3|python|py)[ \t]*\n(.*?)```", re.DOTALL) # v10.1: the tag is REQUIRED -- an untagged/other-language fence is never executed as Python
|
| 3555 |
|
| 3556 |
EXECUTE_CODE_TIMEOUT_S = int(os.environ.get("GIJL_EXECUTE_CODE_TIMEOUT_S", "20"))
|
| 3557 |
EXECUTE_CODE_MAX_OUTPUT_CHARS = 4000
|
|
|
|
| 3624 |
return {"passed": passed, "overlap_ratio": round(overlap, 3), "reason": reason}
|
| 3625 |
|
| 3626 |
|
| 3627 |
+
_NON_PYTHON_FIRST_LINE_RE = re.compile(r"^\s*(?:<[!/?A-Za-z]|#!/|\$ |@(?:media|import|charset)\b)")
|
| 3628 |
+
_CSS_RULE_RE = re.compile(r"^[^\n=()]{1,120}\{\s*[\w-]+\s*:[^}]*;", re.DOTALL)
|
| 3629 |
+
|
| 3630 |
+
|
| 3631 |
+
def _block_is_non_python(code: str) -> bool:
|
| 3632 |
+
"""v10.1: a block the model fenced as python but that is plainly HTML / CSS / shell. Executing it as Python
|
| 3633 |
+
can only fail, which then poisoned the verification result (live case: a CSS flexbox answer). Such a block
|
| 3634 |
+
is simply not a candidate for code execution; verification falls through to the grounding check."""
|
| 3635 |
+
c = (code or "").strip()
|
| 3636 |
+
return bool(_NON_PYTHON_FIRST_LINE_RE.match(c) or _CSS_RULE_RE.match(c))
|
| 3637 |
+
|
| 3638 |
+
|
| 3639 |
def _code_syntax_ok(text: str):
|
| 3640 |
"""Cheap, real, non-LLM pre-check (DeepMind's synthetic-data finding: verify with code execution where
|
| 3641 |
you can). A syntax check only -- used as a fast path so an unparseable block never reaches the heavier
|
|
|
|
| 3714 |
|
| 3715 |
{research_note}
|
| 3716 |
|
| 3717 |
+
If your answer includes a complete Python program or script, put it in a single fenced ```python block --
|
| 3718 |
+
ONLY ```python blocks are executed for real to verify them, so make that block runnable as-is (no placeholders,
|
| 3719 |
+
no "...", include any needed imports), not just a sketch. Code in ANY OTHER language or format (CSS, HTML,
|
| 3720 |
+
JavaScript, shell, SQL, YAML/config, plain snippets) must use its own fence tag (```css, ```html, ```bash, ...)
|
| 3721 |
+
and must NEVER be labeled ```python -- it is not executed, it is simply part of the answer. If it makes factual/technical claims, make sure they are grounded in what
|
| 3722 |
you found above, not guessed. Do not pad the answer with meta-commentary about your process -- just the
|
| 3723 |
answer a real person would receive.
|
| 3724 |
|
|
|
|
| 3755 |
not conflict with your original approach -- reconcile the candidate fixes you found into a single coherent
|
| 3756 |
answer rather than patching in every suggestion. Before the fix itself, write one line starting "RATIONALE:"
|
| 3757 |
explaining briefly why this is the right fix given what you found. Then give the complete, corrected final
|
| 3758 |
+
answer (same format rules as before: Python goes in one runnable ```python block, any other language in its
|
| 3759 |
+
own fence tag and never ```python, no placeholders).
|
| 3760 |
|
| 3761 |
<task>{task_prompt}</task>
|
| 3762 |
|
|
|
|
| 3785 |
"""Harness-selected, not model-selected: picks the verification method from what `proposal` actually
|
| 3786 |
contains, runs it for real, and returns {"method", "passed", "evidence"}. This is what makes
|
| 3787 |
"verify everything you produce" generalize across arbitrary task kinds instead of one fixed routine."""
|
| 3788 |
+
code_blocks = [b for b in _PY_CODE_BLOCK_RE.findall(proposal) if not _block_is_non_python(b)]
|
| 3789 |
if code_blocks:
|
| 3790 |
+
import ast as _ast
|
| 3791 |
+
if not all(_safe_ast_parse(_ast, b) for b in code_blocks):
|
| 3792 |
return {"method": "code_execution", "passed": False,
|
| 3793 |
"evidence": {"stderr": "SyntaxError: the proposed code does not even parse", "stdout": "",
|
| 3794 |
"exit_code": None, "timed_out": False}}
|