← Back to posts

Schema-Driven Validation for Stable LLM Evaluations

In my first eval runs, a single malformed row could flip a whole report. Now schemas sit in front of the dataset and the model outputs, so bad examples never enter the pipeline.

A schema blocks malformed rows at ingest and prevents shape drift as prompts evolve, so the fields you compare over time stay consistent. If you run evals in LangSmith, schema validation is the fastest way to make your signals trustworthy.

In 30 seconds

  • Schemas stop malformed dataset rows at ingest and flag drifting model outputs in the chain.
  • Enforce a minimal dataset schema, then validate outputs in your chain/agent.
  • Expect fewer invalid rows and more reruns that succeed. Run-to-run variance should drop as well.
  • In a weekly run of 200 examples, invalid rows went from the high teens to low single digits.

What shape drift does to your metrics

Prompts and models will keep changing. The hidden issue is shape drift: fields disappear or change type, and defaults leak in. A single malformed row can blow up an eval run or, worse, skew your metrics. Schemas protect you from both.

A schema gives you:

  • Consistent inputs and expected outputs, with clear failures when data is invalid.
  • Stable metrics you can compare week to week.

Schemas do for evals roughly what TypeScript does for JavaScript. Both catch shape mistakes that would otherwise break things silently.

Evals are still imperfect after this. You get fewer false alarms, and the regressions you do see are easier to trust.

What to include in a schema

At first, include only the fields you need to score and compare.

Minimum schema:

  • input (string)
  • expected_output (object or JSON)
  • metadata (object)

Optional but useful:

  • Rubrics or grading criteria
  • Reference outputs for strict checks
  • Failure tags for slicing (hallucination, retrieval miss, format error)

The mistake I see most often is overengineering the schema on day one. Add fields only when you need them.

Example: quote assistant with compliance rules

I worked on a quoting assistant for enterprise software. The agent had to output a structured quote summary: SKU, term length, region, compliance clauses, and a price breakdown. We ran evals on 200 examples every week.

Small prompt changes would introduce missing fields or wrong types. A compliance_clauses array would come back as a string. A term_months integer would appear as "24 months". Evals failed for the wrong reason.

Once we added schemas at ingest and output validation in the chain, invalid rows per weekly run of 200 examples went from the high teens to low single digits.

Step 1: Define the dataset schema

In LangSmith, add the schema to your dataset so invalid examples are rejected at ingest. You can do this in the UI or the SDK.

# Pseudo-code: define schema and enforce at dataset creation
schema = {
    "input": "string",
    "expected_output": {
        "sku": "string",
        "term_months": "integer",
        "region": "string",
        "compliance_clauses": "array"
    },
    "metadata": {
        "intent": "string",
        "catalog_version": "string"
    }
}

dataset = langsmith.create_dataset(
    name="golden-quotes-v1",
    schema=schema
)

If a row does not match the schema, it never enters your golden set.

Step 2: Validate inputs before import

If you bulk import from traces or CSVs, validate before you push. Do not rely on human cleanup after the fact.

# Pseudo-code: reject malformed rows before upload
for row in rows:
    try:
        validate_schema(row, schema)
        dataset.add_example(**row)
    except ValidationError as e:
        quarantine.add_example(row, error=str(e))

This lets you keep the golden set clean while still keeping the bad rows for analysis.

Step 3: Validate model outputs in the chain

Even with a clean dataset, outputs can drift. Add an output schema in your chain so you catch invalid responses early.

# Pseudo-code: output validation with Pydantic
class QuoteOutput(BaseModel):
    sku: str
    term_months: int
    region: str
    compliance_clauses: list[str]

raw = chain.invoke(inputs)
validated = QuoteOutput.model_validate(raw)

If validation fails, log it and move on. A bad output should stay out of your eval results.

Step 4: Quarantine failures

Create a "quarantine" dataset for rows or outputs that fail validation. I treat it like a backlog:

  • Inspect failures weekly.
  • Fix bad examples or adjust the schema deliberately.
  • Promote only corrected rows into the golden set.

Rows and outputs that fail validation wait in the quarantine instead of the regression set, so they do not mix with the failures you debug.

Step 5: Measure stability

If schema validation is working, you should see:

  • Fewer invalid rows at ingest.
  • Higher eval rerun success rates.
  • Lower run-to-run variance.

In my case, weekly reruns used to fail half the time. After the schemas went in, missing fields and wrong types stopped making them fail.

Validation points in the pipeline

Ingest traces → schema validation → dataset → eval run → output validation → results + quarantine

In LangSmith, add a minimal schema to your main dataset and enforce it at ingest, then validate outputs separately in the chain. In my runs, that cut down false regressions.


Profile picture

Written by Florin — full-stack & AI engineer.