@flatfile/improv
Version:
A powerful TypeScript library for building AI agents with multi-threaded conversations, tool execution, and event handling capabilities
417 lines (316 loc) • 11.4 kB
Markdown
# Event System Guide
The Improv library uses an event-driven architecture that allows you to monitor and react to various operations throughout the system. This guide covers how to use events effectively in your applications.
## Table of Contents
- [Overview](#overview)
- [Event Categories](#event-categories)
- [Listening to Events](#listening-to-events)
- [Event Patterns](#event-patterns)
- [Best Practices](#best-practices)
- [Complete Event Reference](#complete-event-reference)
## Overview
All major components in Improv extend the `EventSource` class, which provides a consistent event interface. Events are emitted for key operations like:
- Message lifecycle (adding, sending, receiving)
- Thread state changes (errors, tool additions)
- Agent operations (thread creation, knowledge updates)
- Tool execution (started, completed, failed)
- Workflow orchestration (gig and piece lifecycle)
## Event Categories
### Message Events
Events related to individual message operations:
```typescript
import { MESSAGE_EVENTS } from '@flatfile/improv';
// Available events:
MESSAGE_EVENTS.ADDED // message.added
MESSAGE_EVENTS.REMOVED // message.removed
MESSAGE_EVENTS.SENT // message.sent
MESSAGE_EVENTS.RECEIVED // message.received
MESSAGE_EVENTS.CLEARED // messages.cleared
```
### Thread Events
Events related to thread-level operations:
```typescript
import { THREAD_EVENTS } from '@flatfile/improv';
// Available events:
THREAD_EVENTS.MAX_STEPS_REACHED // thread.max-steps-reached
THREAD_EVENTS.ERROR_OCCURRED // thread.error-occurred
THREAD_EVENTS.TOOL_ADDED // thread.tool-added
```
### Tool Events
Events related to tool execution:
```typescript
import { TOOL_EVENTS } from '@flatfile/improv';
// Available events:
TOOL_EVENTS.STARTED // tool.started
TOOL_EVENTS.COMPLETED // tool.completed
TOOL_EVENTS.FAILED // tool.failed
TOOL_EVENTS.FOLLOWUP_ADDED // tool.followup-added
TOOL_EVENTS.MESSAGES_GENERATED // tool.messages-generated
```
### Agent Events
Events related to agent operations:
```typescript
import { AGENT_EVENTS } from '@flatfile/improv';
// Available events:
AGENT_EVENTS.TOOL_ADDED // agent.tool-added
AGENT_EVENTS.THREAD_CREATED // agent.thread-created
AGENT_EVENTS.THREAD_CLOSED // agent.thread-closed
AGENT_EVENTS.KNOWLEDGE_ADDED // agent.knowledge-added
AGENT_EVENTS.INSTRUCTION_ADDED // agent.instruction-added
```
### Gig & Piece Events
Events related to workflow orchestration:
```typescript
import { GIG_EVENTS, PIECE_EVENTS } from '@flatfile/improv';
// Gig events:
GIG_EVENTS.PERFORMANCE_COMPLETED // gig.performance-completed
GIG_EVENTS.PERFORMANCE_FAILED // gig.performance-failed
// Piece events:
PIECE_EVENTS.EXECUTION_STARTED // piece.execution-started
PIECE_EVENTS.EXECUTION_COMPLETED // piece.execution-completed
PIECE_EVENTS.EXECUTION_FAILED // piece.execution-failed
PIECE_EVENTS.EXECUTION_SKIPPED // piece.execution-skipped
PIECE_EVENTS.RETRY_ATTEMPTED // piece.retry-attempted
```
## Listening to Events
### Basic Event Listening
```typescript
import { Agent, MESSAGE_EVENTS, THREAD_EVENTS } from '@flatfile/improv';
const agent = new Agent({ driver });
// Listen to message events
agent.on(MESSAGE_EVENTS.ADDED, ({ thread, message }) => {
console.log(`Message added to thread ${thread.id}: ${message.content}`);
});
// Listen to thread events
agent.on(THREAD_EVENTS.ERROR_OCCURRED, ({ thread, error }) => {
console.error(`Thread ${thread.id} error:`, error.message);
});
```
### Tool Event Monitoring
```typescript
import { Tool, TOOL_EVENTS } from '@flatfile/improv';
const tool = new Tool({
name: 'calculator',
description: 'Performs calculations',
parameters: z.object({ operation: z.string() }),
executeFn: async (args) => {
// Tool implementation
}
});
// Monitor tool execution
tool.on(TOOL_EVENTS.STARTED, ({ tool, name, args }) => {
console.log(`Tool ${name} started with args:`, args);
});
tool.on(TOOL_EVENTS.COMPLETED, ({ tool, name, args, result }) => {
console.log(`Tool ${name} completed with result:`, result);
});
tool.on(TOOL_EVENTS.FAILED, ({ tool, name, args, error }) => {
console.error(`Tool ${name} failed:`, error);
});
```
### Workflow Event Monitoring
```typescript
import { Gig, PIECE_EVENTS, GIG_EVENTS } from '@flatfile/improv';
const gig = new Gig({ label: 'Data Processing', driver });
// Monitor individual pieces
gig.on(PIECE_EVENTS.EXECUTION_STARTED, ({ piece }) => {
console.log(`Starting piece: ${piece}`);
});
gig.on(PIECE_EVENTS.EXECUTION_COMPLETED, ({ piece, recording }) => {
console.log(`Completed piece ${piece} with result:`, recording);
});
// Monitor overall gig performance
gig.on(GIG_EVENTS.PERFORMANCE_COMPLETED, ({ gig, results }) => {
console.log('Gig completed successfully:', results);
});
gig.on(GIG_EVENTS.PERFORMANCE_FAILED, ({ gig, error, results }) => {
console.error('Gig failed:', error, results);
});
```
## Event Patterns
### Event Forwarding
Events are automatically forwarded with context:
```typescript
const agent = new Agent({ driver });
// When you listen on the agent, you'll receive events from all its threads
agent.on(MESSAGE_EVENTS.ADDED, ({ thread, message, agent }) => {
// The agent context is automatically added
console.log(`Agent ${agent} received message in thread ${thread.id}`);
});
```
### Wildcard Event Listening
```typescript
// Listen to all events from a component
agent.on('**', (eventData) => {
console.log('Any event occurred:', eventData);
});
// Listen to all message events
agent.on('message.*', (eventData) => {
console.log('Message event occurred:', eventData);
});
// Listen to all tool events
agent.on('tool.*', (eventData) => {
console.log('Tool event occurred:', eventData);
});
```
### Error Handling Pattern
```typescript
// Centralized error handling
function setupErrorHandling(component) {
component.on(THREAD_EVENTS.ERROR_OCCURRED, ({ thread, error }) => {
logError('Thread Error', { threadId: thread.id, error: error.message });
});
component.on(TOOL_EVENTS.FAILED, ({ tool, name, error }) => {
logError('Tool Error', { toolName: name, error });
});
component.on(GIG_EVENTS.PERFORMANCE_FAILED, ({ gig, error }) => {
logError('Gig Error', { gigLabel: gig.label, error: error.message });
});
}
function logError(type, details) {
console.error(`[${type}]`, details);
// Send to monitoring service, etc.
}
```
### Progress Monitoring Pattern
```typescript
// Monitor workflow progress
function setupProgressMonitoring(gig) {
const progress = {
total: 0,
completed: 0,
failed: 0,
skipped: 0
};
gig.on(PIECE_EVENTS.EXECUTION_STARTED, () => {
progress.total++;
updateProgress(progress);
});
gig.on(PIECE_EVENTS.EXECUTION_COMPLETED, () => {
progress.completed++;
updateProgress(progress);
});
gig.on(PIECE_EVENTS.EXECUTION_FAILED, () => {
progress.failed++;
updateProgress(progress);
});
gig.on(PIECE_EVENTS.EXECUTION_SKIPPED, () => {
progress.skipped++;
updateProgress(progress);
});
}
function updateProgress(progress) {
const percentage = ((progress.completed + progress.failed + progress.skipped) / progress.total) * 100;
console.log(`Progress: ${percentage.toFixed(1)}% (${progress.completed} completed, ${progress.failed} failed, ${progress.skipped} skipped)`);
}
```
## Best Practices
### 1. Use Event Constants
Always use the provided event constants instead of string literals:
```typescript
// ✅ Good
agent.on(MESSAGE_EVENTS.ADDED, handler);
// ❌ Bad
agent.on('message.added', handler);
```
### 2. Handle Errors Gracefully
Always handle potential errors in event handlers:
```typescript
agent.on(MESSAGE_EVENTS.ADDED, ({ thread, message }) => {
try {
// Your event handling logic
processMessage(message);
} catch (error) {
console.error('Error processing message event:', error);
}
});
```
### 3. Clean Up Event Listeners
Remove event listeners when they're no longer needed:
```typescript
const handler = ({ thread, message }) => {
// Handle event
};
// Add listener
agent.on(MESSAGE_EVENTS.ADDED, handler);
// Remove listener when done
agent.off(MESSAGE_EVENTS.ADDED, handler);
```
### 4. Use Semantic Event Names
Choose the right event category for your use case:
```typescript
// ✅ Good - Listening to message lifecycle
thread.on(MESSAGE_EVENTS.ADDED, handleNewMessage);
// ✅ Good - Listening to thread state changes
thread.on(THREAD_EVENTS.ERROR_OCCURRED, handleThreadError);
// ✅ Good - Listening to tool execution
tool.on(TOOL_EVENTS.COMPLETED, handleToolResult);
```
### 5. Centralize Event Monitoring
Create dedicated modules for event monitoring:
```typescript
// events/monitor.ts
export class EventMonitor {
constructor() {
this.setupGlobalHandlers();
}
private setupGlobalHandlers() {
// Global error handling
// Performance monitoring
// Logging
}
monitorAgent(agent: Agent) {
agent.on(AGENT_EVENTS.THREAD_CREATED, this.handleThreadCreated);
agent.on(AGENT_EVENTS.THREAD_CLOSED, this.handleThreadClosed);
}
monitorGig(gig: Gig) {
gig.on(GIG_EVENTS.PERFORMANCE_COMPLETED, this.handleGigCompleted);
gig.on(GIG_EVENTS.PERFORMANCE_FAILED, this.handleGigFailed);
}
}
```
## Complete Event Reference
For a complete list of all events, their payloads, and descriptions, see the [Events Reference](./events.md).
## TypeScript Support
All events are fully typed in TypeScript with multiple approaches for different use cases:
### Individual Event Payload Types
```typescript
import { MessageEventPayloads, MESSAGE_EVENTS } from '@flatfile/improv';
// Event handlers are properly typed
const handler = (payload: MessageEventPayloads[typeof MESSAGE_EVENTS.ADDED]) => {
// payload.thread and payload.message are properly typed
console.log(payload.thread.id, payload.message.content);
};
```
### Universal Event Payload Helper
```typescript
import { EventPayload, MESSAGE_EVENTS, TOOL_EVENTS } from '@flatfile/improv';
// Generic helper type that works with any event
const messageHandler = (payload: EventPayload<typeof MESSAGE_EVENTS.ADDED>) => {
// Fully typed: payload.thread and payload.message
console.log(payload.thread.id, payload.message.content);
};
const toolHandler = (payload: EventPayload<typeof TOOL_EVENTS.COMPLETED>) => {
// Fully typed: payload.tool, payload.name, payload.args, payload.result
console.log(`Tool ${payload.name} completed with result:`, payload.result);
};
```
### Union Type for All Events
```typescript
import { EventPayloads } from '@flatfile/improv';
// Handle any event payload
const universalHandler = (payload: EventPayloads) => {
// TypeScript will properly narrow the type based on usage
console.log('Event received:', payload);
};
```
### Typed Event Listeners
```typescript
import { Agent, EventPayload, MESSAGE_EVENTS } from '@flatfile/improv';
const agent = new Agent({ driver });
// TypeScript infers the correct payload type
agent.on(MESSAGE_EVENTS.ADDED, (payload: EventPayload<typeof MESSAGE_EVENTS.ADDED>) => {
// payload.thread and payload.message are fully typed
console.log(`Message added: ${payload.message.content}`);
});
```
This ensures type safety and excellent IDE support with autocomplete and error checking.