From fb3c69df5b85f3f3e83a539f485454ae0b37f570 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Sun, 26 Jul 2026 08:48:35 -0700 Subject: [PATCH 1/2] thinking-probes: read the reasoning field under both names @Blackwellboy's offlabel#8 fix landed in spine-probes/run_probes.py but not in the thinking probes, which is the worse place to be missing it: these two tools exist specifically to measure whether a model reasons. llama.cpp populates reasoning_content; vLLM exposes reasoning. Reading only the first makes every arm on a vLLM lane classify as NO_REASONING, so thinking_ab would report a model as never thinking and persona_ab would report the persona gate as perfectly effective in all four cells. Both are fabricated results, and they are the same class of error as the enable_thinking claim these tools were written to correct: concluding from a field rather than measuring the thing. README gains the field-name note and one guard rail: NO_REASONING across every arm including true means check the field name before believing it, since a stack exposing thinking under a third name still reads as silent here. Signed-off-by: Christopher Maher --- scripts/thinking-probes/README.md | 14 ++++++++++++-- .../__pycache__/persona_ab.cpython-314.pyc | Bin 0 -> 8991 bytes .../__pycache__/thinking_ab.cpython-314.pyc | Bin 0 -> 10402 bytes scripts/thinking-probes/persona_ab.py | 8 +++++++- scripts/thinking-probes/thinking_ab.py | 8 +++++++- 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc create mode 100644 scripts/thinking-probes/__pycache__/thinking_ab.cpython-314.pyc diff --git a/scripts/thinking-probes/README.md b/scripts/thinking-probes/README.md index 1339bc5..45aef2f 100644 --- a/scripts/thinking-probes/README.md +++ b/scripts/thinking-probes/README.md @@ -65,15 +65,25 @@ cannot make: | signal | verdict | |---|---| -| `reasoning_content` non-empty | `REASONED_PARSED` | +| `reasoning_content` or `reasoning` non-empty | `REASONED_PARSED` | | `` markers in content | `REASONED_UNPARSED` | | neither | `NO_REASONING` | `REASONED_UNPARSED` is the important one. Any occurrence means a stack reading -only `reasoning_content` would report that arm as not firing when it did, so a +only the reasoning field would report that arm as not firing when it did, so a published firing rate from such a stack is measuring the parser as much as the model. +**Servers disagree on the field name.** llama.cpp populates `reasoning_content`; +vLLM exposes `reasoning`. These tools read both, because reading only the first +makes every arm on a vLLM lane look like `NO_REASONING`, which is this tool +inventing the very answer it exists to measure. Reported by @Blackwellboy in +offlabel#8 after a run where all 42 rows logged zero reasoning. + +If a run reports `NO_REASONING` across **every** arm including `true`, do not +conclude the model does not reason. Check the field name first: a stack that +exposes thinking under a third name would still read as silent here. + ## `persona_ab.py`: does a system prompt change it Crosses a persona system prompt against the kwarg, four cells: diff --git a/scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc b/scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc69d4cac76ca347047676c6621bfbb7c5dea6ec GIT binary patch literal 8991 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 literal 0 HcmV?d00001 diff --git a/scripts/thinking-probes/persona_ab.py b/scripts/thinking-probes/persona_ab.py index 4838104..5d1c2a2 100755 --- a/scripts/thinking-probes/persona_ab.py +++ b/scripts/thinking-probes/persona_ab.py @@ -104,7 +104,13 @@ def main(): d = post(args.base + "/v1/chat/completions", body, args.timeout) ch = d["choices"][0] content = ch["message"].get("content") or "" - reasoning = ch["message"].get("reasoning_content") or "" + # Servers disagree on the field name: llama.cpp populates + # reasoning_content, vLLM exposes reasoning. Reading only the first + # makes every arm look like NO_REASONING on a vLLM lane, which is + # this tool inventing the answer it exists to measure + # (offlabel#8, @Blackwellboy). + reasoning = (ch["message"].get("reasoning_content") + or ch["message"].get("reasoning") or "") finish = ch.get("finish_reason") reasoned = bool(reasoning.strip()) or bool(THINK_MARKER.search(content)) except Exception as exc: # noqa: BLE001 diff --git a/scripts/thinking-probes/thinking_ab.py b/scripts/thinking-probes/thinking_ab.py index a038ce6..f7487b7 100755 --- a/scripts/thinking-probes/thinking_ab.py +++ b/scripts/thinking-probes/thinking_ab.py @@ -128,7 +128,13 @@ def main(): d = post(args.base + "/v1/chat/completions", body, args.timeout) ch = d["choices"][0] content = ch["message"].get("content") or "" - reasoning = ch["message"].get("reasoning_content") or "" + # Servers disagree on the field name: llama.cpp populates + # reasoning_content, vLLM exposes reasoning. Reading only the first + # makes every arm look like NO_REASONING on a vLLM lane, which is + # this tool inventing the answer it exists to measure + # (offlabel#8, @Blackwellboy). + reasoning = (ch["message"].get("reasoning_content") + or ch["message"].get("reasoning") or "") finish = ch.get("finish_reason") v = verdict(content, reasoning) except Exception as exc: # noqa: BLE001 From 923119f60cd4fc8a86daf432269ee71ea376bb36 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Sun, 26 Jul 2026 19:01:03 -0700 Subject: [PATCH 2/2] thinking-probes: drop committed bytecode, make the field-name guard self-checking Two review items from @Blackwellboy on #13. Mechanical: the branch carried two .pyc files under scripts/thinking-probes/__pycache__. Untracked them, and added __pycache__/ and *.pyc to .gitignore, which previously held only the confidentiality entries and so had nothing stopping this. Verified by regenerating the cache with py_compile and confirming git now ignores it. Substantive: the README told the reader to check the field name when every arm comes back NO_REASONING, but a guard rail only works on someone reading it at the moment they need it. The condition is machine-checkable, so both sampling tools now print it at summary time. That turns advice into a positive control, which matters here because the thing being measured is absence and a wrong key produces absence too. The check stays quiet whenever it would be guessing: - nothing scored at all (every request errored): no conclusion available - the thinking-enabled arm errored rather than returning nothing: the arm that would carry the signal was never measured - any REASONED_PARSED or REASONED_UNPARSED anywhere: the reader works The two tools key on different things and are not copies of each other. thinking_ab.py derives the enabled arm from ARMS rather than hardcoding "true", so it stays correct if the arms change. persona_ab.py has no true arm at all: its false cells are the structural control where zero is the expected result, so it keys on the absent cells instead. Signed-off-by: Christopher Maher --- .gitignore | 4 +++ scripts/thinking-probes/README.md | 21 ++++++++++++ .../__pycache__/persona_ab.cpython-314.pyc | Bin 8991 -> 0 bytes .../__pycache__/thinking_ab.cpython-314.pyc | Bin 10402 -> 0 bytes scripts/thinking-probes/persona_ab.py | 31 +++++++++++++++++ scripts/thinking-probes/thinking_ab.py | 32 ++++++++++++++++++ 6 files changed, 88 insertions(+) delete mode 100644 scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc delete mode 100644 scripts/thinking-probes/__pycache__/thinking_ab.cpython-314.pyc diff --git a/.gitignore b/.gitignore index 4d397bf..1b3f465 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ models/qwopus-coder.md cards/qwopus-coder.html cards/img/qwopus-coder.png + +# python build artifacts +__pycache__/ +*.pyc diff --git a/scripts/thinking-probes/README.md b/scripts/thinking-probes/README.md index 45aef2f..4053733 100644 --- a/scripts/thinking-probes/README.md +++ b/scripts/thinking-probes/README.md @@ -84,6 +84,27 @@ If a run reports `NO_REASONING` across **every** arm including `true`, do not conclude the model does not reason. Check the field name first: a stack that exposes thinking under a third name would still read as silent here. +**Both sampling tools now say this themselves.** A guard rail only works on +someone who reads it at the moment they need it, and this condition is +machine-checkable, so the tools print a warning at summary time rather than +leaving it to the reader: + +``` + WARNING: every arm returned NO_REASONING, including true where thinking is + explicitly enabled. That is either a genuinely non-reasoning model or a + reasoning field this tool does not read. +``` + +That makes it a positive control rather than advice, which matters more here +than usual: the thing being measured is absence, and a wrong key produces +absence too. + +It deliberately stays quiet when it would be guessing. If the enabled arm +errored rather than returning nothing, or every request failed, nothing was +measured and no field-name conclusion is available. `persona_ab.py` has no +`true` arm at all, so there it keys on the `absent` cells, since zero in the +`false` control cells is the expected result rather than a symptom. + ## `persona_ab.py`: does a system prompt change it Crosses a persona system prompt against the kwarg, four cells: diff --git a/scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc b/scripts/thinking-probes/__pycache__/persona_ab.cpython-314.pyc deleted file mode 100644 index dc69d4cac76ca347047676c6621bfbb7c5dea6ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8991 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 diff --git a/scripts/thinking-probes/persona_ab.py b/scripts/thinking-probes/persona_ab.py index 5d1c2a2..19be042 100755 --- a/scripts/thinking-probes/persona_ab.py +++ b/scripts/thinking-probes/persona_ab.py @@ -59,6 +59,32 @@ THINK_MARKER = re.compile(r"", re.I) +def field_name_warning(rows): + """Positive control for the one failure this tool cannot otherwise see. + + What is being measured is absence, and a reasoning field this tool does + not read produces absence too. Note this grid has no explicit `true` + arm: the `false` cells are the structural control, where zero is the + expected result, so the signal is zero in the `absent` cells as well. + + Returns a warning string, or None when the check does not apply. + """ + scored = [r for r in rows if r["reasoned"] is not None] + if not scored: + return None # nothing was measured; not this failure + if any(r["reasoned"] for r in scored): + return None # something reasoned; the reader works + available = [k for (_, k) in CELLS if k != "false"] + if not any(r["kwarg"] in available for r in scored): + return None # only control cells scored; says nothing + return (" WARNING: no cell reasoned, including " + f"{'/'.join(sorted(set(available)))} where thinking is available\n" + " rather than structurally suppressed. That is either a genuinely\n" + " non-reasoning model or a reasoning field this tool does not read.\n" + " Check the field name against the server's response before\n" + " believing these numbers.") + + def post(url, body, timeout): req = urllib.request.Request( url, data=json.dumps(body).encode(), @@ -135,6 +161,11 @@ def main(): mr = sum(r["reasoning_chars"] for r in rs) / max(len(rs), 1) print(f" {persona:<11} {kwarg:<7} {n:>4}/{len(rs):<5} {mr:>18.0f}") + warning = field_name_warning(rows) + if warning: + print() + print(warning) + print("\n=== persona effect WITHIN the absent arm, per prompt shape ===") for pname in PROMPTS: line = f" {pname:<15}" diff --git a/scripts/thinking-probes/thinking_ab.py b/scripts/thinking-probes/thinking_ab.py index f7487b7..beddeea 100755 --- a/scripts/thinking-probes/thinking_ab.py +++ b/scripts/thinking-probes/thinking_ab.py @@ -75,6 +75,33 @@ def rendered_tail(base, arm, timeout): return p[-28:], p.rstrip().endswith("") +def field_name_warning(rows): + """Positive control for the one failure this tool cannot otherwise see. + + What is being measured is absence, and a reasoning field this tool does + not read produces absence too. The two are indistinguishable in the + numbers, so when nothing reasoned anywhere, including the arm that + explicitly enables thinking, say so rather than let the zeros stand. + + Returns a warning string, or None when the check does not apply. + """ + scored = [r for r in rows if r["verdict"] != "ERROR"] + if not scored: + return None # nothing was measured; not this failure + if any(r["verdict"].startswith("REASONED_") for r in scored): + return None # something reasoned; the reader works + # Derived, not hardcoded, so it stays correct if ARMS changes. + enabled = [a for a, kw in ARMS.items() + if kw and kw.get("enable_thinking") is True] + if not any(r["arm"] in enabled for r in scored): + return None # the enabled arm produced no scored sample + return (" WARNING: every arm returned NO_REASONING, including " + f"{'/'.join(enabled)} where thinking is explicitly enabled.\n" + " That is either a genuinely non-reasoning model or a reasoning\n" + " field this tool does not read. Check the field name against the\n" + " server's response before believing these numbers.") + + def verdict(content, reasoning): if reasoning.strip(): return "REASONED_PARSED" @@ -159,6 +186,11 @@ def main(): print(f" {arm:<8} {c('REASONED_PARSED'):>7} {c('REASONED_UNPARSED'):>9} " f"{c('NO_REASONING'):>6} {c('ERROR'):>4} {mr:>10.0f}") + warning = field_name_warning(rows) + if warning: + print() + print(warning) + print("\n=== per prompt shape (is the effect prompt-specific?) ===") for pname in PROMPTS: line = f" {pname:<15}"