← Back to Blog
Engineering·12 min read·Oct 19, 2026

Structured Output Validation in Clinical Dental AI: Schema-Checking Agent Responses Before They Reach the PMS

Structured Output Validation in Clinical Dental AI: Schema-Checking Agent Responses Before They Reach the PMS

What does your practice management system do when an agent hands it tooth number 33? If the honest answer is that Dentrix, Eaglesoft or Open Dental would write the value straight to the chart, the gap sits in the layer between the model and the write path.

That layer is structured output validation. In a clinical dental stack, it is the last deterministic checkpoint before a model's opinion becomes a legal medical record — and accordingly, it deserves the same engineering rigor you would give a payments ledger.

Structured output validation checks every agent response against a strict schema and field-level rules before any PMS write. Malformed tooth numbers, impossible dates and unknown provider IDs are rejected, not repaired.

Why Model Output Breaks At The PMS Boundary

Large language models produce text that is shaped like data, which is different from producing data. Even with Claude on Bedrock and native tool-use schemas turned on, you will still see responses that parse cleanly while carrying values your PMS should never accept.

The failure modes are rarely exotic. In clinical charting and claim-prep workflows, the recurring offenders include but are not limited to:

  • Out-of-range tooth numbers. A Universal-numbered practice receives "33," or an FDI value like "19" that has no meaning in that system. A related failure is a primary-tooth letter written into a field that only accepts permanent teeth.
  • Mixed numbering systems. The transcript said "upper right first molar," the model emitted FDI "16," and the downstream field expects Universal "3." Both values are individually plausible, which is exactly why this one survives naive checks.
  • Impossible or implausible dates. February 30, a procedure date three weeks in the future, or a date of service that predates the patient's date of birth. Date-only fields also arrive with time zones attached and shift a day on write.
  • Missing or invented provider IDs. The model fills a required provider field with a name string, a plausible-looking ID that does not exist in the provider table, or a hygienist ID on a procedure that requires a dentist.
  • Malformed procedure codes. "D274" instead of "D2740," a lowercase prefix, or a code retired in the current CDT edition.

All of these have one thing in common — they are cheap to detect before the write and expensive to unwind after it. A wrong tooth on a crown entry can follow a patient into a claim, an attachment and a narrative before anyone at the front desk notices.

The Three Layers Of A Validation Gate

We think about validation as three stacked layers, each catching a different class of error. Skipping any one of them leaves a predictable hole, and the order matters because each layer assumes the one before it passed.

Here is how the layers divide the work:

LayerWhat It ChecksExample CatchTypical Cost
Schema enforcementShape, types, required fields, enumsMissing provider_id; tooth sent as an integer instead of a stringSub-millisecond
Field-level validatorsPer-field domain rulesUniversal tooth "33"; date of "2026-02-30"Sub-millisecond
Cross-field and referential checksRelationships between fields and live PMS stateProvider ID not active at this location; surface "O" on an anterior toothOne cached lookup, usually a few milliseconds

Overall, the first two layers are pure functions you can unit-test exhaustively. The third touches state, which is why it needs caching, timeouts and a clear failure policy of its own.

Layer One: JSON Schema Enforcement

Start with a versioned JSON Schema (draft 2020-12 is a sensible default) for every write type the agent can propose: a procedure entry, a perio reading, an appointment change, a clinical note attachment. Each schema lives in your tool registry next to the tool definition the model sees, so the contract the model is prompted with and the contract you enforce cannot drift apart.

Provider-native structured output reduces malformed JSON but does not replace your own validator. Enforce the same schema server-side with Ajv, Pydantic or an equivalent, and treat the model-side schema as a hint.

Keep in mind that a schema is only as strict as its loosest field. A few rules we apply to every clinical write schema:

  • Set additionalProperties: false everywhere. Extra keys are how a model smuggles in fields your mapper later picks up by accident. Reject them outright.
  • Type tooth identifiers as strings. Universal primary teeth are letters, FDI values carry meaningful leading digits, and supernumerary notation does not fit an integer. A string with a pattern is honest about what the field holds.
  • Use enums for every closed set. Surfaces, numbering system, procedure status and quadrant all belong in an enum, not a free-text field with a hopeful description.
  • Require an explicit numbering-system field. Never let the validator infer whether "16" is FDI or Universal. Make the model declare it, then check that the declaration matches the practice's configuration.
  • Pin the schema version in the payload. A schema_version field lets you reject responses generated against a stale tool definition after a deploy.

Schema enforcement catches shape errors. That said, a perfectly shaped payload can still describe a tooth that does not exist, which is where the second layer earns its keep.

Layer Two: Field-Level Validators

Field-level validators encode clinical and administrative domain rules as small, deterministic functions. Each one takes a single value plus minimal context, returns pass or a structured error, and never calls the model.

Tooth Numbers

Tooth validation depends on the numbering system the practice uses, which we cover in depth in our guide to Universal, FDI and Palmer tooth numbering standards. For the validator, the rules reduce to a handful of checks per system.

The core checks include:

  • Universal permanent. Integers 1 through 32, expressed as strings. Anything outside that range fails, including 0.
  • Universal primary. Letters A through T, uppercase only. Normalizing case is acceptable; guessing a letter from a number is not.
  • FDI permanent. Two digits where the first is a quadrant from 1 to 4 and the second is a tooth position from 1 to 8. A value like "19" or "50" fails.
  • FDI primary. Quadrants 5 to 8 with positions 1 to 5, so "55" passes and "56" fails.
  • Supernumerary teeth. Route these to a human review queue by default. They are rare enough that an automated write is rarely worth the risk.

Remember that a valid tooth number can still be the wrong tooth for the patient. A validator should also reject a procedure on a tooth the chart already marks as extracted or missing, which brings in patient state — a layer-three concern we return to below.

Dates And Times

Date validation is where otherwise careful teams get surprised. Parse every date with a strict ISO 8601 parser that rejects February 30 rather than rolling it to March 2, and keep date-of-service fields as calendar dates with no time component.

Beyond calendar validity, add plausibility windows. A completed procedure dated in the future fails, a date of service before the patient's date of birth fails, and a date more than a configurable lookback (we often start at 30 days for charting workflows) goes to review rather than straight to the chart.

Provider IDs

Provider fields should accept only the PMS's internal identifier format, never a display name. In Open Dental that is a ProvNum; in Dentrix it is the provider ID string configured at the practice.

At this layer, the validator checks format only — non-empty, correct pattern, no whitespace. Whether that provider exists, is active and is credentialed for the procedure is a referential question that belongs in layer three.

Procedure Codes And Surfaces

CDT codes get a format check here: an uppercase "D" followed by exactly four digits. Mapping a clinical description to the right code is a separate problem, covered in our breakdown of CDT code mapping for clinical AI; the validator only confirms the code is well-formed and present in the current edition's table.

Surfaces get an enum check — M, O, D, B, L, I, F — plus a de-duplication pass. "MOOD" is a common model artifact that should normalize to "MOD" or fail, depending on how strict your practice wants to be.

Field validators should normalize only what is unambiguous, such as letter case or whitespace. Anything that requires guessing, like converting FDI to Universal, should fail and go back to the model or a human.

Layer Three: Cross-Field And Referential Checks

The last layer asks whether the fields make sense together and against live PMS state. Many of the most damaging errors pass layers one and two cleanly, because every individual value is legitimate.

Some examples of cross-field and referential rules include:

  • Surface-to-tooth compatibility. An occlusal surface on an anterior tooth fails; incisal on a molar fails. This is a pure lookup table, but it needs both fields at once.
  • Code-to-tooth requirements. Some CDT codes require a tooth number, some require a quadrant or arch, and some must not carry either. A full-mouth debridement code with a tooth number attached is a sign the model confused two procedures.
  • Provider existence and status. The provider ID must exist, be active, and be assigned to the location in the request. Cache the provider table with a short TTL and fail closed if the cache is cold and the PMS is unreachable.
  • Patient-state conflicts. A restoration on a tooth charted as extracted, or a second identical procedure on the same tooth and date, gets held for review.

Because this layer reads PMS state, it inherits every stale-state read problem your integration has. Read-through caches help, but each referential lookup counts against the vendor's API budget, so plan it alongside your PMS rate limiting strategy rather than after it.

How Should A Reject-And-Retry Loop Work?

When a response fails validation, the gate has three options: retry with the model, route to a human, or drop the action. The decision should be driven by the error type, not by a single global retry counter.

Retry only errors the model can fix from the source material, such as a missing field or a malformed code. Cap retries at one or two, then route to human review with the full error list attached.

Here is the loop we recommend, step by step:

  1. Collect every error, not the first one. Run all three layers and gather a complete list. Returning one error per attempt turns a single fix into four round trips.
  2. Classify each error as retryable or terminal. A missing provider ID that appears in the source note is retryable. A provider that does not exist in the PMS is terminal, because the model cannot conjure a valid one.
  3. Send structured feedback, not a scolding prompt. Return the field path, the rule that failed, and the allowed values or pattern. "tooth_number: '33' outside Universal permanent range 1-32" gives the model something concrete to correct.
  4. Re-validate the full payload on every attempt. Models occasionally fix one field and break another. Never accept a partial diff as proof of correctness.
  5. Stop at a hard ceiling. One or two retries is usually enough. Beyond that, latency climbs, token cost compounds, and the odds of a correct answer stop improving meaningfully.
  6. Escalate with context. The human reviewer should see the source transcript, the proposed payload and every validation error in one view, not a generic "AI failed" ticket.

Be aware that retries are a place where hallucination can masquerade as correction. If the source note never mentioned a tooth, the model will happily invent one to satisfy the validator, so a missing-from-source field should escalate rather than retry.

For latency budgets, a single retry on a Claude Sonnet-class model typically adds one to a few seconds to a background charting job. That is acceptable for asynchronous write-back; for a live AI phone agent booking an appointment, it usually means validating as the conversation goes rather than at the end.

Where Validation Sits Relative To Idempotency

Validation runs before the idempotency key is minted and before any write is attempted. If a payload never passes the gate, it never touches the write queue, so there is nothing to deduplicate or roll back.

The ordering we use is consistent across workflows: validate, then mint the idempotency key from the validated payload, then enqueue, then write. The write-side half of that pipeline — keys, dead-letter queues and compensating transactions — is covered in our post on idempotent PMS write-back.

Validate first, then mint the idempotency key from the validated payload. Keying on raw model output means two semantically identical retries can produce different keys and slip past deduplication.

Validation is also a security boundary. A schema with additionalProperties: false and closed enums sharply limits what an injected instruction can do, which makes the gate one of the more practical defenses against prompt injection in clinical AI systems.

What To Log When A Response Is Rejected

Rejection logs are some of the most useful telemetry you will collect, and they are also full of PHI. A rejected payload contains tooth numbers, dates of service, provider IDs and often patient identifiers — all of which fall under your BAA and HIPAA obligations.

Accordingly, we split the record in two:

  • Operational metrics, PHI-free. Rule ID, field path, schema version, model ID and version, retry count and final disposition. These go to CloudWatch or your observability stack and drive dashboards.
  • Full payloads, PHI-scoped. The proposed write and source references go to an encrypted store under a KMS key with access logged in CloudTrail. Retention follows your clinical record policy, not your application log policy.

This split lets engineering watch rejection rates per rule without granting broad PHI access. For the wider compliance picture, see our guide to HIPAA requirements for clinical AI in dentistry.

How Do You Know Your Validation Layer Is Working?

A validation gate that never fires is either guarding a perfect model or quietly broken. You need evidence in both directions — that it rejects what it should and passes what it should.

Here is what we track and test:

  • A golden set of known-bad payloads. Every tooth-number, date and provider failure you have ever seen in production becomes a fixture. The suite runs in CI on every schema or validator change.
  • Rejection rate per rule, per model version. A jump in one rule after a model-version change is usually the first sign of a regression. Pin model versions so the comparison means something.
  • Retry success rate. If most retries for a given rule fail, that rule is probably catching something the model cannot fix, and it should escalate directly instead.
  • Human-review overturn rate. When reviewers frequently approve payloads the gate held, the rule is too strict. When they frequently fix payloads the gate passed, it is too loose.

Validation and evals answer different questions. The gate tells you a payload is safe to write; whether it is clinically correct is the job of the evaluation suite described in our post on clinical AI evals, and you need both running.

A schema-valid payload can still be clinically wrong, such as a real tooth that was never treated. Validation proves a write is safe to attempt; evals and human review prove it is accurate.

Common Questions About Schema Validation For PMS Writes

These are the questions that come up most often when teams move a validation gate from design into production.

How many retries should a validation loop allow before escalating?

One or two — past that, latency and token cost climb while the chance of a correct fix flattens. Errors the model cannot fix from the source, like an unknown provider, should escalate immediately with zero retries.

Should the validator silently correct obviously wrong values?

Only for unambiguous normalization such as uppercasing "d2740" or trimming whitespace. Converting FDI 16 to Universal 3 or shifting a date is a clinical guess, so fail it and log the rule that fired.

Where should the JSON schemas live in the codebase?

Alongside the tool definitions in your tool registry, versioned together in the same repo. The schema the model is prompted with and the schema the server enforces should come from one source file.

What happens if the PMS is unreachable during referential checks?

Fail closed. Hold the validated payload in a queue with its schema version, and re-run the referential layer when the PMS recovers rather than writing against a stale provider or patient cache.

Does validation add noticeable latency to clinical workflows?

Schema and field checks run in well under a millisecond. Referential lookups add a few milliseconds when cached, so retries to the model, not the validator itself, are what drive any user-visible delay.

Put A Validation Gate In Front Of Your PMS

Every clinical AI write path eventually meets a tooth number, a date or a provider ID it should not accept. The teams that ship safely are the ones that decided, in code, what happens at that moment before the first patient record was touched.

If you are wiring an agent into Dentrix, Eaglesoft or Open Dental and want a second set of eyes on the write path, NexV builds HIPAA-grade clinical AI on Amazon Bedrock for dental groups. We will review your schemas, map the validators and retry rules your workflows need, and leave you with a gate design you can test in shadow mode before it ever writes to a chart.