{"slug": "structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026", "title": "Structured Outputs vs Tool Use vs Prefills: Getting JSON Out of Claude in 2026", "summary": "Anthropic's Claude models Opus 4.6+ and Fable 5 no longer support last-assistant-turn prefills for forcing JSON output, returning a 400 error. Developers must now use structured outputs via `output_config.format` with Zod validation or tool use with `strict: true` for reliable schema enforcement. The choice depends on whether the JSON is the answer (structured outputs) or arguments to an action (tool use).", "body_md": "For a long time the trick to force a model into JSON was to prefill the assistant turn with an opening brace. That trick is dead on the current Claude models. It returns a 400. If you are still doing it, your code breaks the moment you bump the model string. Here is the modern way to get reliable structured output, and how to pick between the two approaches that replaced prefills.\n\nThe old pattern:\n\n```\n// WRONG on Opus 4.6+, Fable 5: prefilling the last assistant turn now 400s\nmessages: [\n  { role: \"user\", content: \"Extract the name.\" },\n  { role: \"assistant\", content: '{\"name\": \"' }, // forced JSON start\n]\n```\n\nThis returned a 400 the first time I tried it on Opus 4.8. Last-assistant-turn prefills are no longer supported on Opus 4.6, 4.7, 4.8, or Fable 5. The replacement depends on what the prefill was doing.\n\nIf the prefill was forcing a JSON schema, the direct replacement is `output_config.format`\n\n. The SDK validates the response against your schema automatically, and with Zod you get type safety for free:\n\n``` js\nimport { z } from \"zod\";\nimport { zodOutputFormat } from \"@anthropic-ai/sdk/helpers/zod\";\n\nconst Finding = z.object({\n  vulnerability: z.string(),\n  severity: z.enum([\"low\", \"medium\", \"high\", \"critical\"]),\n  function_name: z.string(),\n  line: z.number(),\n});\n\nconst response = await client.messages.parse({\n  model: \"claude-opus-4-8\",\n  max_tokens: 16000,\n  messages: [{ role: \"user\", content: `Find the bug:\\n\\n${contract}` }],\n  output_config: { format: zodOutputFormat(Finding) },\n});\n\n// Typed and validated. null if parsing failed.\nconsole.log(response.parsed_output?.severity);\n```\n\nThe output is guaranteed to match the schema or `parsed_output`\n\nis null. No regex, no `JSON.parse`\n\nin a try/catch, no \"the model added a markdown fence again.\"\n\nA few schema limits to know: numerical constraints like `minimum`\n\n/`maximum`\n\nare not enforced server-side (the SDK strips them and validates client-side), recursive schemas are not supported, and every object needs `additionalProperties: false`\n\n. The Zod helper handles most of this for you.\n\nIf what you really want is for the model to *do* something with structured arguments, a tool is the cleaner fit. The classic case is classification: you want a label, and the label is the tool input.\n\n``` js\nconst response = await client.messages.create({\n  model: \"claude-opus-4-8\",\n  max_tokens: 1024,\n  tools: [{\n    name: \"classify_severity\",\n    description: \"Record the severity classification of a finding.\",\n    strict: true,\n    input_schema: {\n      type: \"object\",\n      properties: {\n        severity: { type: \"string\", enum: [\"low\", \"medium\", \"high\", \"critical\"] },\n      },\n      required: [\"severity\"],\n      additionalProperties: false,\n    },\n  }],\n  tool_choice: { type: \"tool\", name: \"classify_severity\" },\n  messages: [{ role: \"user\", content: `Classify:\\n\\n${finding}` }],\n});\n```\n\nWith `strict: true`\n\nthe arguments are guaranteed valid against the schema. Forcing `tool_choice`\n\nto the specific tool means the model has to call it.\n\nThe deciding question: is the JSON the *answer*, or is the JSON the *arguments to an action*?\n\n`output_config.format`\n\n).For my auditor, the report is the answer, so I use structured outputs. For the routing logic that decides which analysis pass to run, the decision is an action, so I use a tool.\n\nPrefills were used for more than JSON. Here is the mapping:\n\nThe prefill hack worked by exploiting how the model continues text. The modern features work by constraining the output format at the API level, which is both more reliable and impossible to break with a model bump. If you are still prefilling braces, you are one model-string change away from a 400. Move to structured outputs or tool use and stop fighting the formatter.", "url": "https://wpnews.pro/news/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026", "canonical_source": "https://dev.to/pavelespitia/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026-5fi0", "published_at": "2026-07-08 16:05:29+00:00", "updated_at": "2026-07-08 16:11:18.684210+00:00", "lang": "en", "topics": ["large-language-models", "developer-tools", "ai-products"], "entities": ["Anthropic", "Claude", "Opus 4.6", "Opus 4.7", "Opus 4.8", "Fable 5", "Zod"], "alternates": {"html": "https://wpnews.pro/news/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026", "markdown": "https://wpnews.pro/news/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026.md", "text": "https://wpnews.pro/news/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026.txt", "jsonld": "https://wpnews.pro/news/structured-outputs-vs-tool-use-vs-prefills-getting-json-out-of-claude-in-2026.jsonld"}}