This document was AI-generated by Cascade on 2025-01-02, based on analysis of the Eliza agent core source code from v.01.6-alpha5. While efforts have been made to ensure accuracy, this documentation may contain inaccuracies. Please use with discretion and refer to the original source code for definitive implementation details.
Providers are core components in Eliza that inject dynamic context and real-time information into agent interactions. They serve as a bridge between the agent and various external systems, enabling access to data, state management, and contextual information.
sequenceDiagram
participant R as Runtime
participant P as Provider Pool
participant I as Individual Provider
participant S as State
participant M as Message
R->>P: getProviders(runtime, message, state)
loop For each provider
P->>I: provider.get(runtime, message, state)
I->>S: Check State
I->>M: Process Message
I-->>P: Return Context
end
P-->>R: Concatenated Results
// In plugin definition
const myPlugin: Plugin = {
name: "my-plugin",
providers: [myProvider],
// ...other plugin properties
};
// Core runtime provider handling
async function getProviders(
runtime: IAgentRuntime,
message: Memory,
state?: State
) {
const providerResults = await Promise.all(
runtime.providers.map(async (provider) => {
return await provider.get(runtime, message, state);
})
);
return providerResults
.filter(result => result != null && result !== "")
.join("\n");
}
-
Initialization
- Runtime loads registered providers
- Each provider is initialized with runtime context
-
Message Processing
- Runtime receives incoming message
- Current state is gathered
- Providers are called in parallel
-
State Management
- Provider checks current state
- Updates state if necessary
- Returns modified state
-
Context Generation
- Provider processes message and state
- Generates contextual information
- Returns formatted context string
interface Provider {
get: (
runtime: IAgentRuntime,
message: Memory,
state?: State
) => Promise<any>;
}
-
Runtime Access
- Full access to runtime services
- Database operations
- Message management
- Cache control
-
Message Context
- Current message details
- User information
- Room/conversation context
- Content analysis
-
State Management
- Current operation state
- Historical context
- Operation flags
- Temporary data storage
const stateProvider: Provider = {
get: async (runtime, message, state) => {
// Manage and return operation state
return {
...state,
operationStatus: "active",
lastUpdate: Date.now()
};
}
};
const dataProvider: Provider = {
get: async (runtime, message, state) => {
// Fetch and return external data
const data = await runtime.database.query({
table: "user_data",
userId: message.userId
});
return formatData(data);
}
};
const contextProvider: Provider = {
get: async (runtime, message, state) => {
// Generate contextual information
const context = await runtime.messageManager.getRecentContext(message);
return processContext(context);
}
};
- Always check state before modifications
- Return modified state consistently
- Handle null/undefined states gracefully
const robustProvider: Provider = {
get: async (runtime, message, state) => {
try {
// Provider operations
return result;
} catch (error) {
runtime.logger.error("Provider error", error);
return state; // Return unchanged state on error
}
}
};
- Use caching when appropriate
- Implement rate limiting
- Handle async operations efficiently
- Filter out empty results
- Format data consistently
- Provide clear, structured output
- Handle missing data gracefully
- Maintain context relevance
-
Parallel Execution
- Providers run concurrently
- Results are aggregated
- Empty results are filtered
- Final context is concatenated
-
State Consistency
- State updates are sequential
- Last provider wins in conflicts
- State is preserved across calls
-
Error Handling
- Individual provider failures don't block others
- System continues with available results
- Errors are logged but not propagated
-
Resource Management
- Providers should be lightweight
- Heavy operations should be cached
- Resources should be released properly
Providers are a crucial part of Eliza's context management system, enriching the conversation with dynamic, contextual information. They serve as a bridge between the agent's knowledge base and the ongoing conversation.
graph TD
M[Message] --> R[Runtime]
R --> P1[Provider 1]
R --> P2[Provider 2]
R --> P3[Provider 3]
P1 --> C[Context]
P2 --> C
P3 --> C
C --> LLM[Language Model]
-
Context Generation
// Provider implementation get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { // Access runtime services const db = runtime.databaseAdapter; // Generate context based on message const relevantData = await db.query(...); // Return context string return `Relevant context: ${relevantData}`; }
-
Context Aggregation
// In runtime const context = await getProviders(runtime, message, state); // context = "Provider1Context\nProvider2Context\nProvider3Context"
-
Runtime Context
- Database access via
runtime.databaseAdapter
- Memory management via
runtime.messageManager
- Service access via
runtime.getService()
- Cache access via
runtime.cacheManager
- Database access via
-
Message Context
interface Memory { id: UUID; // Message ID content: Content; // Message content roomId: UUID; // Conversation room userId?: UUID; // Sender ID timestamp: Date; // Creation time metadata?: any; // Additional data }
-
State Context
interface State { userId?: UUID; agentId?: UUID; roomId: UUID; bio: string; lore: string; actors: string; actorsData?: Actor[]; goals?: string; goalsData?: Goal[]; recentMessages: string; recentMessagesData: Memory[]; }
-
Efficient Context Generation
const provider: Provider = { get: async (runtime, message, state) => { // 1. Cache expensive operations const cacheKey = `context_${message.id}`; const cached = await runtime.cacheManager.get(cacheKey); if (cached) return cached; // 2. Generate minimal relevant context const context = await generateContext(message); // 3. Cache for reuse await runtime.cacheManager.set(cacheKey, context); return context; } };
-
Context Formatting
- Keep context concise and relevant
- Use clear section headers
- Format data consistently
return ` RELEVANT_KNOWLEDGE: ${knowledge} USER_PREFERENCES: ${preferences} CONVERSATION_HISTORY: ${history} `;
-
Context Priority
- Return
null
or empty string for irrelevant contexts - Order context by importance (most important first)
- Limit context length based on token limits
- Return
-
Error Handling
get: async (runtime, message, state) => { try { const context = await generateContext(message); return context; } catch (error) { // Log error but don't fail console.error('Context generation failed:', error); return null; } }
-
Dynamic Context Loading
get: async (runtime, message, state) => { // Load context based on message content if (message.content.includes('weather')) { return await getWeatherContext(message); } if (message.content.includes('schedule')) { return await getScheduleContext(message); } return null; }
-
Context Chaining
get: async (runtime, message, state) => { // Build context chain const baseContext = await getBaseContext(message); const enhancedContext = await enhanceContext(baseContext); const finalContext = await formatContext(enhancedContext); return finalContext; }
-
Stateful Context
get: async (runtime, message, state) => { if (!state) return null; // Update state state.contextVersion = (state.contextVersion || 0) + 1; // Return both context and state return `Context Version: ${state.contextVersion}`; }
-
Context Inspection
get: async (runtime, message, state) => { const context = await generateContext(message); // Log context for debugging console.log('Generated context:', { length: context.length, sections: context.split('\n\n').length, preview: context.slice(0, 100) }); return context; }
-
Context Validation
function validateContext(context: string): boolean { // Check context format if (!context.includes('RELEVANT_KNOWLEDGE:')) return false; // Check context length if (context.length > MAX_CONTEXT_LENGTH) return false; return true; }
sequenceDiagram
participant A as Action Handler
participant R as Runtime
participant P as Provider
participant S as State Object
A->>R: Process Message
R->>P: getProviders(runtime, message, state)
P->>S: Check Current State
P->>S: Modify State (if needed)
P-->>R: Return Modified State
R-->>A: Forward State to Handler
-
Provider's Return Value
// Provider implementation get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { // Modifying state directly works because state is an object (passed by reference) if (state) { state.shouldSave = true; // This modification persists return state; // Returning state is optional if modified in place } return null; // Can return null/undefined for no changes }
-
Runtime's State Handling
// In core runtime async function getProviders( runtime: IAgentRuntime, message: Memory, state?: State ) { const providerResults = await Promise.all( runtime.providers.map(async (provider) => { return await provider.get(runtime, message, state); }) ); // Note: Only provider context strings are concatenated // State modifications happen in-place return providerResults .filter(result => result != null && result !== "") .join("\n"); }
-
State Modification Methods
- Direct Modification: State is an object passed by reference, so changes made directly to the state object persist
- Return Value: Provider can return the modified state, but this is optional if state was modified in place
- Context String: Provider can return a string to be included in the context, separate from state management
-
State Persistence
const stateProvider: Provider = { get: async (runtime, message, state) => { if (state) { // Method 1: Direct modification (Recommended) state.someFlag = true; // This persists // Method 2: Return new state (Not necessary if modified in place) return { ...state, someFlag: true }; // Method 3: Return context string only return "Some context"; // State changes still persist from Method 1 } } };
-
Common Issues
- Lost State Changes: If you create a new state object without returning it, changes won't be visible to other components
- Undefined State: Always check if state exists before modifying
- Context vs State: Distinguish between returning context strings and modifying state
-
Best Practices
const robustStateProvider: Provider = { get: async (runtime, message, state) => { // 1. Always check state existence if (!state) return null; // 2. Modify state directly for changes state.someFlag = true; // 3. Return context string if needed return "Provider context"; // State changes persist without explicit return } };
const actionHandler: Handler = async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback
) => {
// State includes all provider modifications
if (state.shouldSave) {
// This flag will be available if set by provider
// No special return handling needed
}
};
-
Initialization
- Runtime creates initial state object
- State passed to providers during message processing
-
Provider Phase
- Each provider receives same state object reference
- Modifications are made directly to state
- Context strings returned separately
-
Action Phase
- Modified state passed to action handlers
- All provider changes available in handler
- State continues to be mutable
-
State Lifetime
- State persists for duration of message processing
- Cleared/reset for each new message
- No automatic persistence between messages
This deep dive explains why your shouldSave
flag modifications in the provider should work as long as:
- You're modifying the state object directly
- The state object exists when the modification is made
- You're checking for the flag in the correct action handler context
If you're not seeing the flag in your action handler, verify:
- The state object exists when provider runs
- The modification is happening before the action handler executes
- You're looking at the same state object instance
import { Provider, IAgentRuntime, Memory, State } from "@ai16z/eliza";
interface CustomState extends State {
customData?: any;
}
const customProvider: Provider = {
get: async (
runtime: IAgentRuntime,
message: Memory,
state?: CustomState
) => {
// 1. Log operation start
runtime.logger.debug("Custom provider started", {
messageId: message.id
});
try {
// 2. Check/initialize state
const currentState = state || {};
// 3. Process message
const processedData = await processMessage(message);
// 4. Update state
const newState = {
...currentState,
customData: processedData
};
// 5. Generate context
const context = formatContext(processedData);
// 6. Return result
return {
state: newState,
context: context
};
} catch (error) {
// 7. Handle errors
runtime.logger.error("Custom provider error", error);
return state;
}
}
};
This document outlines the core operation of providers in the Eliza runtime system. Providers are essential for maintaining state, providing context, and integrating external data into the agent's operation flow.
A provider that retrieves relevant knowledge based on semantic similarity.
interface KnowledgeProvider extends Provider {
get: async (runtime: IAgentRuntime, message: Memory, state?: State) => {
// 1. Extract query from message
const query = message.content.text;
// 2. Get embeddings for query using core embed function
const embedding = await embed(runtime, query);
// 3. Search knowledge base using memory manager
const results = await runtime.knowledgeManager.searchMemoriesByEmbedding(
embedding,
{
match_threshold: 0.7,
count: 3,
roomId: message.roomId,
unique: true
}
);
// 4. Format results
if (!results.length) return null;
return `
RELEVANT_KNOWLEDGE:
${results.map(r => `- ${r.content.text}`).join('\n')}
`;
}
}
// Usage:
const runtime = new AgentRuntime({
providers: [new KnowledgeProvider()],
// ... other config
});
Maintains and provides user preferences across conversations.
interface UserPreferences {
language: string;
timezone: string;
notificationPrefs: string[];
}
class PreferenceProvider implements Provider {
private cache: Map<string, UserPreferences> = new Map();
async get(runtime: IAgentRuntime, message: Memory, state?: State) {
if (!state?.userId) return null;
// 1. Check cache first
let prefs = this.cache.get(state.userId);
if (!prefs) {
// 2. Load from database
const results = await runtime.databaseAdapter.query(
'SELECT preferences FROM users WHERE id = ?',
[state.userId]
);
if (results.length) {
prefs = results[0].preferences;
this.cache.set(state.userId, prefs);
}
}
// 3. Return formatted preferences
if (!prefs) return null;
return `
USER_PREFERENCES:
Language: ${prefs.language}
Timezone: ${prefs.timezone}
Notifications: ${prefs.notificationPrefs.join(', ')}
`;
}
}
Provides relevant historical conversations based on current context.
class ConversationMemoryProvider implements Provider {
async get(runtime: IAgentRuntime, message: Memory, state?: State) {
if (!state?.roomId) return null;
// 1. Get recent messages
const recentMessages = await runtime.messageManager.getMessages({
roomId: state.roomId,
limit: 10,
order: 'DESC'
});
// 2. Find relevant older messages
const olderMessages = await this.findRelevantHistory(
runtime,
message,
state.roomId
);
// 3. Combine and format
return this.formatConversationContext(
recentMessages,
olderMessages
);
}
private async findRelevantHistory(
runtime: IAgentRuntime,
currentMessage: Memory,
roomId: string
) {
// Get embedding for current message
const embedding = await embed(runtime, currentMessage.content.text);
// Search message history
return runtime.messageManager.searchMemoriesByEmbedding(
embedding,
{
match_threshold: 0.8,
count: 5,
roomId,
unique: true
}
);
}
private formatConversationContext(
recent: Memory[],
historical: Memory[]
): string {
return `
RECENT_CONVERSATION:
${this.formatMessages(recent)}
RELEVANT_HISTORY:
${this.formatMessages(historical)}
`;
}
private formatMessages(messages: Memory[]): string {
return messages
.map(m => `${m.userId}: ${m.content.text}`)
.join('\n');
}
}
Integrates real-time data from external APIs.
interface WeatherData {
temperature: number;
condition: string;
forecast: string[];
}
class WeatherProvider implements Provider {
private readonly API_KEY: string;
private readonly CACHE_TTL = 1800000; // 30 minutes
constructor(apiKey: string) {
this.API_KEY = apiKey;
}
async get(runtime: IAgentRuntime, message: Memory, state?: State) {
// 1. Check if weather info is needed
if (!this.isWeatherRelevant(message.content.text)) {
return null;
}
// 2. Get user's location from state
const location = await this.getUserLocation(runtime, state);
if (!location) return null;
// 3. Check cache
const cacheKey = `weather_${location}`;
const cached = await runtime.cacheManager.get(cacheKey);
if (cached) return cached;
// 4. Fetch weather data
try {
const weather = await this.fetchWeatherData(location);
// 5. Format response
const context = this.formatWeatherContext(weather);
// 6. Cache result
await runtime.cacheManager.set(
cacheKey,
context,
this.CACHE_TTL
);
return context;
} catch (error) {
console.error('Weather API error:', error);
return null;
}
}
private isWeatherRelevant(text: string): boolean {
const weatherKeywords = [
'weather', 'temperature', 'forecast',
'rain', 'sunny', 'cold', 'hot'
];
return weatherKeywords.some(kw =>
text.toLowerCase().includes(kw)
);
}
private async fetchWeatherData(
location: string
): Promise<WeatherData> {
const response = await fetch(
`https://api.weather.com/v1/forecast?` +
`location=${encodeURIComponent(location)}` +
`&apikey=${this.API_KEY}`
);
return response.json();
}
private formatWeatherContext(weather: WeatherData): string {
return `
WEATHER_INFORMATION:
Current Temperature: ${weather.temperature}°C
Condition: ${weather.condition}
3-Day Forecast:
${weather.forecast.join('\n')}
`;
}
}
Manages and provides context about ongoing tasks or conversations.
interface TaskState {
currentStep: number;
totalSteps: number;
taskType: string;
startTime: Date;
}
class TaskProvider implements Provider {
async get(runtime: IAgentRuntime, message: Memory, state?: State) {
if (!state) return null;
// 1. Get or initialize task state
let taskState = state.taskState as TaskState;
if (!taskState && this.isTaskInitiation(message)) {
taskState = await this.initializeTask(message);
state.taskState = taskState;
}
if (!taskState) return null;
// 2. Update task state based on message
taskState = await this.updateTaskState(
taskState,
message
);
state.taskState = taskState;
// 3. Generate task context
return this.generateTaskContext(taskState);
}
private isTaskInitiation(message: Memory): boolean {
const initiationPhrases = [
'let\'s start',
'begin task',
'new task'
];
return initiationPhrases.some(phrase =>
message.content.text.toLowerCase().includes(phrase)
);
}
private async initializeTask(
message: Memory
): Promise<TaskState> {
return {
currentStep: 1,
totalSteps: await this.analyzeTotalSteps(message),
taskType: await this.determineTaskType(message),
startTime: new Date()
};
}
generateTaskContext(task: TaskState): string {
const duration = Date.now() - task.startTime.getTime();
return `
CURRENT_TASK_STATUS:
Type: ${task.taskType}
Progress: Step ${task.currentStep} of ${task.totalSteps}
Duration: ${Math.floor(duration / 1000)}s
TASK_CONTEXT:
${this.getStepSpecificContext(task)}
`;
}
}
These case studies demonstrate:
- Integration with runtime services
- Efficient caching strategies
- State management
- Error handling
- External API integration
- Context formatting
- Real-world implementation patterns
Each example shows a different aspect of provider implementation and can be used as a template for similar use cases.