@@ -285,7 +285,7 @@ def api_call(model: str, payload: dict, server: str, api_key: str, timeout: int
285285 ("math_balls_boxes" , "hard" ,
286286 "In how many ways can 3 indistinguishable balls be placed into 4 distinguishable boxes?" ),
287287 ("math_coin_prob" , "hard" ,
288- "A fair coin is flipped 5 times. What is the probability of getting exactly 3 heads? Express your answer as a fraction in lowest terms ." ),
288+ "A fair coin is flipped 5 times. What is the probability of getting exactly 3 heads? Give the numerator and denominator of that fraction, separated by a slash, with no spaces (for example: 1/2) ." ),
289289 ("math_log_eq" , "hard" ,
290290 "Solve for x: log base 2 of x plus log base 2 of (x minus 2) equals 4. Give the exact value of x." ),
291291 ("math_sequence" , "hard" ,
@@ -298,16 +298,17 @@ def api_call(model: str, payload: dict, server: str, api_key: str, timeout: int
298298# and MATH (hendrycks/math) style problems, adapted for single-shot scoring.
299299ANSWER_KEYWORDS = {
300300 "gsm_clips" : ["72" ],
301- "gsm_babysit" : ["10" , "$10" ],
301+ "gsm_babysit" : ["earned $ 10" , "earn $10" , "= $10" , "**$10**" , "$10." ], # bare "10" too ambiguous ("105")
302302 "gsm_letter" : ["624" ],
303303 "gsm_wallet" : ["5" , "$5" ],
304304 "gsm_book" : ["42" ],
305305 "gsm_flowers" : ["35" ],
306306 "math_heads_legs" : ["10" ],
307307 "math_div_or" : ["220" ],
308308 "math_balls_boxes" : ["20" ],
309- "math_coin_prob" : ["5/16" ],
310- "math_log_eq" : ["\u221a 17" , "sqrt(17)" ],
309+ "math_coin_prob" : ["5/16" , "5 / 16" ],
310+ "math_log_eq" : ["+ \u221a 17" , "+\u221a 17" , "- \u221a 17" , "-\u221a 17" ,
311+ "sqrt(17)" , "\u221a 17" ],
311312 "math_sequence" : ["33" ],
312313}
313314
@@ -324,7 +325,12 @@ def score_answer(prompt_key: str, result: dict) -> dict:
324325 if not result ["success" ]:
325326 return scoring
326327
327- c = (result .get ("answer" , "" ) or "" ) + "\n " + (result .get ("reasoning_content" , "" ) or "" )
328+ ans_full = result .get ("answer" )
329+ if not isinstance (ans_full , str ):
330+ # re-scoring from stored records: answer_text holds the (possibly
331+ # tail-truncated) answer — use it instead of an empty string
332+ ans_full = result .get ("answer_text" , "" ) or ""
333+ c = ans_full + "\n " + (result .get ("reasoning_content" , "" ) or "" )
328334 rc = result .get ("reasoning_content" , "" ) or ""
329335
330336 scoring ["valid_response" ] = len (c .strip ()) > 10
@@ -341,10 +347,14 @@ def score_answer(prompt_key: str, result: dict) -> dict:
341347 def kw_hit (kw : str ) -> bool :
342348 k = kw .lower ()
343349 for m in re .finditer (re .escape (k ), content_lower ):
344- before = content_lower [m .start () - 1 ] if m .start () > 0 else ""
345- after = content_lower [m .end ()] if m .end () < len (content_lower ) else ""
346- if before .isdigit () or after .isdigit ():
347- continue
350+ # digit-boundary guard only applies to purely-numeric keywords:
351+ # reject "10" inside "100", but let "1+√17" match (the leading
352+ # 1 is part of the expression, not a false-positive boundary)
353+ if k .isdigit ():
354+ before = content_lower [m .start () - 1 ] if m .start () > 0 else ""
355+ after = content_lower [m .end ()] if m .end () < len (content_lower ) else ""
356+ if before .isdigit () or after .isdigit ():
357+ continue
348358 return True
349359 return False
350360
@@ -511,10 +521,16 @@ def run_benchmark(models, levels, runs, server, api_key, mode="full", report_pat
511521 else :
512522 scoring = score_answer (prompt_key , api_result )
513523 rc = api_result .get ("reasoning_content" , "" ) or ""
524+ # store the answer text (truncated) for report inspection;
525+ # when content is empty keep the reasoning tail instead
526+ ans = (api_result .get ("answer" ) or "" ).strip ()
527+ if not ans :
528+ ans = ("…" + rc [- 700 :]) if len (rc ) > 700 else rc
514529 record = {
515530 "model" : model , "level" : level , "run" : run_idx ,
516531 "prompt_key" : prompt_key , "difficulty" : difficulty ,
517532 "success" : True , ** scoring ,
533+ "answer_text" : ans [:800 ],
518534 "elapsed_ms" : api_result ["elapsed_ms" ],
519535 "finish_reason" : api_result .get ("finish_reason" ),
520536 "total_tokens" : api_result .get ("total_tokens" , 0 ),
@@ -692,40 +708,71 @@ def write_report(path: str, models, levels, summaries, fidelity, versions, all_r
692708 a (f"| { level } | " + " | " .join (cells ) + " |" )
693709 a ("" )
694710
695- # ── Per-prompt detail ──
711+ # ── Per-prompt matrix (prompts × levels — compact pivot) ──
696712 a ("## Per-prompt results" )
697713 a ("" )
698- a ("Each prompt × level cell: correctness, reasoning length, wall time, and the "
699- "model's answer (truncated). Lets you inspect WHY a level scores the way it does." )
714+ a ("One row per problem: ✅ pass / ⚠ wrong answer / ❌ no answer / ⏭ skipped, "
715+ "with reasoning chars in each level column. Full answers for the "
716+ "discriminating cells (not passed at every level) follow below." )
700717 for model in models :
701718 a (f"### { model } " )
702719 a ("" )
703- a ("| Prompt | Level | Correct | Reasoning chars | Time | Answer (truncated) |" )
704- a ("|---|---|---|---|---|---|" )
720+ a ("| Problem | Tier | " + " | " . join ( levels ) + " |" )
721+ a ("|---|---|" + " ---|" * len ( levels ) )
705722 for prompt_key , difficulty , _ in PROMPTS :
706- row_levels = [l for l in levels
707- if any (r ["model" ] == model and r ["level" ] == l and r ["prompt_key" ] == prompt_key
708- for r in all_results )]
709- for level in row_levels :
723+ cells = []
724+ for level in levels :
710725 rs = [r for r in all_results if r ["model" ] == model and r ["level" ] == level
711726 and r ["prompt_key" ] == prompt_key ]
712727 if not rs :
728+ cells .append ("·" )
713729 continue
714730 r = rs [0 ] # first run
715731 if not r .get ("success" ):
716- a ( f"| { prompt_key } | { level } | ⏭ skipped | — | — | { _md_escape ( r . get ( 'error' , '?' ))[: 100 ] } | " )
732+ cells . append ( "⏭ " )
717733 continue
718734 mark = "✅" if r ["correct_answer" ] else ("⚠" if r ["valid_response" ] else "❌" )
719- # answer may live in reasoning_content when content is empty
720- ans_text = r .get ("answer" ) or ""
721- if not ans_text .strip ():
722- rc_tail = (r .get ("reasoning_content" ) or "" )[- 200 :]
723- ans_text = ("…" if len (r .get ("reasoning_content" ) or "" ) > 200 else "" ) + rc_tail
724- ans = _md_escape (ans_text )[:150 ]
725- a (f"| { prompt_key } | { level } | { mark } | { r ['reasoning_chars' ]} | "
726- f"{ r ['elapsed_ms' ]/ 1000 :.1f} s | { ans } |" )
735+ rc = r ["reasoning_chars" ]
736+ cells .append (f"{ mark } { rc } " if rc > 0 else mark )
737+ a (f"| `{ prompt_key } ` | { difficulty } | " + " | " .join (cells ) + " |" )
727738 a ("" )
728739
740+ # ── Answers for discriminating cells (compact table per problem) ──
741+ for model in models :
742+ interesting = []
743+ for prompt_key , difficulty , _ in PROMPTS :
744+ outcomes = {}
745+ for level in levels :
746+ rs = [r for r in all_results if r ["model" ] == model and r ["level" ] == level
747+ and r ["prompt_key" ] == prompt_key and r .get ("success" )]
748+ if rs :
749+ outcomes [level ] = rs [0 ]["correct_answer" ]
750+ if len (outcomes ) > 1 or (outcomes and not all (outcomes .values ())):
751+ interesting .append ((prompt_key , difficulty ))
752+ if not interesting :
753+ continue
754+ a (f"### Answers — { model } (discriminating problems)" )
755+ a ("" )
756+ for prompt_key , difficulty in interesting :
757+ _ , _ , prompt_text = next (p for p in PROMPTS if p [0 ] == prompt_key )
758+ a (f"**`{ prompt_key } `** ({ difficulty } ) — { _md_escape (prompt_text )[:160 ]} " )
759+ a ("" )
760+ a ("| Level | Result | Answer (truncated) |" )
761+ a ("|---|---|---|" )
762+ for level in levels :
763+ rs = [r for r in all_results if r ["model" ] == model and r ["level" ] == level
764+ and r ["prompt_key" ] == prompt_key ]
765+ if not rs :
766+ continue
767+ r = rs [0 ]
768+ if not r .get ("success" ):
769+ a (f"| { level } | ⏭ skipped | { _md_escape (r .get ('error' , '?' ))[:120 ]} |" )
770+ continue
771+ mark = "✅" if r ["correct_answer" ] else ("⚠" if r ["valid_response" ] else "❌" )
772+ ans_text = (r .get ("answer_text" ) or "" ).strip () or "(empty)"
773+ a (f"| { level } | { mark } | { _md_escape (ans_text )[:200 ]} |" )
774+ a ("" )
775+
729776 # ── Interpretation notes (auto-generated observations) ──
730777 a ("## Notes" )
731778 a ("" )
@@ -791,19 +838,29 @@ def write_report(path: str, models, levels, summaries, fidelity, versions, all_r
791838 for r in failed [:20 ]:
792839 a (f"- { r ['model' ]} @ { r ['level' ]} — { r ['prompt_key' ]} : { r .get ('error' , '?' )[:120 ]} " )
793840
794- # ── Wire fidelity (plugin params → backend ) ──
841+ # ── Wire fidelity (synthetic — details only on failure ) ──
795842 a ("" )
796843 a ("## Wire fidelity (plugin params → backend)" )
797844 a ("" )
798- a ("| Model | Level | Check | Result | Detail |" )
799- a ("|---|---|---|---|---|" )
845+ fid_items = []
800846 for f in fidelity :
801- # f is a 5-tuple from the live run, or a dict from the JSON path
802847 if isinstance (f , dict ):
803- m , l , lb , ok , dt = f ["model" ], f ["level" ], f ["label" ], f ["ok" ], f ["detail" ]
848+ fid_items . append (( f ["model" ], f ["level" ], f ["label" ], f ["ok" ], f ["detail" ]))
804849 else :
805- m , l , lb , ok , dt = f
806- a (f"| { m } | { l } | { lb } | { '✅' if ok else '❌' } | { dt } |" )
850+ fid_items .append (tuple (f ))
851+ fid_ok = sum (1 for _ , _ , _ , ok , _ in fid_items if ok )
852+ if fid_ok == len (fid_items ):
853+ a (f"✅ **{ fid_ok } /{ len (fid_items )} checks passed** — every plugin-tuned param "
854+ f"(P2 budget, effortMap, P3 sampling, P5 offParams) reached the backend and "
855+ f"had its expected effect." )
856+ else :
857+ a (f"❌ **{ fid_ok } /{ len (fid_items )} checks passed** — failures below:" )
858+ a ("" )
859+ a ("| Model | Level | Check | Detail |" )
860+ a ("|---|---|---|---|" )
861+ for m , l , lb , ok , dt in fid_items :
862+ if not ok :
863+ a (f"| { m } | { l } | { lb } | { dt } |" )
807864
808865 failed = [r for r in all_results if not r ["success" ] and not r .get ("template_rejection" )]
809866
@@ -824,6 +881,11 @@ def write_report(path: str, models, levels, summaries, fidelity, versions, all_r
824881 "machine-verified before inclusion. Prompts and keywords live in "
825882 "`support/thinking-benchmark.py` (PROMPTS / ANSWER_KEYWORDS)." )
826883 a ("" )
884+ a ("**Token-count caveat:** `completion_tokens` includes MTP draft tokens for "
885+ "multi-token-prediction models (Qwen3.6-MTP variants), so its totals are not "
886+ "comparable to dense models (Qwen3.8). Use reasoning chars and wall time "
887+ "for cross-model comparison." )
888+ a ("" )
827889 a ("_Generated by `support/thinking-benchmark.py --report`. Re-run after every pi / "
828890 "plugin / server version bump and diff against the previous report._" )
829891 p = Path (path )
0 commit comments