@elevasis/sdk 1.30.2 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +111 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.js +40 -3
- package/dist/node/index.d.ts +1 -0
- package/dist/test-utils/index.d.ts +1 -0
- package/dist/test-utils/index.js +14 -2
- package/package.json +2 -2
- package/reference/claude-config/skills/om/SKILL.md +42 -25
- package/reference/claude-config/skills/om/operations/scaffold.md +1 -1
- package/reference/claude-config/sync-notes/2026-05-28-om-snapshot-sdk-workflow-config.md +33 -0
- package/reference/index.mdx +7 -3
- package/reference/sdk/concepts.mdx +1 -1
- package/reference/sdk/framework/agent.mdx +156 -156
- package/reference/sdk/framework/index.mdx +4 -4
- package/reference/sdk/framework/project-structure.mdx +280 -280
- package/reference/sdk/framework/resource-documentation.mdx +2 -2
- package/reference/sdk/getting-started.mdx +7 -7
- package/reference/sdk/index.mdx +2 -1
- package/reference/sdk/platform-tools/index.mdx +7 -7
- package/reference/sdk/resources/index.mdx +3 -3
- package/reference/sdk/templates/data-enrichment.mdx +162 -162
- package/reference/sdk/templates/lead-scorer.mdx +175 -175
- package/reference/sdk/templates/text-classifier.mdx +147 -147
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Template: Lead Scorer"
|
|
3
|
-
description: "LLM-based lead scoring with Supabase storage -- receive a lead, score it with an LLM, store the result"
|
|
4
|
-
loadWhen: "Applying the lead-scorer workflow template"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
**Category:** CRM
|
|
8
|
-
|
|
9
|
-
**Platform Tools:** `llm` (scoring), `supabase` (store score)
|
|
10
|
-
|
|
11
|
-
**Credentials Required:**
|
|
12
|
-
|
|
13
|
-
- `my-database` -- Supabase project URL and service role key
|
|
14
|
-
- LLM API keys are resolved server-side from platform configuration (no credential name needed)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## What This Workflow Does
|
|
19
|
-
|
|
20
|
-
Receives a lead with company and contact details, uses an LLM to score the lead on multiple criteria, and stores the scored lead in a Supabase table. The scoring criteria and output fields are customizable. Suitable for inbound lead qualification, prioritization queues, and sales routing.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Input Schema
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
z.object({
|
|
28
|
-
leadId: z.string(),
|
|
29
|
-
company: z.string(),
|
|
30
|
-
role: z.string().optional(),
|
|
31
|
-
email: z.string().optional(),
|
|
32
|
-
notes: z.string().optional(), // Any additional context about the lead
|
|
33
|
-
scoringCriteria: z.string().optional(), // Custom scoring criteria (overrides default)
|
|
34
|
-
})
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Output Schema
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
z.object({
|
|
41
|
-
leadId: z.string(),
|
|
42
|
-
score: z.number(), // 0-100 overall score
|
|
43
|
-
tier: z.enum(['hot', 'warm', 'cold']),
|
|
44
|
-
reasoning: z.string(), // LLM explanation of the score
|
|
45
|
-
stored: z.boolean(),
|
|
46
|
-
})
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Workflow Code Pattern
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
55
|
-
import { StepType } from '@elevasis/sdk'
|
|
56
|
-
import { platform } from '@elevasis/sdk/worker'
|
|
57
|
-
import { z } from 'zod'
|
|
58
|
-
|
|
59
|
-
const inputSchema = z.object({
|
|
60
|
-
leadId: z.string(),
|
|
61
|
-
company: z.string(),
|
|
62
|
-
role: z.string().optional(),
|
|
63
|
-
email: z.string().optional(),
|
|
64
|
-
notes: z.string().optional(),
|
|
65
|
-
scoringCriteria: z.string().optional(),
|
|
66
|
-
})
|
|
67
|
-
const outputSchema = z.object({
|
|
68
|
-
leadId: z.string(),
|
|
69
|
-
score: z.number(),
|
|
70
|
-
tier: z.enum(['hot', 'warm', 'cold']),
|
|
71
|
-
reasoning: z.string(),
|
|
72
|
-
stored: z.boolean(),
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const DEFAULT_CRITERIA = `
|
|
76
|
-
Score this lead from 0-100 based on:
|
|
77
|
-
- Company fit (industry, size, budget signals) -- 40 points
|
|
78
|
-
- Role relevance (decision-maker or influencer) -- 30 points
|
|
79
|
-
- Engagement potential (any signals from notes) -- 30 points
|
|
80
|
-
|
|
81
|
-
Return JSON: { "score": number, "tier": "hot"|"warm"|"cold", "reasoning": "..." }
|
|
82
|
-
Hot = 70+, Warm = 40-69, Cold = below 40.
|
|
83
|
-
`
|
|
84
|
-
|
|
85
|
-
type Input = z.infer<typeof inputSchema>
|
|
86
|
-
|
|
87
|
-
export const leadScorer: WorkflowDefinition = {
|
|
88
|
-
config: {
|
|
89
|
-
resourceId: 'lead-scorer',
|
|
90
|
-
name: 'Lead Scorer',
|
|
91
|
-
type: 'workflow',
|
|
92
|
-
description: 'Scores leads using an LLM and stores results in Supabase',
|
|
93
|
-
version: '1.0.0',
|
|
94
|
-
status: 'dev',
|
|
95
|
-
},
|
|
96
|
-
contract: { inputSchema, outputSchema },
|
|
97
|
-
steps: {
|
|
98
|
-
score: {
|
|
99
|
-
id: 'score',
|
|
100
|
-
name: 'Score Lead',
|
|
101
|
-
description: 'Score the lead using an LLM',
|
|
102
|
-
inputSchema,
|
|
103
|
-
outputSchema: z.object({ leadId: z.string(), score: z.number(), tier: z.enum(['hot', 'warm', 'cold']), reasoning: z.string() }),
|
|
104
|
-
handler: async (input) => {
|
|
105
|
-
const { leadId, company, role, notes, scoringCriteria } = input as Input
|
|
106
|
-
const criteria = scoringCriteria ?? DEFAULT_CRITERIA
|
|
107
|
-
|
|
108
|
-
const result = await platform.call({
|
|
109
|
-
tool: 'llm',
|
|
110
|
-
method: 'generate',
|
|
111
|
-
params: {
|
|
112
|
-
provider: 'openai',
|
|
113
|
-
model: 'gpt-
|
|
114
|
-
messages: [{
|
|
115
|
-
role: 'user',
|
|
116
|
-
content: `${criteria}\n\nLead:\nCompany: ${company}\nRole: ${role ?? 'Unknown'}\nNotes: ${notes ?? 'None'}`,
|
|
117
|
-
}],
|
|
118
|
-
responseSchema: {
|
|
119
|
-
type: 'object',
|
|
120
|
-
properties: {
|
|
121
|
-
score: { type: 'number' },
|
|
122
|
-
tier: { type: 'string', enum: ['hot', 'warm', 'cold'] },
|
|
123
|
-
reasoning: { type: 'string' },
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
}) as { score: number; tier: 'hot' | 'warm' | 'cold'; reasoning: string }
|
|
128
|
-
|
|
129
|
-
return { leadId, score: result.score, tier: result.tier, reasoning: result.reasoning }
|
|
130
|
-
},
|
|
131
|
-
next: { type: StepType.LINEAR, target: 'store' },
|
|
132
|
-
},
|
|
133
|
-
store: {
|
|
134
|
-
id: 'store',
|
|
135
|
-
name: 'Store Score',
|
|
136
|
-
description: 'Save the lead score to Supabase',
|
|
137
|
-
inputSchema: z.object({ leadId: z.string(), score: z.number(), tier: z.enum(['hot', 'warm', 'cold']), reasoning: z.string() }),
|
|
138
|
-
outputSchema,
|
|
139
|
-
handler: async (input, context) => {
|
|
140
|
-
const { leadId, score, tier, reasoning } = input as { leadId: string; score: number; tier: 'hot' | 'warm' | 'cold'; reasoning: string }
|
|
141
|
-
|
|
142
|
-
await platform.call({
|
|
143
|
-
tool: 'supabase',
|
|
144
|
-
method: 'update',
|
|
145
|
-
credential: 'my-database',
|
|
146
|
-
params: {
|
|
147
|
-
table: 'leads',
|
|
148
|
-
filter: { id: `eq.${leadId}` },
|
|
149
|
-
data: { score, tier, scoring_reasoning: reasoning, scored_at: new Date().toISOString() },
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
context.logger.info('Lead scored and stored', { leadId, score, tier })
|
|
154
|
-
return { leadId, score, tier, reasoning, stored: true }
|
|
155
|
-
},
|
|
156
|
-
next: null,
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
entryPoint: 'score',
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Adaptation Notes
|
|
166
|
-
|
|
167
|
-
- **Credential name:** Replace `'my-database'` with the user's database credential name.
|
|
168
|
-
- **Table name:** Replace `'leads'` with the user's actual table name. Check `data/schema.ts` if it exists.
|
|
169
|
-
- **Scoring criteria:** Customize `DEFAULT_CRITERIA` based on the user's ICP (ideal customer profile). Ask the user what matters most for their scoring.
|
|
170
|
-
- **Table columns:** The template writes `score`, `tier`, `scoring_reasoning`, `scored_at`. Ensure these columns exist or adapt the field names.
|
|
171
|
-
- **LLM model:** Default uses `gpt-
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
**Last Updated:** 2026-02-26
|
|
1
|
+
---
|
|
2
|
+
title: "Template: Lead Scorer"
|
|
3
|
+
description: "LLM-based lead scoring with Supabase storage -- receive a lead, score it with an LLM, store the result"
|
|
4
|
+
loadWhen: "Applying the lead-scorer workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** CRM
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `llm` (scoring), `supabase` (store score)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- `my-database` -- Supabase project URL and service role key
|
|
14
|
+
- LLM API keys are resolved server-side from platform configuration (no credential name needed)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What This Workflow Does
|
|
19
|
+
|
|
20
|
+
Receives a lead with company and contact details, uses an LLM to score the lead on multiple criteria, and stores the scored lead in a Supabase table. The scoring criteria and output fields are customizable. Suitable for inbound lead qualification, prioritization queues, and sales routing.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Input Schema
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
z.object({
|
|
28
|
+
leadId: z.string(),
|
|
29
|
+
company: z.string(),
|
|
30
|
+
role: z.string().optional(),
|
|
31
|
+
email: z.string().optional(),
|
|
32
|
+
notes: z.string().optional(), // Any additional context about the lead
|
|
33
|
+
scoringCriteria: z.string().optional(), // Custom scoring criteria (overrides default)
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Output Schema
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
z.object({
|
|
41
|
+
leadId: z.string(),
|
|
42
|
+
score: z.number(), // 0-100 overall score
|
|
43
|
+
tier: z.enum(['hot', 'warm', 'cold']),
|
|
44
|
+
reasoning: z.string(), // LLM explanation of the score
|
|
45
|
+
stored: z.boolean(),
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Workflow Code Pattern
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
55
|
+
import { StepType } from '@elevasis/sdk'
|
|
56
|
+
import { platform } from '@elevasis/sdk/worker'
|
|
57
|
+
import { z } from 'zod'
|
|
58
|
+
|
|
59
|
+
const inputSchema = z.object({
|
|
60
|
+
leadId: z.string(),
|
|
61
|
+
company: z.string(),
|
|
62
|
+
role: z.string().optional(),
|
|
63
|
+
email: z.string().optional(),
|
|
64
|
+
notes: z.string().optional(),
|
|
65
|
+
scoringCriteria: z.string().optional(),
|
|
66
|
+
})
|
|
67
|
+
const outputSchema = z.object({
|
|
68
|
+
leadId: z.string(),
|
|
69
|
+
score: z.number(),
|
|
70
|
+
tier: z.enum(['hot', 'warm', 'cold']),
|
|
71
|
+
reasoning: z.string(),
|
|
72
|
+
stored: z.boolean(),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const DEFAULT_CRITERIA = `
|
|
76
|
+
Score this lead from 0-100 based on:
|
|
77
|
+
- Company fit (industry, size, budget signals) -- 40 points
|
|
78
|
+
- Role relevance (decision-maker or influencer) -- 30 points
|
|
79
|
+
- Engagement potential (any signals from notes) -- 30 points
|
|
80
|
+
|
|
81
|
+
Return JSON: { "score": number, "tier": "hot"|"warm"|"cold", "reasoning": "..." }
|
|
82
|
+
Hot = 70+, Warm = 40-69, Cold = below 40.
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
type Input = z.infer<typeof inputSchema>
|
|
86
|
+
|
|
87
|
+
export const leadScorer: WorkflowDefinition = {
|
|
88
|
+
config: {
|
|
89
|
+
resourceId: 'lead-scorer',
|
|
90
|
+
name: 'Lead Scorer',
|
|
91
|
+
type: 'workflow',
|
|
92
|
+
description: 'Scores leads using an LLM and stores results in Supabase',
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
status: 'dev',
|
|
95
|
+
},
|
|
96
|
+
contract: { inputSchema, outputSchema },
|
|
97
|
+
steps: {
|
|
98
|
+
score: {
|
|
99
|
+
id: 'score',
|
|
100
|
+
name: 'Score Lead',
|
|
101
|
+
description: 'Score the lead using an LLM',
|
|
102
|
+
inputSchema,
|
|
103
|
+
outputSchema: z.object({ leadId: z.string(), score: z.number(), tier: z.enum(['hot', 'warm', 'cold']), reasoning: z.string() }),
|
|
104
|
+
handler: async (input) => {
|
|
105
|
+
const { leadId, company, role, notes, scoringCriteria } = input as Input
|
|
106
|
+
const criteria = scoringCriteria ?? DEFAULT_CRITERIA
|
|
107
|
+
|
|
108
|
+
const result = await platform.call({
|
|
109
|
+
tool: 'llm',
|
|
110
|
+
method: 'generate',
|
|
111
|
+
params: {
|
|
112
|
+
provider: 'openai',
|
|
113
|
+
model: 'gpt-5.4-mini',
|
|
114
|
+
messages: [{
|
|
115
|
+
role: 'user',
|
|
116
|
+
content: `${criteria}\n\nLead:\nCompany: ${company}\nRole: ${role ?? 'Unknown'}\nNotes: ${notes ?? 'None'}`,
|
|
117
|
+
}],
|
|
118
|
+
responseSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
score: { type: 'number' },
|
|
122
|
+
tier: { type: 'string', enum: ['hot', 'warm', 'cold'] },
|
|
123
|
+
reasoning: { type: 'string' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}) as { score: number; tier: 'hot' | 'warm' | 'cold'; reasoning: string }
|
|
128
|
+
|
|
129
|
+
return { leadId, score: result.score, tier: result.tier, reasoning: result.reasoning }
|
|
130
|
+
},
|
|
131
|
+
next: { type: StepType.LINEAR, target: 'store' },
|
|
132
|
+
},
|
|
133
|
+
store: {
|
|
134
|
+
id: 'store',
|
|
135
|
+
name: 'Store Score',
|
|
136
|
+
description: 'Save the lead score to Supabase',
|
|
137
|
+
inputSchema: z.object({ leadId: z.string(), score: z.number(), tier: z.enum(['hot', 'warm', 'cold']), reasoning: z.string() }),
|
|
138
|
+
outputSchema,
|
|
139
|
+
handler: async (input, context) => {
|
|
140
|
+
const { leadId, score, tier, reasoning } = input as { leadId: string; score: number; tier: 'hot' | 'warm' | 'cold'; reasoning: string }
|
|
141
|
+
|
|
142
|
+
await platform.call({
|
|
143
|
+
tool: 'supabase',
|
|
144
|
+
method: 'update',
|
|
145
|
+
credential: 'my-database',
|
|
146
|
+
params: {
|
|
147
|
+
table: 'leads',
|
|
148
|
+
filter: { id: `eq.${leadId}` },
|
|
149
|
+
data: { score, tier, scoring_reasoning: reasoning, scored_at: new Date().toISOString() },
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
context.logger.info('Lead scored and stored', { leadId, score, tier })
|
|
154
|
+
return { leadId, score, tier, reasoning, stored: true }
|
|
155
|
+
},
|
|
156
|
+
next: null,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
entryPoint: 'score',
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Adaptation Notes
|
|
166
|
+
|
|
167
|
+
- **Credential name:** Replace `'my-database'` with the user's database credential name.
|
|
168
|
+
- **Table name:** Replace `'leads'` with the user's actual table name. Check `data/schema.ts` if it exists.
|
|
169
|
+
- **Scoring criteria:** Customize `DEFAULT_CRITERIA` based on the user's ICP (ideal customer profile). Ask the user what matters most for their scoring.
|
|
170
|
+
- **Table columns:** The template writes `score`, `tier`, `scoring_reasoning`, `scored_at`. Ensure these columns exist or adapt the field names.
|
|
171
|
+
- **LLM model:** Default uses `gpt-5.4-mini`. For more nuanced scoring, suggest `gpt-5`.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
**Last Updated:** 2026-02-26
|
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Template: Text Classifier"
|
|
3
|
-
description: "Multi-label text classification with structured output -- classify text into predefined categories using an LLM with JSON output"
|
|
4
|
-
loadWhen: "Applying the text-classifier workflow template"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
**Category:** AI
|
|
8
|
-
|
|
9
|
-
**Platform Tools:** `llm` (structured generation)
|
|
10
|
-
|
|
11
|
-
**Credentials Required:**
|
|
12
|
-
|
|
13
|
-
- LLM API keys are resolved server-side from platform configuration (no credential name needed)
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## What This Workflow Does
|
|
18
|
-
|
|
19
|
-
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.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Input Schema
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
z.object({
|
|
27
|
-
text: z.string(), // Text to classify
|
|
28
|
-
categories: z.array(z.string()), // Available category labels
|
|
29
|
-
multiLabel: z.boolean().optional(), // Allow multiple categories (default: false)
|
|
30
|
-
instructions: z.string().optional(), // Additional instructions for the LLM
|
|
31
|
-
})
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Output Schema
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
z.object({
|
|
38
|
-
categories: z.array(z.string()), // Assigned category labels
|
|
39
|
-
reasoning: z.string(), // LLM explanation of classification
|
|
40
|
-
confidence: z.enum(['high', 'medium', 'low']),
|
|
41
|
-
})
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Workflow Code Pattern
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
50
|
-
import { platform } from '@elevasis/sdk/worker'
|
|
51
|
-
import { z } from 'zod'
|
|
52
|
-
|
|
53
|
-
const inputSchema = z.object({
|
|
54
|
-
text: z.string(),
|
|
55
|
-
categories: z.array(z.string()),
|
|
56
|
-
multiLabel: z.boolean().optional(),
|
|
57
|
-
instructions: z.string().optional(),
|
|
58
|
-
})
|
|
59
|
-
const outputSchema = z.object({
|
|
60
|
-
categories: z.array(z.string()),
|
|
61
|
-
reasoning: z.string(),
|
|
62
|
-
confidence: z.enum(['high', 'medium', 'low']),
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
type Input = z.infer<typeof inputSchema>
|
|
66
|
-
|
|
67
|
-
export const textClassifier: WorkflowDefinition = {
|
|
68
|
-
config: {
|
|
69
|
-
resourceId: 'text-classifier',
|
|
70
|
-
name: 'Text Classifier',
|
|
71
|
-
type: 'workflow',
|
|
72
|
-
description: 'Classifies text into predefined categories using an LLM',
|
|
73
|
-
version: '1.0.0',
|
|
74
|
-
status: 'dev',
|
|
75
|
-
},
|
|
76
|
-
contract: { inputSchema, outputSchema },
|
|
77
|
-
steps: {
|
|
78
|
-
classify: {
|
|
79
|
-
id: 'classify',
|
|
80
|
-
name: 'Classify Text',
|
|
81
|
-
description: 'Use LLM to classify the input text',
|
|
82
|
-
inputSchema,
|
|
83
|
-
outputSchema,
|
|
84
|
-
handler: async (input) => {
|
|
85
|
-
const { text, categories, multiLabel, instructions } = input as Input
|
|
86
|
-
const mode = multiLabel ? 'one or more' : 'exactly one'
|
|
87
|
-
const categoryList = categories.map(c => `- ${c}`).join('\n')
|
|
88
|
-
|
|
89
|
-
const prompt = `Classify the following text into ${mode} of these categories:
|
|
90
|
-
${categoryList}
|
|
91
|
-
${instructions ? `\nAdditional instructions: ${instructions}` : ''}
|
|
92
|
-
|
|
93
|
-
Text to classify:
|
|
94
|
-
"${text}"
|
|
95
|
-
|
|
96
|
-
Return JSON with:
|
|
97
|
-
- categories: array of matching category names (from the list above only)
|
|
98
|
-
- reasoning: brief explanation of your classification
|
|
99
|
-
- confidence: "high" (very clear match), "medium" (reasonable but uncertain), or "low" (ambiguous)`
|
|
100
|
-
|
|
101
|
-
const result = await platform.call({
|
|
102
|
-
tool: 'llm',
|
|
103
|
-
method: 'generate',
|
|
104
|
-
params: {
|
|
105
|
-
provider: 'openai',
|
|
106
|
-
model: 'gpt-
|
|
107
|
-
messages: [{ role: 'user', content: prompt }],
|
|
108
|
-
responseSchema: {
|
|
109
|
-
type: 'object',
|
|
110
|
-
properties: {
|
|
111
|
-
categories: { type: 'array', items: { type: 'string' } },
|
|
112
|
-
reasoning: { type: 'string' },
|
|
113
|
-
confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
}) as { categories: string[]; reasoning: string; confidence: 'high' | 'medium' | 'low' }
|
|
118
|
-
|
|
119
|
-
// Validate that returned categories are from the allowed list
|
|
120
|
-
const validCategories = result.categories.filter(c => categories.includes(c))
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
categories: validCategories,
|
|
124
|
-
reasoning: result.reasoning,
|
|
125
|
-
confidence: result.confidence,
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
next: null,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
entryPoint: 'classify',
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## Adaptation Notes
|
|
138
|
-
|
|
139
|
-
- **Category design:** Help the user define clear, mutually exclusive category names. Ambiguous categories produce inconsistent results.
|
|
140
|
-
- **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").
|
|
141
|
-
- **Confidence routing:** Common pattern: use the output with `StepType.CONDITIONAL` routing. High confidence routes to automated processing; low confidence routes to human review.
|
|
142
|
-
- **Model selection:** `gpt-
|
|
143
|
-
- **Skill adaptation:** For beginners, explain what "structured output" means and why the LLM returns JSON instead of free text.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
**Last Updated:** 2026-02-26
|
|
1
|
+
---
|
|
2
|
+
title: "Template: Text Classifier"
|
|
3
|
+
description: "Multi-label text classification with structured output -- classify text into predefined categories using an LLM with JSON output"
|
|
4
|
+
loadWhen: "Applying the text-classifier workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** AI
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `llm` (structured generation)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- LLM API keys are resolved server-side from platform configuration (no credential name needed)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What This Workflow Does
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Input Schema
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
z.object({
|
|
27
|
+
text: z.string(), // Text to classify
|
|
28
|
+
categories: z.array(z.string()), // Available category labels
|
|
29
|
+
multiLabel: z.boolean().optional(), // Allow multiple categories (default: false)
|
|
30
|
+
instructions: z.string().optional(), // Additional instructions for the LLM
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Output Schema
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
z.object({
|
|
38
|
+
categories: z.array(z.string()), // Assigned category labels
|
|
39
|
+
reasoning: z.string(), // LLM explanation of classification
|
|
40
|
+
confidence: z.enum(['high', 'medium', 'low']),
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Workflow Code Pattern
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
50
|
+
import { platform } from '@elevasis/sdk/worker'
|
|
51
|
+
import { z } from 'zod'
|
|
52
|
+
|
|
53
|
+
const inputSchema = z.object({
|
|
54
|
+
text: z.string(),
|
|
55
|
+
categories: z.array(z.string()),
|
|
56
|
+
multiLabel: z.boolean().optional(),
|
|
57
|
+
instructions: z.string().optional(),
|
|
58
|
+
})
|
|
59
|
+
const outputSchema = z.object({
|
|
60
|
+
categories: z.array(z.string()),
|
|
61
|
+
reasoning: z.string(),
|
|
62
|
+
confidence: z.enum(['high', 'medium', 'low']),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
type Input = z.infer<typeof inputSchema>
|
|
66
|
+
|
|
67
|
+
export const textClassifier: WorkflowDefinition = {
|
|
68
|
+
config: {
|
|
69
|
+
resourceId: 'text-classifier',
|
|
70
|
+
name: 'Text Classifier',
|
|
71
|
+
type: 'workflow',
|
|
72
|
+
description: 'Classifies text into predefined categories using an LLM',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
status: 'dev',
|
|
75
|
+
},
|
|
76
|
+
contract: { inputSchema, outputSchema },
|
|
77
|
+
steps: {
|
|
78
|
+
classify: {
|
|
79
|
+
id: 'classify',
|
|
80
|
+
name: 'Classify Text',
|
|
81
|
+
description: 'Use LLM to classify the input text',
|
|
82
|
+
inputSchema,
|
|
83
|
+
outputSchema,
|
|
84
|
+
handler: async (input) => {
|
|
85
|
+
const { text, categories, multiLabel, instructions } = input as Input
|
|
86
|
+
const mode = multiLabel ? 'one or more' : 'exactly one'
|
|
87
|
+
const categoryList = categories.map(c => `- ${c}`).join('\n')
|
|
88
|
+
|
|
89
|
+
const prompt = `Classify the following text into ${mode} of these categories:
|
|
90
|
+
${categoryList}
|
|
91
|
+
${instructions ? `\nAdditional instructions: ${instructions}` : ''}
|
|
92
|
+
|
|
93
|
+
Text to classify:
|
|
94
|
+
"${text}"
|
|
95
|
+
|
|
96
|
+
Return JSON with:
|
|
97
|
+
- categories: array of matching category names (from the list above only)
|
|
98
|
+
- reasoning: brief explanation of your classification
|
|
99
|
+
- confidence: "high" (very clear match), "medium" (reasonable but uncertain), or "low" (ambiguous)`
|
|
100
|
+
|
|
101
|
+
const result = await platform.call({
|
|
102
|
+
tool: 'llm',
|
|
103
|
+
method: 'generate',
|
|
104
|
+
params: {
|
|
105
|
+
provider: 'openai',
|
|
106
|
+
model: 'gpt-5.4-mini',
|
|
107
|
+
messages: [{ role: 'user', content: prompt }],
|
|
108
|
+
responseSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
categories: { type: 'array', items: { type: 'string' } },
|
|
112
|
+
reasoning: { type: 'string' },
|
|
113
|
+
confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
}) as { categories: string[]; reasoning: string; confidence: 'high' | 'medium' | 'low' }
|
|
118
|
+
|
|
119
|
+
// Validate that returned categories are from the allowed list
|
|
120
|
+
const validCategories = result.categories.filter(c => categories.includes(c))
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
categories: validCategories,
|
|
124
|
+
reasoning: result.reasoning,
|
|
125
|
+
confidence: result.confidence,
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
next: null,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
entryPoint: 'classify',
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Adaptation Notes
|
|
138
|
+
|
|
139
|
+
- **Category design:** Help the user define clear, mutually exclusive category names. Ambiguous categories produce inconsistent results.
|
|
140
|
+
- **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").
|
|
141
|
+
- **Confidence routing:** Common pattern: use the output with `StepType.CONDITIONAL` routing. High confidence routes to automated processing; low confidence routes to human review.
|
|
142
|
+
- **Model selection:** `gpt-5.4-mini` is fast and cost-effective for classification. For complex or nuanced categorization, suggest `gpt-5`.
|
|
143
|
+
- **Skill adaptation:** For beginners, explain what "structured output" means and why the LLM returns JSON instead of free text.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
**Last Updated:** 2026-02-26
|