Skip to main content
Tool calling enables language models to interact with external functions and APIs, extending their capabilities beyond text generation.

Define a Tool

Create tools using defineTool(). parameters takes a Standard Schema with JSON Schema support, such as a Zod 4.2+ schema, or plain JSON Schema. See Schemas for the supported libraries.

Strict tool schemas

Set strict: true on a tool to make the provider enforce the tool’s parameter schema during argument generation. Strictness is a per-tool opt-in: omitting strict or setting strict: false keeps that tool non-strict on every provider.

Strict-capable schema contract

A strict tool’s schema must fit the strict-capable subset that schema-enforcing providers accept. Core-ai validates strict tools against this contract locally, before any API call, and reports every violation with a fix hint. Non-strict tools are never validated against it. The contract applies to the tool’s JSON Schema, so it covers Standard Schemas and plain JSON Schema alike. The examples below use Zod; other libraries produce the equivalent JSON Schema keywords.
  • Closed objects whose keys the provider can require. A key must be required, accept null (.nullable()), or declare a default (.default()), so the model can always send a value for it. Use .nullable() for values the model may leave out: a key that is only .optional() is rejected.
  • Object root. parameters must describe an object, such as a z.object(); a primitive, array, or union at the root is rejected.
  • Allowed keywords — the intersection of what OpenAI and Anthropic accept in strict mode, not the union: $ref / $defs, type (object, array, string, number, integer, boolean, null), properties, required, additionalProperties: false, items, enum, const, unions (anyOf from z.union(), and oneOf from z.discriminatedUnion(), which providers receive as anyOf), format (date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid), pattern, title, description, default, examples.
  • Rejected: numeric bounds (minimum, maximum, multipleOf — Zod’s implicit safe-integer bounds on z.int() are exempt and stripped), string and array length constraints (minLength, maxLength, minItems, maxItems), URL formats (z.url() emits format: "uri", which OpenAI does not accept — use z.string()), intersections (z.intersection() / .and() emit allOf — flatten into one z.object()), tuples (prefixItems), not, open objects (z.record(), .catchall(), .passthrough(), z.looseObject()), and recursive schemas (z.lazy()).
Check constraints you remove from the schema (ranges, lengths) in your tool implementation.

Check model support

model.capabilities.tools.strictSchemas reports whether the selected model supports strict tools and any provider limit:
Unknown and future model IDs resolve optimistically to { supported: true }: core-ai forwards the explicit strict: true and lets the provider reject the request if the model genuinely lacks support. This keeps newly released models usable without a registry update. Fine-tuned OpenAI ft:<base>:... models inherit their base model’s capabilities instead.

Errors

Invalid strict configuration throws ToolSchemaStrictnessError before the provider request. Its reason is one of:
  • 'unsupported' — the model is known not to support strict tool schemas.
  • 'limit-exceeded' — the request contains more strict tools than the provider’s maxStrictTools limit.
  • 'union-limit-exceeded' — the request’s strict tools have more union-typed parameters than the provider’s maxUnionParameters limit. A union-typed parameter is a property whose schema is an anyOf, a oneOf, or a type array with more than one entry, including every nullable key.
  • 'invalid-schema' — a strict tool’s schema is outside the strict-capable contract. The error carries a violations array naming each offending schema path:

Parallel tool calls

OpenAI does not guarantee schema adherence when parallel tool calls are enabled. Core-ai does not change that setting for you: when you combine strict tools with providerOptions.openai.parallelToolCalls: true, set it to false yourself if you need guaranteed adherence.

Provider support

On the OpenAI family (openai, azure-openai, kimi, openai-compat, omnifact), strict tools are sent with strict: true and a normalized schema ($schema stripped, additionalProperties: false added recursively, oneOf rewritten to anyOf, and nullable or defaulted keys listed in required — all semantics-preserving). Non-strict tools are sent with the tool’s JSON Schema unchanged. On the Responses API they carry an explicit strict: false, because that API otherwise tries to normalize tools into strict mode on its own; on Chat Completions, which is non-strict by default, the field is omitted. Anthropic follows the same split: strict tools get the provider’s normalization (which also drops the numeric and length constraints its strict grammar rejects), non-strict tools are sent raw. Anthropic also rejects a single strict schema it finds too complex (“Schema is too complex”; 30 plain required keys in one tool exceed it) with a provider error. core-ai cannot check that limit because Anthropic does not publish it.
Strict generation reduces invalid arguments but does not make tool execution trusted. Check each call with validateToolCall(), or validate ToolCall.arguments yourself for plain JSON Schema tools, before executing code or making external requests. See Validate tool calls.
Strict tool schemas apply to function arguments during generate() and stream(). They are separate from the structured response formats used by generateObject() and streamObject(). A provider can support one without supporting the other.

Basic Tool Usage

Implement a complete tool calling flow:

Tool Choice Strategies

Control when and how tools are used:

Multiple Tools

Provide multiple tools for the model to choose from:

Validate tool calls

Tool arguments come from the model, so validate them before you execute anything, whether the tool is strict or not. How you validate depends on the tool’s parameters:
  • Standard Schema: pass the tool and the call to validateToolCall(). It returns { value }, the schema’s output typed as such, or { issues }, listing each problem with a message and an optional path. Invalid arguments are reported, never thrown. So is a validator that throws, such as a transform calling new URL(): its error message becomes the only issue.
  • Plain JSON Schema: core-ai sends the schema as is and has no validator for it, so validateToolCall() returns undefined. Validate ToolCall.arguments yourself.
Check call.name first: validateToolCall() throws a ValidationError for a call to another tool. value has the schema’s defaults and transforms applied, while ToolCall.arguments keeps the arguments exactly as the model sent them. See Handling tool calls for a complete loop.

JSON Schema tools

Use plain JSON Schema for tools whose schemas you receive at runtime, such as tools listed by an MCP server:
validateToolCall() returns undefined for these tools. Validate their arguments with a JSON Schema validator, or let the MCP server reject invalid input. To validate them with validateToolCall(), wrap your validator in the Standard Schema interface:

Handling Tool Calls

Execute validated tool calls the same way for strict and non-strict tools. Send validation issues back as an error tool result so the model can correct its call:

Tool discovery

For large tool sets, such as the tools of several MCP servers, declare a few tools up front and let the model discover the rest with toolDiscovery. See Tool Discovery. With discovery enabled, finishReason: 'tool-calls' also covers pending discovery calls, which arrive in result.discoveryCalls and never in toolCalls. Loop while finishReason is 'tool-calls' and answer both toolCalls and discoveryCalls: a step can contain only discovery calls.

Tool Calling with Streaming

Handle tool calls during streaming:

Advanced Tool Examples

Tool Result Messages

Structure tool results properly:

Helper: Convert Result to Message

Use resultToMessage() to simplify conversation building:

Best Practices

Check calls to Standard Schema tools with validateToolCall(), and validate call.arguments yourself for plain JSON Schema tools:
Help the model understand when to use tools:
Return errors as tool results instead of throwing:
Prevent tools from hanging:

Next Steps

Chat Completion

Learn basic message generation

Structured Outputs

Combine tools with structured responses

Streaming

Stream tool calls in real-time

Tool Discovery

Let the model discover tools on demand