[Backend] Replace the Anthropic assistant prefill with structured outputs #339
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: MEDIUM (blocker for hosted). Found in the August 2026 session lifecycle review (#319).
The defect
_structured_anthropicsteers JSON output with an assistant-message prefill of"{"(llm_service.py:280-296). Assistant prefill was removed on Claude 4.6+ and the entire 5-family and now returns a 400.The pinned default (
claude-haiku-4-5-20251001) still accepts it, so this is latent — but any operator who configures a current model breaks every structured tool in the product at once, with an error that will read as a Quest Board bug. For a hosted tier that intends to use a modern Claude model, this is a hard blocker.Proposed fix
Replace the prefill with the supported mechanism:
output_config: {format: {...}}with a JSON schema onmessages.create(). Note this is the current parameter — the olderoutput_formattop-level parameter is deprecated.While in here, confirm the rest of the Anthropic path against current API behaviour rather than recalled behaviour, since several shapes changed across 2025-26.
Acceptance criteria
"{"prefill is removedoutput_config.formatwith a schemaVerified before closing. I went in expecting to find the prefill still lurking, given it was silently broken once. It is genuinely gone.
"{"literal and norole: "assistant"message anywhere inllm_service.pyoraudio_service.py. Only explanatory comments reference the old behaviour. Pinned bytest_anthropic_no_longer_prefills_the_assistant_turn, which assertsroles == ["user"].body["output_config"] = {"format": {"type": "json_schema", "schema": schema}}(llm_service.py:405-408, sent from:672). Wire shape pinned bytest_anthropic_sends_the_schema_as_output_config_format.docs/OPERATIONS.md:96-121, including the dated-snapshot-versus-current-id note.The note, and it matters given this issue's own history. The criterion asking that a current-generation model be exercised is met only by mocked tests. They name
claude-opus-5, but the HTTP layer is stubbed — so what is proven is our request shape, not that Anthropic's endpoint accepts it. There is no live-API test infrastructure in the repo and no key available to check independently.That is precisely the gap that made the original prefill bug latent: a unit test agreeing with the same belief that produced the code. It is also exactly what #281 later caught on the llama.cpp side — a documented request shape, shipped, silently ignored in production, with a green test the whole time.
So: closing, because the work asked for is done and the mocked tests are correct about what they cover. But the Anthropic path has never been confirmed against a real endpoint, and until someone runs one call with a real key, "verified" here means verified-by-construction. Worth doing before any hosted launch that offers Anthropic as a provider.
Part of a full acceptance-criteria pass across the v4.0.0 milestone.