Combining Code Execution with Structured Outputs

Ma Mahalakshmi V Updated 16 Sep 2026
7 min read ·Lesson 93 of 224
In this post

The Tension Between Two Features Unit 6 covered structured outputs: constraining a model's response to conform exactly to a JSON schema you define, so your application can parse the result reliably in

The Tension Between Two Features

Unit 6 covered structured outputs: constraining a model's response to conform exactly to a JSON schema you define, so your application can parse the result reliably instead of scraping numbers and labels out of free-form prose. Code interpreter, by contrast, produces exactly the kind of output structured outputs is meant to replace — a code_interpreter_call item full of executed Python, plus a natural-language message summarizing what happened. These two features solve different problems and do not simply combine into "structured code interpreter output" by adding both to one call. Understanding why, and what pattern actually works, is the point of this lesson.

The core issue is that a JSON schema for structured outputs describes the shape of the model's final text message. It has no way to constrain what a tool call does internally — the code the model writes for code_interpreter is not JSON and is not subject to your schema at all. What a schema can do is constrain the final summary the model writes after it has already seen the tool's result. This distinction shapes every pattern in this lesson.

Pattern 1: Structured Final Answer After Tool Use

The most direct approach lets the model use code interpreter freely to do the actual computation, and then constrains only its concluding message to a defined schema:

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()


class RevenueSummary(BaseModel):
    total_revenue: float
    top_region: str
    top_region_revenue: float
    month_over_month_growth_pct: float


response = client.responses.parse(
    model="gpt-5.6-terra",
    tools=[{
        "type": "code_interpreter",
        "container": {"type": "auto", "file_ids": [uploaded.id]},
    }],
    input=(
        "Analyze monthly_revenue.csv. Compute total revenue, the region "
        "with the highest revenue and its revenue figure, and the average "
        "month-over-month growth rate as a percentage."
    ),
    text_format=RevenueSummary,
)

summary = response.output_parsed
print(summary.total_revenue, summary.top_region, summary.month_over_month_growth_pct)

This works because responses.parse (the structured-outputs entry point introduced in Unit 6) still allows tool use during the turn — the model can call code_interpreter as many times as it needs to actually perform the computation, and only its final answer is required to conform to the RevenueSummary schema. The schema does not see or constrain the Python code the model wrote; it only shapes the concluding message, which the model composes after reading the tool's real output.

response.output_parsed gives you a validated RevenueSummary instance — the same guarantee structured outputs provides everywhere else in this course, now sitting on top of a value that was actually computed by executed code rather than estimated by the model. This combination is genuinely powerful: you get both numerical accuracy (from code execution) and a type-safe, predictable shape your application code can consume directly (from structured outputs), without gluing them together yourself.

Note: Support for combining tools (including code_interpreter) with text_format / responses.parse in a single call, and any constraints on schema complexity when tool use is involved, are the kind of capability that can change between API versions. Confirm current support and any limitations against the official documentation before depending on this pattern in production.

Pattern 2: Two Explicit Steps

For workflows where you want more control — for example, logging the raw analysis separately from the structured extraction, or when the schema needs information that spans a longer exploratory conversation — splitting the work into two calls is more transparent and easier to debug:

analysis = client.responses.create(
    model="gpt-5.6-terra",
    tools=[{
        "type": "code_interpreter",
        "container": {"type": "auto", "file_ids": [uploaded.id]},
    }],
    input=(
        "Analyze monthly_revenue.csv thoroughly. Compute total revenue, "
        "revenue by region, and month-over-month growth. Explain your "
        "findings in plain language."
    ),
)

extraction = client.responses.parse(
    model="gpt-5.6-terra",
    input=(
        "Extract the following fields from this analysis, using only the "
        f"values explicitly stated in it:\n\n{analysis.output_text}"
    ),
    text_format=RevenueSummary,
)

summary = extraction.output_parsed

The first call does the real work with code interpreter enabled and no schema constraint, producing a free-form but computationally grounded explanation. The second call has no tools at all — it is a pure text-to-structured-data extraction over the already computed analysis text, which is a task structured outputs handles very reliably since it is simply reformatting information that is already present in the input rather than performing new reasoning or computation.

This two-step pattern costs an extra API call, but it buys you two things Pattern 1 does not give you as cleanly: you can log and inspect the full, un-truncated analysis independently of the extraction, and you can reuse the same extraction schema across analyses produced by very different prompts, since the extraction step no longer needs to know anything about code interpreter or datasets at all.

Choosing Between the Two Patterns

ConsiderationPattern 1 (single call)Pattern 2 (two calls)
Number of API calls12
Cost and latencyLowerHigher
Visibility into full analysisOnly via output_text/output on the same responseExplicit, separately stored
Schema reuse across different analysis promptsTied to the same callFully decoupled
Debugging a wrong extracted valueRequires re-reading one combined responseCan isolate whether the analysis or the extraction was wrong

Pattern 1 is the right default for most application code — it is simpler and cheaper. Reach for Pattern 2 specifically when you need an audit trail of the full analysis independent of the structured summary, or when the same extraction schema needs to sit on top of analyses generated through several different upstream prompts or even different tools.

A Validation Layer on Top of Either Pattern

Because a schema only guarantees shape, not correctness — a RevenueSummary with a total_revenue field of the wrong value is still perfectly valid JSON — it is worth adding a lightweight sanity check on the structured result before trusting it downstream. This is a preview of the deeper validation techniques covered in Lesson 9:

def sanity_check_summary(summary: RevenueSummary) -> list[str]:
    problems = []
    if summary.total_revenue <= 0:
        problems.append("total_revenue is not positive")
    if summary.top_region_revenue > summary.total_revenue:
        problems.append("top_region_revenue exceeds total_revenue, which is impossible")
    if not (-100 <= summary.month_over_month_growth_pct <= 1000):
        problems.append("month_over_month_growth_pct is outside a plausible range")
    return problems


def test_sanity_check_summary_flags_impossible_values():
    bad_summary = RevenueSummary(
        total_revenue=1000.0,
        top_region="West",
        top_region_revenue=1500.0,
        month_over_month_growth_pct=5.0,
    )
    problems = sanity_check_summary(bad_summary)
    assert "top_region_revenue exceeds total_revenue, which is impossible" in problems
    print("PASS: sanity_check_summary flags an impossible top_region_revenue value")


def test_sanity_check_summary_accepts_valid_values():
    good_summary = RevenueSummary(
        total_revenue=1000.0,
        top_region="West",
        top_region_revenue=400.0,
        month_over_month_growth_pct=3.2,
    )
    assert sanity_check_summary(good_summary) == []
    print("PASS: sanity_check_summary returns no problems for consistent values")


test_sanity_check_summary_flags_impossible_values()
test_sanity_check_summary_accepts_valid_values()

sanity_check_summary encodes domain knowledge (a region's revenue cannot exceed the total) that the schema itself has no way to express — Pydantic and JSON Schema can validate types and ranges on individual fields, but not relationships between fields that depend on the meaning of the data. Writing these checks explicitly, and testing them with deliberately constructed valid and invalid examples as shown, is cheap and catches a category of error that structured outputs alone cannot.

Common Mistakes

Believing a valid schema means a correct answer. Structured outputs guarantee the response parses into your defined shape; they say nothing about whether the values inside that shape are numerically correct. Pair structured outputs with the kind of validation shown above and expanded in Lesson 9.

Trying to put the tool's raw code output directly into a structured schema field. A schema field like computation_code: str technically works, but defeats the purpose of structured outputs, which is to give you clean, typed data — not another blob of text to parse further. Extract the result of the computation into typed fields, not the code that produced it.

Using Pattern 2 by default even for simple analyses, paying for two API calls when one would have worked. Start with Pattern 1 and move to Pattern 2 only when you have a concrete reason (auditability, schema reuse) that justifies the extra cost.

Best Practices

Default to a single responses.parse call with both tools and text_format for straightforward analysis-to-structured-result workflows, reserving the two-step pattern for cases with a specific need for separation.

Add cross-field sanity checks on structured results derived from computation, since schema validation alone cannot catch logically impossible combinations of otherwise well-typed values.

Keep structured schemas focused on the final answer, not the process. Fields should represent conclusions ("total_revenue", "top_region") rather than intermediate artifacts of how code interpreter arrived at them, keeping the schema stable even if the underlying analysis approach changes.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Combining Code Execution with Structured Outputs and get answers drawn from it.

Signed-in readers only.