ASK KNOX
beta
LESSON 532

Tool Calling and Structured Output

Connect models to real data with tool calling, and lock response shapes with structured output — the two primitives that turn a chatbot into a production API.

10 min read·Getting Started with OpenAI Codex

The Two Missing Pieces

A model that only accepts text and returns text is a very capable autocomplete. What makes models useful in production systems is the ability to act on real data and return data in a shape your code can rely on. Tool calling and Structured Output are the two primitives that bridge that gap.

Tool calling lets the model request execution of real functions — database queries, API calls, filesystem reads, search operations — and incorporate the results into its reasoning. The model decides when to call a tool, what arguments to pass, and how to use the result.

Structured Output lets you define an exact schema for the model's response — specific fields, specific types, no extra keys — and get a guarantee that the response will conform to that schema. No more parsing errors, no more missing fields, no more hallucinated keys.

These are also how Codex itself works under the hood. Every file read, file write, and shell command Codex executes is a tool call. The environment executes it and returns the result. Understanding this mechanism at the API level demystifies the agent.

Tool Calling: The Sequence

The loop has four phases:

Phase 1 — Define. You define the tools available to the model using JSON Schema. Each tool has a name, a description (which the model reads to decide when to call it), and a parameters schema (which constrains what arguments the model can provide).

Phase 2 — The model requests. When the model determines it needs real-world data, it emits a tool_call output item. This is not execution — it is a request. The model tells you which function to call and what arguments to use, encoded as JSON.

Phase 3 — You execute. Your code receives the tool call, executes the real function (call the database, call the API, read the file), and captures the result as a string.

Phase 4 — The model continues. You send the tool result back to the model in the next API call. The model now has real grounded data and generates the final user-facing response.

Defining a Tool

These examples continue with the TypeScript openai SDK from the previous lesson — same setup (npm install openai, run with npx tsx file.ts, OPENAI_API_KEY exported). A tool definition looks like this:

const tools: OpenAI.Responses.Tool[] = [
  {
    type: "function",
    name: "get_market_price",
    description: "Retrieve the current market price for a given asset symbol. Use when the user asks about prices or current market data.",
    parameters: {
      type: "object",
      properties: {
        symbol: {
          type: "string",
          description: "The asset symbol, e.g. 'BTC', 'ETH', 'AAPL'",
        },
        currency: {
          type: "string",
          enum: ["USD", "EUR", "GBP"],
          description: "The currency for the price quote. Defaults to USD.",
        },
      },
      required: ["symbol"],
      additionalProperties: false,
    },
    strict: false,
  },
]

One field deserves a note before anything else: strict. The SDK's Tool type requires it, and the API defaults it to true. Under strict: true, every property in your parameters schema must also appear in required — optional parameters are expressed as required-but-nullable (type: ["string", "null"]) rather than left out of required. This example keeps currency genuinely optional, so it sets strict: false explicitly. When you can list every property in required, prefer strict: true: the API then guarantees the model's arguments conform to your schema exactly — the same guarantee Structured Output gives you later in this lesson.

The description field is load-bearing — it is the signal the model uses to decide when to call this tool. A vague description produces vague routing. A description that specifies when to use the tool ("use when the user asks about prices or current market data") produces reliable routing.

Handling the Tool Call in Code

After the first API call, check if the model emitted a tool call:

const response = await client.responses.create({ model: "<model-name>", input: "What is the current price of ETH?", tools })

for (const item of response.output) {
  if (item.type === "function_call") {
    const args = JSON.parse(item.arguments)
    const price = await getMarketPrice(args.symbol, args.currency ?? "USD")

    // Send the result back to the model
    const finalResponse = await client.responses.create({
      model: "<model-name>",
      input: [
        { role: "user", content: "What is the current price of ETH?" },
        ...response.output,
        {
          type: "function_call_output",
          call_id: item.call_id,
          output: JSON.stringify({ symbol: args.symbol, price, currency: args.currency ?? "USD" }),
        },
      ],
      tools,
    })

    console.log(finalResponse.output_text)
  }
}

The key detail: the second API call includes both the original output items (so the model has context on what it asked for) and the tool result. The model combines these to generate a grounded final answer.

Structured Output

Structured Output constrains what the model can return. Instead of hoping the model returns valid JSON with the right fields, you provide a JSON Schema and set strict: true. The API guarantees the response will conform.

Here is how to enable it in the Responses API:

const response = await client.responses.create({
  model: "<model-name>",
  input: "Extract the event details: 'The AI Summit will be held in Raleigh on June 15, 2026.'",
  text: {
    format: {
      type: "json_schema",
      name: "EventExtraction",
      strict: true,
      schema: {
        type: "object",
        properties: {
          city: { type: "string", description: "The city where the event takes place" },
          date: { type: "string", description: "The event date in ISO 8601 format (YYYY-MM-DD)" },
          event_name: { type: "string", description: "The name of the event" },
        },
        required: ["city", "date", "event_name"],
        additionalProperties: false,
      },
    },
  },
})

const event = JSON.parse(response.output_text)
// event.city === "Raleigh"
// event.date === "2026-06-15"
// event.event_name === "AI Summit"

The response is guaranteed to be valid JSON that parses without error and has exactly the fields you specified. additionalProperties: false blocks the model from adding extra fields. The required array ensures all specified fields are present.

Combining Tool Calling and Structured Output

These primitives compose. You can define tools that the model uses to gather data, and then return the final answer as a structured JSON object:

// Tools for data gathering
const tools = [{ type: "function", name: "lookup_player_stats", ... }]

// Structured output for the final answer
const text = {
  format: {
    type: "json_schema",
    name: "PlayerAnalysis",
    strict: true,
    schema: {
      type: "object",
      properties: {
        player_name: { type: "string" },
        season_average: { type: "number" },
        recommendation: { type: "string", enum: ["start", "bench", "drop"] },
      },
      required: ["player_name", "season_average", "recommendation"],
      additionalProperties: false,
    },
  },
}

This pattern — tools to gather, schema to constrain the output — is how you build reliable AI pipelines. The model handles the reasoning and data integration. Your schema guarantees the output shape. This is also how production knowledge systems work: MCP tools (like a search_knowledge tool and a save_memory tool) are function definitions the model can call, and the results feed structured reasoning. The tool calling protocol you are learning here is the same protocol that powers every MCP server.

Why This Matters Beyond Chatbots

Tool calling and Structured Output are the bridge between language models and the rest of your software stack. A model that can call your functions and return typed data is not a chatbot — it is an inference engine you can wire into any application.

Classification pipelines. Data extraction from unstructured text. Decision support systems that pull live data before reasoning. Intent detection in customer support queues. Every one of these is a variation of the same pattern: define tools, define output schema, call the API, parse the result.

This is what rewiredminds.io means when it talks about AI operating at a systems level — not just generating text, but acting as a typed, callable component in a larger architecture. Tool calling and Structured Output are what make that possible.