Skip to content
R.

When should you use Structured Outputs instead of function calling in the OpenAI API?

Use `response_format` when the model should answer in a fixed schema. Use function calling when the model should ask your app to do work. This article shows the contract boundary, a local request-shape test, and the failure cases where the wrong choice adds confusion.

About 10 min readComments

The first prototype usually blurs two different jobs together: the model should either answer in a predictable schema or ask your app to take an action. Both can involve JSON Schema, which makes them easy to confuse.

This article separates the two contracts. Structured Outputs shape the model's response. Function calling shapes the handoff to your application.

That distinction is practical, not academic. A product team can spend the same amount of engineering effort on either path and still end up with very different maintenance costs, because one path makes the response a final payload and the other path turns the response into a proposed action.

The contract is not the same just because both paths can use schema

The OpenAI docs draw a useful line between two related mechanisms. Structured Outputs are for cases where the model should answer the user in a strict schema. Function calling is for cases where the model should propose a tool input so your code can fetch data or perform an action.

That distinction matters more than the syntax. If you treat every schema as a tool call, your app starts carrying orchestration it does not need. If you treat every tool call as a structured answer, the model can look neatly typed while your app boundary disappears.

A lot of the confusion comes from the word 'structured.' In ordinary conversation, structured can mean 'valid JSON,' 'typed fields,' or 'something the UI can render without extra parsing.' The OpenAI docs are narrower than that. They are asking you to decide whether the model is producing the final payload or whether it is proposing an action that the app still owns. That is the question this article keeps in view.

Structured Outputs versus function calling
MechanismWhat the model is doingWhat your app ownsTypical fit
Structured OutputsReturning a schema-shaped answer to the userRendering or validating the response payloadSummaries, extraction, labels, and any fixed response shape
Function callingAsking the app to execute a tool or retrieve dataRunning the tool, checking permissions, and continuing the workflowSearch, lookup, calculator, side effects, and multi-step app actions

SourcesOpenAI Structured Outputs guide (opens a new tab)OpenAI Function Calling guide (opens a new tab)

Use `response_format` when the model should answer in a fixed schema

The simplest Structured Outputs use case is a response your UI can consume directly. That could be a short extraction, a typed summary, or a record the next screen can render without another conversion step.

The benefit is not just cleaner JSON. It is that the model's job ends at a schema the user can see or your app can validate. There is no hidden tool call, no extra permission surface, and no need to pretend the model is acting on external state.

A useful mental test is to ask whether a human reviewer would expect the model to 'answer' or 'act.' If the result is something like a support summary, a classification label, or a compact set of fields for the page, the model is answering. If the result must change a remote system, consult a database, or check permissions, the model is not done yet. That is when a schema-shaped final response becomes the wrong abstraction.

This also explains why `response_format` is helpful in simple extraction pipelines. The app can validate the payload and stop there. You do not need an intermediate tool registry just to say 'extract the incident title and the next step.' The schema is serving the user-facing contract, not an internal action protocol.

  • Use this shape when the model is producing the final UI payload, not a proposed action.
  • Keep the schema small enough that the contract is still readable in code review.
  • Validate the payload in your app even when the model is asked to follow the schema strictly.
A schema-shaped answer / javascript
const request = {
  model: 'gpt-4.1-mini',
  input: 'Summarize this incident in three fields: title, impact, and next_step.',
  response_format: {
    type: 'json_schema',
    json_schema: {
      name: 'incident_summary',
      strict: true,
      schema: {
        type: 'object',
        properties: {
          title: { type: 'string' },
          impact: { type: 'string' },
          next_step: { type: 'string' },
        },
        required: ['title', 'impact', 'next_step'],
        additionalProperties: false,
      },
    },
  },
};

SourcesOpenAI Structured Outputs guide (opens a new tab)

Use function calling when the next step belongs to your app

Function calling is the right contract when the model should not finish the job by itself. The model can identify what it needs, but your application still decides whether the tool exists, whether the user is allowed to use it, and what happens after the tool returns.

This is the boundary that keeps model output and application authority separate. A weather lookup, an order status check, or a database-backed search are all better expressed as tool proposals than as final schema-shaped answers.

The distinction becomes easier to defend when the tool has side effects or access control. If the model can trigger a tool, then the app needs a place to enforce authorization, rate limits, idempotency, and audit logging. Those concerns do not disappear just because the tool arguments are valid JSON. In fact, valid JSON is the easy part. Deciding whether the call should happen is the hard part, and that decision belongs in your code.

Function calling also helps when the same user request can branch into different tools. The assistant may need to look up an order, file a ticket, or ask a clarifying question. That workflow is naturally sequential. A single final schema would be too blunt, because the important step is not the shape of the payload but the next action the app must take after reading it.

  • The model proposes the call.
  • Your app executes or rejects it.
  • The tool result becomes input to the next turn.

SourcesOpenAI Function Calling guide (opens a new tab)

A local fixture can prove the boundary without a live API call

This article does not need a live model request to prove the decision. The local fixture only needs to show which request shape the app would emit for a schema-only response and which shape it would emit for a tool-first workflow.

That keeps the evidence focused on the architectural choice instead of turning the article into a benchmark. The test proves contract selection, not answer quality.

The test is also useful because it makes the negative case concrete. When the helper returns `response_format`, there should be no tool list. When it returns `tools`, there should be no schema-shaped final payload pretending to be the end of the story. That simple assertion catches a class of integration mistakes where the code starts mixing the two modes in the same request object.

This is the right level of proof for a daily article. A live call could tell you whether one model version happened to satisfy a particular schema on a given day, but it would not prove the contract selection rule. The reader needs the rule more than the transient output.

Request-shape helper / javascript
export function buildOpenAIRequest(mode, input) {
  if (mode === 'structured-output') {
    return {
      input,
      response_format: {
        type: 'json_schema',
        json_schema: {
          name: 'result',
          strict: true,
          schema: {
            type: 'object',
            properties: { result: { type: 'string' } },
            required: ['result'],
            additionalProperties: false,
          },
        },
      },
    };
  }

  return {
    input,
    tools: [{ type: 'function', name: 'lookup_order_status' }],
  };
}

SourcesOpenAI Structured Outputs guide (opens a new tab)OpenAI Function Calling guide (opens a new tab)

The wrong choice fails in different ways

The temptation is to use Structured Outputs for every clean-looking payload. That works until the next step needs the model to reach into your app or a live system. At that point, a schema-shaped answer hides the real dependency instead of exposing it.

The opposite mistake is to use function calling just because the response is JSON-shaped. That makes the code harder to follow when no tool is actually needed, and it can leave you with an unnecessary tool registry and permission path.

There is also a schema limit. A strict schema is only useful when the shape is stable and manageable. If the shape changes every turn or depends on too many external branches, the contract becomes harder to maintain than the problem it was supposed to simplify.

One subtle failure case is debugging. A final schema can make a response look deterministic even when the app actually needed a tool lookup. If the next engineer reads the request body later, they may not see the missing action boundary and will chase the wrong layer. The reverse is also true: a function call can make a payload look more 'agentic' than it really is, while the app is simply handing a typed lookup back to the model. In both cases, the contract choice should reflect the real owner of the next step, not the novelty of the syntax.

Another limit is versioning. A fixed schema is easiest to keep when the fields are stable. If the product team keeps renaming fields or changing required properties to match UI experiments, the strict schema stops being a convenience and starts being a maintenance tax. At that point, the article's decision rule still holds, but the implementation may need a narrower schema, a separate versioned contract, or a tool step that lets the app reshape the result before it reaches the UI.

Failure case and boundary checklist
ScenarioWrong choiceWhy it hurtsBetter fit
UI needs a typed payloadFunction callingAdds a tool layer that does not do workStructured Outputs
App must fetch or mutate stateStructured OutputsHides the action behind a final-looking answerFunction calling
Schema changes every requestStrict schemaThe contract becomes brittle and hard to reviewLooser app logic or a different workflow split

SourcesOpenAI Structured Outputs guide (opens a new tab)OpenAI Function Calling guide (opens a new tab)

The decision rule is simple

Ask who owns the next step. If the model owns the final user-facing shape, use Structured Outputs. If your application owns the next action, use function calling.

That rule keeps the contract boundary visible in code review. It also protects you from an easy mistake: treating JSON Schema as if it always meant the same thing. In the OpenAI API, it does not.

If you want a stricter shortcut, ask whether the output would still make sense if the model were replaced by a deterministic formatter. If yes, Structured Outputs is probably enough. If no, because the next step depends on live data, permissions, or a side effect, function calling is the better fit.

  • Model speaks to the user: Structured Outputs.
  • Model asks the app to do work: function calling.
  • If both are needed, split the workflow instead of forcing one contract to do both jobs.

SourcesOpenAI Structured Outputs guide (opens a new tab)OpenAI Function Calling guide (opens a new tab)

Share LinkedIn Email Subscribe

Discussion

Leave a comment

Comments appear after review. No email needed.

Image preview

Follow the blog

New articles in your feed. No email needed.

Use OpenRSS

Preview the feed, then choose a reader to subscribe.

Open in OpenRSS (opens a new tab)

Already have a reader?

Paste this link into your reader's Add feed option.

Get full articles in your reader, not your inbox. View XML feed