---
title: "Template: Text Classifier"
description: "Multi-label text classification with structured output -- classify text into predefined categories using an LLM with JSON output"
loadWhen: "Applying the text-classifier workflow template"
---

**Category:** AI

**Platform Tools:** `llm` (structured generation)

**Credentials Required:**

- LLM API keys are resolved server-side from platform configuration (no credential name needed)

---

## What This Workflow Does

Classifies input text into one or more predefined categories using an LLM. Returns structured output with category assignments and confidence reasoning. Suitable for ticket categorization, email routing, content tagging, sentiment analysis, and any text classification task where the categories are known in advance.

---

## Input Schema

```typescript
z.object({
  text: z.string(),                        // Text to classify
  categories: z.array(z.string()),         // Available category labels
  multiLabel: z.boolean().optional(),      // Allow multiple categories (default: false)
  instructions: z.string().optional(),     // Additional instructions for the LLM
})
```

## Output Schema

```typescript
z.object({
  categories: z.array(z.string()),         // Assigned category labels
  reasoning: z.string(),                   // LLM explanation of classification
  confidence: z.enum(['high', 'medium', 'low']),
})
```

---

## Workflow Code Pattern

```typescript
import type { WorkflowDefinition } from '@elevasis/sdk'
import { platform } from '@elevasis/sdk/worker'
import { z } from 'zod'

const inputSchema = z.object({
  text: z.string(),
  categories: z.array(z.string()),
  multiLabel: z.boolean().optional(),
  instructions: z.string().optional(),
})
const outputSchema = z.object({
  categories: z.array(z.string()),
  reasoning: z.string(),
  confidence: z.enum(['high', 'medium', 'low']),
})

type Input = z.infer<typeof inputSchema>

export const textClassifier: WorkflowDefinition = {
  config: {
    resourceId: 'text-classifier',
    name: 'Text Classifier',
    type: 'workflow',
    description: 'Classifies text into predefined categories using an LLM',
    version: '1.0.0',
    status: 'dev',
  },
  contract: { inputSchema, outputSchema },
  steps: {
    classify: {
      id: 'classify',
      name: 'Classify Text',
      description: 'Use LLM to classify the input text',
      inputSchema,
      outputSchema,
      handler: async (input) => {
        const { text, categories, multiLabel, instructions } = input as Input
        const mode = multiLabel ? 'one or more' : 'exactly one'
        const categoryList = categories.map(c => `- ${c}`).join('\n')

        const prompt = `Classify the following text into ${mode} of these categories:
${categoryList}
${instructions ? `\nAdditional instructions: ${instructions}` : ''}

Text to classify:
"${text}"

Return JSON with:
- categories: array of matching category names (from the list above only)
- reasoning: brief explanation of your classification
- confidence: "high" (very clear match), "medium" (reasonable but uncertain), or "low" (ambiguous)`

        const result = await platform.call({
          tool: 'llm',
          method: 'generate',
          params: {
            provider: 'openai',
            model: 'gpt-5.4-mini',
            messages: [{ role: 'user', content: prompt }],
            responseSchema: {
              type: 'object',
              properties: {
                categories: { type: 'array', items: { type: 'string' } },
                reasoning: { type: 'string' },
                confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
              },
            },
          },
        }) as { categories: string[]; reasoning: string; confidence: 'high' | 'medium' | 'low' }

        // Validate that returned categories are from the allowed list
        const validCategories = result.categories.filter(c => categories.includes(c))

        return {
          categories: validCategories,
          reasoning: result.reasoning,
          confidence: result.confidence,
        }
      },
      next: null,
    },
  },
  entryPoint: 'classify',
}
```

---

## Adaptation Notes

- **Category design:** Help the user define clear, mutually exclusive category names. Ambiguous categories produce inconsistent results.
- **Multi-label use:** Enable `multiLabel: true` when a single piece of text can legitimately belong to multiple categories (e.g., a support ticket about "billing AND account access").
- **Confidence routing:** Common pattern: use the output with `StepType.CONDITIONAL` routing. High confidence routes to automated processing; low confidence routes to human review.
- **Model selection:** `gpt-5.4-mini` is fast and cost-effective for classification. For complex or nuanced categorization, suggest `gpt-5`.
- **Skill adaptation:** For beginners, explain what "structured output" means and why the LLM returns JSON instead of free text.

---

**Last Updated:** 2026-02-26
