Skip to content

Schema validation

The SDK uses schemas both to describe structured data to the model and to validate data inside your application. Choose the schema form based on whether you need local validation and type inference.

Schema formLocal validation and transformsTypeScript inferenceSupported surfaces
Zod objectYesYesFunction-tool and agent-tool parameters, function-tool outputSchema, agent outputType, and handoff inputType
Standard Schema with Standard JSON Schema conversionYes, synchronouslyYes, from the validation output typeFunction-tool and agent-tool parameters, agent outputType, and handoff inputType
Raw JSON SchemaNo; the SDK only parses the JSONNo; values are typed as unknownFunction-tool and agent-tool parameters, function-tool outputSchema, agent outputType, and handoff inputType

Use Zod when it already fits your application. Use Standard Schema when you want the same SDK validation and inferred output types with another compatible validation library. Use raw JSON Schema when you already own the wire schema and will validate values elsewhere.

A compatible value must implement Standard Schema V1 validation and Standard JSON Schema conversion. Some libraries expose both capabilities directly. Others provide an adapter. This example uses Valibot with toStandardJsonSchema() from @valibot/to-json-schema.

Install the example dependencies with npm install valibot @valibot/to-json-schema in an application that already uses @openai/agents.

Valibot schemas for tools, agent output, and a handoff
import { Agent, handoff, tool } from '@openai/agents';
import { toStandardJsonSchema } from '@valibot/to-json-schema';
import * as v from 'valibot';
const LookupOrderParameters = toStandardJsonSchema(
v.object({
orderId: v.pipe(v.string(), v.minLength(1)),
includeHistory: v.optional(v.boolean(), false),
}),
);
const lookupOrder = tool({
name: 'lookup_order',
description: 'Look up an order by ID.',
parameters: LookupOrderParameters,
// The argument type is inferred after Valibot validation and defaults run.
execute: async ({ orderId, includeHistory }) => ({
orderId,
status: 'shipped',
history: includeHistory ? ['placed', 'shipped'] : undefined,
}),
});
const Resolution = toStandardJsonSchema(
v.object({
orderId: v.string(),
message: v.string(),
}),
);
const supportAgent = new Agent({
name: 'Order support',
instructions: 'Resolve order questions and return a structured summary.',
tools: [lookupOrder],
// The final output is converted to JSON Schema, then validated by Valibot.
outputType: Resolution,
});
const supportAgentTool = supportAgent.asTool({
toolName: 'resolve_order',
toolDescription: 'Resolve an order question with the support specialist.',
parameters: LookupOrderParameters,
// inputBuilder receives the same validated and inferred parameter type.
inputBuilder: ({ params }) =>
`Resolve order ${params.orderId}. Include history: ${params.includeHistory}.`,
});
const EscalationDetails = toStandardJsonSchema(
v.object({
reason: v.pipe(v.string(), v.minLength(1)),
priority: v.optional(v.picklist(['normal', 'urgent']), 'normal'),
}),
);
const billingAgent = new Agent({
name: 'Billing specialist',
instructions: 'Resolve billing questions.',
});
const billingHandoff = handoff(billingAgent, {
inputType: EscalationDetails,
// The callback receives the validated value, including Valibot defaults.
onHandoff: async (_context, details) => {
if (details) {
await recordEscalation(details.reason, details.priority);
}
},
});
async function recordEscalation(_reason: string, _priority: string) {}
export { billingHandoff, supportAgent, supportAgentTool };

The same schema contract has a different role on each surface:

  • tool({ parameters }) converts the schema for the model, validates each tool call locally, applies library transforms or defaults, and passes the inferred validation output to execute.
  • agent.asTool({ parameters }) applies the same validation and passes the inferred validation output to inputBuilder before the nested agent runs.
  • new Agent({ outputType }) requests structured model output, validates the parsed value locally, and exposes the inferred validation output through result.finalOutput.
  • handoff(..., { inputType }) validates the handoff tool-call arguments and passes the inferred validation output to onHandoff.

Standard Schema support has a deliberately narrow contract:

  • The value must provide synchronous ~standard.validate behavior and both ~standard.jsonSchema.input() and ~standard.jsonSchema.output(). Validation-only Standard Schema values are not supported.
  • The input JSON Schema used by these SDK surfaces must have type: "object" at its root. Wrap scalar or array values in an object schema.
  • Standard Schema function-tool parameters require strict mode. Do not set strict: false when using them.
  • Asynchronous Standard Schema validation is not supported. A validator that returns a Promise fails the operation instead of running a tool fallback or invalid-final-output handler.
  • The generated JSON Schema must use constructs supported by the SDK’s strict-schema normalization. Unsupported constructs fail when the tool, handoff, or agent is created, before a model request.
  • Function-tool outputSchema does not currently accept Standard Schema. Use a Zod schema, a raw JSON Schema, or validate the tool result in application code.

If you are writing your own adapter, import the StandardSchemaWithJSON<Input, Output> type from @openai/agents to check that it exposes the required validation and conversion methods. Prefer a library-maintained adapter when one is available.