Field guide

AI Data Pipeline / Schema BuilderGeneration

Schema Generator Field Guide: Designing Strict JSON Schemas and Deterministic AI Prompts

json schemaai promptsdata extractionstructured outputpipelines
Senrok Team
BySenrok Team
Published

Most AI extraction pipelines fail in the same place: the model returns something almost right. A field is named phone_number instead of phone. A date is "2026-07-11" instead of "2026-07-11T00:00:00Z". A nested object is flattened. The downstream system that was supposed to ingest the structured output throws, and someone has to hand-fix the bad rows.

The Schema Builder is the tool we use to prevent that failure mode. It generates two artifacts together — a strict JSON Schema and a deterministic AI prompt that produces output matching that schema. The pair is the smallest unit of work that survives a messy real-world input. This guide is the operator's notes: how to design a schema that does not fold under weird input, how to write a prompt the model will actually follow, and how to wire both into a production pipeline.

The two artifacts

The builder produces, side by side, two things that have to be designed together:

  1. A JSON Schema that defines the exact shape, types, and constraints of the structured output you want.
  2. A system prompt that tells the model how to extract that output from an unstructured input.

The schema is the contract. The prompt is the instruction. Both are required; one without the other is a half-built system. A schema without a prompt leaves the model to interpret the schema on its own (it will, but inconsistently). A prompt without a schema leaves the output shape to chance.

The output is exported as one JSON blob: the schema, the prompt, and the call signature. You paste both into your pipeline; the pipeline enforces the schema on every response.

Designing a schema that survives messy input

A few rules we follow when designing extraction schemas. They are not theoretical — they are what we have learned from running extraction pipelines against PDF invoices, contract PDFs, email bodies, and scanned forms.

Be explicit about required fields. If a field is required, mark it required. If a field is optional, mark it optional and decide what the model should do when it is missing (omit, null, or empty string). Ambiguity here is the single biggest source of downstream errors.

Use format for anything that has a canonical format. format: "date-time" for ISO 8601 timestamps. format: "email" for email addresses. format: "uri" for URLs. The model is more likely to emit a correctly-formatted value when the schema names the format.

Constrain enumerations. If a field can only be one of three values, declare it as an enum. The model will follow the constraint, and you will not have to clean up "pending" vs "Pending" vs "PENDING" in the output.

Nest objects deliberately. A flat schema is easier to read but harder to validate. If the input is a multi-section document (an invoice with line items, a contract with clauses), nest the schema to match. A line_items array with its own item schema is easier to enforce than fifteen top-level fields with line_item_1_* prefixes.

Use additionalProperties: false at every object level. This is the single most effective setting for catching model drift. If the schema says the object has exactly four fields and the model returns five, the schema is wrong, not the model. The strictest mode catches the bug at parse time instead of downstream.

Writing a prompt the model will follow

A schema tells the model what the output should look like. A prompt tells the model how to produce that output. A few rules for the prompt side:

Be explicit about edge cases. "If a date is ambiguous, return null" beats "extract the date". The first instruction removes a class of errors; the second invites the model to guess.

Give one example, not three. One worked example is enough to anchor the format. Three examples crowd the prompt and invite the model to follow the last one most closely. If you need three examples, you probably need to fix the schema.

Tell the model what to ignore. "Ignore boilerplate like the company address" and "Ignore footer text" are the two most effective instructions for invoice and contract extraction. They tell the model where the noise lives and prevent it from filling your schema with footer content.

Use system messages for the instructions, user messages for the document. A common bug is putting the extraction instructions in the same message as the document. The model treats the document as the primary content and the instructions as the context. Splitting them is faster, cheaper, and more reliable.

Constrain temperature to 0 for extraction work. Extraction is deterministic; the schema is the contract. Temperature 0.0 is the right setting. Save higher temperatures for creative work.

Common patterns

A few schema + prompt pairs we have used in production. They are not templates — they are starting points. Adjust the field names and constraints to your input.

Invoice extraction

A schema with invoice_number, invoice_date, due_date, vendor.name, vendor.address, line_items[] (with description, quantity, unit_price, total), and total_amount. Required: invoice_number, invoice_date, line_items, total_amount. Optional: due_date, vendor.address. A prompt that says "Extract the invoice number, date, vendor, and line items. If the line items are in a table, preserve the order. If a value is illegible, return null."

Contract clause extraction

A schema with parties[] (with name, role, entity_type), effective_date, term_length, termination_clause, governing_law, and key_obligations[]. A prompt that says "Extract the parties to the agreement, the effective date, the term length, the termination conditions, the governing law, and the primary obligations of each party. Ignore boilerplate like the signature block and the recitals."

Email triage

A flat schema with intent (enum: inquiry, complaint, request, other), urgency (enum: low, medium, high), summary (string, 1-2 sentences), and requires_response (boolean). A prompt that says "Classify the email by intent and urgency. Summarize it in one or two sentences. Decide whether it requires a response."

Adding it to a production pipeline

The export from the builder is meant to drop into a pipeline. The canonical integration:

  1. Schema validation at parse time. Every model response is validated against the schema before it is written to the database. Validation failures are routed to a human review queue, not silently dropped.
  2. Retry with a feedback message on validation failure. If the schema fails, send the original input back to the model with the validation error message appended. The model will usually fix it on the second pass.
  3. Escalate after N retries. After two retries, route to a human. The model is not going to get it right on the third try, and the cost of a long retry loop is not worth it.
  4. Track validation success rate per schema. A schema with a 60% pass rate is a bad schema. Either the schema is too strict for the input, or the prompt is not specific enough. Track the metric and fix the schema when it drops.

That is the entire loop. The schema is the contract; the prompt is the instruction; the validator is the gate; the human is the fallback.

When the schema is the wrong tool

A few situations where a JSON Schema is not the right abstraction:

  • The output is a long-form text response. Schemas are for structured output. If the model is supposed to write a 200-word summary, a schema is the wrong shape. Use a single field with type: "string" and a max length, or skip the schema entirely.
  • The output is a classification with thousands of labels. Schemas with very large enum arrays get expensive and slow. For high-cardinality classification, use a multi-stage approach: classify into a small number of buckets first, then refine.
  • The output is multimodal. Schemas do not handle image or audio outputs. If the model is producing both structured data and a generated image, the schema is one part of a larger output spec.

In any of those cases, the right move is a different abstraction. The Schema Builder is the right tool for structured extraction. It is not the right tool for everything.

When to graduate to a custom system

If you are running more than a few schemas through the builder, you are probably at the point where a custom system makes more sense. A few signals that it is time:

  • You have a library of dozens of schemas, and you need a registry to manage them.
  • You need versioned schemas, with the ability to roll back when a new version drops the pass rate.
  • You need a CI gate that runs the validation suite against the latest model checkpoint before deploying a new prompt version.
  • You need to enforce schema compliance across teams, not just one project.

In any of those cases, the right move is a custom schema-management system, possibly backed by a dedicated evaluation harness. The free builder is the right starting point, not the final word.


The builder is at /tools/schema-generator. It runs entirely in your browser; nothing is sent to a server. The schema and prompt you build are exportable as a single JSON blob, ready to drop into a pipeline.