Providers
Understanding providers in ElizaOS, including the Provider interface, data retrieval patterns, and real-world examples from plugin-bootstrap for contextual information
Providers are data sources that supply contextual information to agents in ElizaOS. They retrieve, format, and deliver external data and services to enhance agent responses and capabilities. This page covers the Provider interface, implementation patterns, and practical examples from the plugin-bootstrap package.
Provider Interface
The Provider interface defines the structure for all data providers:
interface Provider {
/** Provider name/identifier */
name: string;
/** Description of the provider */
description?: string;
/** Whether the provider is dynamic */
dynamic?: boolean;
/** Position of the provider in the provider list, positive or negative */
position?: number;
/** Whether the provider is private */
private?: boolean;
/** Data retrieval function */
get: (runtime: IAgentRuntime, message: Memory, state: State) => Promise<ProviderResult>;
}
Core Components
ProviderResult
The result structure returned by providers:
interface ProviderResult {
/** Key-value pairs for template substitution */
values?: {
[key: string]: any;
};
/** Additional data for processing */
data?: {
[key: string]: any;
};
/** Formatted text for direct inclusion */
text?: string;
}
Provider Properties
- name: Unique identifier for the provider
- description: Human-readable description of what the provider does
- dynamic: Whether the provider updates dynamically based on context
- position: Controls ordering in provider list (positive or negative values)
- private: If true, provider is not shown in regular lists and must be called explicitly
- get: Function that retrieves and formats data
Real-World Example: Character Provider
Let's examine the character provider from plugin-bootstrap, which provides character information to agents:
export const characterProvider: Provider = {
name: "CHARACTER",
description: "Character information",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const character = runtime.character;
// Character name
const agentName = character.name;
// Handle bio (string or random selection from array)
const bioText = Array.isArray(character.bio)
? character.bio
.sort(() => 0.5 - Math.random())
.slice(0, 10)
.join(" ")
: character.bio || "";
const bio = addHeader(`# About ${character.name}`, bioText);
// System prompt
const system = character.system ?? "";
// Select random topic if available
const topicString =
character.topics && character.topics.length > 0
? character.topics[Math.floor(Math.random() * character.topics.length)]
: null;
const topic = topicString || "";
// Format topics list
const topics =
character.topics && character.topics.length > 0
? `${character.name} is also interested in ${character.topics
.filter((topic) => topic !== topicString)
.sort(() => 0.5 - Math.random())
.slice(0, 5)
.map((topic, index, array) => {
if (index === array.length - 2) {
return `${topic} and `;
}
if (index === array.length - 1) {
return topic;
}
return `${topic}, `;
})
.join("")}`
: "";
// Select random adjective if available
const adjectiveString =
character.adjectives && character.adjectives.length > 0
? character.adjectives[Math.floor(Math.random() * character.adjectives.length)]
: "";
const adjective = adjectiveString || "";
// Format post examples
const formattedCharacterPostExamples = !character.postExamples
? ""
: character.postExamples
.sort(() => 0.5 - Math.random())
.map((post) => {
const messageString = `${post}`;
return messageString;
})
.slice(0, 50)
.join("\n");
const characterPostExamples =
formattedCharacterPostExamples &&
formattedCharacterPostExamples.replaceAll("\n", "").length > 0
? addHeader(`# Example Posts for ${character.name}`, formattedCharacterPostExamples)
: "";
// Format message examples
const formattedCharacterMessageExamples = !character.messageExamples
? ""
: character.messageExamples
.sort(() => 0.5 - Math.random())
.slice(0, 5)
.map((example) => {
const exampleNames = Array.from({ length: 5 }, () =>
Math.random().toString(36).substring(2, 8)
);
return example
.map((message) => {
let messageString = `${message.name}: ${message.content.text}${
message.content.action || message.content.actions
? ` (actions: ${message.content.action || message.content.actions?.join(", ")})`
: ""
}`;
exampleNames.forEach((name, index) => {
const placeholder = `{{name${index + 1}}}`;
messageString = messageString.replaceAll(placeholder, name);
});
return messageString;
})
.join("\n");
})
.join("\n\n");
const characterMessageExamples =
formattedCharacterMessageExamples &&
formattedCharacterMessageExamples.replaceAll("\n", "").length > 0
? addHeader(
`# Example Conversations for ${character.name}`,
formattedCharacterMessageExamples
)
: "";
const room = state.data.room ?? (await runtime.getRoom(message.roomId));
const isPostFormat = room?.type === ChannelType.FEED || room?.type === ChannelType.THREAD;
// Style directions
const postDirections =
(character?.style?.all?.length && character?.style?.all?.length > 0) ||
(character?.style?.post?.length && character?.style?.post?.length > 0)
? addHeader(
`# Post Directions for ${character.name}`,
(() => {
const all = character?.style?.all || [];
const post = character?.style?.post || [];
return [...all, ...post].join("\n");
})()
)
: "";
const messageDirections =
(character?.style?.all?.length && character?.style?.all?.length > 0) ||
(character?.style?.chat?.length && character?.style?.chat?.length > 0)
? addHeader(
`# Message Directions for ${character.name}`,
(() => {
const all = character?.style?.all || [];
const chat = character?.style?.chat || [];
return [...all, ...chat].join("\n");
})()
)
: "";
const directions = isPostFormat ? postDirections : messageDirections;
const examples = isPostFormat ? characterPostExamples : characterMessageExamples;
const values = {
agentName,
bio,
system,
topic,
topics,
adjective,
messageDirections,
postDirections,
directions,
examples,
characterPostExamples,
characterMessageExamples,
};
const data = {
bio,
adjective,
topic,
topics,
character,
directions,
examples,
system,
};
const topicSentence = topicString
? `${character.name} is currently interested in ${topicString}`
: "";
const adjectiveSentence = adjectiveString ? `${character.name} is ${adjectiveString}` : "";
// Combine all text sections
const text = [bio, adjectiveSentence, topicSentence, topics, directions, examples, system]
.filter(Boolean)
.join("\n\n");
return {
values,
data,
text,
};
},
};
More Provider Examples from Plugin-Bootstrap
Time Provider
export const timeProvider: Provider = {
name: "TIME",
get: async (_runtime: IAgentRuntime, _message: Memory) => {
const currentDate = new Date();
// Get UTC time since bots will be communicating with users around the globe
const options = {
timeZone: "UTC",
dateStyle: "full" as const,
timeStyle: "long" as const,
};
const humanReadable = new Intl.DateTimeFormat("en-US", options).format(currentDate);
return {
data: {
time: currentDate,
},
values: {
time: humanReadable,
},
text: `The current date and time is ${humanReadable}. Please use this as your reference for any time-based operations or responses.`,
};
},
};
Recent Messages Provider
export const recentMessagesProvider: Provider = {
name: "RECENT_MESSAGES",
description: "Recent conversation messages",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const recentMessages = await runtime.getMemories({
tableName: "messages",
roomId: message.roomId,
count: 10,
unique: false,
});
const formattedMessages = recentMessages
.reverse()
.map((msg) => {
const timestamp = new Date(msg.createdAt).toLocaleTimeString();
return `[${timestamp}] ${msg.entityId}: ${msg.content.text}`;
})
.join("\n");
const values = {
recentMessages: formattedMessages,
messageCount: recentMessages.length,
};
const data = {
messages: recentMessages,
oldestMessage: recentMessages[0],
newestMessage: recentMessages[recentMessages.length - 1],
};
const text = addHeader("# Recent Messages", formattedMessages);
return {
values,
data,
text,
};
},
};
Facts Provider
export const factsProvider: Provider = {
name: "FACTS",
description: "Key facts that the agent knows",
dynamic: true,
get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => {
// Get recent messages for context
const recentMessages = await runtime.getMemories({
tableName: "messages",
roomId: message.roomId,
count: 10,
unique: false,
});
// Join the text of the last 5 messages for embedding
const last5Messages = recentMessages
.slice(-5)
.map((message) => message.content.text)
.join("\n");
// Generate embedding from recent conversation context
const embedding = await runtime.useModel(ModelType.TEXT_EMBEDDING, {
text: last5Messages,
});
// Search for relevant facts using embedding similarity
const [relevantFacts, recentFactsData] = await Promise.all([
runtime.searchMemories({
tableName: "facts",
embedding,
roomId: message.roomId,
worldId: message.worldId,
count: 6,
query: message.content.text,
}),
runtime.searchMemories({
embedding,
query: message.content.text,
tableName: "facts",
roomId: message.roomId,
entityId: message.entityId,
count: 6,
}),
]);
// Join and deduplicate facts
const allFacts = [...relevantFacts, ...recentFactsData].filter(
(fact, index, self) => index === self.findIndex((t) => t.id === fact.id)
);
if (allFacts.length === 0) {
return {
values: { facts: "" },
data: { facts: allFacts },
text: "No facts available.",
};
}
const formattedFacts = allFacts
.reverse()
.map((fact) => fact.content.text)
.join("\n");
const text = `Key facts that ${runtime.character.name} knows:\n${formattedFacts}`;
return {
values: { facts: formattedFacts },
data: { facts: allFacts },
text,
};
},
};
Relationships Provider
export const relationshipsProvider: Provider = {
name: "RELATIONSHIPS",
description:
"Relationships between {{agentName}} and other people, or between other people that {{agentName}} has observed interacting with",
dynamic: true,
get: async (runtime: IAgentRuntime, message: Memory) => {
// Get all relationships for the current user
const relationships = await runtime.getRelationships({
entityId: message.entityId,
});
if (!relationships || relationships.length === 0) {
return {
data: { relationships: [] },
values: { relationships: "No relationships found." },
text: "No relationships found.",
};
}
// Sort relationships by interaction strength (descending)
const sortedRelationships = relationships
.filter((rel) => rel.metadata?.interactions)
.sort(
(a, b) =>
((b.metadata?.interactions as number) || 0) -
((a.metadata?.interactions as number) || 0)
)
.slice(0, 30); // Get top 30
// Fetch entities for all unique target IDs
const uniqueEntityIds = Array.from(
new Set(sortedRelationships.map((rel) => rel.targetEntityId as UUID))
);
const entities = await Promise.all(
uniqueEntityIds.map((id) => runtime.getEntityById(id))
);
// Create entity lookup map
const entityMap = new Map<string, Entity | null>();
entities.forEach((entity, index) => {
if (entity) {
entityMap.set(uniqueEntityIds[index], entity);
}
});
// Format relationships with entity names and metadata
const formattedRelationships = sortedRelationships
.map((rel) => {
const entity = entityMap.get(rel.targetEntityId as UUID);
if (!entity) return null;
const names = entity.names.join(" aka ");
const tags = rel.tags ? rel.tags.join(", ") : "";
const metadata = JSON.stringify(
Object.entries(entity.metadata)
.map(([key, value]) => `${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`)
.join("\n")
);
return `${names}\n${tags}\n${metadata}\n`;
})
.filter(Boolean)
.join("\n");
return {
data: { relationships: formattedRelationships },
values: { relationships: formattedRelationships },
text: `# ${runtime.character.name} has observed ${message.content.senderName || message.content.name} interacting with these people:\n${formattedRelationships}`,
};
},
};
Capabilities Provider
export const capabilitiesProvider: Provider = {
name: "CAPABILITIES",
get: async (runtime: IAgentRuntime, _message: Memory): Promise<ProviderResult> => {
// Get all registered services
const services = runtime.getAllServices();
if (!services || services.size === 0) {
return {
text: "No services are currently registered.",
};
}
// Extract capability descriptions from all services
const capabilities: string[] = [];
for (const [serviceType, service] of services) {
if (service.capabilityDescription) {
capabilities.push(
`${serviceType} - ${service.capabilityDescription.replace("{{agentName}}", runtime.character.name)}`
);
}
}
if (capabilities.length === 0) {
return {
text: "No capability descriptions found in the registered services.",
};
}
// Format the capabilities into a readable list
const formattedCapabilities = capabilities.join("\n");
return {
data: {
capabilities,
},
text: `# ${runtime.character.name}'s Capabilities\n\n${formattedCapabilities}`,
};
},
};
Provider Implementation Patterns
Basic Provider Structure
export const myProvider: Provider = {
name: "MY_PROVIDER",
description: "Description of what this provider does",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
// Data retrieval logic
const data = await fetchData();
// Format data for template substitution
const values = {
myValue: data.value,
myCount: data.items.length,
};
// Additional structured data
const dataObject = {
rawData: data,
processedData: processData(data),
};
// Formatted text for direct inclusion
const text = formatDataAsText(data);
return {
values,
data: dataObject,
text,
};
},
};
Data Retrieval Patterns
Database Provider
export const databaseProvider: Provider = {
name: "DATABASE",
description: "Database query results",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const query = state.values?.query || "SELECT * FROM table";
const results = await runtime.database.query(query);
const values = {
resultCount: results.length,
query: query,
};
const data = {
results,
metadata: {
timestamp: Date.now(),
queryExecutionTime: results.executionTime,
},
};
const text = addHeader(
"# Database Results",
results.map((row) => JSON.stringify(row)).join("\n")
);
return { values, data, text };
},
};
API Provider
export const apiProvider: Provider = {
name: "API",
description: "External API data",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const apiKey = runtime.getSetting("api_key");
const endpoint = state.values?.endpoint || "https://api.example.com/data";
try {
const response = await fetch(endpoint, {
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
});
const data = await response.json();
const values = {
status: response.status,
dataCount: data.items?.length || 0,
lastUpdated: data.lastUpdated,
};
const formattedData = {
apiData: data,
responseHeaders: Object.fromEntries(response.headers),
cached: false,
};
const text = addHeader("# API Data", JSON.stringify(data, null, 2));
return { values, data: formattedData, text };
} catch (error) {
return {
values: { error: error.message },
data: { error },
text: addHeader("# API Error", `Failed to fetch data: ${error.message}`),
};
}
},
};
File System Provider
export const fileSystemProvider: Provider = {
name: "FILE_SYSTEM",
description: "File system information",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const path = state.values?.path || "./";
const files = await fs.readdir(path);
const fileStats = await Promise.all(
files.map(async (file) => {
const stats = await fs.stat(`${path}/${file}`);
return {
name: file,
size: stats.size,
isDirectory: stats.isDirectory(),
modified: stats.mtime,
};
})
);
const values = {
path,
fileCount: files.length,
totalSize: fileStats.reduce((sum, file) => sum + file.size, 0),
};
const data = {
files: fileStats,
directories: fileStats.filter((f) => f.isDirectory),
regularFiles: fileStats.filter((f) => !f.isDirectory),
};
const text = addHeader(
"# File System",
fileStats.map((f) => `${f.name} (${f.size} bytes)`).join("\n")
);
return { values, data, text };
},
};
Dynamic Provider Patterns
Context-Aware Provider
export const contextAwareProvider: Provider = {
name: "CONTEXT_AWARE",
description: "Context-sensitive information",
dynamic: true,
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const context = analyzeContext(message, state);
let data;
let text;
switch (context.type) {
case "technical":
data = await getTechnicalData(context);
text = addHeader("# Technical Context", formatTechnicalData(data));
break;
case "social":
data = await getSocialData(context);
text = addHeader("# Social Context", formatSocialData(data));
break;
default:
data = await getGeneralData(context);
text = addHeader("# General Context", formatGeneralData(data));
}
const values = {
contextType: context.type,
confidence: context.confidence,
relevance: context.relevance,
};
return { values, data, text };
},
};
Caching Provider
export const cachingProvider: Provider = {
name: "CACHING",
description: "Cached data with automatic refresh",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const cacheKey = `provider-cache-${message.roomId}`;
const cacheTimeout = 5 * 60 * 1000; // 5 minutes
let cachedData = await runtime.getCache(cacheKey);
if (!cachedData || Date.now() - cachedData.timestamp > cacheTimeout) {
// Fetch fresh data
const freshData = await fetchExpensiveData();
cachedData = {
data: freshData,
timestamp: Date.now(),
};
await runtime.setCache(cacheKey, cachedData);
}
const values = {
cached: Date.now() - cachedData.timestamp < cacheTimeout,
cacheAge: Date.now() - cachedData.timestamp,
dataCount: cachedData.data.items?.length || 0,
};
const data = {
...cachedData.data,
cacheMetadata: {
timestamp: cachedData.timestamp,
fresh: Date.now() - cachedData.timestamp < cacheTimeout,
},
};
const text = addHeader(
"# Cached Data",
`Data cached at ${new Date(cachedData.timestamp).toLocaleString()}`
);
return { values, data, text };
},
};
Advanced Provider Patterns
Composite Provider
export const compositeProvider: Provider = {
name: "COMPOSITE",
description: "Combines multiple data sources",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const [userInfo, preferences, activity] = await Promise.all([
getUserInfo(message.entityId),
getUserPreferences(message.entityId),
getRecentActivity(message.entityId),
]);
const values = {
userName: userInfo.name,
userRole: userInfo.role,
preferredLanguage: preferences.language,
activityLevel: activity.level,
lastActive: activity.lastSeen,
};
const data = {
userInfo,
preferences,
activity,
composite: {
completeness: calculateCompleteness(userInfo, preferences, activity),
riskLevel: assessRiskLevel(userInfo, activity),
},
};
const sections = [
addHeader("# User Information", formatUserInfo(userInfo)),
addHeader("# Preferences", formatPreferences(preferences)),
addHeader("# Activity", formatActivity(activity)),
];
const text = sections.filter(Boolean).join("\n\n");
return { values, data, text };
},
};
Streaming Provider
export const streamingProvider: Provider = {
name: "STREAMING",
description: "Real-time streaming data",
dynamic: true,
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const stream = await getDataStream(message.roomId);
const buffer = [];
// Collect streaming data for a short period
const collectData = new Promise((resolve) => {
const timeout = setTimeout(() => {
stream.close();
resolve(buffer);
}, 1000); // Collect for 1 second
stream.on("data", (chunk) => {
buffer.push(chunk);
});
stream.on("end", () => {
clearTimeout(timeout);
resolve(buffer);
});
});
const streamData = await collectData;
const values = {
dataPoints: streamData.length,
streamStatus: "active",
lastUpdate: Date.now(),
};
const data = {
streamData,
statistics: calculateStreamStats(streamData),
trends: analyzeTrends(streamData),
};
const text = addHeader(
"# Streaming Data",
`Received ${streamData.length} data points in the last second`
);
return { values, data, text };
},
};
Conditional Provider
export const conditionalProvider: Provider = {
name: "CONDITIONAL",
description: "Provides different data based on conditions",
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const userLevel = await getUserLevel(message.entityId);
const timeOfDay = new Date().getHours();
const isWeekend = [0, 6].includes(new Date().getDay());
let data;
let text;
if (userLevel === "admin") {
data = await getAdminData();
text = addHeader("# Admin Data", formatAdminData(data));
} else if (timeOfDay >= 22 || timeOfDay <= 6) {
data = await getNightModeData();
text = addHeader("# Night Mode", formatNightData(data));
} else if (isWeekend) {
data = await getWeekendData();
text = addHeader("# Weekend Info", formatWeekendData(data));
} else {
data = await getStandardData();
text = addHeader("# Standard Info", formatStandardData(data));
}
const values = {
userLevel,
timeOfDay,
isWeekend,
dataType: determineDataType(userLevel, timeOfDay, isWeekend),
};
return { values, data, text };
},
};
Best Practices
Provider Design
- Single Responsibility: Each provider should have a clear, focused purpose
- Efficient Data Retrieval: Cache expensive operations and use appropriate timeouts
- Error Handling: Gracefully handle failures and provide fallback data
- Consistent Formatting: Use standard formatting patterns for text output
Performance Optimization
// Good: Efficient data retrieval with caching
get: async (runtime, message, state) => {
const cacheKey = `provider-${message.roomId}`;
const cached = await runtime.getCache(cacheKey);
if (cached && Date.now() - cached.timestamp < 60000) {
return cached.data;
}
const data = await fetchData();
await runtime.setCache(cacheKey, { data, timestamp: Date.now() });
return data;
};
// Better: Parallel data fetching
get: async (runtime, message, state) => {
const [userData, preferences, activity] = await Promise.all([
getUserData(message.entityId),
getUserPreferences(message.entityId),
getRecentActivity(message.entityId),
]);
return formatCombinedData(userData, preferences, activity);
};
Error Handling
// Good: Comprehensive error handling
get: async (runtime, message, state) => {
try {
const data = await fetchExternalData();
return formatData(data);
} catch (error) {
logger.error("Provider failed:", error);
// Return fallback data
return {
values: { error: "Data unavailable" },
data: { error },
text: addHeader("# Error", "Unable to retrieve data at this time"),
};
}
};
Data Formatting
// Good: Consistent formatting
const formatData = (data) => {
const values = {
count: data.items?.length || 0,
status: data.status || "unknown",
lastUpdated: data.timestamp || Date.now(),
};
const formattedText = data.items
? data.items.map((item) => `• ${item.name}: ${item.value}`).join("\n")
: "No data available";
return {
values,
data: { raw: data, processed: processData(data) },
text: addHeader("# Data", formattedText),
};
};
Provider Configuration
Private Providers
// Private provider - must be called explicitly
export const privateProvider: Provider = {
name: "PRIVATE_DATA",
description: "Sensitive information",
private: true,
get: async (runtime, message, state) => {
// Only accessible through explicit calls
const sensitiveData = await getSensitiveData();
return formatSensitiveData(sensitiveData);
},
};
Positioned Providers
// High priority provider (runs first)
export const urgentProvider: Provider = {
name: "URGENT",
description: "Urgent information",
position: -100,
get: async (runtime, message, state) => {
return getUrgentData();
},
};
// Low priority provider (runs last)
export const supplementaryProvider: Provider = {
name: "SUPPLEMENTARY",
description: "Additional information",
position: 100,
get: async (runtime, message, state) => {
return getSupplementaryData();
},
};
Testing Providers
Unit Testing
describe("characterProvider", () => {
it("should return character information", async () => {
const runtime = mockRuntime();
const message = mockMessage();
const state = mockState();
const result = await characterProvider.get(runtime, message, state);
expect(result.values.agentName).toBe("TestAgent");
expect(result.text).toContain("About TestAgent");
});
it("should handle array bio correctly", async () => {
const runtime = mockRuntime();
runtime.character.bio = ["Bio 1", "Bio 2", "Bio 3"];
const result = await characterProvider.get(runtime, mockMessage(), mockState());
expect(result.values.bio).toContain("Bio");
expect(result.data.bio).toContain("Bio");
});
});
Integration Testing
describe("Provider Integration", () => {
it("should work with runtime", async () => {
const runtime = new AgentRuntime({
character: mockCharacter(),
// ... other config
});
runtime.registerProvider(myProvider);
const state = await runtime.composeState(mockMessage(), ["MY_PROVIDER"]);
expect(state.values.myProviderData).toBeDefined();
});
});
Common Pitfalls
Performance Issues
// Bad: Synchronous expensive operations
get: async (runtime, message, state) => {
const data = expensiveOperation(); // Blocks execution
return formatData(data);
};
// Good: Asynchronous operations with timeout
get: async (runtime, message, state) => {
const data = await Promise.race([
expensiveOperation(),
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5000)),
]);
return formatData(data);
};
Data Consistency Issues
// Bad: Inconsistent data structure
get: async (runtime, message, state) => {
if (someCondition) {
return { values: { count: 5 } };
} else {
return { data: { items: [] } }; // Inconsistent structure
}
};
// Good: Consistent structure
get: async (runtime, message, state) => {
const baseResult = {
values: {},
data: {},
text: "",
};
if (someCondition) {
baseResult.values.count = 5;
} else {
baseResult.data.items = [];
}
return baseResult;
};
Using Providers in Custom Actions
Providers can be dynamically selected and used within action handlers based on context:
Dynamic Provider Selection
export const smartAction: Action = {
name: "SMART_ACTION",
description: "Action that dynamically selects providers based on context",
handler: async (runtime, message, state, options, callback, responses) => {
// Collect providers from previous action responses
const providersFromResponses = responses?.flatMap(
(res) => res.content?.providers ?? []
) ?? [];
// Add context-specific providers based on message content
const additionalProviders = [];
if (message.content.text?.includes("image") || message.content.text?.includes("photo")) {
additionalProviders.push("ATTACHMENTS");
}
if (message.content.text?.includes("who") || message.content.text?.includes("person")) {
additionalProviders.push("ENTITIES", "RELATIONSHIPS");
}
if (message.content.text?.includes("remember") || message.content.text?.includes("fact")) {
additionalProviders.push("FACTS");
}
// Compose state with dynamic providers
const updatedState = await runtime.composeState(
message,
[...providersFromResponses, ...additionalProviders, "RECENT_MESSAGES"]
);
// Use the enriched state for action logic
const prompt = composePromptFromState({
state: updatedState,
template: myActionTemplate,
});
// Continue with action implementation...
},
};
Provider-Driven Action Flow
The messageHandler in plugin-bootstrap demonstrates how to use an LLM to select providers:
// First, use LLM to determine which providers are needed
let state = await runtime.composeState(message, [
"PROVIDERS", // List of available providers
"CHARACTER",
"RECENT_MESSAGES",
"ENTITIES",
]);
const prompt = composePromptFromState({
state,
template: messageHandlerTemplate, // Template that asks LLM to select providers
});
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
prompt,
});
// Parse the LLM response to get selected providers
const parsedResponse = parseKeyValueXml(response);
const selectedProviders = parsedResponse.providers || [];
// Re-compose state with the LLM-selected providers
state = await runtime.composeState(message, selectedProviders);
// Now use the enriched state for the actual action
const actionPrompt = composePromptFromState({
state,
template: actionTemplate,
});
Creating Custom Dynamic Providers
export const knowledgeProvider: Provider = {
name: "KNOWLEDGE",
description: "External knowledge based on conversation context",
dynamic: true, // Marks this as a dynamic provider
private: false, // Available for general use
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
// Extract keywords from conversation
const keywords = extractKeywords(message.content.text);
// Search knowledge base
const knowledge = await searchKnowledgeBase(keywords);
// If no relevant knowledge, check if we should fetch from external sources
if (!knowledge.length && shouldFetchExternal(keywords)) {
const externalData = await fetchExternalKnowledge(keywords);
// Store for future use
for (const item of externalData) {
await runtime.createMemory({
content: { text: item.text },
type: "knowledge",
roomId: message.roomId,
});
}
knowledge.push(...externalData);
}
return {
values: {
knowledge: knowledge.map(k => k.text).join("\n"),
knowledgeCount: knowledge.length,
},
data: {
knowledgeItems: knowledge,
keywords: keywords,
},
text: knowledge.length > 0
? addHeader("# Relevant Knowledge", knowledge.map(k => k.text).join("\n"))
: "No relevant knowledge found.",
};
},
};
Related Components
- Agents: How providers integrate with agent runtime
- Actions: How providers supply data to actions
- Evaluators: How providers support evaluation processes
- Character Definition: How character provider works with definitions
Summary
Providers are the data backbone of ElizaOS agents, supplying contextual information that enables intelligent responses and behaviors. The character provider demonstrates sophisticated data formatting and context awareness, while other providers show patterns for different data sources. Well-designed providers are efficient, reliable, and provide consistent data structures that enhance agent capabilities without compromising performance.
Evaluators
Understanding evaluators in ElizaOS, including the Evaluator interface, reflection patterns, and behavior assessment systems with real examples from plugin-bootstrap
Tasks
Understanding task management in ElizaOS, including Task and TaskWorker interfaces, scheduling patterns, and the TaskService implementation from plugin-bootstrap