Explicit Output Requirements

Ma Mahalakshmi V Updated 19 Sep 2026
9 min read ·Lesson 150 of 224

Reducing Ambiguity With Explicit Output Requirements

Lessons 5 and 6 each introduced a version of the same underlying principle applied to a specific task type: state the exact valid output shape rather than describing it loosely, and let the model infer the rest. This lesson generalizes that principle into a standalone skill, because it is the single highest-leverage technique for making any prompt's output reliable enough for application code to depend on, regardless of task type. An ambiguous prompt does not fail loudly — it produces output that is usually fine, which is often worse than output that fails consistently, because the occasional divergence is what breaks a parser, a downstream field, or a user-facing display in production.

Why Ambiguity Is the Root Cause of Most Prompt Reliability Problems

When an instruction leaves a decision unspecified, the model still has to make that decision on every single call — it cannot leave a field blank in the way a person filling out a form might. Some of those implicit decisions come out consistent across calls; others do not, especially for decisions near a genuine boundary case. The practical consequence is that ambiguity in a prompt does not show up as an obvious bug during initial testing (where a handful of typical inputs all look fine) — it shows up later, as a low but nonzero rate of malformed output once the prompt runs against the full diversity of real production input.

Consider a deceptively simple prompt:

# Ambiguous: several unstated decisions
instructions = "Extract the person's name and age from the text."

This leaves unstated: What format should the output be in — a sentence, a JSON object, a comma-separated pair? What happens if age is not mentioned? What happens if multiple people are named? What happens if age is given as a range ("in her thirties") rather than a specific number? Each of these is a real decision the model must make on every call, and without explicit guidance, it will make each one via a plausible-sounding but uncontrolled default that can silently change between calls, between inputs, or between model versions.

The General Technique: Enumerate Every Output Decision

The fix is systematic: before finalizing a prompt, list every decision the output format leaves open, and answer each one explicitly in the instructions.

# Explicit: same task, every output decision answered
instructions = """Extract the person's name and age from the text.

Respond with a JSON object with exactly two keys: "name" and "age".
- "name": the person's full name as a string, or null if not mentioned.
- "age": the person's age as an integer, or null if not stated as a
  specific number (do not estimate from vague descriptions like "in her
  thirties" -- use null in that case).

If multiple people are mentioned, extract only the first person named."""

Every ambiguity identified above now has an explicit answer: format is JSON with named keys; missing values become null; vague ages become null rather than an estimate; multiple people resolve to "the first one." None of these answers are objectively "correct" in the abstract — a different application might legitimately want an estimated age from a vague description, or might want all people extracted as a list. The point is not that this specific set of answers is universal; it is that some explicit answer to each question must be given, because leaving it unanswered does not mean the model skips the decision — it means the model decides inconsistently instead of your application deciding consistently.

A Practical Checklist for Common Ambiguities

Across tasks, certain categories of ambiguity recur often enough to check systematically:

Ambiguity categoryQuestion to answer explicitlyExample resolution
Output formatWhat exact structure — prose, JSON, list, single word?"Respond with only a JSON object with keys X, Y."
Missing dataWhat if the requested information isn't present?"Use null for any field not found in the text."
Multiple matchesWhat if there are several valid candidates?"Extract only the first occurrence."
Boundary/edge casesWhat about inputs at the edge of the task's scope?"If the text is not in English, respond with an empty object."
PrecisionHow exact must a value be (rounding, units, format)?"Round to two decimal places. Use ISO 8601 for dates."
LengthHow long or short should output be?"Respond in exactly one sentence, under 25 words."
ExplanationShould reasoning accompany the answer, or only the answer?"Respond with only the category name, no explanation."

Not every prompt needs every row addressed — a summarization prompt has little use for "multiple matches," and a classification prompt has little use for "precision." The checklist's value is in the systematic pass: reading down the list while reviewing a prompt catches ambiguities that are easy to overlook when writing the prompt for the first time with only the typical case in mind.

Worked Example: Applying the Checklist

Take a realistic prompt before and after a checklist pass:

# Before: ambiguous on nearly every axis
BEFORE = "Find the deadline mentioned in this email and tell me if it's urgent."
# After: checklist applied
AFTER = """Find the deadline mentioned in the email below.

Respond with a JSON object with these keys:
- "deadline": the deadline as a string in YYYY-MM-DD format, or null if
  no specific date is mentioned.
- "is_urgent": true if the deadline is within 3 days of the email's own
  date (stated or implied), false otherwise, or null if deadline is null.

If more than one deadline is mentioned, use the earliest one.
Do not include any explanation, only the JSON object."""

Walking through what changed: output format went from free text to a named JSON structure (format ambiguity resolved); a missing deadline now maps to null rather than an undefined response (missing data resolved); "urgent" was previously a subjective judgment call left entirely to the model's own notion of urgency — now it has a concrete, checkable definition ("within 3 days") that application code could even second-guess or recompute independently if needed (precision resolved); multiple deadlines now resolve to "the earliest" (multiple matches resolved); and an explicit "no explanation" line prevents the model from prefacing the JSON with a sentence of commentary that would break naive json.loads parsing (explanation ambiguity resolved).

When Looser Instructions Are the Right Choice

Explicit constraints are not free — they add prompt length, and an overly rigid specification can sometimes prevent the model from handling a genuinely novel input sensibly (a rule that assumed one deadline format may fumble when the real text uses an unanticipated one). Favor looser instructions when:

  • The task is genuinely open-ended and any reasonable output is acceptable (creative writing, brainstorming, general Q&A) — over-constraining these can make output feel mechanical or miss useful variety.
  • The application has a robust human review step before output is used for anything consequential, reducing the cost of occasional format drift.
  • Early prototyping, where the goal is to understand what the model naturally produces before deciding which behaviors need to be pinned down.

Favor explicit constraints whenever output feeds directly into code — parsing, routing, storage, another API call — because unparseable or inconsistent output there causes an application-level failure, not just a slightly-off response a human reader can mentally correct for.

Verifying That an Instruction Actually Reduced Ambiguity

Adding an explicit instruction is not automatically effective — the model must actually follow it. The only way to know is to test the prompt against a range of inputs, especially edge cases, and check the output mechanically:

import json
from openai import OpenAI

client = OpenAI()

def extract_deadline(email_text: str) -> dict:
    response = client.responses.create(
        model="gpt-5.6-terra",
        instructions=AFTER,
        input=email_text,
    )
    return json.loads(response.output_text)

def check_output_shape(result: dict) -> list[str]:
    problems = []
    if set(result.keys()) != {"deadline", "is_urgent"}:
        problems.append(f"unexpected keys: {result.keys()}")
    if result["deadline"] is not None and not isinstance(result["deadline"], str):
        problems.append("deadline is not a string or null")
    if result["is_urgent"] not in (True, False, None):
        problems.append("is_urgent is not a boolean or null")
    return problems

check_output_shape is a mechanical validator, not a judgment of correctness — it checks that the shape of the output matches what the instructions promised, independent of whether the extracted deadline is the right one. Running this against a batch of representative inputs (Lesson 9 covers building and using such a dataset systematically) turns "I made the instructions more explicit" from a hopeful guess into a measured, checkable claim: if check_output_shape returns problems on 2% of a representative sample, that is a concrete, trackable defect rate rather than an unknown one.

Testing the Validator Itself

def test_check_output_shape_flags_wrong_keys():
    problems = check_output_shape({"deadline": "2027-01-01", "extra": True})
    assert any("unexpected keys" in p for p in problems)
    print("PASS: extra key detected as a shape problem")

def test_check_output_shape_accepts_valid_output():
    problems = check_output_shape({"deadline": None, "is_urgent": None})
    assert problems == []
    print("PASS: valid null-filled output produces no problems")

def test_check_output_shape_flags_bad_type():
    problems = check_output_shape({"deadline": "2027-01-01", "is_urgent": "yes"})
    assert any("is_urgent" in p for p in problems)
    print("PASS: non-boolean is_urgent value flagged")

test_check_output_shape_flags_wrong_keys()
test_check_output_shape_accepts_valid_output()
test_check_output_shape_flags_bad_type()

These tests validate the validator using hand-constructed dictionaries rather than live model output — cheap, fast, and precisely targeted at the shape-checking logic itself, which is exactly the kind of bug (an overly strict or overly lenient check) that would otherwise only surface once real, messier model output started flowing through it.

Common Mistakes

Assuming a prompt is unambiguous because it worked on the first few test inputs. Ambiguity in an instruction usually surfaces as inconsistent behavior only on edge cases and less common inputs, which a handful of manual tests during development are unlikely to include.

Over-specifying a genuinely open-ended task. Applying the "answer every ambiguity" checklist to a creative or exploratory prompt can produce mechanical, overly rigid output where variety was actually desirable — this technique is for cases where output feeds directly into code, not for every prompt.

Adding a constraint without verifying the model follows it. Writing a more explicit instruction is only half the work; the other half is checking, against real or representative inputs, that the output actually conforms — an instruction the model ignores provides no more reliability than no instruction at all.

Best Practices

Run the ambiguity checklist deliberately before finalizing any prompt whose output feeds application code. Treat format, missing data, multiple matches, edge cases, precision, length, and explanation as a standing list to check against, not something to notice only after a production incident.

Pair every explicit output requirement with a mechanical validator in code. A check_output_shape-style function turns "the model should now do X" into something measurable, and can be reused for both spot-checks during development and systematic evaluation (Lesson 9).

Match the level of constraint to how the output will be used. Tight, explicit constraints for anything parsed or routed by code; looser, more open instructions for genuinely open-ended tasks reviewed by a human.

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 Explicit Output Requirements and get answers drawn from it.

Signed-in readers only.