#!/usr/bin/env python3 """Format probe 4 — annotated parens. Does giving every paren its nesting depth (`(#N ` ... ` #N)`) help Qwen keep brackets balanced at depth? The model writes the annotated dialect; we STRIP the annotations with a round-trip-verified converter (see qwen_fmt4 verification in session) back to plain Form-A, then `ail check` / `ail run`. Strip-variant: we trust the bare parens, the annotation is the model's self-discipline scaffold. Same ladder as qwen_ablation.py so results are comparable. Reads $IONOS_API_TOKEN (never printed). Live IONOS — run with consent. """ from __future__ import annotations import json, os, re, subprocess, urllib.request from pathlib import Path ENDPOINT = "https://openai.inference.de-txl.ionos.com/v1/chat/completions" MODEL = "Qwen/Qwen3-Coder-Next" REPO = Path(__file__).resolve().parents[2] AIL = os.environ.get("AIL_BIN", str(REPO / "target/debug/ail")) DOC = REPO / "experiments/2026-05-12-cross-model-authoring/qwen-fmt4.md" def annotate(src): out=[]; depth=0; in_str=False; esc=False for ch in src: if in_str: out.append(ch) if esc: esc=False elif ch=='\\': esc=True elif ch=='"': in_str=False continue if ch=='"': in_str=True; out.append(ch) elif ch=='(': out.append(f'(#{depth} '); depth+=1 elif ch==')': depth-=1; out.append(f' #{depth})') else: out.append(ch) return ''.join(out) def strip_ann(src): out=[]; i=0; in_str=False; esc=False while iown series appends & returns; " "Series.at (borrow series)(own Int)->own elem reads index back from " "newest (0=newest); Series.len / Series.total_count " "(borrow series)->own Int):\n" + annotate(S) + "\n") SMA_OUT = "3.0\n5.33333\n5.66667\n5.33333\n" SMA_TASK = ("Write a module `m` computing a simple moving average, window 3, over " "the float stream 1.0, 5.0, 3.0, 8.0, 6.0, 2.0 using the Series library: " "create a Float Series lookback 3, push each value in turn, and after each " "push once at least 3 values have been pushed, print the average of the " "most recent 3 (their sum / 3.0) on its own line. Expected:\n" + SMA_OUT) STAGES = [ ("L0_const", BASE, "Write a module `m` with a function `answer` taking no parameters that returns the Int 42.", None), ("L1_arith", BASE, "Write a module `m` with a function `triple` taking one Int parameter, returning it multiplied by 3.", None), ("L2_print", BASE, "Write a module `m` whose `main` prints the Int 7 on its own line.", "7\n"), ("L3_adt", BASE, "Write a module `m` with an ADT `Light` (ctors Red, Green) and a function `code` returning Int 0 for Red and 1 for Green via match.", None), ("L4_rec", BASE, "Write a module `m` with an IntList ADT (Nil/Cons), a function `length` counting elements by recursion, and a `main` that prints the length of the list [1,2,3] on its own line.", "3\n"), ("L5_series_len", SERIES, "Write a module `m` using the Series library: create a Float Series lookback 3, push 1.0 then 2.0 then 3.0, then print its Series.len on its own line.", "3\n"), ("L6_series_at", SERIES, "Write a module `m` using the Series library: create a Float Series lookback 3, push 1.0 then 2.0 then 3.0, then print (Series.at on index 0, the newest) on its own line.", "3.0\n"), ("L7_sma", SERIES, SMA_TASK, SMA_OUT), ] def call(system, task): body=json.dumps({"model":MODEL,"temperature":0.2,"max_tokens":1800, "messages":[{"role":"system","content":system},{"role":"user","content":task}]}).encode() req=urllib.request.Request(ENDPOINT,data=body,headers={ "Authorization":f"Bearer {os.environ['IONOS_API_TOKEN']}","Content-Type":"application/json"}) with urllib.request.urlopen(req,timeout=120) as r: d=json.loads(r.read()) return d["choices"][0]["message"]["content"], d.get("usage",{}) def extract(resp): m=re.search(r"```(?:[a-zA-Z]*)?\n(.*?)```",resp,re.S) code=m.group(1) if m else resp i=code.find("(#0") if i<0: i=code.find("(module") return code[i:].strip() if i>=0 else code.strip() def evaluate(annotated_code, expected): plain = strip_ann(annotated_code) # <-- verified converter m=re.search(r"\(module\s+([A-Za-z_][A-Za-z0-9_]*)", plain) name=m.group(1) if m else "m" f=Path(f"/tmp/abl/{name}.ail"); f.parent.mkdir(exist_ok=True); f.write_text(plain+"\n") chk=subprocess.run([AIL,"check",str(f)],capture_output=True,text=True) if chk.returncode!=0: return "check",(chk.stderr or chk.stdout).strip(), plain if expected is None: return "green","(check-only)", plain run=subprocess.run([AIL,"run",str(f)],capture_output=True,text=True) if run.returncode!=0: return "run",(run.stderr or run.stdout).strip(), plain if run.stdout!=expected: return "output",f"got {run.stdout!r}, expected {expected!r}", plain return "green",run.stdout, plain def main(): log=["# Qwen format probe 4 — annotated parens `(#N ... #N)`\n", f"**Model:** {MODEL}; single-shot per level; annotated-paren dialect; " "output stripped via round-trip-verified converter then ail-checked.\n","---\n"] summary=[] for sid,ctx,task,exp in STAGES: resp,usage=call(ctx,task) code=extract(resp) stage,fb,plain=evaluate(code,exp) ok=stage=="green"; mark="✅ green" if ok else f"❌ {stage}" summary.append((sid,mark)); print(f"{sid}: {mark}") log+=[f"## {sid} — {mark}", f"\ntokens: prompt={usage.get('prompt_tokens','?')} completion={usage.get('completion_tokens','?')}\n", "annotated output:\n```\n"+code+"\n```\n", "stripped to Form-A:\n```\n"+plain+"\n```\n", ("" if ok else f"**`ail {stage}`:**\n```\n{fb[:700]}\n```\n")] log+=["\n---\n## Ladder\n"]+[f"- {s}: {m}" for s,m in summary] DOC.write_text("\n".join(log)); print("doc →",DOC) if __name__=="__main__": main()