Text size
Dark mode
RecallRunGuides & benchmarks
Learn to build real systemsGuides•Benchmarks•Experiments•Community
Search the site/Free checklist

Guides/OpenAI SDK

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
  1. 1. Two kinds of checking
  2. 2. Unit tests with a fake client
  3. 3. Evals: a golden set and a score
  4. 4. LLM-as-judge
  5. 5. The workflow
  6. Recap

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 testsEvals
QuestionIs my code correct?Is the output good enough, and is it getting better or worse?
Modelfake / scriptedreal
Resultpass / faila score (accuracy, pass rate, cost, latency)
Runsevery commit, secondsbefore 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

  1. Collect real examples, including the hard and weird ones, and label them. Add every production failure you find.
  2. Run the eval to get a baseline score for the current prompt and model.
  3. Change one thing (prompt, model, reasoning effort, retrieval) and re-run.
  4. Ship only if the score holds or improves at acceptable cost and latency.
  5. 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.

One email to confirm. Unsubscribe any time. What's in it · Privacy

Want the production version of this? See ShipRAG: A production RAG + MCP starter kit for FastAPI: hybrid search, validated citations, evals in CI, Docker, 32 tests.

Written by

Alby Tomy

Python/FastAPI Engineer | Data Engineer | AI Engineer building RAG and MCP systems.

X · GitHub · Contact & enquiries