Text classification
Turn model="auto" into a production text classifier: a closed label set declared as an enum in response_format, a few labeled examples, and a label your code can branch on. This page builds two of them — a banking-support intent classifier and a hate-speech moderation gate that routes violations to human review.
Classification is a chat-shaped job, so it goes to the one chat interface: pa.chat.completions.create(model="auto", ...), OpenAI-compatible on the wire. There is nothing to pick and nothing to deploy — auto routes every request server-side, and each call is one metered debit against your org balance regardless of internal routing.
Setup
Python
from pareta import Pareta
pa = Pareta.from_env() # reads PARETA_API_KEY (and optional PARETA_BASE_URL)
TypeScript
import { Pareta } from "pareta";
const pa = Pareta.fromEnv(); // reads PARETA_API_KEY (and optional PARETA_BASE_URL)
See installation for getting the SDK and key in place.
Intent classification
The response_format schema is the contract: its enum lists every allowed label, Pareta constrains decoding to it and validates the answer against it before delivery, so the label is a member of the set by construction. The system prompt describes the task, but schema instructions written only in the prompt are not enforced — the schema is. The few-shot pairs are the part worth your attention — on pattern tasks like intent routing, the examples in the prompt move accuracy far more than any sampling knob. When the classifier keeps confusing two intents, add a pair that shows the right answer; that is the tuning loop.
Python
import json
LABELS = ("card_arrival", "card_not_working", "lost_or_stolen_card", "transfer_failed",
"balance_inquiry", "exchange_rate", "top_up_failed", "other")
LABEL_FORMAT = { # the output contract
"type": "json_schema",
"json_schema": {
"name": "intent_label",
"schema": {
"type": "object",
"properties": {"label": {"type": "string", "enum": list(LABELS)}},
"required": ["label"],
"additionalProperties": False,
},
},
}
SYSTEM = (
"You classify banking-support messages into exactly one intent label: "
+ ", ".join(LABELS)
+ ". If no label fits, use: other."
)
FEW_SHOT = ( # the lever on pattern tasks — swap pairs to steer the classifier
("My new card was supposed to arrive two weeks ago and it still hasn't", "card_arrival"),
("The shop terminal declined my card even though my account has money", "card_not_working"),
("I made a transfer to my landlord yesterday and it bounced back", "transfer_failed"),
)
def classify(text: str) -> str:
messages = [{"role": "system", "content": SYSTEM}]
for user, label in FEW_SHOT:
messages.append({"role": "user", "content": user})
messages.append({"role": "assistant", "content": json.dumps({"label": label})})
messages.append({"role": "user", "content": text})
resp = pa.chat.completions.create(
model="auto", messages=messages, response_format=LABEL_FORMAT,
)
label = json.loads(resp.choices[0].message.content)["label"]
return label if label in LABELS else "other" # closed set — belt and braces
TypeScript
const LABELS = ["card_arrival", "card_not_working", "lost_or_stolen_card", "transfer_failed",
"balance_inquiry", "exchange_rate", "top_up_failed", "other"] as const;
type Label = (typeof LABELS)[number];
const LABEL_FORMAT = { // the output contract
type: "json_schema",
json_schema: {
name: "intent_label",
schema: {
type: "object",
properties: { label: { type: "string", enum: [...LABELS] } },
required: ["label"],
additionalProperties: false,
},
},
};
const SYSTEM =
"You classify banking-support messages into exactly one intent label: " +
LABELS.join(", ") +
". If no label fits, use: other.";
const FEW_SHOT: Array<[string, Label]> = [ // the lever on pattern tasks
["My new card was supposed to arrive two weeks ago and it still hasn't", "card_arrival"],
["The shop terminal declined my card even though my account has money", "card_not_working"],
["I made a transfer to my landlord yesterday and it bounced back", "transfer_failed"],
];
async function classify(text: string): Promise<Label> {
const messages = [{ role: "system", content: SYSTEM }];
for (const [user, label] of FEW_SHOT) {
messages.push({ role: "user", content: user }, { role: "assistant", content: JSON.stringify({ label }) });
}
messages.push({ role: "user", content: text });
const resp = await pa.chat.completions.create({
model: "auto", messages, response_format: LABEL_FORMAT,
});
const label = JSON.parse(resp.choices[0].message.content ?? "{}").label;
return (LABELS as readonly string[]).includes(label) ? (label as Label) : "other";
}
The schema caps each answer at the label itself — no max_tokens ceiling to tune — and one call classifies one utterance; a batch is just a loop. The fallback to other is belt and braces: the schema already guarantees a member of the set, and the check keeps your downstream switch total even if LABELS and the schema ever drift apart.
Full runnable example: python/classification/intent.py · typescript/classification/intent.ts
Content moderation (hate speech)
Moderation is the same recipe with three labels — hate, offensive, neither — and one extra obligation: acting on the verdict. The prompt carries a one-line definition per label because the hate/offensive boundary (group-directed vs. individual-directed hostility) is exactly what untrained judgment gets wrong, and the two classes usually get different handling downstream. The enum schema is what makes the branch after the call safe to write.
Python
LABELS = ("hate", "offensive", "neither")
MODERATION_FORMAT = { # the output contract
"type": "json_schema",
"json_schema": {
"name": "moderation_label",
"schema": {
"type": "object",
"properties": {"label": {"type": "string", "enum": list(LABELS)}},
"required": ["label"],
"additionalProperties": False,
},
},
}
SYSTEM = (
"You are a content-moderation classifier. Label the text with exactly one of:\n"
"hate — demeans or attacks a group of people based on a group identity\n"
"offensive — insulting, hostile, or demeaning toward an individual, but not group-based\n"
"neither — none of the above"
)
def moderate(text: str) -> str:
resp = pa.chat.completions.create(
model="auto",
messages=[{"role": "system", "content": SYSTEM},
{"role": "user", "content": text}],
response_format=MODERATION_FORMAT,
)
label = json.loads(resp.choices[0].message.content)["label"]
return label if label in LABELS else "offensive" # fail closed → human review
review_queue = []
for text in incoming_texts:
label = moderate(text)
if label != "neither":
review_queue.append((label, text)) # violations go to a human
TypeScript
const LABELS = ["hate", "offensive", "neither"] as const;
type Label = (typeof LABELS)[number];
const MODERATION_FORMAT = { // the output contract
type: "json_schema",
json_schema: {
name: "moderation_label",
schema: {
type: "object",
properties: { label: { type: "string", enum: [...LABELS] } },
required: ["label"],
additionalProperties: false,
},
},
};
const SYSTEM = [
"You are a content-moderation classifier. Label the text with exactly one of:",
"hate — demeans or attacks a group of people based on a group identity",
"offensive — insulting, hostile, or demeaning toward an individual, but not group-based",
"neither — none of the above",
].join("\n");
async function moderate(text: string): Promise<Label> {
const resp = await pa.chat.completions.create({
model: "auto",
messages: [{ role: "system", content: SYSTEM }, { role: "user", content: text }],
response_format: MODERATION_FORMAT,
});
const label = JSON.parse(resp.choices[0].message.content ?? "{}").label;
return (LABELS as readonly string[]).includes(label) ? (label as Label) : "offensive";
}
const reviewQueue: Array<[Label, string]> = [];
for (const text of incomingTexts) {
const label = await moderate(text);
if (label !== "neither") reviewQueue.push([label, text]); // violations go to a human
}
Note the failure direction: an answer outside the label set is coerced to offensive, not neither, so anything the classifier fumbles still reaches human eyes. Fail-open moderation is the one bug this recipe cannot afford.
Full runnable example: python/classification/moderation.py · typescript/classification/moderation.ts
See also
- Inference (OpenAI-compatible) — the full chat surface behind
model="auto". - Chat reference — request params and response schema.
- Streaming chat completions — token-by-token output for longer generations.
- Prove it on your own data: run an eval that benchmarks
"auto"against frontier baselines on your own labeled texts.