Function calling with Zod
Purpose and Scope
Function calling lets a model ask the application to run named capabilities and return the results back into the conversation. In this SDK, the reader-facing pattern appears in two complementary forms: a runnable chat completion example that hand-writes function schemas and a Zod helper module that turns typed schemas into strict JSON schema objects for structured outputs and parsing. This page connects those pieces so application developers can understand the full loop: describe a capability, let the model request it, execute trusted local code, append the tool result, and continue until the model can answer normally.
Sources: README.md, examples/function-call.ts, src/helpers/zod.ts, tests/helpers/zod.test.ts
The repository README frames the package as the official TypeScript and JavaScript library for the OpenAI REST API, generated from the OpenAPI specification, and points users toward the Responses API as the primary model interaction surface while also keeping Chat Completions as a supported standard. That matters for function calling because examples and helper APIs may appear across both eras. The function calling example uses Chat Completions and the legacy functions array, while the Zod helper is designed for modern schema-driven parsing and strict schema generation across chat and response-oriented helper paths.
Sources: README.md, examples/function-call.ts, src/helpers/zod.ts
Relevant Source Files
- README.md — Establishes SDK installation, client construction, the primary Responses API recommendation, and the continuing Chat Completions example surface that contextualizes function calling.
- examples/function-call.ts — Provides a runnable book-database function calling loop using Chat Completions, typed message arrays, JSON arguments, local function dispatch, and function-role result messages.
- src/helpers/zod.ts — Implements Zod-to-OpenAI schema helpers, including strict JSON schema conversion, Zod v3 and v4 handling, parseable response format creation, and conversion behavior for discriminated unions.
- tests/helpers/zod.test.ts — Verifies the generated strict schema shape, Zod v3 and v4 behavior, definition reference hygiene, and special handling for discriminated unions in strict schemas.
Core Primitives
A function is an application capability that the model may request when it needs external data or action. The example models a tiny book database with three capabilities: list books by genre, search by name, and get details by identifier. Each capability is described with a name, a human-readable description, and a JSON schema for accepted arguments. The model does not execute those functions itself. Instead, it emits a structured request, the application parses the arguments, runs the corresponding local code, and sends the result back as another message.
Sources: examples/function-call.ts
A schema is the contract between the model and the application. Hand-written JSON schema is enough for simple examples, but larger TypeScript projects often want a single source of truth for validation and inferred types. The Zod helper module addresses that by accepting Zod schemas and producing strict OpenAI-compatible schema objects. It supports both Zod v3 and Zod v4 inputs through a shared inferred-type helper, then chooses the appropriate conversion implementation at runtime. The returned objects are parseable helper values, so compatible parsing methods can attach validated parsed data to SDK results.
Sources: src/helpers/zod.ts, tests/helpers/zod.test.ts
Execution Flow
The example flow begins by constructing the OpenAI client from the default environment-based API key behavior, then building a message array with system and user messages. Inside a loop, the application calls chat completions with a model, accumulated messages, and the available functions. The returned assistant message is appended to history before any dispatch happens. If that message does not contain a function request, the example exits because the assistant has produced its final answer. If it does contain a request, the application treats it as work to perform, not as final user-visible output.
Sources: README.md, examples/function-call.ts
When a function request is present, the example parses the JSON arguments and dispatches by name. The dispatch function calls the matching local implementation for listing, searching, or retrieving books, and throws an error when no known function is found. After local execution, the result is serialized and appended as a function-role message whose name matches the requested function. The loop then asks the model again with the expanded history. This explicit repetition is the important operational pattern: model reasoning, local action, tool result, and final answer are separate steps controlled by the application.
Sources: examples/function-call.ts
const completion = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages,
functions,
});
const message = completion.choices[0]!.message;
messages.push(message);
if (message.function_call) {
const result = await callFunction(message.function_call);
messages.push({
role: 'function',
name: message.function_call.name!,
content: JSON.stringify(result),
});
}Zod Schema Helpers and Strict Outputs
The central helper shown in the source is zodResponseFormat. It creates a response format object with type json schema, adds the supplied schema name, forces strict mode, and stores the converted schema. It also provides a parser callback that parses JSON content and validates it with the original Zod object. The comments document an important distinction: passing the helper to compatible parse, stream, or tool-running chat methods can produce a parsed property, while passing the same object directly to a plain create call still leaves parsing to the caller.
Sources: src/helpers/zod.ts
Zod v3 and Zod v4 are handled differently because their schema conversion APIs differ. For Zod v3, the helper delegates to the vendored converter with OpenAI strict mode, duplicate reference naming, extracted root references, and property-based nullable handling. For Zod v4, the helper uses Zod’s JSON schema output targeted at draft seven, then transforms the result into the strict subset expected by OpenAI. The implementation also rewrites discriminated union output when necessary, replacing one form of union representation with another that remains inside the supported strict schema subset.
Sources: src/helpers/zod.ts, tests/helpers/zod.test.ts
The tests are useful design documentation because they spell out what the SDK promises from the conversion. A basic object with city, temperature, and unit fields becomes an object schema with all properties required, additional properties disabled, a schema draft marker, and strict mode enabled. Another test ensures references and generated definition names do not include whitespace, which helps avoid fragile schemas when nested objects are reused. A separate test confirms that Zod v4 discriminated unions are represented without the unsupported union form and still preserve both alternatives.
Sources: tests/helpers/zod.test.ts
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod/v4';
const Weather = z.object({
city: z.string(),
temperature: z.number(),
units: z.enum(['c', 'f']),
});
const responseFormat = zodResponseFormat(Weather, 'location');Implementation Details and Edge Cases
Treat model-supplied arguments as untrusted input. The example calls JSON parsing on the function call arguments and then uses a switch statement to restrict execution to known function names. That is intentionally different from dynamically invoking arbitrary names supplied by the model. In production, developers should keep the same boundary: the model may request a tool, but application code decides whether the name is allowed, validates the arguments, runs the implementation, handles failures, and decides what result text to send back into the model context.
Sources: examples/function-call.ts
Strict schemas reduce ambiguity but also surface schema compatibility details earlier. The helper’s Zod v4 discriminated union branch throws if the generated schema simultaneously contains incompatible union representations, because that cannot be represented safely in the strict subset. The tests around references and required properties show another practical concern: generated schema names should be stable and valid enough for API submission, not merely valid TypeScript. When a schema grows, use tests that snapshot or inspect the generated schema before relying on it in a tool or structured-output workflow.
Sources: src/helpers/zod.ts, tests/helpers/zod.test.ts
Practical Next Steps
Start with the runnable function calling example if you need to understand the application loop. Replace the book database functions with a small, deterministic function from your own domain, keep the dispatch table explicit, and log each assistant and function message while developing. Once the function contract grows beyond a few fields, introduce Zod schemas and the helper module so validation, type inference, and strict JSON schema generation stay together. For newer model workflows, compare this page with the structured outputs, Responses API, Chat Completions, and tools documentation pages before choosing the final surface.