OpenAI SDK: Testing & evaluating LLM code
LLM features need two different kinds of checking. Tests check your code : does the tool loop pass the right call_id back, handle a 429, parse the schema, refuse an…
In this post
What this post covers
LLM features need two different kinds of checking. Tests check your code: does the tool loop pass the right call_id back, handle a 429, parse the schema, refuse an unknown tool? They must be fast, deterministic and offline, so they use a fake client. Evals measure the model’s behaviour on realistic examples. They are statistical, cost money, and are how you decide whether a prompt, model or setting change is an improvement.
1. Two kinds of checking
| Unit tests | Evals | |
|---|---|---|
| Question | Is my code correct? | Is the output good enough, and is it getting better or worse? |
| Model | fake / scripted | real |
| Result | pass / fail | a score (accuracy, pass rate, cost, latency) |
| Runs | every commit, seconds | before changing prompt, model or settings; nightly |
2. Unit tests with a fake client
import pathlib, textwrap, pytest
pathlib.Path("labeller.py").write_text(textwrap.dedent('''
from typing import Literal
from pydantic import BaseModel
class Label(BaseModel):
category: Literal["delivery", "refund", "other"]
def label_ticket(client, text: str) -> str:
"""Returns the category, or 'other' if the model refuses or returns nothing usable."""
r = client.responses.parse(model="gpt-5.6-luna", input=text, text_format=Label,
instructions="Classify the support ticket.")
return r.output_parsed.category if r.output_parsed else "other"
'''))
pathlib.Path("test_labeller.py").write_text(textwrap.dedent('''
import openai, pytest
from fake_openai import fake_client
from labeller import label_ticket
def test_returns_parsed_category():
client = fake_client(script=['{"category": "refund"}'])
assert label_ticket(client, "I want my money back") == "refund"
def test_sends_instructions_and_schema():
client = fake_client(script=['{"category": "other"}'])
label_ticket(client, "hello")
body = client.fake.requests[0]["body"]
assert body["instructions"] == "Classify the support ticket."
assert body["text"]["format"]["schema"]["properties"]["category"]["enum"] == ["delivery", "refund", "other"]
def test_retries_transient_errors_then_succeeds():
client = fake_client(script=[{"status": 503}, '{"category": "delivery"}'], max_retries=1)
assert label_ticket(client, "late parcel") == "delivery"
def test_auth_errors_are_not_swallowed():
client = fake_client(script=[{"status": 401}], max_retries=0)
with pytest.raises(openai.AuthenticationError):
label_ticket(client, "anything")
'''))
pytest.main(["-q", "--no-header", "-p", "no:cacheprovider", "test_labeller.py"])
Output
.... [100%]
4 passed in 3.44s
The code under test takes the client as a parameter (dependency injection, Advanced Python part 20), so tests pass a fake one. They check behaviour you control: request shape, parsing, error handling. They never assert what a real model would say.
3. Evals: a golden set and a score
a minimal eval harness
import json
from fake_openai import fake_client
GOLDEN = [ # real, labelled examples: 50–200 in practice
{"text": "Parcel hasn't arrived after 10 days", "expected": "delivery"},
{"text": "I was charged twice, please refund", "expected": "refund"},
{"text": "Do you ship to Sri Lanka?", "expected": "delivery"},
{"text": "Cancel my order and return the money", "expected": "refund"},
{"text": "Love the new lamp!", "expected": "other"},
]
def run_eval(client, prompt_version):
results = []
for case in GOLDEN:
r = client.responses.create(model="gpt-5.6-luna", input=case["text"],
instructions=f"[{prompt_version}] Reply with one word: delivery, refund or other.")
got = r.output_text.strip().lower()
results.append({**case, "got": got, "pass": got == case["expected"]})
score = sum(r["pass"] for r in results) / len(results)
return score, [r for r in results if not r["pass"]]
# two prompt versions; the fake's replies stand in for what two real prompts produced
v1 = fake_client(script=["delivery", "refund", "other", "refund", "other"])
v2 = fake_client(script=["delivery", "refund", "delivery", "refund", "other"])
for name, client in [("prompt v1", v1), ("prompt v2", v2)]:
score, failures = run_eval(client, name)
print(f"{name}: {score:.0%} passed; failures: {[f['text'] for f in failures]}")
Output
prompt v1: 80% passed; failures: ['Do you ship to Sri Lanka?']
prompt v2: 100% passed; failures: []
Exact checks
Labels, enums, extracted fields, JSON validity, "contains the order id". Cheap and objective, so prefer them whenever the task allows.
LLM-as-judge
A model grades open-ended answers against a rubric ("faithful to the source? polite? complete?"). Useful, but calibrate it against human grades on a sample.
Operational
Cost per case, latency, refusal rate, schema failures. A "better" prompt that doubles cost may not be better.
4. LLM-as-judge
from pydantic import BaseModel
class Grade(BaseModel):
faithful: bool # every claim supported by the source?
complete: bool # answers the whole question?
reason: str
def judge(client, question, source, answer) -> Grade:
return client.responses.parse(
model="gpt-5.6-terra", # usually a stronger model than the one judged
instructions="You are a strict grader. Judge only against the SOURCE.",
input=f"QUESTION:\n{question}\n\nSOURCE:\n{source}\n\nANSWER:\n{answer}",
text_format=Grade,
).output_parsed
OpenAI also offers a hosted Evals API (client.evals) for defining graders and running
them over datasets. Libraries such as DeepEval, Ragas and promptfoo, and platforms such as Langfuse (AI
Engineering part 18), do the same with your own infrastructure.
5. The workflow
- Collect real examples, including the hard and weird ones, and label them. Add every production failure you find.
- Run the eval to get a baseline score for the current prompt and model.
- Change one thing (prompt, model, reasoning effort, retrieval) and re-run.
- Ship only if the score holds or improves at acceptable cost and latency.
- Re-run on every model upgrade before switching.
Recap
- Unit tests: fake client, deterministic, test your code on every commit.
- Evals: a golden set, real model, a score. Compare versions, change one thing at a time.
- Exact checks first, LLM-as-judge for open-ended output (calibrated), plus cost and latency.
Get the free RAG production checklist
25 checks to run before real users see your RAG feature: exact-match misses, invented citations, prompt injection through documents, quality that slips after a prompt change. Confirm your email and the PDF comes with the welcome email, plus each new experiment.