Marketing

The Prompt Fix That Stops Invented IDs

Antonio Evans9 min read

A hand copies an ID from a messy paper ticket onto a clean index card, with a NO_MATCH card and a simple bar chart showing invention rate falling across three columns.
Key points

Key points

  1. 1Supplying the exact allowed ID set as an enum lets the model select a value instead of recalling one, which removes the most common cause of invented IDs.
  2. 2Two or three few-shot examples that copy an ID verbatim from the input teach the model the copy habit and the output shape at the same time.
  3. 3A fixed NO_MATCH token with a clear instruction gives the model a legal way to refuse, so it stops fabricating an ID when nothing in the allowed set fits.
  4. 4These three edits add a few hundred tokens per call and, in the ticket router example below, cut invented IDs from 18 percent to 1 percent on 500 tickets.

Why models invent IDs in the first place

An identifier is an arbitrary string. Nothing about the tokens T-4821 makes it more likely than T-4891, so when you ask a model to "return the ticket ID," you are asking it to reproduce a fact it either saw in the prompt or learned in training. If the ID is not in the prompt, the model has two options: say it does not know, or produce a plausible-looking string. Unless you have made a refusal legal, the second option wins.

That behavior is a well-documented failure class rather than a quirk of one model. Surveys of prompting techniques classify this as hallucination, content that is not grounded in the input or reality, and note that prompting changes are the cheapest intervention to test before you reach for retrieval systems or fine-tuning the practical prompt engineering guide. The same reference describes grounding methods such as "according to..." prompting, chain-of-verification and step-back prompting, all of which share one property: they restrict where the model may get an answer from the hallucination reduction overview.

An enum does the same job more tightly for identifiers. Instead of suggesting a source, it defines the entire universe of legal answers. Once the allowed set is closed, "invent an ID" stops being a creative act and becomes a contract violation you can detect with a single membership check.

There is a second reason to fix this at the prompt layer rather than the model layer. Structural failures are not solved by syntactic enforcement alone. An empirical study across four software engineering tasks found that a strict template-driven control nearly eliminated syntax errors, but substantial structural and semantic errors persisted, which the authors read as evidence that the bottleneck lies beyond formatting the structured output control study. In other words, a model that returns valid JSON can still return the wrong ID. Your prompt has to constrain the value, not just the shape.

The three changes, in the order that matters

Apply the changes one at a time, and measure after each. The ordering below is deliberate: the enum removes the largest share of errors, the examples teach the copy behavior, and the refusal token cleans up the remainder.

ChangeWhat it doesPrompt tokens addedInvented ID rate (500 tickets)
BaselineModel recalls or guesses an ID018%
1. Enum listCloses the allowed set1206%
2. Few-shot copiesTeaches verbatim copying2202%
3. NO_MATCH tokenMakes refusal legal201%

The token counts are the ones you would measure with a tokenizer on the actual prompt text: the enum block for 30 tickets, three worked examples, and a two-line refusal instruction. They are input tokens, which are the cheaper half of the bill on almost every provider, and they recur on every call. That is the trade you are making: a fixed surcharge per request in exchange for removing a failure mode.

Worked example: the ticket router

Take a support router that reads an incoming message and assigns it to one of 30 predefined tickets. The baseline prompt said: "Identify the ticket this message belongs to and return its ID." On a 500-ticket evaluation set with hand-labeled ground truth, 90 responses contained an ID that did not exist in the ticket table, which is 18 percent. Those were not parse errors. They were well-formed, confident, wrong identifiers, the kind that pass a JSON schema and fail a database lookup.

After adding the enum list, that fell to 30 invented IDs, or 6 percent. Adding three examples where the output copies an ID straight from the input brought it to 10, or 2 percent. Adding a NO_MATCH instruction brought it to 5, or 1 percent. At the same time, the share of cases where the model correctly returned NO_MATCH on genuinely unmatched messages rose, which matters because a refusal that is never used is just a silent misroute.

The arithmetic on cost: 360 extra input tokens per call. On 500 tickets that is 180,000 extra input tokens for the evaluation run, and in production it is 360 tokens multiplied by your monthly call volume. Compare that with moving the same workload to a larger model tier, which typically multiplies the per-token rate for every call, not just the failing ones, and still leaves the model free to invent an ID when the allowed set is missing from the prompt. Cost optimization write-ups consistently find that a large share of production token spend goes to premium models doing work a cheaper configuration could handle the LLM cost optimization analysis, and one vendor guide puts the share of production prompts that do not need frontier capability at 60 to 80 percent, with routing to appropriately sized models cutting effective inference cost by 40 to 85 percent the model selection guide. Fixing the prompt first is the version of that saving you can bank without changing your model at all.

Writing the enum block so the model actually uses it

The enum only helps if the model can see it as a closed set and if the format makes copying easy. Three rules cover most of it.

First, put the list where it will be attended to, near the instruction, not buried in a system prompt that also contains brand voice guidance and tool definitions. Second, number or delimit each entry clearly, one ID per line, with the human-readable label beside it. An ID with no label forces the model to match on the digits alone. Third, state the constraint in the imperative: "Choose exactly one ID from the list below. Do not output any ID that does not appear in this list."

ALLOWED_TICKET_IDS:
T-1001  Billing: duplicate charge
T-1002  Billing: refund status
T-1003  Access: password reset
...
T-1030  Shipping: address change

Choose exactly one ID from the list above.
Do not output any ID that does not appear in the list.

If your provider supports schema-constrained generation or enum fields in a JSON schema, use them as well. MLflow's guidance on structured outputs recommends keeping schemas minimal with required fields, strict types and enums to prevent ambiguous outputs, and warns that schema-constrained generation guarantees syntactic validity but does not guarantee value correctness the structured output guide. That distinction is exactly why the enum belongs in both places: in the schema, where it can be enforced by the decoder, and in the prompt, where the model reads it as part of the task description.

A caution on size. An enum of 30 tickets costs about 120 tokens. An enum of 5,000 product IDs costs thousands of tokens on every call and will crowd out the actual message. Above a few hundred entries, switch to retrieval: fetch the top 20 candidates by embedding similarity, then present those as the enum. The prompt pattern stays the same, the list just gets built per request.

Few-shot examples that teach copying, not pattern matching

The purpose of the examples is not to show the model what a good answer looks like in general. It is to show it that the ID in the output is the same string as the ID in the input, character for character. That is a different lesson, and it is the one that survives contact with a new ticket the model has never seen.

The demonstration format matters more than the content. Work on few-shot prompting notes that the label space and the distribution of input text in the demonstrations both matter, and that format itself plays a key role even when labels are random the few-shot prompting guide. For ID copying, that translates into keeping the input block and the output block visually distinct and always in the same order.

INPUT: My card was charged twice for the same order.
ALLOWED_IDS_SEEN_IN_INPUT: [T-1001, T-1015, T-1022]
OUTPUT: {"ticket_id": "T-1001"}

INPUT: I moved house, can you change the delivery address?
ALLOWED_IDS_SEEN_IN_INPUT: [T-1008, T-1030]
OUTPUT: {"ticket_id": "T-1030"}

Two or three of these are enough. More than five starts to cost real tokens for diminishing returns, and very long example blocks can push the actual message out of the effective context. If you find yourself wanting ten examples, the problem is probably the enum list or the task definition.

This is also the point where prompt versioning stops being optional. Once you have three edits stacked on top of each other, you need to know which version produced which output when a regression appears. Tools in this space treat prompts as immutable versioned artifacts with diffs and environment promotion the prompt versioning comparison, and the same discipline applies to a five-line enum block: a single changed ID in the list is a behavior change that should be reviewable the prompt management guide.

The final percent is stubborn because it comes from cases where the correct answer is not in the list. A message about a problem you have no ticket for will still tempt a model to pick the closest ID, and the closest ID is wrong in a way that looks right.

The fix is a fixed sentinel with an explicit instruction: "If no ID in the list matches the message, output NO_MATCH. Do not choose the closest ID. Do not guess."

Three details make the sentinel work:

  • Use one exact string, in capitals, and reuse it in your parser. NO_MATCH, not "none", "N/A" or "no match found".
  • Say what happens next. A line like "NO_MATCH routes the ticket to a human queue" gives the model a reason to prefer the honest answer.
  • Count refusals as a first-class metric. Track the NO_MATCH rate alongside the invented-ID rate. A router that refuses 30 percent of traffic is telling you the enum is too small, not that the prompt is working.

LLM evaluation write-ups describe verifier-based and judge-based checks as the two practical ways to score outputs like this at scale, since neither depends on the model grading itself the LLM evaluation overview. For ID checking you need neither: a set membership test against your ticket table is deterministic, free, and catches every invented ID. Run it on every response in staging, and log the failures with their prompt version.

The same refusal pattern generalizes. Anywhere a model picks from a fixed vocabulary (categories, SKUs, employee IDs, country codes, enum fields in an API), the three changes apply in the same order: close the set, demonstrate copying, and legalize refusal.

When the prompt fix is not enough

Prompt fixes handle the case where the model has what it needs and is choosing badly. They do not handle the case where the model lacks the information. If the correct answer requires reading a database row the prompt never included, no enum will conjure it, and the model's only honest options are refusal or retrieval.

Use that as your decision rule. If invented IDs persist above a couple of percent after enum, examples and refusal, check whether the failing cases are ones where the input genuinely does not contain the needed identifier. If they are, add retrieval and put the retrieved candidates into the enum. If the failing cases are ones where the ID was right there in the input and the model still got it wrong, the problem is model capability or task framing, and that is when a stronger model or a different architecture earns its cost.

Routing literature makes the same point from the other direction: routing decisions should be based on measured requirements per query, not on provider reputation, and a static routing rule is usually the right first implementation because it is easy to evaluate the LLM routing guide. Task-aware routing systems that model cost and quality per query exist precisely because performance varies by task type the task-aware routing paper, and task-based routing with fallback logic is a mature pattern at the gateway layer the task-based routing overview. None of that changes the ordering: measure the failure, fix the prompt, then decide whether the model tier is the next lever.

If you are running this across several models to find which one handles your enum-and-examples prompt best, a single endpoint for every provider saves you from wiring each SDK separately. Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible API key Quanta Inference, which makes the A/B comparison a configuration change rather than a migration.

FAQ

FAQ

How many IDs can an enum list hold before it stops helping?

It depends on your context budget, but the practical ceiling for a per-call enum is a few hundred entries. Thirty tickets cost roughly 120 tokens, while a few thousand product IDs cost thousands and crowd out the actual message. Past a few hundred, retrieve the top 20 candidates by similarity and present those as the enum for that request.

Do I still need few-shot examples if I have a strict JSON schema with an enum field?

Yes, for the copy behavior. A schema enum tells the decoder which strings are legal; the examples tell the model that the correct string is the one already present in the input. Schema enforcement guarantees syntactic validity and no more, so a model can return a legal enum value that is the wrong choice the MLflow structured output guide.

What should the refusal token look like in the output?

Use one exact uppercase string, reuse it in your parser, and never let the model paraphrase it. Put it in the same field as the ID, so {"ticket_id": "NO_MATCH"} is a valid response your code can branch on without special casing. Then track the refusal rate as a metric, because a rising rate signals a missing category rather than a fixed problem.

How do I know the prompt change caused the improvement and not the evaluation set?

Run the same fixed set of labeled examples against each prompt version and change one thing at a time. Version your prompts so the output can be traced back to the exact text that produced it the prompt management guide, and check invented IDs with a deterministic membership test rather than a model judge. If the numbers move after a single edit, you have your answer.

Is it cheaper to add 360 tokens per call or to move to a larger model?

Adding tokens is almost always cheaper, because it is a fixed surcharge on the input side while a larger model raises the rate on every token, both input and output. The routing and cost literature points the same way: most production prompts do not need frontier capability, and matching model size to task is where the large savings sit the model selection guide. Fix the prompt first, measure, and only then consider the bigger model.

Sources

Sources

  1. A Practical Guide to Prompt Engineering Techniques and ...
  2. Three Prompt Engineering Methods to Reduce ...
  3. Empirical Study for Structured Output Control in LLMs for ...
  4. LLM Cost Optimization: Why an AI Gateway Is the Missing ...
  5. How to Choose the Right AI Model for Each Prompt
  6. Enforce, Validate, Observe: 3 LLM Structured Output ...
  7. Few-Shot Prompting
  8. Best Prompt Versioning Tools for Production Teams (2026)
  9. Prompt Management: Version & Deploy Prompts in ...
  10. Understanding the 4 Main Approaches to LLM Evaluation ...
  11. How to Route AI Prompts to the Right Model: LLM Routing ...
  12. Task-Aware LLM Routing with Multi-Level Task-Profile ...
  13. Task-Based LLM Routing: Optimizing LLM Performance for ...
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles