Classification and sentiment

Classification is one of the cheapest, highest-ROI uses of an LLM. Few-shot examples + enum-constrained output = robust classifier in 10 lines.

Sentiment (3-class)

resp = client.chat.completions.create(
    model="epithre-lyt",
    messages=[
        {"role": "system", "content": [
            {"type": "text", "text": """Klasifikasi sentimen review produk Tokopedia.
Output: hanya satu kata: positif, netral, atau negatif.

Contoh:
Review: "Barangnya bagus, sesuai foto. Pengiriman cepet, packing rapi."
Output: positif

Review: "Barang nyampe tapi kemasan agak penyok. Isinya ok."
Output: netral

Review: "Salah kirim. Komplain 2 minggu gak direspon. Refund lama banget."
Output: negatif""",
             "cache_control": {"type": "ephemeral"}}
        ]},
        {"role": "user", "content": "Barang sampe rusak, refund cepet sih."},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "sentiment",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "sentiment": {"type": "string", "enum": ["positif", "netral", "negatif"]},
                    "confidence": {"type": "number", "minimum": 0, "maximum": 1},
                },
                "required": ["sentiment", "confidence"],
                "additionalProperties": False,
            },
        },
    },
)
import json
result = json.loads(resp.choices[0].message.content)
# {"sentiment": "netral", "confidence": 0.78}

Use epithre-lyt for high-volume classification. ~5x cheaper than omni and accuracy is sufficient for sentiment / intent.

Intent classification (multi-class)

intents = ["greeting", "ask_price", "ask_stock", "complaint", "compliment", "small_talk", "other"]

resp = client.chat.completions.create(
    model="epithre-lyt",
    messages=[
        {"role": "system", "content": f"""Klasifikasi maksud pesan customer.
Pilih dari: {', '.join(intents)}.

ask_price: pertanyaan tentang harga produk
ask_stock: pertanyaan tentang ketersediaan barang
complaint: keluhan tentang produk atau layanan
compliment: pujian atau testimoni positif
greeting: sapaan / pembuka
small_talk: obrolan ringan
other: tidak masuk kategori di atas

Contoh:
"Halo, masih buka?" -> greeting
"Pupuk urea berapa harganya per kg?" -> ask_price
"Beli kemarin tapi salah kirim warna" -> complaint"""},
        {"role": "user", "content": "Selamat siang, kalau kirim ke Bandung lewat ekspedisi mana?"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "intent",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "intent": {"type": "string", "enum": intents},
                },
                "required": ["intent"],
                "additionalProperties": False,
            },
        },
    },
)

Topic / category (open-ended)

When you don't have a fixed enum:

resp = client.chat.completions.create(
    model="epithre-omni",
    messages=[
        {"role": "system", "content":
            "Klasifikasi topik artikel berita Indonesia. Output 1-3 topik utama, "
            "pakai vocab konsisten (e.g. 'politik', 'ekonomi', 'olahraga')."},
        {"role": "user", "content": article_text},
    ],
    response_format={
        "type": "json_object",
    },
)

If you want consistent vocab across many articles, supply a controlled vocabulary as part of the system prompt + use enum schema.

Multi-label classification

resp = client.chat.completions.create(
    model="epithre-lyt",
    messages=[
        {"role": "system", "content":
            "Tag artikel dengan SEMUA topik yang relevan dari daftar."},
        {"role": "user", "content": article_text},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "tags",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "tags": {
                        "type": "array",
                        "items": {"type": "string",
                                  "enum": ["politik", "ekonomi", "olahraga", "hiburan",
                                           "teknologi", "pendidikan", "kesehatan"]}
                    }
                },
                "required": ["tags"],
                "additionalProperties": False,
            },
        },
    },
)

Bulk classification via Batch

For backfilling historical data:

import json

with open("classify.jsonl", "w") as f:
    for i, review in enumerate(my_reviews):
        f.write(json.dumps({
            "custom_id": f"review-{review['id']}",
            "method": "POST",
            "url": "/v1/chat/completions",
            "body": {
                "model": "epithre-lyt",
                "messages": [
                    {"role": "system", "content": SENTIMENT_PROMPT},
                    {"role": "user", "content": review["text"]},
                ],
                "response_format": {"type": "json_schema", "json_schema": SENTIMENT_SCHEMA},
            }
        }) + "\n")

# Upload + batch
file = client.files.create(file=open("classify.jsonl","rb"), purpose="batch_input")
batch = client.batches.create(input_file_id=file.id, endpoint="/v1/chat/completions")

50% off realtime. For a million-review backfill, this is the difference between Rp1,000,000 and Rp500,000.

Calibration

If you need calibrated probabilities (not just labels), ask the model to output confidence (0-1) per label. Run on a labeled holdout to compute correlation between model confidence and accuracy. The default raw output isn't well-calibrated; treat confidence as relative ordering.

For real calibration, use the evaluation cookbook.

See also