Try this the next time you build a ticket classifier. Most teams write a long prompt that ends with “respond with only one of: billing, technical, account.” Then they add a regex or a retry loop for the day the model answers “Billing issue” or “technical/account.” Over a few months that glue code grows. Someone adds lowercase normalization, then a fuzzy match, then a fallback queue for anything that still doesn’t fit. This approach does it differently, and it’s more reliable: put the allowed labels in the response schema and let the prompt do only the explaining.
That’s the idea behind a note on Ling-3.2-flash from Ant Ling. Their structured-output examples use enum constraints in JSON Schema. The schema says what the model is allowed to output. The instructions say what each category means and how to pick between them. Think of it as the difference between a dropdown menu and a free-text box. Nobody builds a form with a free-text field and then writes a paragraph begging users to type one of three words.
The old way vs. the new way
The old way mixes two jobs in one place. The prompt has to define the vocabulary (“use exactly these three words”) and also teach judgment (“billing is about charges, technical is about things breaking”). When the output drifts, you can’t tell whether the model misunderstood the categories or just ignored the formatting rule. You end up editing the prompt blindly, adding capital letters and “IMPORTANT” and hoping something sticks.
The new way splits them:
- Schema: owns the vocabulary. The
categoryfield is anenumwith the permitted labels. Nothing else is a valid answer. - Prompt: owns the meaning. It explains each category and what to do when a ticket sits between two of them.
Now each failure has one home. A malformed label is a schema problem. A wrong-but-valid label is a prompt problem. That alone makes debugging much faster. When you review a bad batch, you sort the errors into two piles. The first pile should be empty, since the schema blocks it. Everything in the second pile is a wording fix you can make in the prompt, and you can measure whether the fix worked.
Here is a quick example. A ticket says, “I was charged twice and now I can’t open the app.” The old prompt might return “Billing and technical” because the model wanted to be helpful and the formatting rule was buried in paragraph four. The schema version cannot return that string. It has to pick one value, so the prompt’s tie-break rules decide which one.
How to set it up 🛠️
- Define the schema. One object with a
categoryfield of type string and anenumofbilling,technicalandaccount. Mark the field as required so the model can’t skip it. If you also want a short reason, add a separatereasonfield. Keep it second so the label comes first. - Write one line per category in the prompt. Say what belongs there, in plain words. “Billing: charges, refunds, invoices, plan prices.” “Technical: crashes, error messages, slow loading, features that don’t work.” “Account: login, password, email changes, deleting an account.” Short lines work better than long definitions.
- Add tie-break rules. Borderline cases are where classifiers fail. “If a customer can’t log in because a payment failed, choose billing, because that’s the root cause.” Write down a rule for every pair of categories that tends to overlap. If your team argues about a case in a meeting, that argument belongs in the prompt.
- Give the model a legal way out. This one comes from a good comment on the thread. If the enum only allows three labels, a feature request or a spam message still gets forced into one of them. Add an
otherorneeds_reviewvalue and tell the model when to use it. Then route those tickets to a human, and read that queue every week. It tells you which categories you forgot to create. - Test on ugly examples. Run 20 or 30 real tickets, especially the confusing ones, and look at where the labels land. Include tickets with two problems, tickets written in a hurry, and tickets with typos. Save the set and rerun it every time you change the prompt, so you can see whether an edit helped or quietly broke something else.
Step 4 is the one people skip, and it matters most. A hard constraint without an escape hatch doesn’t make the model more accurate. It just makes the wrong answers look clean. A clean-looking wrong label is harder to catch than a messy one, because nobody has a reason to look at it. A messy answer at least trips your parser and gets noticed.
Why this is worth the ten minutes
Parsing gets simpler because the output is always one of your known values. Your downstream code can branch on it without cleanup. You can delete the regex, the retry loop and the “did it say Billing or billing” check. And your prompt gets shorter and easier to read, because it’s only explaining categories instead of policing formatting. A shorter prompt is also easier to hand to a teammate, who can read it in a minute and tell you which definition is off.
There’s a smaller benefit too. Once labels live in the schema, adding a fourth category is a two-step change: add the value to the enum and add one line to the prompt. You don’t have to hunt through instructions for every place the old list was repeated.
One caveat: I’m going off the Reddit summary here, so check the Ling-3.2-flash docs for the exact schema syntax your setup supports. The pattern itself carries over to most models that support structured output.
Pick one classifier you already have running. Move the labels into an enum, add an escape value, and compare the results on your messiest 20 tickets. I’d bet you find a few surprises!
Frequently Asked Questions
Q: Why add an “other” or “needs_review” label if I’m already using an enum?
A strict enum forces every ticket into one of your categories, so a feature request or spam message can end up labeled as billing with full confidence. An escape value like other or needs_review gives the model a legal way out for those cases. Add one sentence to the prompt that says when to use it, and send those tickets to a person.
Q: How detailed should the label definitions be in the prompt?
Keep them short and contrastive instead of long. Something like “billing means money already charged or invoiced, while account covers login, profile, and access” tells the model where the boundary sits. Brief definitions that name the line between two labels do more work than paragraphs describing each one.
Q: Do borderline examples really help more than extra rules?
Borderline examples tend to help more than adding more rules. A pair of look-alike tickets, one labeled billing and one labeled account, shows the model exactly where the line is. Rules pile up and can conflict with each other, while a well-chosen pair makes the decision concrete.
Q: How do I know if the classifier is accurate enough?
Label a small set of tickets by hand and run the classifier on them. Accuracy usually drops between the two labels that sit closest together, so look at that confusion first. You don’t need a huge dataset to find the weak spot.
Ling-3.2-flash documents enum-constrained output for classification prompts
by u/DraxNation in PromptEngineering