Context-aware widgets can provide relevant information to the AI assistant automatically. This guide shows you how to implement context in your web components.
Prerequisites
Section titled “Prerequisites”Before starting, you should understand:
Overview
Section titled “Overview”Widgets provide context by implementing a getContextForLlm() method that returns an array of ContextSourceDef objects. The system calls this method to discover what context the widget can provide.
Basic Implementation
Section titled “Basic Implementation”Step 1: Implement getContextForLlm()
Section titled “Step 1: Implement getContextForLlm()”Add the method to your web component:
class OrderWidget extends HTMLElement { private currentOrder: Order | null = null;
getContextForLlm(): ContextSourceDef[] | undefined { // Return undefined or empty array if no context to provide if (!this.currentOrder) { return undefined; }
return [{ // Unique identifier for this context source sourceId: 'current-order',
// Description for LLM-based relevance filtering llmInclusionDescription: 'Details about the order currently being viewed, including items, status, and shipping information',
// Origin: 'auto' for automatic inclusion, 'user' for user-requested origin: 'auto',
// Display information for UI chips title: `Order #${this.currentOrder.id}`, description: `${this.currentOrder.items.length} items, ${this.currentOrder.status}`,
// The actual context data data: this.currentOrder,
// Should this be added automatically when relevant? addAutomatically: true,
// Optional: How long this context stays relevant (in milliseconds) maxAgeMs: 5 * 60 * 1000 // 5 minutes }]; }}Step 2: Notify on Context Changes
Section titled “Step 2: Notify on Context Changes”When your widget's data changes, notify the system:
class OrderWidget extends HTMLElement { private chatAppState: IChatAppState;
async loadOrder(orderId: string) { this.currentOrder = await fetchOrder(orderId);
// Notify system that context has changed this.chatAppState.updateWidgetContext(this.instanceId); }}Understanding Context Fields
Section titled “Understanding Context Fields”Required Fields
Section titled “Required Fields”interface ContextSourceDef { // Unique ID for tracking across conversation sourceId: string;
// Description for LLM relevance filtering llmInclusionDescription: string;
// How context was added: 'auto' or 'user' origin: WidgetContextSourceOrigin;
// Display text for UI chip title: string; description: string;
// The actual context data (sent to LLM) data: unknown;}Optional Fields
Section titled “Optional Fields”interface ContextSourceDef { // Should context be added automatically? addAutomatically?: boolean; // Default: false
// How long context stays relevant (milliseconds) maxAgeMs?: number; // Default: undefined (never expires)
// Lucide icon name for UI chip lucideIconName?: string; // Example: 'package', 'user', 'chart'}Field Guidelines
Section titled “Field Guidelines”sourceId
Section titled “sourceId”Purpose: Unique identifier for deduplication and tracking.
Best Practices:
- Use kebab-case:
current-order,user-profile,chart-data - Make it globally unique across your application
- Keep it stable (don't include dynamic IDs unless necessary)
- For multiple instances, use:
${baseId}-${uniqueId}
// GoodsourceId: 'current-order'sourceId: 'user-profile'
// Also good (when you need multiple instances)sourceId: `chart-${this.chartId}`sourceId: `order-${orderId}`
// Bad (too generic)sourceId: 'data'sourceId: 'context'llmInclusionDescription
Section titled “llmInclusionDescription”Purpose: Helps the filtering LLM determine if this context is relevant to the user's question.
Best Practices:
- Be specific about what the context contains
- Mention key fields or categories
- Write for an LLM, not a human
- 1-2 sentences is ideal
// GoodllmInclusionDescription: 'Order details including items, quantities, prices, shipping address, and current status'
llmInclusionDescription: 'User profile with account information, preferences, subscription tier, and billing details'
llmInclusionDescription: 'Time-series chart showing sales data over the past 30 days, broken down by region and product category'
// Bad (too vague)llmInclusionDescription: 'Order information'llmInclusionDescription: 'User data'addAutomatically
Section titled “addAutomatically”Purpose: Controls whether context appears automatically or requires user action.
When to use true (default for most widgets):
- Spotlight widgets (always visible)
- Canvas widgets (embedded in responses)
- Any context that's clearly relevant to visible UI
When to use false:
- Dialog widgets (temporary/modal)
- Widgets with sensitive data requiring explicit consent
- Context that's rarely relevant
// Spotlight widget - always visible, always relevantaddAutomatically: true
// Dialog widget - user opened it for a reasonaddAutomatically: falsemaxAgeMs
Section titled “maxAgeMs”Purpose: Defines how long context stays relevant without changing.
When to set it:
- Real-time data (stock prices, system status)
- Time-sensitive information (recent orders, today's schedule)
- Data that becomes stale quickly
When to omit it:
- Stable reference data (user profile, settings)
- Historical data that doesn't change
// Real-time stock prices - refresh every 30 secondsmaxAgeMs: 30 * 1000
// Recent orders - relevant for 5 minutesmaxAgeMs: 5 * 60 * 1000
// User profile - no expiration neededmaxAgeMs: undefinedMultiple Context Items
Section titled “Multiple Context Items”A single widget can return multiple context items:
class DashboardWidget extends HTMLElement { getContextForLlm(): ContextSourceDef[] | undefined { const contexts: ContextSourceDef[] = [];
// Add metrics context if (this.metrics) { contexts.push({ sourceId: 'dashboard-metrics', llmInclusionDescription: 'Key performance metrics including revenue, user count, and conversion rates', origin: 'auto', title: 'Dashboard Metrics', description: 'Current KPIs', data: this.metrics, addAutomatically: true, maxAgeMs: 60 * 1000 // 1 minute }); }
// Add alerts context if (this.alerts && this.alerts.length > 0) { contexts.push({ sourceId: 'active-alerts', llmInclusionDescription: 'Active system alerts and warnings requiring attention', origin: 'auto', title: 'Active Alerts', description: `${this.alerts.length} alerts`, data: this.alerts, addAutomatically: true, maxAgeMs: 30 * 1000 // 30 seconds }); }
return contexts.length > 0 ? contexts : undefined; }}Advanced Patterns
Section titled “Advanced Patterns”Conditional Context
Section titled “Conditional Context”Only provide context when it's relevant:
getContextForLlm(): ContextSourceDef[] | undefined { // Don't provide context if widget is minimized if (this.isMinimized) { return undefined; }
// Don't provide context if data is loading if (this.isLoading) { return undefined; }
// Don't provide context if error state if (this.hasError) { return undefined; }
return [{ // ... context definition }];}Context with Metadata
Section titled “Context with Metadata”Include metadata in your data for better LLM understanding:
getContextForLlm(): ContextSourceDef[] | undefined { return [{ sourceId: 'order-details', llmInclusionDescription: 'Complete order information', origin: 'auto', title: `Order #${this.order.id}`, description: this.order.status, data: { // Include metadata for context _metadata: { orderDate: this.order.createdAt, lastUpdated: this.order.updatedAt, currency: this.order.currency }, // Actual order data ...this.order }, addAutomatically: true }];}Scoped Context
Section titled “Scoped Context”For widgets showing filtered or scoped data:
getContextForLlm(): ContextSourceDef[] | undefined { return [{ sourceId: `orders-${this.filterType}`, llmInclusionDescription: `List of ${this.filterType} orders with summary information`, origin: 'auto', title: `${this.filterType} Orders`, description: `${this.orders.length} orders`, data: { filter: this.filterType, dateRange: this.dateRange, orders: this.orders }, addAutomatically: true, maxAgeMs: 2 * 60 * 1000 // 2 minutes }];}Testing Context
Section titled “Testing Context”Manual Testing
Section titled “Manual Testing”- Open Developer Console and run:
// Get your widget instanceconst widget = document.querySelector('your-widget');
// Check contextconst context = widget.getContextForLlm();console.log('Context:', context);- Test in Chat UI:
- Open chat input
- Verify context chip appears
- Click chip to see details
- Ask a question and verify context is used
Context Lifecycle
Section titled “Context Lifecycle”// Initial state - no contextgetContextForLlm() { return undefined; // Widget shows no chip}
// Data loadsasync connectedCallback() { await this.loadData(); this.chatAppState.updateWidgetContext(this.instanceId); // Now shows chip}
// Data changesasync refreshData() { await this.loadData(); this.chatAppState.updateWidgetContext(this.instanceId); // Chip updates}
// Widget removeddisconnectedCallback() { // Chip automatically removed}Common Patterns by Widget Type
Section titled “Common Patterns by Widget Type”Spotlight Widgets
Section titled “Spotlight Widgets”// Always visible - provide context with high confidencegetContextForLlm(): ContextSourceDef[] | undefined { return [{ sourceId: 'spotlight-data', llmInclusionDescription: 'Data from the always-visible spotlight panel', origin: 'auto', title: 'Spotlight Data', description: 'Current view', data: this.data, addAutomatically: true, // Spotlight is intentionally visible maxAgeMs: undefined // Stable data }];}Canvas Widgets
Section titled “Canvas Widgets”// Embedded in responses - context is highly relevantgetContextForLlm(): ContextSourceDef[] | undefined { return [{ sourceId: `canvas-${this.instanceId}`, llmInclusionDescription: 'Data visualization embedded in the AI response', origin: 'auto', title: 'Chart Data', description: this.chartType, data: this.chartData, addAutomatically: true, // AI put it there for a reason maxAgeMs: 5 * 60 * 1000 // Relevant for duration of conversation }];}Dialog Widgets
Section titled “Dialog Widgets”// User explicitly opened - context is intentionalgetContextForLlm(): ContextSourceDef[] | undefined { return [{ sourceId: 'dialog-data', llmInclusionDescription: 'Data from the dialog the user opened', origin: 'user', // User action initiated this title: 'Dialog Data', description: 'User-requested', data: this.data, addAutomatically: false, // User should choose maxAgeMs: undefined }];}Troubleshooting
Section titled “Troubleshooting”Context Not Appearing
Section titled “Context Not Appearing”Check:
- Is
getContextForLlm()returning a value? - Is
addAutomaticallyset totrue? - Did you call
updateWidgetContext()after data loaded? - Is the widget instance registered with chat app state?
// Debug logginggetContextForLlm(): ContextSourceDef[] | undefined { const contexts = /* ... build contexts ... */; console.log('[WidgetContext] Providing contexts:', contexts); return contexts;}Context Not Being Used
Section titled “Context Not Being Used”Check:
- Is
llmInclusionDescriptionspecific enough? - Is the description relevant to the question?
- Try asking a more specific question
- Check if context was filtered out (see browser network tab)
Context Sent Too Often
Section titled “Context Sent Too Often”Check:
- Set
maxAgeMsfor time-sensitive data - Ensure
sourceIdis stable (not changing unnecessarily) - Check if data is actually changing when you think it's stable