UNPKG

@botpress/adk-cli

Version:

Command-line interface for the Botpress Agent Development Kit (ADK)

515 lines (396 loc) 12.9 kB
# Tags Tags are key-value pairs that can be attached to bots, users, conversations, and workflows. They provide a flexible way to track metadata, categorize entities, and store cross-handler state. ## Basic Concepts ### What are Tags? - **Key-value pairs**: String keys with string values - **Entity-scoped**: Available on bot, user, conversation, and workflow - **Schema-defined**: Must be declared in `agent.config.ts` - **Automatically persisted**: Changes are tracked and saved automatically - **System vs Custom**: System tags (with `:`) are read-only ### Tag Types | Type | Example Key | Description | | ---------- | ---------------- | ------------------------------ | | **Custom** | `tier`, `region` | User-defined, read-write | | **System** | `webchat:owner` | Integration-managed, read-only | ## Defining Tags Tags must be defined in your `agent.config.ts` to be persisted: ### User Tags ```typescript import { defineConfig, z } from '@botpress/runtime' export default defineConfig({ name: 'my-bot', user: { // User state (mutable object data) state: z.object({ preferences: z.object({}).passthrough(), }), // User tags (key-value pairs for categorization) // Tags are defined as { title: string, description?: string } objects, NOT Zod schemas tags: { tier: { title: 'Subscription Tier', description: 'User subscription level' }, region: { title: 'Region', description: 'User geographic region' }, source: { title: 'Acquisition Source' }, referralCode: { title: 'Referral Code' }, }, }, }) ``` ### Conversation Tags ```typescript export default defineConfig({ name: 'my-bot', conversation: { tags: { category: { title: 'Category' }, priority: { title: 'Priority', description: 'Conversation priority level' }, assignedTo: { title: 'Assigned To' }, department: { title: 'Department' }, }, }, }) ``` ### Bot Tags ```typescript export default defineConfig({ name: 'my-bot', bot: { state: z.object({ version: z.number().default(1), }), tags: { environment: { title: 'Environment', description: 'Deployment environment' }, region: { title: 'Region' }, version: { title: 'Version' }, }, }, }) ``` ### Workflow Tags ```typescript export default defineConfig({ name: 'my-bot', workflow: { tags: { type: { title: 'Workflow Type' }, priority: { title: 'Priority' }, createdBy: { title: 'Created By' }, }, }, }) ``` ## Using Tags ### In Conversations ```typescript import { Conversation, user } from '@botpress/runtime' export default new Conversation({ channel: 'webchat.channel', // conversation is provided as a handler parameter — no need for context.get() async handler({ message, conversation }) { // Read user tags console.log(`User tier: ${user.tags.tier}`) console.log(`User region: ${user.tags.region}`) // Set user tags user.tags.tier = 'pro' user.tags.source = 'website' // Read conversation tags console.log(`Category: ${conversation.tags.category}`) // Set conversation tags conversation.tags.priority = 'high' conversation.tags.department = 'support' // Conditional logic based on tags if (user.tags.tier === 'enterprise') { // Priority handling for enterprise users conversation.tags.priority = 'urgent' } }, }) ``` ### In Workflows ```typescript import { Workflow, z, bot } from '@botpress/runtime' export const ProcessingWorkflow = new Workflow({ name: 'processing', input: z.object({ userId: z.string() }), async handler({ input, workflow, step }) { // Set workflow tags workflow.tags.type = 'data-processing' workflow.tags.priority = 'high' // Access bot tags console.log(`Environment: ${bot.tags.environment}`) await step('process', async () => { // Processing logic }) return { success: true } }, }) ``` ### In Actions ```typescript import { Action, z, user, context } from '@botpress/runtime' export default new Action({ name: 'upgradeUser', input: z.object({ newTier: z.enum(['free', 'pro', 'enterprise']), }), output: z.object({ success: z.boolean() }), async handler({ input }) { const oldTier = user.tags.tier const conversation = context.get('conversation', { optional: true }) // Update user tag user.tags.tier = input.newTier // Log the upgrade in conversation if (conversation) { conversation.tags.category = 'upgrade' } console.log(`Upgraded user from ${oldTier} to ${input.newTier}`) return { success: true } }, }) ``` ## Tags vs State Understanding when to use tags versus state: | Aspect | Tags | State | | ------------- | ------------------------- | ----------------------------------- | | **Structure** | Flat key-value pairs | Nested objects | | **Types** | Strings only | Any type (objects, arrays, numbers) | | **Use case** | Categorization, filtering | Complex data storage | | **Queryable** | Yes (via API) | Limited | | **Size** | Small values | Larger structures | ### When to Use Tags ```typescript // ✅ Good for tags - simple categorization user.tags.tier = 'pro' user.tags.region = 'us-east' conversation.tags.priority = 'high' // ❌ Bad for tags - complex data belongs in state user.tags.preferences = JSON.stringify({ theme: 'dark' }) // Don't do this! ``` ### When to Use State ```typescript // ✅ Good for state - complex structures user.state.preferences = { theme: 'dark', notifications: true, language: 'en', } user.state.history = [ { action: 'login', timestamp: new Date() }, { action: 'purchase', timestamp: new Date() }, ] ``` ## System Tags System tags are managed by integrations and are **read-only**. They contain a colon (`:`) in their key. ### Common System Tags ```typescript // Webchat system tags (read-only) console.log(user.tags['webchat:owner']) // User who initiated webchat console.log(conversation.tags['webchat:sessionId']) // Session identifier // Integration-specific system tags console.log(user.tags['slack:userId']) // Slack user ID console.log(conversation.tags['discord:channelId']) // Discord channel ``` ### System Tag Behavior ```typescript // System tags are silently ignored when you try to set them user.tags['webchat:owner'] = 'new-value' // No error, but no effect // Only custom tags (without ':') can be modified user.tags.tier = 'enterprise' // ✅ Works user.tags.customField = 'value' // ✅ Works ``` ## Advanced Patterns ### Tag-Based Routing ```typescript import { Conversation, conversation, user } from '@botpress/runtime' export default new Conversation({ channel: 'webchat.channel', async handler({ message, execute }) { // Route based on conversation priority const priority = conversation.tags.priority if (priority === 'urgent') { // Fast-track urgent conversations await execute({ instructions: 'This is an urgent request. Prioritize resolution.', model: 'openai:gpt-4o', // Use best model }) } else { await execute({ instructions: 'Help the user with their request.', model: 'openai:gpt-4o-mini', // Cost-efficient model }) } }, }) ``` ### Tag-Based Analytics ```typescript import { Trigger } from '@botpress/runtime' import { AnalyticsTable } from '../tables/Analytics' export default new Trigger({ name: 'trackConversation', events: ['conversation.ended'], async handler({ event }) { const { conversationId, tags } = event.payload // Store conversation metrics by tags await AnalyticsTable.createRows({ rows: [ { conversationId, category: tags.category || 'uncategorized', priority: tags.priority || 'normal', department: tags.department || 'general', timestamp: new Date(), }, ], }) }, }) ``` ### Progressive Tag Collection ```typescript import { Conversation, user, adk } from '@botpress/runtime' export default new Conversation({ channel: 'webchat.channel', async handler({ message }) { // Infer and set tags from conversation if (!user.tags.region && message?.type === 'text') { const region = await adk.zai.extract(message.payload.text, z.string().optional(), { instructions: 'Extract geographic region if mentioned', }) if (region) { user.tags.region = region } } // Set source tag if not already set if (!user.tags.source) { user.tags.source = 'webchat' } }, }) ``` ### Tag Inheritance in Workflows ```typescript import { Workflow, z, user } from '@botpress/runtime' export const SupportWorkflow = new Workflow({ name: 'support', input: z.object({ conversationId: z.string(), issue: z.string(), }), async handler({ input, workflow, step }) { // Inherit priority from user tier const tier = user.tags.tier if (tier === 'enterprise') { workflow.tags.priority = 'high' } else if (tier === 'pro') { workflow.tags.priority = 'normal' } else { workflow.tags.priority = 'low' } // Set workflow type workflow.tags.type = 'support-ticket' await step('process', async () => { // Processing based on priority }) return { processed: true } }, }) ``` ## Best Practices ### 1. Use Descriptive Tag Names ```typescript // ✅ Good - clear purpose tags: { subscriptionTier: { title: "Subscription Tier", description: "User subscription level" }, acquisitionChannel: { title: "Acquisition Channel" }, supportPriority: { title: "Support Priority" }, } // ❌ Bad - unclear tags: { t: { title: "T" }, lvl: { title: "Lvl" }, p: { title: "P" }, } ``` ### 2. Use Enums for Constrained Values ```typescript // ✅ Good - descriptive titles tags: { status: { title: "Account Status", description: "Active, inactive, or suspended" }, tier: { title: "Subscription Tier", description: "Free, pro, or enterprise" }, } // ❌ Bad - missing descriptions tags: { status: { title: "Status" }, // Unclear what values are expected } ``` ### 3. Document Tag Purposes ```typescript export default defineConfig({ user: { tags: { tier: { title: 'Subscription Tier', description: 'Customer subscription level for feature gating' }, region: { title: 'Region', description: 'Geographic region for compliance and routing' }, acquisitionSource: { title: 'Acquisition Source', description: 'How the user discovered the bot' }, }, }, }) ``` ### 4. Set Tags Early ```typescript // Set identification tags early in the conversation async handler({ message, type }) { if (type === "conversation_started") { // Set source immediately conversation.tags.source = "webchat"; conversation.tags.startedAt = new Date().toISOString(); } } ``` ### 5. Use Tags for Filtering, Not Storage ```typescript // ✅ Good - tags for filtering/categorization user.tags.tier = 'enterprise' conversation.tags.category = 'billing' // ❌ Bad - storing complex data in tags user.tags.preferences = JSON.stringify({ notifications: true }) // Use state instead ``` ## Troubleshooting ### Tags Not Persisting 1. **Check schema definition**: Tags must be defined in `agent.config.ts` 2. **Verify tag key**: Custom tags cannot contain `:` 3. **Check value type**: Tags only accept string values ```typescript // ✅ Correct user.tags.tier = 'pro' // String value // ❌ Wrong - will be ignored or cause errors user.tags.count = 5 // Number (should be string) user.tags.active = true // Boolean (should be string) ``` ### System Tags Appearing Read-Only System tags (containing `:`) are managed by integrations: ```typescript // These are read-only - modifications are silently ignored user.tags['webchat:owner'] = 'new' // No effect conversation.tags['slack:channel'] = 'new' // No effect // Only custom tags can be modified user.tags.myCustomTag = 'value' // ✅ Works ``` ### Tag Not in Schema Error If you see errors about undefined tags, add them to your config: ```typescript // Before: Tag not in schema user.tags.newTag = 'value' // Warning: Tag not defined // Fix: Add to agent.config.ts export default defineConfig({ user: { tags: { newTag: { title: 'New Tag' }, // Now it will persist }, }, }) ``` ## See Also - **[Agent Configuration](./agent-config.md)** - Full agent.config.ts reference - **[Conversations](./conversations.md)** - Using tags in conversation handlers - **[Workflows](./workflows.md)** - Workflow tags and state management - **[Context API](./context-api.md)** - Accessing bot, user, conversation context