crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
306 lines (305 loc) • 11.8 kB
JavaScript
/**
* LiteAgent Implementation
* A lightweight, performance-optimized agent implementation that focuses on minimal resource usage
*/
import { v4 as uuidv4 } from 'uuid';
import { Task } from '../task/Task.js';
/**
* A lightweight agent implementation optimized for memory efficiency and performance.
* Implements the BaseAgent interface with minimal resource usage and simpler execution patterns.
*/
export class LiteAgent {
id;
role;
goal;
backstory;
_llm;
_functionCallingLlm;
_tools = [];
_verbose;
_allowDelegation;
_maxIterations;
_maxCacheSize;
_enableCaching;
_executionTimeout;
_useSystemPrompt;
_systemPrompt;
// Efficient cache implementation using Map instead of object
_resultCache = new Map();
/**
* Create a new LiteAgent instance
* @param config Configuration for the agent
*/
constructor(config) {
this.id = uuidv4();
this.role = config.role;
this.goal = config.goal;
this.backstory = config.backstory;
// Initialize LLMs if provided
if (typeof config.llm === 'string') {
// Will implement LLM string-to-instance conversion later
throw new Error('String LLM initialization not yet implemented');
}
else {
this._llm = config.llm;
}
if (typeof config.functionCallingLlm === 'string') {
// Will implement LLM string-to-instance conversion later
throw new Error('String functionCallingLlm initialization not yet implemented');
}
else {
this._functionCallingLlm = config.functionCallingLlm;
}
// Initialize tools
if (config.tools) {
this._tools = [...config.tools];
}
// Set optimization options with sensible defaults
this._verbose = config.verbose ?? false;
this._allowDelegation = config.allowDelegation ?? true;
this._maxIterations = config.maxIterations ?? 10; // LiteAgent uses fewer iterations by default
this._maxCacheSize = config.maxCacheSize ?? 100;
this._enableCaching = config.enableCaching ?? true;
this._executionTimeout = config.executionTimeout ?? 60000; // 1 minute default
this._useSystemPrompt = config.useSystemPrompt ?? true;
this._systemPrompt = config.systemPrompt;
}
/**
* Execute a task with this agent
* @param task Task to execute
* @param context Additional context for the task
* @param tools Optional tools for the task
* @returns Promise resolving to TaskExecutionResult
*/
async executeTask(task, context, tools) {
// Start tracking execution time
const startTime = Date.now();
// Create a unique hash for the task+context+tools combination for caching
const taskHash = this._enableCaching ? this._hashTask(task, context, tools) : '';
// Check cache if enabled
if (this._enableCaching && taskHash) {
const cachedResult = this._resultCache.get(taskHash);
if (cachedResult) {
if (this._verbose) {
console.log(`[LiteAgent] Using cached result for task: ${task.description}`);
}
return cachedResult;
}
}
// Check if LLM is configured
if (!this._llm) {
throw new Error('LLM not configured for agent');
}
// Prepare tools for execution
const combinedTools = tools ? [...this._tools, ...tools] : this._tools;
// Prepare prompt for the LLM
const prompt = this._createTaskPrompt(task, context, combinedTools);
// Execute the task with a timeout
try {
const executionPromise = this._executeTaskWithLLM(prompt, combinedTools);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Task execution timed out')), this._executionTimeout);
});
// Use Promise.race to implement timeout
const result = await Promise.race([executionPromise, timeoutPromise]);
// Calculate execution time
const executionTime = Date.now() - startTime;
// Add execution time to result metadata
const resultWithMetadata = {
...result,
metadata: {
...result.metadata,
executionTime
}
};
// Cache the result if caching is enabled
if (this._enableCaching && taskHash) {
this._addToCache(taskHash, resultWithMetadata);
}
return resultWithMetadata;
}
catch (error) {
// Handle errors
const errorMessage = error instanceof Error ? error.message : String(error);
if (this._verbose) {
console.error(`[LiteAgent] Error executing task: ${errorMessage}`);
}
return {
output: `Error executing task: ${errorMessage}`,
metadata: {
executionTime: Date.now() - startTime,
error: errorMessage
}
};
}
}
/**
* Get tools for delegating tasks to other agents
* @param agents Agents that can be delegated to
* @returns Array of BaseTool instances for delegation
*/
getDelegationTools(agents) {
// If delegation is not allowed, return an empty array
if (!this._allowDelegation) {
return [];
}
// Create a tool for each agent that can be delegated to
return agents
.filter(agent => agent.id !== this.id) // Don't create a delegation tool for self
.map(agent => {
// Transform agent name to a valid tool name
const agentName = agent.role.toLowerCase().replace(/\s+/g, '_');
return {
name: `delegate_to_${agentName}`,
description: `Delegate a task to ${agent.role} who has the goal: ${agent.goal}`,
verbose: this._verbose,
cacheResults: false, // Delegation results should not be cached
execute: async (input) => {
if (this._verbose) {
console.log(`[LiteAgent] Delegating to ${agent.role}: ${input.task}`);
}
// Create properly structured task object
const taskConfig = {
description: input.task,
expectedOutput: input.expectedOutput || 'Detailed response',
agent,
async: false,
context: [],
cachingStrategy: 'none'
};
// Create a proper Task instance
const delegatedTask = new Task(taskConfig);
// Execute the delegated task
const result = await agent.executeTask(delegatedTask);
return {
success: true,
result: result.output
};
},
getMetadata: () => ({
name: `delegate_to_${agentName}`,
description: `Delegate a task to ${agent.role} who has the goal: ${agent.goal}`
})
};
});
}
/**
* Set knowledge for the agent
* @param knowledge Knowledge sources or embeddings
*/
async setKnowledge(knowledge) {
// Placeholder implementation
// Will be implemented in a future update
if (this._verbose) {
console.log(`[LiteAgent] Knowledge setting not yet implemented`);
}
}
/**
* Create a hash for the task, context, and tools for caching purposes
* @private
*/
_hashTask(task, context, tools) {
try {
// Create a simple object representing the task execution parameters
const taskObj = {
taskId: task.id,
description: task.description,
expectedOutput: task.expectedOutput,
context,
tools: tools?.map(t => t.name).sort() // Only include tool names for simplicity
};
// Use JSON.stringify for simple hashing - could be improved with a proper hash function
return JSON.stringify(taskObj);
}
catch (error) {
// If hashing fails, return an empty string to disable caching for this task
if (this._verbose) {
console.error(`[LiteAgent] Error creating task hash: ${error}`);
}
return '';
}
}
/**
* Add a result to the cache, managing cache size
* @private
*/
_addToCache(key, result) {
if (!key)
return; // Skip empty keys for safety
// Add the new result
this._resultCache.set(key, result);
// Manage cache size - if we exceed the max size, remove the oldest entries
if (this._resultCache.size > this._maxCacheSize) {
// Get the first key using the iterator
const oldestKey = this._resultCache.keys().next().value;
if (oldestKey) { // Ensure key exists before attempting to delete
this._resultCache.delete(oldestKey);
}
}
}
/**
* Create a prompt for the task
* @private
*/
_createTaskPrompt(task, context, tools) {
// Build a prompt for the LLM
let prompt = '';
// Add system prompt if enabled
if (this._useSystemPrompt && this._systemPrompt) {
prompt += `${this._systemPrompt}\n\n`;
}
// Add agent information
prompt += `You are ${this.role}, with the goal: ${this.goal}.`;
if (this.backstory) {
prompt += `\nBackstory: ${this.backstory}`;
}
// Add task description
prompt += `\n\nTask: ${task.description}`;
// Add expected output if available
if (task.expectedOutput) {
prompt += `\nExpected output: ${task.expectedOutput}`;
}
// Add context if available
if (context) {
prompt += `\n\nAdditional Context: ${context}`;
}
// Add tools if available
if (tools && tools.length > 0) {
prompt += '\n\nTools available:';
for (const tool of tools) {
prompt += `\n- ${tool.name}: ${tool.description}`;
}
}
// Add final instruction
prompt += '\n\nPlease complete this task to the best of your ability.';
return prompt;
}
/**
* Execute the task with the LLM
* @private
*/
async _executeTaskWithLLM(prompt, tools) {
if (!this._llm) {
throw new Error('LLM not configured for agent');
}
// For now, a simple implementation that calls the LLM
// Future enhancement: add tool usage support
// Create a user message with the prompt
const message = {
role: 'user',
content: prompt
};
// Call the LLM using the complete method
const response = await this._llm.complete([message]);
// Return the result with optimized metadata handling
return {
output: response.content,
metadata: {
iterations: 1, // Only one iteration for now
totalTokens: response.totalTokens,
promptTokens: response.promptTokens,
completionTokens: response.completionTokens
}
};
}
}