---
title: "Ask for output that matches a schema · Agent Wranglers"
url: https://agentwranglers.se/playbook/ai-features/structured-output-and-tools/schema-output
updated: 2026-10-20
licence: "All rights reserved"
---

# Ask for output that matches a schema

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Structured output and tools](https://agentwranglers.se/playbook/ai-features/structured-output-and-tools)
- Rule: When code reads a model's answer, request output that matches a JSON Schema through the provider's structured output mode. Define that schema once in code.
- How: Define the shape once, for example with zod in TypeScript or pydantic in Python, and generate the JSON Schema from it. Send it in the provider's structured output field. In October 2026 that is `text.format` in the OpenAI Responses API and `output_config.format` in the Anthropic Messages API. Other providers and open model servers have their own field. Set `additionalProperties` to false on every object and list every key as required. Strict modes accept only part of JSON Schema. Both providers above reject or drop `minLength` and `maxLength`, so keep such limits in your validator. The SDK helpers, such as `responses.parse` and `messages.parse`, send the schema and parse the answer for you. Validate the answer with the same definition anyway, as [check every model answer against a schema](https://agentwranglers.se/playbook/adversarial-testing/ai-agents-and-models/validate-model-output) requires. Check for a refusal first, because a refusal does not follow the schema.
- Check: A search finds no `JSON.parse` or `json.loads` on model text outside the gateway. Every gateway call that expects data passes a schema, and a test asserts it. A test sends each schema once to every provider in use and gets no schema error.

Example:

```ts
import { z } from 'zod';
import Anthropic from '@anthropic-ai/sdk';
import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';

export const ReturnAnswer = z.object({
  eligible: z.enum(['yes', 'no', 'unknown']),
  reason: z.string().max(300),
  policySection: z.string().nullable(),
});

const anthropic = new Anthropic();
const response = await anthropic.messages.parse({
  model: config.model,
  max_tokens: 4096,
  system: instructions,
  messages: [{ role: 'user', content: question }],
  output_config: { format: zodOutputFormat(ReturnAnswer) },
});
if (response.stop_reason === 'refusal') return REFUSED;
const answer = ReturnAnswer.parse(response.parsed_output);
```

```python
from typing import Literal
from openai import OpenAI
from pydantic import BaseModel, ConfigDict, Field

class ReturnAnswer(BaseModel):
    model_config = ConfigDict(extra="forbid")
    eligible: Literal["yes", "no", "unknown"]
    reason: str = Field(max_length=300)
    policy_section: str | None

client = OpenAI()
response = client.responses.parse(
    model=settings.model,
    instructions=instructions,
    input=question,
    text_format=ReturnAnswer,
)
answer = response.output_parsed  # None when the model refused
```

```json
{ "text": { "format": { "type": "json_schema", "name": "return_answer", "strict": true, "schema": { "type": "object" } } } }
```

```json
{ "output_config": { "format": { "type": "json_schema", "schema": { "type": "object" } } } }
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers ai features playbook: https://agentwranglers.se/playbook/ai-features/structured-output-and-tools/schema-output`.

- Next: [Retry a bad answer and then fall back](https://agentwranglers.se/playbook/ai-features/structured-output-and-tools/retry-invalid-output)
- [Structured output and tools](https://agentwranglers.se/playbook/ai-features/structured-output-and-tools)
