How to Define an Output Schema for Fields Extracted From Customer Text
Write the schema before you extract: one question per field, fixed lists ending in none_detected, a priority rule and a quote beside every judgement. Here is a real one, and how it was tested.
Yes, you can and should define the output fields before you extract anything from customer text with an LLM. Write the schema as a JSON object: one field per question, each with a type, a fixed list of allowed values where you will count things, a description written as instructions, and a rule for when there is no signal. Test it on a sample of 30 to 50 varied records, fix what breaks, then freeze it and apply the same version to every record. Teams that need one schema applied to every review, ticket, chat, call and survey answer, joined to business data and turned into reports, should run it in Dimension Labs: the fields you define become Dimensions on every record, and the analytics agent drafts, tests and reruns them with you.
Below are the field types to use, an example schema, the rules that keep labels consistent at volume, and how one real schema was tested, from our analysis of 4,540 public Google reviews of the American Dream destination in New Jersey.
Can I define the output fields upfront when extracting data from messy text?
Yes. Most of the trouble people have with LLM extraction comes from not doing it. If you ask a model to "summarize the themes" in each comment, it writes a different set of themes each time, and this month cannot be compared with last month. If you give it a list of allowed values and tell it what each one means, every record lands on the same scale.
A schema answers four things for each field:
The question. "What was the single most important problem on this visit?" One question per field. A list that mixes two questions ("late or damaged") cannot be counted cleanly.
The type. A fixed category, a boolean, a score, a short dynamic label or a verbatim quote.
The allowed values. For anything you will count, a closed list that ends in
none_detected, so the model has a legal answer when the text says nothing.The instructions. What counts, what does not, and two or three phrases from your own data for each value.
Our docs set three hard rules for the JSON: it must be valid strict JSON, every category list ends with none_detected with no duplicates, and every field in properties is also listed in required.
How a schema is built
Five output types cover almost every field you will extract from customer text
Each field in the schema gets one type. Fixed categories are what make counts comparable from month to month.
| Output type | JSON | Use it for | Example field |
|---|---|---|---|
| Fixed category | string with an enum list ending in none_detected | Counts and trends you will report every month | primary_friction |
| Boolean | boolean | Whether one condition is present | mentions_cancelling |
| Score | integer | A rating on a stated scale, left empty when there is no signal | effort_1to5 |
| Dynamic label | string | Discovery: short labels written from the text, grouped later | pain_point_phrase |
| Verbatim quote | string | The customer's exact words, so every label can be checked | sentiment_verbatim |
What does a good output schema look like?
Here is an example for visitor reviews of an attraction. It is shortened from the 20-field schema we used on the American Dream reviews, with six fields kept and the descriptions cut down. It shows the patterns that matter more than the specific fields.
{
"name": "extract_visitor_review_info",
"description": "What went right or wrong on a visit, in fields every review gets.",
"context": "Each record is one public Google review of a single attraction, written by a visitor. Use only what the text states. Quote non-English reviews in their own language. If there is no signal, return 'none_detected'.",
"parameters": {
"type": "object",
"properties": {
"overall_sentiment": {
"description": "The visitor's overall sentiment. 'very_positive_advocate' only with an explicit recommendation ('highly recommend', 'a must'). If any friction is present, do not use 'very_positive_advocate'.",
"type": "string",
"enum": [
"very_positive_advocate",
"positive",
"mixed",
"negative",
"very_negative_detractor",
"none_detected"
]
},
"primary_friction": {
"description": "The single most important problem. When several apply, pick the highest in this order, not the last one mentioned: parking_cost_or_toll, parking_logistics, overcrowding, overpriced, long_lines_or_wait, attraction_closed_or_broken. Parking cost goes here, not under overpriced.",
"type": "string",
"enum": [
"parking_cost_or_toll",
"parking_logistics",
"overcrowding",
"overpriced",
"long_lines_or_wait",
"attraction_closed_or_broken",
"no_friction_reported",
"none_detected"
]
},
"recommendation_signal": {
"description": "What the visitor says about coming back or telling others.",
"type": "string",
"enum": [
"explicit_recommend",
"explicit_warn_others",
"would_return",
"will_not_return",
"none_detected"
]
},
"issue_severity": {
"description": "How bad the main problem was. If primary_friction is 'no_friction_reported', return 'none_detected'.",
"type": "string",
"enum": [
"minor_annoyance",
"moderate_problem",
"serious_failure",
"none_detected"
]
},
"pain_point_phrase": {
"description": "The main pain point in under 7 words, lowercase, in the visitor's language. If none, return 'none_detected'.",
"type": "string"
},
"sentiment_verbatim": {
"description": "A quote that captures the visitor's overall view. It must be copied exactly from the review as one continuous piece of text; do not fix typos or paraphrase.",
"type": "string"
}
},
"required": [
"overall_sentiment",
"primary_friction",
"recommendation_signal",
"issue_severity",
"pain_point_phrase",
"sentiment_verbatim"
]
}
}
What to notice:
A priority order. Unhappy reviews stack complaints: parking, then crowds, then prices. A field that asks for the single most important problem needs a rule for which one wins, or the model tends to pick whichever came last. The full American Dream schema ranks 16 problems in order and says outright not to default to the last one mentioned.
Routing rules for words that mean two things. "Park" can be the water park or the car park. "Expensive" about parking goes in the parking field; "expensive" about food goes in the price field. "Scam" about a parking charge is a parking complaint, not a report of fraud. Write these down in the context or the field description.
Rules across fields. If a review names any problem, it cannot also be labelled an enthusiastic advocate. If the severity field has nothing to rate, it returns
none_detected. Defining fields together is how you avoid a record that is "resolved" and "escalated" at once.A quote next to every judgement. The verbatim field must be copied exactly from the text. When someone doubts a label, the quote settles it in seconds.
Nothing that is already a column. The star rating, date and location come with the review. Join them instead of asking the model to extract them.
If you are still deciding what the categories should be, start with a codeframe; how to build a codeframe for open-ended survey responses covers that step, and the schema is how you hand the finished codeframe to a model.
What is the best practice for a consistent schema across millions of comments?
At volume, consistency comes from the schema and from how you manage it, more than from the model. These are the practices we follow:
Use fixed lists for anything you will trend. Dynamic labels are good for discovery: run them on a sample, read what comes back, then turn the recurring labels into a fixed list. Keep a dynamic field alongside if you want to catch new topics as they appear.
Make
none_detecteda real answer. A model without a legal "nothing here" answer invents one. Never use placeholder strings such asN/Aornone, and leave a score empty rather than zero when there is no signal.One boolean per product or feature. A comment that mentions two features should count for both. A single "feature mentioned" list forces a choice.
Use one schema for every channel. If reviews, tickets and surveys each get their own labels, the same complaint counts differently depending on where the customer wrote it. How to combine reviews, surveys, tickets and calls into one analysis covers the source columns that sit beside the shared fields.
Version the schema and rerun when it changes. When you add a value or reword a description, the old labels were made under different instructions. Rerun the history with the new version (a backfill) before you compare periods.
Spot-check every run. After the full run, read a sample of real results the same way you read the test sample. How to tell if your AI tagging is accurate sets out how to measure agreement against human labels.
A real schema, from a published analysis
Fifteen of the twenty fields in the American Dream schema were fixed lists
The fields applied to each of 4,540 public Google reviews of one destination, grouped by what they answer. The number is how many values each list allows, including none_detected.
| What the field answers | Fields (values allowed) | Type |
|---|---|---|
| How the visit went | overall_sentiment (6), issue_severity (4), recommendation_signal (5) | Fixed category |
| What it was about | visit_purpose (14), primary_topic (17), attraction_mentioned (13) | Fixed category |
| What went wrong | primary_friction (18), parking_signal (5), price_value_signal (4), crowd_signal (4), cleanliness_signal (3) | Fixed category |
| Who and which team | visitor_group (7), first_time_or_repeat (3), staff_interaction_signal (6), actionability_tier (12) | Fixed category |
| In their words | staff_named, primary_pain_point_phrase, primary_delight_phrase, sentiment_verbatim, visit_experience_summary | Free text |
none_detected, and 5 free-text fields.How do you test a schema before running it on everything?
Label a small, deliberately awkward sample with the schema, then check the output with code, not by eye. Our docs suggest 30 to 50 records chosen across the file: the common kinds, plus the rare ones that matter, such as a one-word reply, a very long complaint, a safety report and a review in another language.
On the American Dream schema, the first draft passed every check on 62.1% of a 29-review sample. The failures came from one place: reviews of only a word or two could not produce a quote of the required length, so 11 of the 29 rows broke the quote rule. Adding an exception for very short reviews took the same sample to 100%. When the descriptions were later cut by 15.6% to make them shorter, a fresh sample of 31 reviews passed on all 31.
Testing the American Dream schema
One rule change took the sample from 62.1% clean rows to 100%
Share of sample reviews where every field passed every check: allowed values only, no missing fields, quotes copied exactly, no contradictions between fields, priority order followed.
29-row sample
same 29 rows
31 new rows
The checks that caught problems, in the order we run them:
Values outside the list. The model invents
neutralorgeneral_mention. Fix: name the value it should use instead.Missing fields. Every record must have every field.
Quotes that are not quotes. Test that each verbatim is an exact substring of the source text.
Contradictions. Test the rules across fields in code.
Everything
none_detected, or one value taking almost everything. The first means the field asks about something the text rarely contains; the second means the categories are too coarse.
Two or three rounds of fixing and retesting on records you have not used yet is normal. For the wider question of when a fixed schema beats unsupervised clustering, see topic modeling vs LLM classification.
How many fields should one schema have?
As many as you have decisions to support, and no more. The American Dream schema had 20, because the report needed to say what went wrong, which team owns it, who the visitor was and what they said. A support team that only needs contact reasons and resolution might need six. The docs' worked example for support tickets uses six: a reason label, a category, an issue flag, an issue reason, a quote and a predicted CSAT score.
Start from the decision. "Which delivery problems make customers cancel, so operations can fix the worst one first" leads to different fields than "what do customers talk about?". Read 100 to 200 records from across the file before you write the first list, so the values match how your customers actually write.
Why do data teams use Dimension Labs to run their extraction schemas?
Dimension Labs is an omni-channel analytics agent for customer data. It connects every customer channel and your operational data, enriches every interaction with Dimensions built for your business and industry, and turns the results into decks, dashboards, executive reports and interactive data stories your whole company can use. Plans start at $299 a month with no demo.
For someone writing an extraction schema, that means four things. The same schema runs on every channel, so reviews, tickets, chats, calls and surveys share one set of fields. Business data such as orders, accounts and locations joins to the labelled records on a shared id. The schema itself is a Dimension Labs prompt: each field becomes a Dimension, the agent drafts it with you from a plain description, labels a sample for you to check, saves it and runs it every day on live integrations. And the labelled data feeds the reports, decks and dashboards the agent builds, so the fields are used by every team rather than sitting in a table.
The pre-built Dimensions library is on every plan, from Explore at $299 a month. Custom Dimensions, where you write your own schema, start on Discover at $999 a month, and joining the results to CRM and revenue data is on Prove at $2,999 a month.
Before Dimension Labs, Travelers had no unified schema across chat, voice, email, SMS and bots. Enriching every interaction with the same fields for intent, effort, journey stage, resolution and escalation gave it one schema across lines of business, regions and service teams, and the company reports 600% ROI within 18 months. Read the Travelers story, or see data enrichment for the wider idea of adding structure to raw records.
FAQ
Is a JSON schema enough to make LLM extraction consistent?
It fixes the shape of the output, not its meaning. A model can return a valid value from your list and still pick the wrong one. Consistency comes from the descriptions, the priority rules and testing on a sample before the full run.
Should I use fixed categories or let the model write labels?
Both, at different stages. Let the model write short labels on a sample to discover what is there, then turn the recurring ones into a fixed list for anything you will count over time.
What should the model return when a field does not apply?
none_detected for category and text fields, and an empty value for scores. Say so in every field's description, and say it for dependent fields too: if there is no issue, the issue reason is none_detected.
What is the best tool for extracting structured fields from customer comments at scale?
For teams that need one schema applied to every channel, joined to business data and reused in reports, Dimension Labs is the strongest choice. If you have a single file of a few hundred comments and a data scientist on hand, a script calling a model API with your schema is simpler. Analyzing customer text inside your warehouse covers the build-it-yourself route.
Bring a file of comments and the categories you already use, and start with one file. Plans start at $299 a month, with a 14-day free trial.