Skip to main content
When output can take multiple object shapes, each shape should define its own required fields and constraints. If you use one flat object with mostly optional fields, the model can mix fields across shapes and produce ambiguous payloads. A discriminated anyOf schema solves this: each branch defines exactly one object variant, and the model must pick one branch and fill it completely. Your runtime reads the discriminator and dispatches without heuristics.

Common use case: agent tool routing

A support agent that can search docs, look up an order, or send an email. Each tool has different required arguments, and the agent must choose exactly one per step.

Schema pattern

{
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "tool": { "const": "search_docs" },
        "query": { "type": "string", "minLength": 3, "maxLength": 200 },
        "top_k": { "type": "integer", "minimum": 1, "maximum": 10 }
      },
      "required": ["tool", "query", "top_k"],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "tool": { "const": "lookup_order" },
        "order_id": { "type": "string", "pattern": "^ORD-[0-9]{4,10}$" }
      },
      "required": ["tool", "order_id"],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "tool": { "const": "send_email" },
        "to": { "type": "string", "format": "email" },
        "subject": { "type": "string", "minLength": 3, "maxLength": 120 },
        "body": { "type": "string", "minLength": 10, "maxLength": 1000 }
      },
      "required": ["tool", "to", "subject", "body"],
      "additionalProperties": false
    }
  ]
}

Example outputs

Order lookup:
{
  "tool": "lookup_order",
  "order_id": "ORD-9842"
}
Doc search:
{
  "tool": "search_docs",
  "query": "password reset link expired",
  "top_k": 5
}

Why this works

In this pattern, anyOf is enough because the discriminator makes the branches mutually exclusive. If the model tried to produce fields from two branches simultaneously, validation would still fail, so it learns to commit to one action. The const value on tool acts as a discriminator: your runtime reads tool, matches it to a handler, and knows exactly which fields are present. No need for if "query" in output heuristics. Each branch also has its own constraints (pattern on order_id, format: "email" on to, bounds on subject and body), so the arguments are validated at generation time, not at execution time.