UNPKG

@virtron/agency

Version:

A framework for building autonomous agents that can perform tasks, manage memory, and interact with tools.

391 lines (344 loc) 13.4 kB
import { Agency } from './Agency.js'; import { TeamFactory } from './TeamFactory.js'; import fs from 'fs/promises'; import { transformWorkflowDefinition } from '../workflow-utils.js'; /** * Factory for creating Agency instances from configuration */ /** * @class AgencyFactory * @classdesc Factory class for creating and loading Agency instances. */ export class AgencyFactory { /** * Create a new AgencyFactory * @param {Object} config - Factory configuration * @param {TeamFactory} config.teamFactory - TeamFactory instance for creating team * @param {AgentFactory} config.agentFactory - AgentFactory instance for creating agents */ constructor(config) { this.teamFactory = config.teamFactory; this.agentFactory = config.agentFactory; } /** * Create an agency from a configuration object * @param {Object} config - Agency configuration * @returns {Agency} - Created agency instance */ /** * Creates an agency instance from a configuration object. * @param {object} config - The agency configuration. * @returns {Agency} The created agency instance. */ createAgency(config) { // Create the agency with updated configuration options const agency = new Agency({ name: config.agency.name, description: config.agency.description, logging: config.agency.logging || { level: 'none', tracing: false, format: 'text', destination: 'console' }, memoryConfig: config.agency.memoryConfig }); // Create and add agents directly if specified if (config.agents) { const agents = this.agentFactory.createAgents(config.agents); for (const [agentId, agent] of Object.entries(agents)) { agency.addAgent(agentId, agent); } } // Create and add team if (config.team) { for (const [teamId, teamConfig] of Object.entries(config.team)) { const team = this.teamFactory.createTeamFromConfig(config, teamId); agency.addTeam(teamId, team); } } // Create and add brief if specified if (config.brief) { for (const [briefId, briefData] of Object.entries(config.brief)) { agency.createBrief(briefId, briefData); } } // Set up workflows if defined if (config.workflows) { for (const [workflowId, workflowConfig] of Object.entries(config.workflows)) { // Store workflow configuration for later use agency.workflows[workflowId] = workflowConfig; } } // Set up job schemas if defined if (config.jobSchemas) { for (const [schemaId, schema] of Object.entries(config.jobSchemas)) { agency.defineJobSchema(schemaId, schema.input, schema.output); } } // Set up error handlers if defined if (config.errorHandlers) { for (const [handlerId, handler] of Object.entries(config.errorHandlers)) { if (typeof handler === 'function') { agency.setErrorHandler(handlerId, handler); } else { agency.log('warn', `Error handler ${handlerId} is not a function`); } } } // Set up workflow error handlers if defined if (config.workflowErrorHandlers) { for (const [workflowId, handler] of Object.entries(config.workflowErrorHandlers)) { if (typeof handler === 'function') { agency.setWorkflowErrorHandler(workflowId, handler); } else { agency.log('warn', `Workflow error handler ${workflowId} is not a function`); } } } return agency; } /** * Load an agency from a JSON file * @param {string} filePath - Path to the JSON file * @returns {Promise<Agency>} - Created agency instance */ /** * Loads an agency from a file. * @param {string} filePath - The path to the agency configuration file. * @returns {Promise<Agency>} A promise that resolves with the loaded agency instance. */ async loadAgencyFromFile(filePath) { try { const configData = await fs.readFile(filePath, 'utf8'); const config = JSON.parse(configData); return this.createAgency(config); } catch (error) { throw new Error(`Failed to load agency from file: ${error.message}`); } } /** * Execute a workflow defined in the configuration * @param {Agency} agency - Agency instance * @param {string} workflowId - ID of the workflow to execute * @param {Object} context - Context data for the workflow * @param {Object} options - Workflow execution options * @returns {Promise<Object>} - Workflow results */ /** * Executes a workflow using a pre-configured agency. * @param {Agency} agency - The agency instance. * @param {string} workflowId - The ID of the workflow to execute. * @param {object} [context={}] - Optional context data to pass to the workflow. * @param {object} [options={}] - Optional execution options. * @returns {Promise<*>} A promise that resolves with the workflow execution results. */ async executeWorkflow(agency, workflowId, context = {}, options = {}) { if (!agency.workflows[workflowId]) { throw new Error(`Workflow ${workflowId} not found`); } const workflowConfig = agency.workflows[workflowId]; const workflowSteps = workflowConfig.steps || []; // Generate a unique execution ID const executionId = `${workflowId}-${Date.now()}`; // Execute the workflow return agency.executeWorkflow(workflowSteps, executionId, context, options); } /** * Creates a shared memory scope for a workflow and populates it with inputs. * @param {Agency} agency - The agency instance. * @param {string} briefId - The ID of the brief. * @param {Object} inputs - Inputs for the workflow. * @returns {Object} - The shared memory scope. */ createSharedMemoryForWorkflow(agency, briefId, inputs) { const sharedMemoryScopeId = `${briefId}SharedData-${Date.now()}`; const sharedMemoryScope = agency.createMemoryScope(sharedMemoryScopeId); // Populate shared memory with inputs for (const [key, value] of Object.entries(inputs)) { sharedMemoryScope.remember(key, value); } return sharedMemoryScope; } /** * Creates a workflow definition from a brief ID and inputs. * @param {Agency} agency - The agency instance. * @param {string} briefId - The ID of the brief. * @param {Object} inputs - Inputs for the workflow. * @returns {Array} - The transformed workflow definition. */ createWorkflowFromBrief(agency, briefId, inputs) { // Dynamically determine the team and job associated with the brief let team = null; let jobId = null; for (const [teamId, teamObj] of Object.entries(agency.team)) { const jobs = teamObj.jobs || {}; for (const [currentJobId, jobConfig] of Object.entries(jobs)) { if (jobConfig.brief === briefId) { team = teamObj; jobId = currentJobId; break; } } if (team) break; } if (!team || !jobId) { throw new Error(`No team or job found for brief ID: ${briefId}`); } return transformWorkflowDefinition(team, agency, briefId, inputs); } /** * Executes a brief by creating a workflow, shared memory, and running the workflow. * @param {Agency} agency - The agency instance. * @param {string} briefId - The ID of the brief to execute. * @param {Object} inputs - Inputs for the workflow. * @returns {Promise<Object>} - The workflow results. */ async executeBrief(agency, briefId, inputs) { if (briefId === 'generate-itinerary') { return this.executeGenerateItinerary(agency, inputs); } // Create shared memory scope const sharedMemoryScope = this.createSharedMemoryForWorkflow(agency, briefId, inputs); // Create workflow definition const workflowDefinition = this.createWorkflowFromBrief(agency, briefId, inputs); // Execute the workflow const workflowContext = { ...agency.brief[briefId], sharedMemoryScope, ...inputs }; const executionId = `${briefId}-workflow-${Date.now()}`; const result = await agency.executeWorkflow(workflowDefinition, executionId, workflowContext); // Process the result return this.processWorkflowResult(agency, briefId, result); } /** * Executes the generate-itinerary brief by chaining search and itinerary workflows. * @param {Agency} agency - The agency instance. * @param {Object} inputs - Inputs for the workflow. * @returns {Promise<Object>} - The workflow results. */ async executeGenerateItinerary(agency, inputs) { // Create shared memory scope for the entire process const sharedMemoryScopeId = 'generateItinerarySharedData'; const sharedMemoryScope = agency.createMemoryScope(sharedMemoryScopeId); sharedMemoryScope.remember('destination', inputs.destination); // Execute search workflow const searchWorkflowDefinition = transformWorkflowDefinition( agency.team.travelSearchTeam, agency, 'travel-search-brief', inputs ); const searchWorkflowContext = { ...agency.brief['travel-search-brief'], sharedMemoryScope, ...inputs }; const searchExecutionId = `search-travel-workflow-${Date.now()}`; const searchResult = await agency.executeWorkflow( searchWorkflowDefinition, searchExecutionId, searchWorkflowContext ); const searchResults = searchResult.results; if (!searchResults.searchTravelInfo) { throw new Error('Error: No travel search results found'); } // Log the output of the searchTravelInfo job console.log("Output of searchTravelInfo job:"); console.log(JSON.stringify(searchResults.searchTravelInfo, null, 2)); // Parse the search results let travelData; try { let resultText = searchResults.searchTravelInfo; if (typeof resultText === 'string') { // Remove Markdown formatting if present resultText = resultText.replace(/```json\n?/, '').replace(/\n?```/, '').trim(); } travelData = typeof resultText === 'string' ? JSON.parse(resultText) : resultText; } catch (parseError) { throw new Error(`Error parsing travel search results: ${parseError.message}`); } // Execute itinerary workflow with the travelData const itineraryInputs = { travelData, tripDuration: 3, travelerPreferences: {} }; const itineraryWorkflowDefinition = transformWorkflowDefinition( agency.team.travelPlanningTeam, agency, 'travel-itinerary-brief', itineraryInputs ); const itineraryWorkflowContext = { ...agency.brief['travel-itinerary-brief'], sharedMemoryScope, ...itineraryInputs }; const itineraryExecutionId = `create-itinerary-workflow-${Date.now()}`; const itineraryResult = await agency.executeWorkflow( itineraryWorkflowDefinition, itineraryExecutionId, itineraryWorkflowContext ); // Process the itinerary result return this.processItineraryResult(itineraryResult); } /** * Processes the itinerary workflow result. * @param {Object} result - The workflow result. * @returns {Object} - The processed result. */ processItineraryResult(result) { const itineraryResults = result.results; if (!itineraryResults.createItinerary) { throw new Error('Error: No itinerary results found'); } try { const parsedResult = typeof itineraryResults.createItinerary === 'string' ? JSON.parse(itineraryResults.createItinerary) : itineraryResults.createItinerary; return parsedResult; } catch (parseError) { // If not valid JSON, return the raw text return itineraryResults.createItinerary; } } /** * Processes the workflow result to extract and parse the relevant output. * @param {Agency} agency - The agency instance. * @param {string} briefId - The ID of the brief. * @param {Object} result - The workflow result. * @returns {Object} - The processed result. */ processWorkflowResult(agency, briefId, result) { const results = result.results; let jobName; if (briefId === 'generate-itinerary') { jobName = 'createItinerary'; // Final job in the chain } else if (briefId === 'travel-search-brief') { jobName = 'searchTravelInfo'; } else if (briefId === 'travel-itinerary-brief') { jobName = 'createItinerary'; } else { throw new Error(`Unsupported brief ID: ${briefId}`); } if (!results[jobName]) { throw new Error(`Error: No results found for job "${jobName}"`); } try { const parsedResult = typeof results[jobName] === 'string' ? JSON.parse(results[jobName]) : results[jobName]; return parsedResult; } catch (parseError) { // If not valid JSON, return the raw text return results[jobName]; } } }