Template - Support
ChoiceFeature request classification
Whether a message is a bug report, a feature request, a how-to question, or something else.
A choice decision that sorts inbound product feedback into the categories product and support teams handle differently.
The decision
What kind of message is this?
Choices
- •
bug: Something that should work does not: an error, crash, or regression. - •
feature_request: A request for a new capability or an enhancement. - •
how_to_question: A question about how to use an existing feature. - •
other: Anything that fits none of the above.
Evidence checks (asked in the same request)
- Technical failure: The message reports an error, crash, or failed request.
Expected output: One of the four values, a confidence value, and a probability for every option, plus one probability per evidence check.
Input
The Decisions API reads one input. Combine these fields into it as labeled text so the model sees the facts the decision depends on.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The customer's message, verbatim. Include the subject line if there is one. |
Worked examples
Asking for a capability that doesn't exist
It would be great if the CSV export supported custom date ranges. Any plans to add that?
A careful reviewer would say: feature_request. The customer wants something the product doesn't do yet.
Run this exampleSomething that used to work
The export button returns an error message since Tuesday. It worked fine last week.
A careful reviewer would say: bug. A regression: existing functionality now fails.
Run this example
Expected answers are human judgments written for this template, not model output.
How it works
Customers rarely label their own messages correctly. "Feature request: the export is broken" is a bug, and "Bug: there's no dark mode" is a feature request. The option descriptions carry the definitions, so write them carefully.
Always include a catch-all option. Without one, the model must pick a category for messages that fit none, and it will do so with misleading confidence.
The per-option probabilities show close calls. A message split evenly between bug and feature request often describes behavior that is working as designed but surprising, which is worth knowing.
Human review and thresholds
Recommended review policy
Tagging is low-risk and can be automated broadly. If a category triggers real work, such as bugs opening engineering tickets, require a higher bar or confirmation for that category.
Choosing thresholds
For tagging and analytics, a moderate bar is fine because a single wrong tag costs little. For routing that creates work for another team, treat each category as its own decision with its own bar.
- Starting automation threshold
- 75%
- Starting don’t-decide threshold
- 50%
Starting points for the playground, not recommendations. The right values depend on your costs and on how the model’s confidence tracks accuracy on your data. Calibrate them.
Limitations
- Messages that contain both a bug and a request get one label. If that is common for you, ask two predicates instead of one choice.
- The boundary between "missing feature" and "bug" depends on what your product promises, which the model doesn't know unless you tell it.
- Category counts from this decision are estimates. Don't report them as exact without measuring accuracy first.
Use it in your code
The template definition is plain JSON: the question, the evidence checks, the input fields, and the starting policy. The SDK call below sends the same question and checks to the Decisions API from your server.
{
"$format": "thethinkloop.template/v1",
"slug": "feature-request-classification",
"title": "Feature request classification",
"decides": "Whether a message is a bug report, a feature request, a how-to question, or something else.",
"question": {
"name": "decision",
"type": "choice",
"instructions": "What kind of message is this?",
"choices": [
{
"value": "bug",
"description": "Something that should work does not: an error, crash, or regression."
},
{
"value": "feature_request",
"description": "A request for a new capability or an enhancement."
},
{
"value": "how_to_question",
"description": "A question about how to use an existing feature."
},
{
"value": "other",
"description": "Anything that fits none of the above."
}
]
},
"evidenceChecks": [
{
"name": "factor_1",
"label": "Technical failure",
"type": "predicate",
"instructions": "The message reports an error, crash, or failed request."
}
],
"inputs": [
{
"name": "message",
"type": "string",
"required": true,
"description": "The customer's message, verbatim. Include the subject line if there is one."
}
],
"expectedOutput": "One of the four values, a confidence value, and a probability for every option, plus one probability per evidence check.",
"examples": [
{
"title": "Asking for a capability that doesn't exist",
"input": "It would be great if the CSV export supported custom date ranges. Any plans to add that?",
"expected": "feature_request"
},
{
"title": "Something that used to work",
"input": "The export button returns an error message since Tuesday. It worked fine last week.",
"expected": "bug"
}
],
"startingPolicy": {
"automateAt": 0.75,
"abstainBelow": 0.5,
"note": "A starting point for exploration, not a recommendation. Calibrate against your own labeled examples."
}
}import OpenAI from "openai"; // openai >= 7.30.0
const client = new OpenAI(); // reads OPENAI_API_KEY on the server
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: "It would be great if the CSV export supported custom date ranges. Any plans to add that?",
questions: [
{
type: "choice",
name: "decision",
instructions: "What kind of message is this?",
choices: [
{
value: "bug",
description: "Something that should work does not: an error, crash, or regression."
},
{
value: "feature_request",
description: "A request for a new capability or an enhancement."
},
{
value: "how_to_question",
description: "A question about how to use an existing feature."
},
{
value: "other",
description: "Anything that fits none of the above."
}
]
},
{
type: "predicate",
name: "factor_1",
instructions: "The message reports an error, crash, or failed request."
}
],
});
// Answers come back in question order. A question can also be refused.
const [main, ...checks] = decision.answers;
if (main.type === "refusal") throw new Error("The model declined to answer.");
if (main.type === "choice") {
console.log(main.choice, main.confidence, main.probabilities);
}
Download it as feature-request-classification.json, or browse all developer resources.
Need this decision tuned to your policy?
Related
- Predicate template
Engineering escalation
Whether a support issue needs engineering, or can be resolved by support.
- Choice template
Customer message routing
Which team should own a customer message: sales, customer success, support, or billing.
- Predicate template
Spam detection
Whether an inbound message is spam rather than a real customer contact.
- Guide
Predicate vs choice vs score
Most decisions can be phrased three ways, and each returns different information. Which type fits which decision, with worked examples and common mistakes.
- Guide
Decisions vs Structured Outputs
Both can classify a ticket. They return different information. Where confidence comes from in each, and a design that uses both.
- Guide
Evaluating AI decisions
Accuracy alone hides the mistakes that matter. How to build a test set, which numbers to report, how to read failures, and when an evaluation is good enough to act on.