OpenAI Python SDK structured outputs, tested with respx: working versions (tested 2026-10-15)
Call the OpenAI Python SDK and validate JSON output with Pydantic, testing success, malformed output and API errors offline by mocking HTTP with respx.
passed3 passed · 0 failed2026-10-15 11:55 UTC- Status
- passed
- Last passed
- 2026-10-15 11:55 UTC
- Tests
- 3 / 3
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("openai-python-respx-mock") (install), or see them on GitHub.
openai==3.31.0 pydantic==2.14.1 respx==0.23.1 httpx==0.28.1 pytest==9.1.1
from openai import OpenAI
from pydantic import BaseModel, ValidationError
class Invoice(BaseModel):
vendor: str
total: float
currency: str
class ExtractionError(Exception):
pass
def extract_invoice(client: OpenAI, text: str) -> Invoice:
res = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Extract vendor, total and currency as JSON."},
{"role": "user", "content": text},
],
)
content = res.choices[0].message.content or ""
try:
return Invoice.model_validate_json(content)
except ValidationError as e:
raise ExtractionError(f"model returned invalid JSON: {e.error_count()} errors") from e
import json
import httpx
import openai
import pytest
import respx
from openai import OpenAI
from extract import ExtractionError, extract_invoice
BASE = "https://api.openai.test/v1"
def completion(content: str):
return {"id": "c1", "object": "chat.completion", "created": 0, "model": "gpt-4o-mini",
"choices": [{"index": 0, "finish_reason": "stop", "message": {"role": "assistant", "content": content}}]}
@pytest.fixture
def client():
# pass an httpx client explicitly so respx can intercept it
return OpenAI(api_key="test", base_url=BASE, max_retries=0, http_client=httpx.Client())
@respx.mock
def test_parses_valid_json(client):
route = respx.post(f"{BASE}/chat/completions").mock(return_value=httpx.Response(200, json=completion('{"vendor":"Acme","total":12.5,"currency":"USD"}')))
inv = extract_invoice(client, "Acme invoice $12.50")
assert inv.vendor == "Acme" and inv.total == 12.5
sent = json.loads(route.calls.last.request.content)
assert sent["response_format"] == {"type": "json_object"}
@respx.mock
def test_invalid_output_raises(client):
respx.post(f"{BASE}/chat/completions").mock(return_value=httpx.Response(200, json=completion('{"vendor":"Acme"}')))
with pytest.raises(ExtractionError):
extract_invoice(client, "x")
@respx.mock
def test_api_errors_surface_as_sdk_exceptions(client):
respx.post(f"{BASE}/chat/completions").mock(return_value=httpx.Response(429, json={"error": {"message": "slow down", "type": "rate_limit"}}))
with pytest.raises(openai.RateLimitError):
extract_invoice(client, "x")
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
OpenAI chat.completions.parse with Pydantic output + respx mocks
Get typed output from OpenAI chat.completions.parse using a Pydantic response_format, handle refusals and schema mismatches, and test offline with respx returning canned JSON.
httpx async API client + respx mocks
A typed async API client on httpx with retries on 5xx and Pydantic parsing, tested offline with respx route mocks.
Typed httpx client parsing into pydantic v2 models, tested with respx
An async httpx client that retries 5xx and transport errors, validates responses into pydantic v2 models, and is tested offline with respx route mocks.
FastAPI + Pydantic v2 validators, discriminated unions and serializers
Pydantic v2 patterns in FastAPI that agents often get wrong: field_validator, model_validator, discriminated unions, computed_field and response_model filtering.