outputSchema on a section does two things at once:
Declares the shape of what the LLM is allowed to emit — a string, a number, a structured object with named fields, an array of objects, and so on. The model is steered to fit this shape.
Drives the rendering of that output via format strings (fieldFormat, itemFormat) so the rendered Markdown/text matches your downstream consumer — an EHR field, a structured pipeline, a free-text note block.
Schema descriptions, enums, patterns, defaults, and min/max constraints are all part of the prompt the model sees. Use them as guidance, not just validation.
Schema design is iterative. Start narrow (string + a tight contentPrompt), then widen the schema as you discover repeatable structure in the outputs.
outputSchema is one of five node types, discriminated by type. The table lists each type and the fields you can set; bold fields are required, the rest are optional.
type
Use when
Required
Optional
string
Free prose, single labels, fixed phrases
type
description, default, enum, pattern
number
Measurements, counts, scores
type
description, default, enum, minimum, maximum
boolean
Yes/no facts (e.g. “fasting?”)
type
description, default
array
Lists of any node type
type, items
description, itemFormat, minItems, maxItems
object
Structured blocks with named fields
type
description, fields[], fieldFormat
Important: the schema for an array’s items can itself be any node — including another array or object. That’s the lever for everything below.
Most fields are technically optional, but description is strongly recommended on every node — it doubles as a prompt to steer the LLM, not just metadata.
The discriminator. One of string, number, boolean, array, object. Always required.
description
Prompt text the model sees when generating this part of the output. Optional but strongly recommended — this is your main lever to clarify what this specific node should contain, in addition to the section’s instructions.contentPrompt.
Fallback string if the model has nothing to emit for this node.
enum
A closed set of allowed values. The model must pick one of these strings.
pattern
A regular expression the output should match. The model is steered toward emitting a string that conforms (e.g. ^\d{2,3}/\d{2,3}$ for a blood-pressure value). Not a hard validator — pair with enum/description for stricter control.
The schema for each element. Can be any node type — string, number, object, even another array.
itemFormat
Per-item rendering. A custom format string that must contain the {item} placeholder — applied per element. For bullets use "- {item}\n"; for prefixed entries use e.g. "Rp. {item}\n". Note: there is no built-in auto-incrementing index token, so a “numbered” rendering (1., 2., 3., …) cannot be produced from itemFormat alone — model the section as a string and ask the model to emit the numbering in its content, or accept bullets via "- {item}\n".
The fixed set of fields the object contains. Each entry requires key, description, and value (any node type).
fieldFormat
Single unified format string controlling how the object is rendered. Two modes — pick one per object: (a) Per-field iteration with generic {key} and {value} placeholders (e.g. "{key}: {value}\n" for inline subheadings, "{key}\n{value}\n" for block subheadings, "**{key}**: {value}\n" for bold Markdown). The template is applied to every field. (b) Custom layout with specific field-key placeholders that match defined fields[] (e.g. "{test}: {result} ({status})"). The format string is rendered once with all fields substituted.
string + enum + keyword rules in description (+ default)
Keyword-driven classification — one value out, nothing else
Encounter disposition (admit / discharge / transfer / …), tobacco status, pregnancy status, severity class — any EHR picker or analytics dimension where the receiver needs a stable categorical value
number + minimum/maximum
Constrained measure
Vital values, scores, counts
array of string + itemFormat
Repeating short items
Diagnoses, Medications, Plan items
array of object containing a nested array with indented itemFormat
Template-level instructions — instructions.prompt on the parent template (when used in a template).
Schema-level guidance — every description, enum, pattern, default, minimum, maximum, minItems, maxItems you supply on the outputSchema.
Schema-level guidance is where you encode per-field rules that vary inside one section (e.g. “this field is ‘Nil’ when denied”, “this field is one of these abbreviations”). Use instructions.contentPrompt for the overall section content/scope; use schema descriptions for field-specific behavior.
A common pattern is: keep a Corti Standard section’s full prompt machinery (heading, contentPrompt, writingStylePrompt, miscPrompt) but swap its outputSchema for one of the patterns above — e.g. take the curated corti-hpi prompts and emit a structured object for an EHR pipeline.Two ways to do this:
Fork the Standard (persistent).POST /documents/sections with inheritFromId: <standard-section-uuid> and only generation.outputSchema in the body. Everything else is inherited. See Corti Standards — override outputSchema.
In both forms, outputSchema overrides are wholesale — whatever you submit fully replaces the parent’s schema; partial schemas are not merged. When the change is structural (e.g. string → object/array), consider overriding writingStylePrompt in the same request so the parent’s wording rules don’t conflict with the new shape.
The patterns below are pulled from real Corti standard sections — adapted so you can copy/paste them into your own POST /documents/sections request, lift specific fields, or reference them as a parent via inheritFromId.Each example shows the full outputSchema (you’d wrap it in the standard name/language/generation.instructions/generation.outputSchema envelope from Create a Section).
Example 1 — Fixed subheadings with explicit 'not discussed' vs 'nil' defaults
Goal: A pre-op screening block that always renders the same fixed subheadings in the same order. When the conversation didn’t touch a topic, render Not discussed. When the patient explicitly denied something, render Nil. Don’t let the model invent in-between phrases like “patient denies anything noteworthy.”Pattern: object with fieldFormat: "{key}\\n{value}\\n" (per-field iteration), fixed fields[], per-field default, and explicit guidance in each field’s description for the negation rule.
{ "type": "object", "description": "Pre-operative screening block. Render every subheading; never omit a field.", "fieldFormat": "{key}\n{value}\n", "fields": [ { "key": "Allergies", "description": "Allergies and intolerances. If the patient explicitly denied any, output exactly: Nil. If allergies were not discussed at all, output exactly: Not discussed.", "value": { "type": "string", "default": "Not discussed", "description": "Use 'Nil' only when the patient explicitly denied allergies. Otherwise 'Not discussed'." } }, { "key": "Current medications", "description": "Current active medications. 'Nil' if patient explicitly denied any. 'Not discussed' if not covered.", "value": { "type": "string", "default": "Not discussed" } }, { "key": "Fasting status", "description": "Whether the patient is fasting and since when. 'Not discussed' if absent from the conversation.", "value": { "type": "string", "default": "Not discussed" } }, { "key": "Anesthesia history", "description": "Any previous anesthesia and complications. 'Nil' if explicitly denied; 'Not discussed' otherwise.", "value": { "type": "string", "default": "Not discussed" } } ]}
What you get (with fieldFormat: "{key}\n{value}\n" — per-field iteration):
AllergiesPenicillin — anaphylaxisCurrent medicationsNilFasting statusNPO since 22:00 yesterdayAnesthesia historyNot discussed
The description on each field carries the negation rule for that field — the model sees both the field-level description and the section-level instructions.contentPrompt. Use field descriptions for field-specific guidance.
Example 2a — Dynamic organ-system labels (free)
Goal: Examination findings where the LLM picks the organ-system label dynamically — only the systems actually examined appear in the output, with a clinically appropriate label for each. No predefined enum.Pattern: array of objects with a title string field (no enum) and a content string field, joined via fieldFormat: "{title}: {content}". The model creates as many entries as needed.
outputSchema — dynamic-key array-of-objects
{ "type": "array", "description": "Open-ended set of examination findings. One entry per body region or system, generated only when relevant findings exist.", "items": { "type": "object", "fields": [ { "key": "title", "description": "Body-region or system label, e.g. 'Abdomen', 'Right knee', 'Neuro', 'Cardiovascular'. Choose the most clinically appropriate label for the findings in this entry.", "value": { "type": "string" } }, { "key": "content", "description": "Concise objective finding text for that label.", "value": { "type": "string" } } ], "fieldFormat": "{title}: {content}" }, "itemFormat": "{item}\n"}
What you get:
Abdomen: Soft, non-tender, no palpable masses.Neuro: GCS 15. Cranial nerves intact.MSK: ROM preserved in all joints; no swelling or deformity.
Example 2b — Constrained organ-system labels (enum)
Goal: Same shape, but every label must come from a fixed clinical abbreviation set you’ve standardized on for an EHR field.Pattern: identical to 2a, but the title field’s string node carries an enum of allowed values.
outputSchema — enum-constrained dynamic keys
{ "type": "array", "description": "Review of systems entries. One entry per domain mentioned. Use only the standardized domain abbreviations.", "items": { "type": "object", "fields": [ { "key": "title", "description": "Standardized review-of-systems domain label.", "value": { "type": "string", "enum": ["C-P", "GI", "Resp.", "Neuro", "MSK", "ENT", "GU", "Derm", "HEENT", "Endo", "Psych", "Heme", "Immune"] } }, { "key": "content", "description": "Symptoms and explicitly denied symptoms for that domain, stated concisely in clinical language.", "value": { "type": "string" } } ], "fieldFormat": "{title}: {content}" }, "itemFormat": "{item}\n"}
What you get:
C-P: Denies chest pain or palpitations.Resp.: Mild dry cough for 3 days; no shortness of breath.GI: Intermittent nausea; no vomiting or diarrhea.Neuro: Headache for 3 days; no focal deficits.
When to use which. Use free titles (2a) when the legal label set is open-ended and clinicians need to choose precise body regions. Use enum (2b) when the downstream consumer (EHR field, analytics pipeline) requires a finite, stable set of labels.
Example 3 — Structured test results with measures and free-text findings
Goal: Lab and imaging results where each entry mixes typed measures (numeric values with ranges and units), a status field drawn from a fixed clinical vocabulary, and a free-text finding narrative.Pattern: array of objects with a result string that the model formats consistently (number + unit, or status placeholder for non-numeric studies), plus a typed status enum and a free-text findings field. Numeric typing is preserved per-test through the description instructions; the rendered line stays clean regardless of test type.
outputSchema — mixed-type test results
{ "type": "array", "description": "Diagnostic test results discussed in the encounter. One entry per test or imaging study.", "items": { "type": "object", "fields": [ { "key": "test", "description": "The name of the test, panel, study or modality (e.g. 'Hemoglobin', 'Chest X-ray', 'Troponin I').", "value": { "type": "string" } }, { "key": "result", "description": "For quantitative tests: numeric value with unit, as stated in the source (e.g. '11.8 g/dL', '0.04 ng/mL'). Do not convert units. For non-quantitative studies (imaging, cultures): use a short placeholder such as 'see findings' or 'pending'. Always non-empty.", "value": { "type": "string" } }, { "key": "status", "description": "Interpretation as stated in the source.", "value": { "type": "string", "enum": ["normal", "low", "high", "abnormal", "pending", "not reported"] } }, { "key": "findings", "description": "Free-text findings, qualifiers, or narrative description from the source. Empty string if the quantitative result alone is sufficient.", "value": { "type": "string", "default": "" } } ], "fieldFormat": "{test}: {result} ({status}). {findings}" }, "itemFormat": "{item}\n"}
What you get:
Hemoglobin: 11.8 g/dL (low). Mildly reduced; no overt anemia.Chest X-ray: see findings (normal). No acute infiltrate or effusion.Troponin I: 0.04 ng/mL (normal). Within reference range at 0h and 3h.
Every row renders cleanly: result is always non-empty, so there’s no double-space gap, and the rendered line works for quantitative labs and imaging studies alike.
Why not separate value and unit numeric fields? It’s tempting to model labs as { value: number, unit: string } for strong typing, but fieldFormat is a static template — when a field’s value is empty or missing, the literal text around it (spaces, parentheses, separators) still renders. An X-ray entry that legitimately has no numeric value would render as Chest X-ray: (normal). — note the double space — because the format template {test}: {value} {unit} ({status}). {findings} substitutes empty strings for value and unit.Collapsing measurement into a single string result field that the model is instructed to format consistently sidesteps this. If you need strict numeric typing for a downstream pipeline (e.g. lab values into analytics), consider:
Splitting into two sections — quantitative-labs (array of { test, value: number, unit, status }) and imaging-studies (array of { study, status, findings }). Each gets a clean, type-appropriate fieldFormat.
Post-processing the rendered string client-side to collapse runs of whitespace.
Keeping the result string for human-readable rendering and adding a parallel typed field that downstream consumers read, e.g. numericValue: number. The numericValue field can be omitted from the fieldFormat rendering entirely and only consumed via document.structuredDocument (see Guided Synthesis — response shape).
Example 4 — Prefixes and standard phrasings via `itemFormat`
Goal: A prescription section where every line is prefixed with Rp., and a separate dictation block where every utterance begins with a standard “Pt reports:” header.Pattern: custom itemFormat on the array. The {item} placeholder is the rendered item; everything else around it is literal text.
outputSchema — prescriptions with Rp. prefix
{ "type": "array", "description": "Prescriptions issued at this visit. One prescription per entry.", "items": { "type": "string", "description": "Prescription line: drug + strength + form, route, frequency, duration." }, "itemFormat": "Rp. {item}"}
What you get:
Rp. Amoxicillin 500 mg PO three times daily for 7 daysRp. Ibuprofen 400 mg PO every 6 hours as needed for pain
outputSchema — dictation with fixed prefix per item
{ "type": "array", "description": "Patient-reported statements. One quote or paraphrase per entry.", "items": { "type": "string" }, "itemFormat": "Pt reports: {item}"}
The same {key}/{value} levers exist on objects via fieldFormat (e.g. "**{key}**: {value}") for Markdown-bold headings, or "## {key}\n{value}" for full Markdown headings.
Example 5 — EHR placeholders that survive verbatim
Goal: A plan section that emits literal placeholder strings — like {treatment_shared_motherhood_ivf} — for an EHR system to substitute downstream. The LLM must not generate or paraphrase these; they’re scaffolding for the EHR, not content.Pattern: bake the placeholders directly into fieldFormat as literal text, escaping the curly braces. fieldFormat parses {fieldKey} as a variable substitution; doubled {{ and }} are escapes that render as a single literal { and } in the output. Any clinician-authored content goes into a normal field that is substituted.
Format-string brace escaping. In fieldFormat:
A single{fieldKey} substitutes the value of that field.
A doubled{{ renders as a literal { in the output; }} renders as a literal }.
So to emit literal {treatment_var} in the output (single braces around a placeholder name), write {{treatment_var}} in the format string — that’s {{ (literal {) + treatment_var (literal text, no substitution because there’s no matching field) + }} (literal }).To emit literal {<value-of-field_1>} (literal braces around a substituted value), write {{{field_1}}} — that’s {{ + {field_1} + }}.To emit double-brace placeholders like {{treatment_var}} literally (Mustache/Handlebars style), each output brace needs its own escape: write {{{{treatment_var}}}} in the format string.
outputSchema — EHR placeholders via escaped literal braces
{ "type": "object", "description": "IVF plan block. Clinician narrative goes into {plan_notes}; the remaining lines are EHR placeholders that survive verbatim for downstream substitution.", "fieldFormat": "{plan_notes}\n\nShared Motherhood IVF: {{treatment_shared_motherhood_ivf}}\nWith or without PGT-A (if IVF): {{treatment_pgt_a}}\nNumber of embryos to transfer (if IVF/FET): {{treatment_number_of_embryos}}", "fields": [ { "key": "plan_notes", "description": "Clinician-authored plan narrative for this visit. May be multi-line; do not include the structured EHR placeholder lines — those are emitted separately.", "value": { "type": "string", "default": "" } } ]}
What you get:
Continue stim protocol. Recheck E2 and follicle count in 48h. Consider OPU end of next week.Shared Motherhood IVF: {treatment_shared_motherhood_ivf}With or without PGT-A (if IVF): {treatment_pgt_a}Number of embryos to transfer (if IVF/FET): {treatment_number_of_embryos}
The clinician-generated plan text fills {plan_notes}; the EHR placeholders pass through as literal text because their braces are escaped. No extra fields and no enum tricks needed.
If your EHR expects double-brace placeholders ({{var}} literal in the output, e.g. Mustache/Handlebars conventions), each delimiter needs its own escape. The format string fragment for that line becomes {{{{treatment_shared_motherhood_ivf}}}} — four braces on each side. Verbose but valid.
Example 6 — Different writing styles per subheader via field `description`
Goal: Inside one section, each subheader (field) follows a different writing style — telegraphic for one, flowing prose for another, comma-separated short phrases for a third — without splitting into multiple sections.Pattern: the section’s instructions.writingStylePrompt sets the global default; each field’s description carries the local style rule for that subheader. The model reads both and applies the field-level rule where the two disagree.
outputSchema — per-field writing styles
{ "type": "object", "description": "Pain assessment block. Each subheader has its own writing style.", "fieldFormat": "{key}\n{value}\n", "fields": [ { "key": "Onset & duration", "description": "Telegraphic; no narrative. Anchor with explicit dates or durations (e.g. '3 weeks ago, intermittent').", "value": { "type": "string" } }, { "key": "Pattern of pain", "description": "Flowing prose, one or two sentences. Describe the qualitative character (sharp, dull, throbbing, radiating).", "value": { "type": "string" } }, { "key": "Aggravating / relieving", "description": "Comma-separated short phrases, no full sentences (e.g. 'worse with stairs, eased by ibuprofen').", "value": { "type": "string" } }, { "key": "Treatments tried", "description": "Chronological list, one intervention per line, with outcome appended (e.g. 'Ibuprofen 400 mg TID for 1 week — partial relief').", "value": { "type": "string" } } ]}
What you get:
Onset & duration3 weeks ago, intermittent.Pattern of painThrobbing pain in the lower back, occasionally radiating to the right leg. Worse in the morning, eases over the course of the day.Aggravating / relievingWorse with stairs, eased by ibuprofen, worse after prolonged sitting.Treatments triedParacetamol 500 mg PRN — limited effectIbuprofen 400 mg TID for 1 week — partial reliefPhysiotherapy, 4 sessions — ongoing
When per-field description is enough vs. when to split into multiple sections. Use this pattern when the styles are contrasts within a coherent section that always renders as one block (e.g. a pain assessment). When the subheaders are large enough to be reused independently across templates — or have meaningfully different contentPrompt rules — it’s cleaner to promote each into its own section in the template instead.
Example 7 — OT Activity & Participation (ICF-style fixed subcategories)
Goal: An occupational therapy block with fixed ICF-aligned subcategories — Self-care, Mobility, Communication, Domestic life, Interpersonal interactions — each producing free text. Subcategories always render in the same order; subcategories not covered in the source still appear with an explicit “Not assessed” placeholder so downstream consumers can rely on the structure.Pattern:object + fieldFormat: "{key}\\n{value}\\n" (per-field iteration) + fixed fields[] with a per-field default of "Not assessed". This is the OT-clinic equivalent of the pre-op screening pattern in Example 1.
outputSchema — OT Activity & Participation block
{ "type": "object", "description": "Occupational therapy: Activity & Participation block (ICF-aligned). One free-text observation per fixed subcategory.", "fieldFormat": "{key}\n{value}\n", "fields": [ { "key": "Self-care", "description": "Performance in washing, dressing, toileting, eating, medication management. Note both independent activities and where assistance is needed.", "value": { "type": "string", "default": "Not assessed" } }, { "key": "Mobility", "description": "Walking distance, transfers, stairs, use of mobility aids. Include assistance level.", "value": { "type": "string", "default": "Not assessed" } }, { "key": "Communication", "description": "Receptive and expressive communication, comprehension of instructions, any use of alternative communication.", "value": { "type": "string", "default": "Not assessed" } }, { "key": "Domestic life", "description": "Meal preparation, household tasks, shopping, money management. Note level of independence.", "value": { "type": "string", "default": "Not assessed" } }, { "key": "Interpersonal interactions", "description": "Engagement with family, caregivers and social network. Note changes since onset.", "value": { "type": "string", "default": "Not assessed" } } ]}
What you get:
Self-careIndependent in washing and dressing; requires verbal cueing for medication intake.MobilityWalks 50 m with a walker; needs assistance for stairs. Transfers independently bed-to-chair.CommunicationReceptive language intact; mild word-finding difficulty in expressive speech.Domestic lifeNot assessedInterpersonal interactionsLives with spouse; reports reduced social engagement since onset.
The “Domestic life” subheader still renders even though the source material didn’t cover it — because the field’s default is "Not assessed". Downstream consumers see a stable structure across encounters.
Example 8 — Standard phrases combined with LLM-generated content
Goal: Output a clinical letter (or any structured note) where standard sentence templates wrap model-generated content. The connective tissue — salutations, framing sentences, closing — is verbatim; only the clinical content varies between encounters.Pattern:object + fieldFormat with full sentences containing {field} placeholders. The fixed phrases are just literal text in the format string; each {field} is substituted with model-generated content from fields[].
outputSchema — standard phrasing scaffold
{ "type": "object", "description": "Referral letter scaffold. Fixed phrasing wraps model-generated clinical content.", "fieldFormat": "Dear colleague,\n\nThank you for seeing this patient. {chief_complaint}\n\nThe relevant history is as follows: {hpi}\n\nOn examination: {exam_findings}\n\nMy clinical impression is {assessment}. I have started {initiated_treatment}.\n\nI would appreciate your further evaluation and management. Please do not hesitate to contact me with any questions.\n\nKind regards,", "fields": [ { "key": "chief_complaint", "description": "One sentence stating the reason for referral.", "value": { "type": "string" } }, { "key": "hpi", "description": "Concise history of present illness as flowing prose, 2-4 sentences.", "value": { "type": "string" } }, { "key": "exam_findings", "description": "Key positive findings and pertinent negatives, telegraphic.", "value": { "type": "string" } }, { "key": "assessment", "description": "Provisional diagnosis or working impression, one sentence.", "value": { "type": "string" } }, { "key": "initiated_treatment", "description": "What you have already started (medication, investigation, lifestyle advice). If none, the default phrasing is used.", "value": { "type": "string", "default": "no treatment to date" } } ]}
What you get:
Dear colleague,Thank you for seeing this patient. The patient presents with a 6-week history of progressive exertional dyspnea.The relevant history is as follows: 64-year-old former smoker with known hypertension. Symptoms worsening over the last two weeks with bilateral ankle swelling and orthopnea.On examination: BP 148/92, HR 88 regular, bibasal crackles, pitting oedema to mid-shin bilaterally.My clinical impression is decompensated heart failure with preserved ejection fraction. I have started furosemide 40 mg PO daily.I would appreciate your further evaluation and management. Please do not hesitate to contact me with any questions.Kind regards,
Compare with Example 5. In Example 5 the {{var}} text is literal in the output — the model never touches it. In Example 8, every {field} is substituted — the literal text is just the connective sentences between the substitutions. Same fieldFormat mechanism, opposite intent for the placeholders.
Example 9 — Bullet points nested inside a numbered list
Goal: A Plan section where each plan item carries a top-level marker (bullet by default — see the numbering caveat below) and 0 or more indented sub-bullets with dosing, follow-up timing, contingencies or anything else worth itemising under that step.Pattern: outer array with a custom itemFormat containing the {item} placeholder. Each item is an object with a summary string field plus a nested details array whose own itemFormat includes a leading indent + bullet marker (e.g. " - {item}\n"). Compose summary + details via fieldFormat. The outer array applies its itemFormat once per top-level entry; the inner array’s itemFormat controls the indented bullet rendering.
outputSchema — plan with nested bullet details
{ "type": "array", "description": "Plan items, each optionally followed by indented sub-bullets with details.", "itemFormat": "* {item}\n", "items": { "type": "object", "fieldFormat": "{summary}\n{details}", "fields": [ { "key": "summary", "description": "One-line summary of the plan item. Do not prefix with a bullet — the outer itemFormat applies the marker.", "value": { "type": "string" } }, { "key": "details", "description": "Sub-bullets with instructions, dosing notes, follow-up timing or contingencies relevant to this plan item. Omit if the summary is self-contained.", "value": { "type": "array", "description": "Zero or more sub-points under the plan item.", "itemFormat": " - {item}\n", "items": { "type": "string" } } } ] }}
What you get:
* Start corticosteroid cream BID - Apply thin layer to affected area - Continue for 2 weeks, then taper to once daily* Order CBC and CMP - Draw before next visit - Patient to fast 8h prior* Schedule follow-up in 4 weeks* Educate patient on warning symptoms - Worsening rash or new lesions - Fever or systemic symptoms - Allergic reaction to topical (rare)
How it works:
The outer array.itemFormat: "* {item}\n" puts an asterisk (or any literal marker you choose — -, •, 1.) in front of each top-level item.
The inner array.itemFormat: " - {item}\n" is the key lever: the two leading spaces indent each bullet under its parent, and - is the bullet marker. Adjust spacing for deeper nesting (e.g. " * {item}\n" for a four-space-indented sub-sub-list).
The object’s fieldFormat: "{summary}\n{details}" concatenates the summary line with the sub-bullets directly underneath, preserving the visual nesting.
If a plan item has no sub-bullets, the details array renders as empty and the entry collapses to just the summary line (item 3 above).
Auto-incrementing numbered lists (1., 2., 3., …) cannot be produced via itemFormat alone. The format string is applied per item with no built-in counter — every item would get the same literal prefix (e.g. "1. {item}" would render "1." in front of every entry).If you need true sequential numbering, change the outer shape to a single string and instruct the model to emit the numbering itself:
{ "type": "string", "description": "Numbered plan with indented sub-bullets. Format each plan item as a numbered line ('1.', '2.', '3.', ...) followed by zero or more indented '- ' sub-bullets two spaces deep."}
The trade-off: you lose the typed array shape (and the details.structuredDocument you’d get from array of object). Keep the array shape if you can live with bullets, switch to string + prompt if the numbering is mandatory.
The same indented-bullet trick scales further within the array shape. To get a third level (outer marker → bullet → sub-bullet), nest another array inside details.items whose own itemFormat uses more leading spaces — e.g. " · {item}\n". The model handles arbitrary nesting; readability for the human reader is usually the constraint.
Example 10 — Diagnoses with indented detail bullets
Goal: A Diagnoses (problem list) section where each diagnosis is rendered with a top-level marker and an optional ICD/SNOMED code, and each diagnosis carries indented sub-bullets describing the clinical findings, reasoning, status, and management pertaining to it. Same structural pattern as Example 9; the field descriptions and the kind of content the LLM is steered toward are diagnosis-focused.Pattern: outer array with a custom itemFormat (e.g. "* {item}\n"), inner items are objects with a diagnosis summary string and a nested details array. The details.itemFormat uses the leading-indent + bullet marker trick to render each sub-point indented under its diagnosis.
outputSchema — diagnoses with indented details
{ "type": "array", "description": "Problem list of diagnoses discussed in the encounter, ordered with diagnoses related to the current visit first. Each diagnosis is followed by indented sub-bullets for findings, reasoning, status, and management.", "itemFormat": "* {item}\n", "items": { "type": "object", "fieldFormat": "{diagnosis}\n{details}", "fields": [ { "key": "diagnosis", "description": "Diagnosis label as stated in the source material. Include an explicitly mentioned ICD-10 or SNOMED code in parentheses after the name when available (e.g. 'Type 2 diabetes mellitus (E11.9)'). Do not prefix with a marker — the outer itemFormat applies one.", "value": { "type": "string" } }, { "key": "details", "description": "Sub-bullets pertaining to this specific diagnosis. Typical entries: relevant clinical findings or evidence, reasoning, current status (acute, chronic, resolving, stable), and management or plan. One bullet per distinct point; omit categories not supported by the source material.", "value": { "type": "array", "description": "Zero or more sub-points pertaining to the diagnosis.", "itemFormat": " - {item}\n", "items": { "type": "string" } } } ] }}
What you get:
* Eczema, atopic dermatitis (L20.9) - Acute flare on bilateral forearms; itchy, no oozing or secondary infection - Likely triggered by recent change in laundry detergent (patient history) - Plan: topical corticosteroid + trigger avoidance, taper after 2 weeks* Seasonal allergic rhinitis - Mild symptoms during spring pollen season - Currently controlled on PRN second-generation antihistamine - No escalation needed* Type 2 diabetes mellitus (E11.9) - Established; on metformin 1000 mg PO BID - Last HbA1c 6.8% (3 months ago); target met - Continue current management; recheck HbA1c in 3 months* Hypertension, essential (I10) - BP today 132/84 mmHg; trending stable on current regimen
How it differs from Example 9:
Structurally identical schema shape (outer array + object item with summary + nested details array). The pattern is reusable.
The field descriptions are diagnosis-specific: diagnosis prompts for label + optional code; details prompts for findings, reasoning, status, management.
Same graceful-collapse behavior — if a diagnosis only has a name (and the source material doesn’t support sub-bullets), the entry renders as just the diagnosis line (item 4 above).
Pair this schema with a writingStylePrompt like “Bullet text is telegraphic; one clinical point per bullet. Do not repeat the diagnosis name inside the bullets.” to keep the indented bullets tight and avoid the model restating the diagnosis label on every line.
Need auto-numbered diagnoses (1., 2., 3., …)? Same caveat as Example 9: itemFormat has no built-in counter, so use string + a contentPrompt that instructs the model to emit numbered lines with two-space-indented sub-bullets. Trade-off: you lose the typed array-of-objects shape and any structuredDocument benefits.
Example 11 — Keyword-driven classification (single enum value out)
Goal: The section emits exactly one value from a closed list based on cues in the source material. No free text, no explanation — just the categorical value. Useful for EHR picker fields, analytics dashboards, routing logic, or any downstream consumer that needs a stable enum value rather than narrative content.Pattern:string + enum (closed value set) + a rich description that teaches the model the keyword-to-value mapping. Add default for the “no signal in the source” case so the field always renders something deterministic.
outputSchema — encounter disposition classifier
{ "type": "string", "description": "Classify the patient's encounter disposition based on the clinician's decisions during the visit. Output exactly one of the enum values — no explanation, no surrounding text. Use these keyword cues from the source material:\n\n- 'admit', 'admission', 'inpatient', 'observation unit', 'staying overnight' → 'admit'\n- 'discharge home', 'send home', 'follow up outpatient', 'home with instructions' → 'discharge'\n- 'transfer to', 'higher level of care', 'tertiary center', 'transferring out' → 'transfer'\n- 'consult', 'specialist referral', 'awaiting specialist' → 'consult-pending'\n- 'against medical advice', 'AMA', 'left without being seen' → 'left-ama'\n\nIf the source material doesn't explicitly state a disposition decision, output the default value.", "enum": ["admit", "discharge", "transfer", "consult-pending", "left-ama", "undetermined"], "default": "undetermined"}
What you get (for a transcript where the clinician says “…let’s send her home with the antibiotic course and a follow-up in two weeks”):
discharge
Just the bare enum value, nothing else. Downstream consumers can map it to a UI dropdown selection, an EHR field, a routing decision, or a metric label without any parsing.How it works:
enum is the hard constraint. The model can only emit one of the listed values — "admit", "discharge", "transfer", "consult-pending", "left-ama", or "undetermined". Free text never leaks through.
description is where the classification logic lives. Spell out which input keywords or phrases map to each enum value. The model reads this alongside the section’s instructions.contentPrompt when picking the output.
default covers the empty-state case — if the source material has nothing to support a disposition decision, the field renders "undetermined" deterministically. (Without a default, the model might still pick a value or return an empty string. With it, your downstream consumer always sees a known value.)
Pair this schema with a tight writingStylePrompt like “Output the enum value only. No quotation marks, no surrounding sentences, no explanations.” if you notice the model occasionally adding narration. The schema alone usually suffices, but the extra instruction is cheap insurance for a strict categorical output.
Other clinical use cases that fit this pattern: triage acuity (ESI levels 1–5), pain severity (mild / moderate / severe), tobacco status (never / former / current), pregnancy status, fall-risk class, cancer stage, AMS level, NYHA functional class, audit/consent yes-no-unknown checks. Each one is a string + enum + descriptive keyword rules. For numeric scoring (Apgar, GCS, pain 0–10), use number + minimum/maximum instead — see Example 3 patterns.