·14 min read

ChatGPT: Building Production Apps with GPT-4o

How we built a document processing pipeline handling 10K+ pages daily using ChatGPT's API — with structured outputs, function calling, and streaming patterns.

The Problem

Imagine you have a stack of 2,400 documents — invoices, contracts, PDFs — that need to be read and understood every single day. That’s about 100 pages every hour. Now imagine each page takes 4 to 12 seconds to process, and nearly 1 in 5 needs a human to redo the work because the computer got it wrong.

That was our reality.

We were using an older AI model (GPT-4 Turbo) with a simple approach: extract text from the PDF, stuff it into a prompt, and hope the JSON came back correctly. It worked fine in demos. In production, every edge case found a crack:

  • A multi-page invoice with line-item tables would produce truncated output
  • A contract with nested clauses would omit entire sections
  • A scanned PDF with OCR errors would inject garbage characters that broke the JSON parser

The cost was also climbing. Processing a 50-page document cost about $0.30. At 2,400 pages per day, that’s $720/month — before counting the rework.

Why this matters: If you’re building any app that processes documents — invoices, contracts, medical records — you’ll hit these same problems. The good news is there’s a much better way.

The Investigation

We tracked every step of the pipeline for 72 hours across 8,500 documents. Here’s what we found:

Metric Old System (GPT-4 Turbo) What We Needed
Average time per page 4.2 seconds Under 2 seconds
Slowest 5% of pages 12.1 seconds Under 5 seconds
JSON parsing failures 11.3% Under 1%
Missing required fields 6.7% Under 0.5%
Documents needing human rework 18.2% Under 5%
Cost per 1,000 pages $300 Under $100
Pages processed per day 2,400 10,000+

What was going wrong?

  1. No rules for the output. The AI could return anything. We were trying to parse it with regex (pattern matching), which is like trying to catch fish with a net that has holes of random sizes.

  2. No structured output mode. We told the AI to return JSON, but without a strict template, it still made mistakes on complex data.

  3. No streaming. Users had to wait for the entire response before seeing anything. Blank screen = frustrated users.

  4. No retry strategy. If the API had a hiccup or the AI refused to answer, the document silently failed.

The Solution

We rebuilt the pipeline using three features that came with GPT-4o (OpenAI’s latest model at the time): Structured Outputs (enforce exact JSON schemas), function calling with strict mode (force the AI to follow rules), and streaming (show results as they come).

How the New Pipeline Works

Upload PDF
  → Extract text (pdfplumber + OCR fallback)
  → Score which pages matter (GPT-4o-mini, cheap model)
  → Extract data only from relevant pages (GPT-4o, strict schema)
  → Validate the output (Pydantic + business rules)
  → Score confidence
  → Save or send to human review

The key insight: don’t send everything to the expensive model. Use a cheap model first to find the important pages, then only send those to GPT-4o.

The Core Extraction Code

Here’s the heart of the new pipeline — a function that extracts structured data from a document and guarantees the output matches your schema:

from openai import OpenAI
from pydantic import BaseModel
from typing import List, Optional
import json

client = OpenAI()

# Define exactly what we want the AI to return
# Think of this as a fill-in-the-blank form
class DocumentExtraction(BaseModel):
    doc_type: str  # "invoice", "contract", "report", etc.
    doc_date: Optional[str]
    parties: List[str]  # People or companies mentioned
    key_fields: dict  # Important values found
    summary: str
    confidence: float  # 0.0 to 1.0

def extract_document(text: str) -> DocumentExtraction:
    """Extract structured data from document text.
    
    This function sends text to GPT-4o and gets back
    a guaranteed-valid structured response.
    """
    response = client.beta.chat.completions.parse(
        model="gpt-4o-2024-08-06",
        messages=[
            {
                "role": "system",
                "content": "Extract key information from this document. "
                           "Return only the requested fields."
            },
            {
                "role": "user",
                "content": text
            }
        ],
        # This is the magic: the AI MUST follow this schema
        response_format=DocumentExtraction,
    )
    
    return response.choices[0].message.parsed

Key lesson: The response_format=DocumentExtraction parameter is what guarantees the AI returns valid data. Without it, the AI can return anything. With it, the output always matches your schema. This single change eliminated 100% of our JSON parsing errors.

How to Use Effectively

Getting Started (5 minutes)

  1. Get an API key: Sign up at platform.openai.com, go to API keys, create a new key
  2. Install the SDK: pip install openai
  3. Set your key: export OPENAI_API_KEY=sk-...
  4. Try this:
from openai import OpenAI

client = OpenAI()

# The simplest possible call
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "Explain what an API is in one sentence."}
    ]
)

print(response.choices[0].message.content)

Best Practices

1. Use structured outputs for anything important If you’re extracting data, always use response_format with a Pydantic model. It’s the difference between getting back clean data vs. garbage you have to parse.

2. Start with GPT-4o-mini, upgrade only when needed GPT-4o-mini costs 15x less than GPT-4o and handles 80% of tasks. Use GPT-4o only for complex extraction or when accuracy is critical.

3. Stream long responses For anything over a few sentences, enable streaming. Users see results in ~300ms instead of waiting 5+ seconds.

# Streaming shows results as they arrive
stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Write a 500-word summary..."}],
    stream=True,
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

4. Handle refusals gracefully Sometimes the AI will refuse to answer (e.g., if it thinks the content is unsafe). Always check for refusals:

message = response.choices[0].message
if message.refusal:
    print(f"AI refused: {message.refusal}")
    # Handle gracefully — log, retry with different prompt, or escalate

Use Cases

1. Invoice Processing

When you’d use this: You receive hundreds of invoices in different formats. You need to extract vendor name, amount, date, and line items automatically.

Why ChatGPT fits: GPT-4o handles PDFs, scanned images, and digital files. Structured outputs guarantee every invoice produces the same fields.

2. Contract Analysis

When you’d use this: Your legal team needs to review NDAs and service agreements. You want to automatically extract parties, dates, and key clauses.

Why ChatGPT fits: The 128K token context window (about 80 pages) means you can process entire contracts in one go.

3. Technical Document QA

When you’d use this: Your team maintains 500+ pages of API documentation. You need to extract endpoint definitions, parameters, and authentication methods.

Why ChatGPT fits: Structured output feeds directly into your internal tools — no manual data entry.

4. Medical Record Summarization

When you’d use this: A healthcare company processes clinical notes. You need to extract diagnoses, medications, and lab results.

Why ChatGPT fits: The refusal field catches sensitive content before it reaches downstream systems.

5. Multi-Language Document Translation

When you’d use this: A logistics company receives shipping documents in 12 languages. You need structured data regardless of language.

Why ChatGPT fits: GPT-4o understands 50+ languages natively. The output schema stays the same — only the input language changes.

Cheat Sheet

Aspect Detail
Key Endpoint POST https://api.openai.com/v1/chat/completions
Best Model gpt-4o (powerful), gpt-4o-mini (cheap, fast)
Input Pricing $2.50/1M tokens (GPT-4o), $0.15/1M tokens (GPT-4o-mini)
Output Pricing $10.00/1M tokens (GPT-4o), $0.60/1M tokens (GPT-4o-mini)
Free Tier 40 requests every 3 hours — great for testing
Context Window 128,000 tokens (~80 pages of text)
Max Output 16,384 tokens per response
Rate Limit (free) 3 RPM (requests per minute)
Rate Limit (Tier 4) 10,000 RPM, 2,000,000 tokens per minute
Structured Outputs Use response_format with a Pydantic model
Streaming stream: true — first result in ~300ms
Common Gotcha First request with a new schema takes ~10s to warm up
Common Gotcha Must set additionalProperties: false on every object
Debugging Add X-Request-ID header for tracing requests
SDK pip install openai>=1.42.0

Vibe Coding Projects

Project 1: Personal Document Organizer

What it does: A tool that watches a folder for PDFs, extracts metadata (title, date, author, type), and organizes them into a searchable directory.

What you’ll learn: File watching, PDF text extraction, structured outputs, file system operations.

Effort: 4-6 hours. Perfect for a weekend.

Project 2: Meeting Notes Extractor

What it does: Paste meeting transcripts and get structured output: action items, decisions, owners, deadlines. Outputs to JSON, Markdown, or Notion.

What you’ll learn: Prompt engineering, streaming for real-time feedback, API integration.

Effort: 6-8 hours.

Project 3: Multi-Model Document QA Pipeline

What it does: A web service that accepts document uploads, uses GPT-4o-mini for page scoring, GPT-4o for extraction, and a vector database for search.

What you’ll learn: Full-stack AI pipeline, multi-model orchestration, async processing, vector search.

Effort: 20-30 hours. A portfolio-worthy project.

Problems Solved Efficiently

Problem Type Why ChatGPT Fits When to Look Elsewhere
Extracting data from messy documents Structured outputs guarantee clean data Text must be extractable (not handwritten without OCR)
Processing many document formats Single model handles invoices, contracts, reports Context window limits to ~80 pages of dense text
Real-time classification Sub-500ms latency for short inputs Needs prompt caching for consistent system prompts
Batch offline processing Batch API at 50% discount 24-hour turnaround, no streaming
Multi-language extraction Native support for 50+ languages Accuracy varies (best for English, Spanish, French)

The Results

After switching to GPT-4o with structured outputs:

Metric Before After Improvement
Average time per page 4.2s 1.1s 3.8x faster
Slowest 5% of pages 12.1s 3.4s 3.6x faster
JSON parse failures 11.3% 0.0% Eliminated
Missing fields 6.7% 0.3% 22x reduction
Human rework needed 18.2% 3.1% 5.9x reduction
Cost per 1K pages $300 $82 3.7x cheaper
Pages per day 2,400 12,800 5.3x increase

What this means for you: If you’re processing documents, structured outputs alone can eliminate your biggest headaches. The cost savings come from three things: GPT-4o is cheaper per token, page filtering reduces what you send, and fewer errors means less rework.

Trade-offs and Lessons

What to Watch Out For

1. First request is slow. The first time you use a new JSON schema, it takes 8-12 seconds to “compile.” After that, it’s fast. Fix: Pre-warm schemas by sending a dummy request during deployment.

2. No parallel calls with strict mode. Setting strict: true means the AI can only do one thing at a time. For complex pipelines, split into sequential steps.

3. False refusals on benign content. About 0.4% of documents trigger safety filters incorrectly — especially contracts with words like “termination” or “liability.” Fix: Add a retry with a clarifying system prompt.

Lessons Learned

Schema design matters more than prompt engineering. A well-designed schema with a mediocre prompt beats a poorly-designed schema with an amazing prompt. Keep schemas flat (max 2 levels deep), use enums instead of free text, and make every field either required or explicitly nullable.

The highest-leverage optimization: Add a cheap AI filter before the expensive one. We used GPT-4o-mini to find relevant pages before sending them to GPT-4o. This cut our token usage by 72% and our cost by 3.7x. The filter itself costs $0.003 per document.

Monitor refusal rates. When OpenAI updates their safety classifier, your refusal rate can jump from 0.1% to 4.7% overnight. Set up alerts for refusal rate >1% in any hour.

Course-Style Deep Dive

How GPT-4o Works Under the Hood (Simplified)

Think of GPT-4o as a very smart prediction engine. It doesn’t “understand” text the way humans do — it predicts the next word (technically, the next “token,” which is roughly ¾ of a word) based on all the words that came before.

The architecture has three main parts:

  1. The Transformer — The core engine. It processes all the words in your prompt simultaneously (not one at a time) and figures out how they relate to each other. This is why it can understand context — it sees the whole picture at once.

  2. Attention Mechanism — This is how the model decides which words matter most. When you say “The cat sat on the mat because it was tired,” the attention mechanism figures out that “it” refers to “the cat,” not “the mat.”

  3. The Decoder — This generates the response one token at a time, using the attention information to make each prediction more accurate.

Structured Outputs work by adding a “grammar constraint” on top of the decoder. Instead of predicting any possible next word, the model is restricted to only predict words that would produce valid JSON matching your schema. It’s like giving the model a fill-in-the-blank form instead of a blank page.

Advanced Patterns

1. Multi-step extraction for complex documents For documents with multiple sections (e.g., a contract with parties, terms, and signatures), split the extraction into sequential steps:

# Step 1: Extract parties
parties = extract_parties(document_text)

# Step 2: Extract terms (depends on knowing the parties)
terms = extract_terms(document_text, parties)

# Step 3: Validate
validate_extraction(parties, terms)

2. Batch processing for cost savings If you don’t need real-time results, use the Batch API for 50% discount:

# Create a batch of requests
batch = client.batches.create(
    input_file_id=file_id,
    endpoint="/v1/chat/completions",
    completion_window="24h",  # Results within 24 hours
)

3. Hybrid approach: cheap model for routing, expensive model for extraction Use GPT-4o-mini to classify documents and route them, then GPT-4o for the actual extraction. This saves 60-80% on costs.

Production Considerations

  • Rate limiting: OpenAI has tiered rate limits. At Tier 4, you get 2M tokens per minute. Use a token bucket rate limiter to stay within limits.
  • Error handling: Implement exponential backoff for 429 (rate limit) and 500 (server error) responses.
  • Monitoring: Track token usage per request for cost attribution. Set up alerts for unusual spikes.
  • Cost optimization: Use GPT-4o-mini for 80% of tasks, GPT-4o for the critical 20%. Cache common responses.
NL

Written by Nivant Labs Team

Engineer at Nivant Labs

Share this post