agentis
Version:
A TypeScript framework for building sophisticated multi-agent systems
358 lines (349 loc) • 14.7 kB
JavaScript
;
// src/agents/Agent.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Agent = void 0;
const Logger_1 = require("../logs/Logger");
const SupabaseClient_1 = require("../utils/SupabaseClient");
const ToolRegistry_1 = require("../tools/ToolRegistry");
const openai_1 = __importDefault(require("openai"));
const EnhancedMemoryClient_1 = require("../memory/EnhancedMemoryClient");
const EnhancedToolOrchestrator_1 = require("../tools/EnhancedToolOrchestrator");
class Agent {
constructor(id, name, lore, role, goals, tools = [], model) {
this.middlewares = [];
this.taskQueue = [];
this.isExecuting = false;
this.id = id;
this.name = name;
this.lore = lore;
this.role = role;
this.goals = goals;
this.shortTermMemory = {};
this.longTermMemory = {};
this.tools = tools;
// Default model configuration if none provided
this.model = model || {
provider: 'anthropic',
name: 'anthropic/claude-3-sonnet-20240229',
temperature: 0.7,
maxTokens: 4096
};
// Initialize the core LLM with proper error handling
const openRouterKey = process.env.OPENROUTER_API_KEY;
if (!openRouterKey) {
throw new Error('OPENROUTER_API_KEY is not set in environment variables');
}
this.llmClient = new openai_1.default({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: openRouterKey,
defaultHeaders: {
'HTTP-Referer': process.env.NEXT_PUBLIC_URL || 'http://localhost:3000',
'X-Title': 'Agentis Framework',
},
});
// Initialize tools and memory
this.toolRegistry = new ToolRegistry_1.ToolRegistry({ defaultTools: tools });
this.toolOrchestrator = new EnhancedToolOrchestrator_1.EnhancedToolOrchestrator({ defaultTools: tools });
this.memoryClient = new EnhancedMemoryClient_1.EnhancedMemoryClient();
}
async initializeMemory() {
const { error } = await SupabaseClient_1.supabase
.from('agents')
.upsert({
id: this.id,
name: this.name,
lore: this.lore,
role: this.role,
goals: this.goals
});
if (error) {
throw new Error(`Failed to initialize agent in database: ${error.message}`);
}
const existingMemories = await this.memoryClient.getMemory(this.id);
this.longTermMemory = existingMemories.reduce((acc, memory) => {
acc[memory.id.toString()] = memory.content;
return acc;
}, {});
}
useMiddleware(middleware) {
this.middlewares.push(middleware);
}
getMemoryClient() {
return this.memoryClient;
}
getToolRegistry() {
return this.toolRegistry;
}
getToolOrchestrator() {
return this.toolOrchestrator;
}
async receiveMessage(message) {
await Logger_1.Logger.log(this.id, Logger_1.LogType.MESSAGE, { event: 'receiveMessage', message });
console.log(`[${this.name}] Received message:`, message.content);
try {
// First, determine if we need to use any tools
const toolPlanningPrompt = `You are ${this.name}, ${this.lore}. Your role is ${this.role}.
You have access to these tools: ${this.tools.map(t => `${t.name}: ${t.description}`).join('\n')}
User message: ${message.content}
Based on this message, should you use any tools to help answer? Respond ONLY with a JSON object in this format:
{
"useTool": boolean,
"toolName": string | null,
"reason": string
}
IMPORTANT: Respond ONLY with the JSON object, no other text.`;
const planningResponse = await this.llmClient.chat.completions.create({
model: this.model?.name || 'anthropic/claude-3-sonnet-20240229',
temperature: this.model?.temperature || 0.7,
max_tokens: this.model?.maxTokens || 4096,
messages: [
{
role: 'system',
content: 'You are a JSON formatting assistant. Always respond with valid JSON only.'
},
{
role: 'user',
content: toolPlanningPrompt
}
]
});
let plan;
try {
plan = JSON.parse(planningResponse.choices[0]?.message?.content?.trim() || '{}');
}
catch (e) {
console.error('Failed to parse planning response:', planningResponse.choices[0]?.message?.content);
plan = { useTool: true, toolName: 'WebSearchTool', reason: 'Fallback to web search due to parsing error' };
}
let toolOutput = '';
if (plan.useTool) {
const tool = this.tools.find(t => t.name === plan.toolName);
if (tool) {
const result = await tool.execute(message.content);
toolOutput = result.result;
}
}
// Generate final response using tool output if available
const finalPrompt = `You are ${this.name}, ${this.lore}. Your role is ${this.role}.
${toolOutput ? `Here is relevant information I found: ${toolOutput}` : ''}
Please respond to: ${message.content}`;
const systemPrompt = `You are ${this.name}, a highly capable AI agent with expertise in ${this.role}.
${this.lore}
You have access to:
- Real-time data and analysis capabilities
- Tavily web search for current information
- Long-term memory storage
When providing information:
- Use Tavily web search to find current data
- Be direct and specific
- Make informed analyses based on available data
- Acknowledge limitations when they exist, but don't be overly cautious
- Current date: ${new Date().toISOString()}`;
const completion = await this.llmClient.chat.completions.create({
model: 'anthropic/claude-3-sonnet-20240229',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: finalPrompt }
]
});
const responseContent = completion.choices[0]?.message?.content || 'I apologize, I am unable to respond at the moment.';
const responseMessage = {
id: `msg-${Date.now()}`,
sender_id: this.id,
recipient_id: message.sender_id,
content: responseContent,
timestamp: Date.now()
};
await this.saveToMemory(message.content, responseContent);
return responseMessage;
}
catch (error) {
console.error('Error processing message:', error);
throw error;
}
}
async planAndExecute(goal) {
// Generate tasks for the goal
const tasks = await this.generateTasks(goal);
this.taskQueue.push(...tasks);
// Start execution if not already running
if (!this.isExecuting) {
await this.executeTaskQueue();
}
}
async executeTaskQueue() {
this.isExecuting = true;
while (this.taskQueue.length > 0) {
const currentTask = this.taskQueue[0];
try {
// 1. Plan tool calls needed for this task
const toolPlan = await this.planToolCalls(currentTask);
// 2. Execute each tool call in sequence
for (const toolCall of toolPlan) {
const tool = this.tools.find(t => t.name === toolCall.tool);
if (!tool) {
throw new Error(`Tool ${toolCall.tool} not found`);
}
const result = await tool.execute(toolCall.input);
// Store result in short-term memory
this.shortTermMemory[`${currentTask.id}-${toolCall.tool}`] = result;
}
// 3. Update task status
currentTask.status = 'completed';
currentTask.updated_at = Date.now();
// 4. Log completion
await Logger_1.Logger.log(this.id, Logger_1.LogType.STATUS_UPDATE, {
event: 'task-complete',
task: currentTask
});
}
catch (error) {
const taskError = {
message: error instanceof Error ? error.message : 'An unknown error occurred',
details: error
};
currentTask.status = 'failed';
currentTask.error = taskError.message;
await Logger_1.Logger.log(this.id, Logger_1.LogType.ERROR, {
event: 'task-failed',
task: currentTask,
error: taskError
});
}
// Remove task from queue
this.taskQueue.shift();
}
this.isExecuting = false;
}
async planToolCalls(task) {
// Use LLM to plan necessary tool calls
const llmTool = this.tools.find(tool => tool.name === 'OpenRouterTool');
const planningPrompt = `
Task: ${task.description}
Available tools: ${this.tools.map(t => `${t.name}: ${t.description}`).join('\n')}
Plan the necessary tool calls to complete this task. Return as JSON array:
[{ "tool": "toolName", "input": "tool input" }]
`;
const response = await llmTool?.execute(planningPrompt);
return JSON.parse(response?.result);
}
async generateTasks(goal) {
const llmTool = this.tools.find(tool => tool.name === 'OpenRouterTool');
const taskPlanningPrompt = `
Goal: ${goal}
Current role: ${this.role}
Break this goal down into sequential tasks. Return as JSON array:
[{ "description": "task description", "priority": 1-5 }]
`;
const response = await llmTool?.execute(taskPlanningPrompt);
const taskPlans = JSON.parse(response?.result);
return taskPlans.map((plan) => ({
id: `task-${this.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
description: plan.description,
priority: plan.priority,
status: 'pending',
assigned_agent_id: this.id,
created_at: Date.now(),
updated_at: Date.now()
}));
}
async executeTask(task) {
// Log task execution start
await Logger_1.Logger.log(this.id, Logger_1.LogType.STATUS_UPDATE, { event: 'executeTask-start', task });
console.log(`[${this.name}] Executing task:`, task.description);
// Example: Use the first available tool to execute the task
if (this.tools.length > 0) {
const tool = this.tools[0];
const result = await tool.execute(task.description);
await Logger_1.Logger.log(this.id, Logger_1.LogType.TOOL_CALL, { event: 'executeTask', tool: tool.name, result });
console.log(`[${this.name}] Tool call result:`, result);
}
// Update task status to completed
task.status = 'completed';
task.updated_at = Date.now();
await Logger_1.Logger.log(this.id, Logger_1.LogType.STATUS_UPDATE, { event: 'executeTask-complete', task });
}
async sendMessage(message) {
await Logger_1.Logger.log(this.id, Logger_1.LogType.MESSAGE, { event: 'sendMessage', message });
// Store in messages table
const { error: messageError } = await SupabaseClient_1.supabase
.from('messages')
.insert([{
id: message.id,
sender_id: message.sender_id,
recipient_id: message.recipient_id,
content: message.content,
timestamp: message.timestamp
}]);
if (messageError) {
console.error("Error storing message:", messageError);
throw messageError;
}
// Also store in memory for context
await this.memoryClient.saveMemory(this.id, {
content: `${message.sender_id}: ${message.content}`,
type: 'message',
metadata: {
sender_id: message.sender_id,
timestamp: message.timestamp
}
});
}
async updateMemory(key, value, type = 'short') {
if (type === 'short') {
this.shortTermMemory[key] = value;
}
else {
this.longTermMemory[key] = value;
await this.memoryClient.saveMemory(this.id, value);
}
}
async saveToMemory(message, response) {
try {
// Save the interaction as a message memory
await this.memoryClient.saveMemory(this.id, {
content: `User: ${message}\nAgent: ${response}`,
type: 'message',
metadata: {
timestamp: Date.now(),
interaction_type: 'user_dialogue'
}
});
// Update short term memory
this.shortTermMemory[`mem-${Date.now()}`] = {
content: `User: ${message}\nAgent: ${response}`,
timestamp: Date.now()
};
await Logger_1.Logger.log(this.id, Logger_1.LogType.STATUS_UPDATE, {
event: 'memory_saved',
messageLength: message.length,
responseLength: response.length
});
}
catch (error) {
console.error('Error saving to memory:', error);
await Logger_1.Logger.log(this.id, Logger_1.LogType.ERROR, {
event: 'memory_save_failed',
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
async recallRelevantMemories(query, type) {
try {
const memories = await this.memoryClient.searchMemories(this.id, query, {
type,
limit: 5,
threshold: 0.8
});
return memories.map(mem => mem.content);
}
catch (error) {
console.error('Error recalling memories:', error);
return [];
}
}
}
exports.Agent = Agent;