Skip to content

fix(anthropic): one structured-output path for both payload builders - #52

Open
rezaho wants to merge 2 commits into
fix/oauth-thinking-clampfrom
s246-oauth-structured-output-parity
Open

fix(anthropic): one structured-output path for both payload builders#52
rezaho wants to merge 2 commits into
fix/oauth-thinking-clampfrom
s246-oauth-structured-output-parity

Conversation

@rezaho

@rezaho rezaho commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Stacked on #51 (merge that first; GitHub will retarget this to main).

Problem. The OAuth payload builder carried a hand-copied twin of the api-key leg's structured-output logic. The twin ASSIGNED output_config (clobbering a reasoning-effort value already in it) and had no supports_structured_output gate, so a schema request on a non-enforcing endpoint degraded to a bare 'reply with JSON' nudge instead of the schema-in-prompt fallback. This is the S246 leg of the Spren-side structured-output integrity repair: identical shape by construction, not by discipline.

Change.

  • One module-level apply_structured_output in anthropic.py; both payload builders call it; the OAuth twin is deleted.
  • AnthropicOAuthAdapter declares supports_structured_output = True — an endpoint capability, live-verified against the real OAuth endpoint (schema honored, conformant output, one call).
  • Merge-not-assign for output_config on both legs: a request carrying reasoning effort AND a response schema emits one config object with both.
  • Tests: payload-shape arms now drive a REAL adapter built by the real constructor (dummy on-disk credentials via fixture, auto_refresh=False as the one suppressed step) instead of a __new__ hand-assembly; parity arms compare both legs on identical inputs.

Tests. tests/models: 221 passed, 39 skipped. Full suite: 1468 passed, 5 failed, 53 skipped — all 5 pre-existing in tests/coordination/**, none referencing anthropic or structured output.

After merge: Spren bumps its framework pin (its gitlink still points at a commit predating both this and #51 — until the bump, a fresh Spren checkout ships the old OAuth payload shape).

rezaho added 2 commits August 8, 2026 01:08
The api-key adapter and the OAuth adapter are deliberately parallel — the OAuth
one is not a subclass, so it builds its own payload — and the structured-output
branch is where that parallelism drifted twice. Both drifts landed on the OAuth
leg: it ASSIGNED `output_config`, so a request carrying both a reasoning effort
and a response schema silently lost the effort (the api-key twin had already
been fixed and test-pinned to merge, never assign); and it had no
`supports_structured_output` gate, so a schema request on an endpoint that
cannot enforce one would have degraded to a bare "please emit JSON" nudge
instead of putting the schema in the prompt.

Both branches are now one module-level `apply_structured_output` that each
builder calls, so the shape is identical by construction rather than by two
people remembering to cut the same line twice. The OAuth adapter declares
`supports_structured_output = True` — an endpoint capability, like the api-key
one, and true here: this IS the first-party Messages API, verified live
2026-08-07 with haiku 4.5 under the Claude Code prefix returning
schema-conformant output. A leg that turns out not to enforce it flips the flag
and inherits the fallback, the way Bedrock does.

Tests drive BOTH builders over identical inputs and compare, for the native and
the fallback arms alike, plus the clamp parity the repair path depends on: a
background model at max_tokens=4096 with the default 8192 budget clamps to 3072
on both legs.
…ot __new__

Every OAuth payload assertion ran against an object built with __new__ and five
hand-set attributes, so anything __init__ does that shapes a request was invisible
to it — and the parity arms compared that hand-assembled object against a real
AnthropicAdapter. The constructor's only obstacle was reading credentials from
disk, so the tests give it a credentials file at the path it already reads from
the environment and turn auto_refresh off (the one step that would leave the
machine). Nothing reaches the network.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant