@amitdeshmukh/ax-crew 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ # API Keys
2
+ OPENAI_API_KEY=sk-******************BlLB
3
+ ANTHROPIC_API_KEY=sk-ant-api********cQAA
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Agents
2
+
3
+ This repo is designed to be used as a module. It simplifies development of AI Agents via config rather than code, so that they can be either reused or rapidly deployed to different applications and processes. The core functionality revolves around the use of AI models to process and respond to queries.
4
+
5
+ ## Getting Started
6
+
7
+ ### Installation
8
+ ```bash
9
+ npm install @buddhic-ai/agents
10
+ ```
11
+
12
+ ### Environment Setup
13
+ Refer to the [.env.example](.env.example) file for the required environment variables. These will need to be set in the environment where the agents are run.
14
+
15
+ ## Crew Configuration
16
+ A Crew is a team of agents that work together to achieve a common goal. The configuration file for a crew is a YAML file that defines the agents in the crew, along with their configuration.
17
+
18
+ An example configuration is provided in `agent_config.example.yaml`.
19
+
20
+ ### Creating the Crew
21
+ To initialize a crew of agents, pass the path to the configuration file to the `AgentCrew` class.
22
+
23
+ ```javascript
24
+ // Import the AgentCrew class
25
+ import AgentCrew from './src/agents/index.js';
26
+
27
+ // Create a new instance of AgentCrew
28
+ const configFilePath = './agent_config.example.yaml';
29
+ const crew = new AgentCrew(configFilePath);
30
+ ```
31
+ ### Adding Agents to the Crew
32
+ You can add a sub-set of defined agents from the configuration file to the crew by passing their names as an array to the `addAgents` method.
33
+
34
+ Please ensure that the agents are defined in the configuration file before adding them to the crew. Also, the order in which the agents are added to the crew is important, as an error will be thrown if an agent is added before its dependent agents.
35
+
36
+ For example, the `Manager` agent in the configuration file depends on the `Planner` and `Calculator` agents. Therefore, the `Planner` and `Calculator` agents must be added to the crew before the `Manager` agent.
37
+
38
+ ```javascript
39
+ // Add agents by providing their names
40
+ const agentNames = ['Planner', 'Calculator', 'Manager'];
41
+ const agents = crew.addAgentsToCrew(agentNames);
42
+
43
+ // Get agent instances
44
+ const Planner = agents.Planner;
45
+ const Manager = agents.Manager;
46
+ ```
47
+
48
+ ### State Management
49
+
50
+ The `StatefulAxAgent` class in `src/agents/index.js` allows for shared state functionality across agents. Sub-agents can be added to an agent to create complex behaviors. All agents in the crew have access to the shared state. State can also be shared with functions that are passed to the agents. To do this, pass the `state` object as an argument to the function class as shown here https://axllm.dev/guides/functions-1/
51
+
52
+
53
+ ```javascript
54
+ // Set some state (key/value) for the crew
55
+ crew.state.set('name', 'Crew1');
56
+ crew.state.set('location', 'Earth');
57
+
58
+ // Get the state for the crew
59
+ crew.state.get('name'); // 'Crew1'
60
+ crew.state.getAll(); // { name: 'Crew1', location: 'Earth' }
61
+ ```
62
+
63
+ State can also be set/get by individual agents in the crew. This state is shared with all agents and functions if passed in through the functions class constructor.
64
+
65
+ ```javascript
66
+ Planner.state.set('plan', 'some plan');
67
+ console.log(Manager.state.getAll()); // { name: 'Crew1', location: 'Earth', plan: 'some plan' }
68
+ ```
69
+
70
+ ## Completing a task
71
+
72
+ An example of how to complete a task using the agents is shown below. The `Planner` agent is used to plan the task, and the `Manager` agent is used to execute the task.
73
+
74
+ ```javascript
75
+ const userQuery = "i referred a friend to active and they were hired and started work on 1st july. But i did not receive my referral bonus. what amount should i have received?";
76
+
77
+ console.log(`\n\nQuestion: ${userQuery}`);
78
+
79
+ const planResponse = await Planner.forward({ task: userQuery });
80
+ const managerResponse = await Manager.forward({ question: userQuery, plan: planResponse.plan });
81
+
82
+ const plan = planResponse.plan;
83
+ const answer = managerResponse.answer;
84
+
85
+ console.log(`\n\nPlan: ${plan}`);
86
+ console.log(`\n\nAnswer: ${answer}`);
87
+ ```
@@ -0,0 +1,41 @@
1
+ # AxLLM Crew config
2
+ crew:
3
+ - name: Planner
4
+ description: Creates a plan to complete a task
5
+ signature: task:string "a task to be completed" -> plan:string "a plan to execute the task based on company policies and operations"
6
+ provider: anthropic
7
+ provider_key_name: ANTHROPIC_API_KEY
8
+ ai:
9
+ model: claude-3-5-sonnet-20240620
10
+ temperature: 0
11
+ options:
12
+ debug: true
13
+
14
+ - name: Calculator
15
+ description: Solves math problems
16
+ signature: mathProblem:string "a math problem to be solved using Python code" -> solution:string "the solution to the math problem"
17
+ provider: google-gemini
18
+ provider_key_name: GEMINI_API_KEY
19
+ ai:
20
+ model: gemini-1.5-pro
21
+ temperature: 0
22
+ options:
23
+ debug: true
24
+ codeExecution: true
25
+
26
+ - name: Manager
27
+ description: Answers questions about company policies and operations
28
+ signature: question:string "a question from a user about company policies and operations", plan:string "a suggested plan to answer the question" -> answer:string "the answer"
29
+ provider: anthropic
30
+ provider_key_name: ANTHROPIC_API_KEY
31
+ ai:
32
+ model: claude-3-5-sonnet-20240620
33
+ temperature: 0
34
+ options:
35
+ debug: true
36
+ functions:
37
+ - CurrentDateTime
38
+ - DaysBetweenDates
39
+ agents:
40
+ - Calculator
41
+
@@ -0,0 +1,113 @@
1
+ import fs from 'fs';
2
+ import yaml from 'js-yaml';
3
+ import { PROVIDER_API_KEYS } from '../config/index';
4
+ import { functions as importedFunctions } from '../functions/index';
5
+ import { AxAIAnthropic, AxAIOpenAI, AxAIAzureOpenAI, AxAICohere, AxAIDeepSeek, AxAIGoogleGemini, AxAIGroq, AxAIHuggingFace, AxAIMistral, AxAIOllama, AxAITogether } from '@ax-llm/ax';
6
+ // Define a mapping from provider names to their respective constructors
7
+ const AIConstructors = {
8
+ 'anthropic': AxAIAnthropic,
9
+ 'azure-openai': AxAIAzureOpenAI,
10
+ 'cohere': AxAICohere,
11
+ 'deepseek': AxAIDeepSeek,
12
+ 'google-gemini': AxAIGoogleGemini,
13
+ 'groq': AxAIGroq,
14
+ 'huggingFace': AxAIHuggingFace,
15
+ 'mistral': AxAIMistral,
16
+ 'ollama': AxAIOllama,
17
+ 'openai': AxAIOpenAI,
18
+ 'together': AxAITogether
19
+ };
20
+ const functions = importedFunctions;
21
+ /**
22
+ * Reads the AI parameters from the YAML configuration file.
23
+ * @param {string} agentConfigFilePath - The path to the agent_config.yaml file.
24
+ * @returns {Object} The parsed agent configs from the config.yaml file.
25
+ * @throws Will throw an error if reading the file fails.
26
+ */
27
+ const parseAgentConfig = (agentConfigFilePath) => {
28
+ try {
29
+ const fileContents = fs.readFileSync(agentConfigFilePath, 'utf8');
30
+ const parsedConfigs = yaml.load(fileContents);
31
+ return parsedConfigs;
32
+ }
33
+ catch (e) {
34
+ console.error('Error reading agent config file:', e);
35
+ throw e;
36
+ }
37
+ };
38
+ /**
39
+ * Initializes the AI agent using the specified agent name and configuration file path.
40
+ * This function parses the agent's configuration, validates the presence of the necessary API key,
41
+ * and creates an instance of the AI agent with the appropriate settings.
42
+ *
43
+ * @param {string} agentName - The identifier for the AI agent to be initialized.
44
+ * @param {string} agentConfigFilePath - The file path to the YAML configuration for the agent.
45
+ * @param {Object} state - The state object for the agent.
46
+ * @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
47
+ * @throws {Error} Throws an error if the agent configuration is missing, the provider is unsupported,
48
+ * the API key is not found, or the provider key name is not specified in the configuration.
49
+ */
50
+ const getAgentConfigParams = (agentName, agentConfigFilePath, state) => {
51
+ try {
52
+ // Retrieve the parameters for the specified AI agent from a config file in yaml format
53
+ const agentConfig = parseAgentConfig(agentConfigFilePath).crew.find(agent => agent.name === agentName);
54
+ if (!agentConfig) {
55
+ throw new Error(`AI agent with name ${agentName} is not configured`);
56
+ }
57
+ // Get the constructor for the AI agent's provider
58
+ const AIConstructor = AIConstructors[agentConfig.provider];
59
+ if (!AIConstructor) {
60
+ throw new Error(`AI provider ${agentConfig.provider} is not supported. Did you mean '${agentConfig.provider.toLowerCase()}'?`);
61
+ }
62
+ // If an API Key property is present, get the API key for the AI agent from the environment variables
63
+ let apiKey = '';
64
+ if (agentConfig.provider_key_name) {
65
+ apiKey = PROVIDER_API_KEYS[agentConfig.provider_key_name] || '';
66
+ if (!apiKey) {
67
+ throw new Error(`API key for provider ${agentConfig.provider} is not set in environment variables`);
68
+ }
69
+ }
70
+ else {
71
+ throw new Error(`Provider key name is missing in the agent configuration`);
72
+ }
73
+ // Create an instance of the AI agent
74
+ const ai = new AIConstructor({
75
+ apiKey,
76
+ config: agentConfig.ai,
77
+ options: {
78
+ debug: agentConfig.debug || false
79
+ }
80
+ });
81
+ // If an apiURL is provided in the agent config, set it in the AI agent
82
+ if (agentConfig.apiURL) {
83
+ ai.setAPIURL(agentConfig.apiURL);
84
+ }
85
+ // Set all options from the agent configuration
86
+ ai.setOptions({ ...agentConfig.options });
87
+ // Prepare functions for the AI agent
88
+ const agentFunctions = (agentConfig.functions || [])
89
+ .map(funcName => {
90
+ const func = functions[funcName];
91
+ if (!func) {
92
+ console.warn(`Warning: Function ${funcName} not found.`);
93
+ return;
94
+ }
95
+ return func;
96
+ })
97
+ .filter(Boolean);
98
+ // Return AI instance and Agent parameters
99
+ return {
100
+ ai,
101
+ name: agentName,
102
+ description: agentConfig.description,
103
+ signature: agentConfig.signature,
104
+ functions: agentFunctions,
105
+ subAgentNames: agentConfig.agents || []
106
+ };
107
+ }
108
+ catch (error) {
109
+ console.error(error);
110
+ throw new Error(`Error setting up AI agent`);
111
+ }
112
+ };
113
+ export { getAgentConfigParams };
@@ -0,0 +1,93 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { getAgentConfigParams } from './agentConfig';
3
+ import { AxAgent } from '@ax-llm/ax';
4
+ import { createState } from '../state/createState';
5
+ // Extend the AxAgent class to include shared state functionality
6
+ class StatefulAxAgent extends AxAgent {
7
+ constructor(ai, options, state) {
8
+ const formattedOptions = {
9
+ ...options,
10
+ functions: options.functions?.map(fn => typeof fn === 'function' ? fn() : fn)
11
+ };
12
+ super(ai, formattedOptions);
13
+ this.state = state;
14
+ }
15
+ }
16
+ /**
17
+ * Represents a crew of agents with shared state functionality.
18
+ */
19
+ class AgentCrew {
20
+ /**
21
+ * Creates an instance of AgentCrew.
22
+ * @param {string} configFilePath - The file path to the agent configuration.
23
+ * @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
24
+ */
25
+ constructor(configFilePath, crewId = uuidv4()) {
26
+ /**
27
+ * Factory function for creating an agent.
28
+ * @param {string} agentName - The name of the agent to create.
29
+ * @returns {StatefulAxAgent} The created StatefulAxAgent instance.
30
+ * @throws Will throw an error if the agent creation fails.
31
+ */
32
+ this.createAgent = (agentName) => {
33
+ try {
34
+ const agentConfigParams = getAgentConfigParams(agentName, this.configFilePath, this.state);
35
+ // Destructure with type assertion
36
+ const { ai, name, description, signature, functions, subAgentNames } = agentConfigParams;
37
+ // Get subagents for the AI agent
38
+ const subAgents = subAgentNames.map((subAgentName) => {
39
+ if (!this.agents?.get(subAgentName)) {
40
+ throw new Error(`Sub-agent '${subAgentName}' does not exist in available agents.`);
41
+ }
42
+ return this.agents?.get(subAgentName);
43
+ });
44
+ // Create an instance of StatefulAxAgent
45
+ const agent = new StatefulAxAgent(ai, {
46
+ name,
47
+ description,
48
+ signature,
49
+ functions: functions.filter((fn) => fn !== undefined),
50
+ agents: subAgents.filter((agent) => agent !== undefined)
51
+ }, this.state);
52
+ return agent;
53
+ }
54
+ catch (error) {
55
+ console.error(`Failed to create agent '${agentName}':`, error);
56
+ throw error;
57
+ }
58
+ };
59
+ this.configFilePath = configFilePath;
60
+ this.crewId = crewId;
61
+ this.agents = new Map();
62
+ this.state = createState(crewId);
63
+ }
64
+ /**
65
+ * Adds an agent to the crew by name.
66
+ * @param {string} agentName - The name of the agent to add.
67
+ */
68
+ addAgent(agentName) {
69
+ if (this.agents && !this.agents.has(agentName)) {
70
+ this.agents.set(agentName, this.createAgent(agentName));
71
+ }
72
+ }
73
+ /**
74
+ * Sets up agents by their names.
75
+ * For each name provided, it will add the agent to the crew if it's not already present.
76
+ * @param {string[]} agentNames - An array of agent names to set up.
77
+ * @returns {Map<string, StatefulAxAgent> | null} A map of agent names to their corresponding instances.
78
+ */
79
+ setupAgents(agentNames) {
80
+ agentNames.forEach(agentName => {
81
+ this.addAgent(agentName);
82
+ });
83
+ return this.agents;
84
+ }
85
+ /**
86
+ * Cleans up the crew by dereferencing agents and resetting the state.
87
+ */
88
+ destroy() {
89
+ this.agents = null;
90
+ this.state.reset();
91
+ }
92
+ }
93
+ export default AgentCrew;
@@ -0,0 +1,24 @@
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+ const DEBUG = process.env.DEBUG === 'true';
4
+ // Weaviate
5
+ const WEAVIATE_SCHEME = process.env.WEAVIATE_SCHEME || 'http';
6
+ const WEAVIATE_HOST = process.env.WEAVIATE_HOST || 'localhost:8080';
7
+ const WEAVIATE_APIKEY = process.env.WEAVIATE_APIKEY || '';
8
+ // AI API keys
9
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
10
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
11
+ const COHERE_API_KEY = process.env.COHERE_API_KEY;
12
+ const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
13
+ const PROVIDER_API_KEYS = {
14
+ COHERE_API_KEY,
15
+ GEMINI_API_KEY,
16
+ OPENAI_API_KEY,
17
+ ANTHROPIC_API_KEY,
18
+ };
19
+ // GCP
20
+ const GCP_PROJECT_ID = process.env.GCP_PROJECT_ID;
21
+ const GCP_LOCATION = process.env.GCP_LOCATION;
22
+ const GCP_RAG_KEYRING = process.env.GCP_RAG_KEYRING;
23
+ const GCP_CRYPTO_SECRET = process.env.GCP_CRYPTO_SECRET;
24
+ export { DEBUG, WEAVIATE_SCHEME, WEAVIATE_HOST, WEAVIATE_APIKEY, PROVIDER_API_KEYS, GCP_PROJECT_ID, GCP_LOCATION, GCP_RAG_KEYRING, GCP_CRYPTO_SECRET };
@@ -0,0 +1,36 @@
1
+ export const CurrentDateTime = {
2
+ name: 'CurrentDateTime',
3
+ description: 'Get the current date and time.',
4
+ parameters: {
5
+ type: 'object',
6
+ properties: {},
7
+ required: []
8
+ },
9
+ func: () => new Date().toISOString()
10
+ };
11
+ export const DaysBetweenDates = {
12
+ name: 'DaysBetweenDates',
13
+ description: 'Calculate the number of days between two dates in ISO format.',
14
+ parameters: {
15
+ type: 'object',
16
+ properties: {
17
+ startDate: {
18
+ type: 'string',
19
+ description: 'The start date in ISO format.'
20
+ },
21
+ endDate: {
22
+ type: 'string',
23
+ description: 'The end date in ISO format.'
24
+ }
25
+ },
26
+ required: ['startDate', 'endDate']
27
+ },
28
+ func: (args) => {
29
+ const { startDate, endDate } = args;
30
+ const start = new Date(startDate);
31
+ const end = new Date(endDate);
32
+ const differenceInTime = end.getTime() - start.getTime();
33
+ const differenceInDays = differenceInTime / (1000 * 3600 * 24);
34
+ return Math.round(differenceInDays);
35
+ }
36
+ };
@@ -0,0 +1,6 @@
1
+ import { CurrentDateTime, DaysBetweenDates } from './dateTime';
2
+ const functions = {
3
+ CurrentDateTime,
4
+ DaysBetweenDates
5
+ };
6
+ export { functions };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import AgentCrew from './agents/index';
2
+ export { AgentCrew };
@@ -0,0 +1,32 @@
1
+ const stateInstances = {};
2
+ const createState = (id) => {
3
+ if (!id) {
4
+ throw new Error('An ID is required to create a new state instance.');
5
+ }
6
+ if (stateInstances[id]) {
7
+ return stateInstances[id];
8
+ }
9
+ let data = {};
10
+ stateInstances[id] = {
11
+ set: (key, value) => {
12
+ data[key] = value;
13
+ },
14
+ get: (key) => {
15
+ return data[key];
16
+ },
17
+ getAll: () => {
18
+ return data;
19
+ },
20
+ getId: () => {
21
+ return id;
22
+ },
23
+ reset: () => {
24
+ data = {};
25
+ }
26
+ };
27
+ return stateInstances[id];
28
+ };
29
+ const getState = (id) => {
30
+ return stateInstances[id];
31
+ };
32
+ export { createState, getState };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Checks if a given function is a class constructor.
3
+ *
4
+ * @param {Function} obj - The object to check.
5
+ * @returns {boolean} True if the object is a class constructor, false otherwise.
6
+ */
7
+ export function isClass(obj) {
8
+ // A class must be a function
9
+ if (typeof obj !== 'function') {
10
+ return false;
11
+ }
12
+ // The function must have a prototype, but not be an instance of another function
13
+ if (obj.prototype === undefined || obj.prototype.constructor !== obj) {
14
+ return false;
15
+ }
16
+ // The function's prototype must have isPrototypeOf method
17
+ if (!Object.prototype.hasOwnProperty.call(obj.prototype, 'isPrototypeOf')) {
18
+ return false;
19
+ }
20
+ // The function must not have its own 'isPrototypeOf' property
21
+ if (obj.hasOwnProperty('isPrototypeOf')) {
22
+ return false;
23
+ }
24
+ return true;
25
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@amitdeshmukh/ax-crew",
4
+ "version": "1.0.0",
5
+ "description": "Build and launch a crew of AI agents with shared state. Built with axllm.dev",
6
+ "main": "dist/index.js",
7
+ "engines": {
8
+ "node": ">=21.0.0"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc --outDir dist",
12
+ "release": "npm run build && npm publish --access public"
13
+ },
14
+ "dependencies": {
15
+ "@ax-llm/ax": "^9.0.23",
16
+ "dotenv": "^16.4.5",
17
+ "js-yaml": "^4.1.0",
18
+ "uuid": "^10.0.0"
19
+ },
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "@types/js-yaml": "^4.0.9",
23
+ "@types/node": "^20.14.9",
24
+ "@types/uuid": "^10.0.0",
25
+ "typescript": "^5.5.3"
26
+ }
27
+ }
@@ -0,0 +1,157 @@
1
+ import fs from 'fs';
2
+ import yaml from 'js-yaml';
3
+ import { PROVIDER_API_KEYS } from '../config/index';
4
+ import { functions as importedFunctions } from '../functions/index';
5
+
6
+ import { AxAIAnthropic, AxAIOpenAI, AxAIAzureOpenAI, AxAICohere, AxAIDeepSeek, AxAIGoogleGemini, AxAIGroq, AxAIHuggingFace, AxAIMistral, AxAIOllama, AxAITogether } from '@ax-llm/ax';
7
+ import type { AxAI, AxModelConfig, AxFunction, AxSignature } from '@ax-llm/ax';
8
+
9
+ // Define a mapping from provider names to their respective constructors
10
+ const AIConstructors: Record<string, any> = {
11
+ 'anthropic': AxAIAnthropic,
12
+ 'azure-openai': AxAIAzureOpenAI,
13
+ 'cohere': AxAICohere,
14
+ 'deepseek': AxAIDeepSeek,
15
+ 'google-gemini': AxAIGoogleGemini,
16
+ 'groq': AxAIGroq,
17
+ 'huggingFace': AxAIHuggingFace,
18
+ 'mistral': AxAIMistral,
19
+ 'ollama': AxAIOllama,
20
+ 'openai': AxAIOpenAI,
21
+ 'together': AxAITogether
22
+ };
23
+
24
+ type ExtendedAxModelConfig = AxModelConfig & {
25
+ model: string;
26
+ };
27
+
28
+ // Define a mapping from function names to their respective handlers
29
+ type FunctionMap = {
30
+ [key: string]: AxFunction | { toFunction: () => AxFunction };
31
+ };
32
+ const functions: FunctionMap = importedFunctions;
33
+
34
+ interface AgentConfig {
35
+ name: string;
36
+ description: string;
37
+ signature: AxSignature;
38
+ provider: string;
39
+ provider_key_name?: string;
40
+ ai: ExtendedAxModelConfig;
41
+ debug?: boolean;
42
+ apiURL?: string;
43
+ options?: Record<string, any>;
44
+ functions?: string[];
45
+ agents?: string[];
46
+ }
47
+
48
+ interface Agent {
49
+ ai: AxAI;
50
+ name: string;
51
+ description: string;
52
+ signature?: AxSignature;
53
+ functions: AxFunction[];
54
+ subAgentNames: string[];
55
+ }
56
+
57
+
58
+ /**
59
+ * Reads the AI parameters from the YAML configuration file.
60
+ * @param {string} agentConfigFilePath - The path to the agent_config.yaml file.
61
+ * @returns {Object} The parsed agent configs from the config.yaml file.
62
+ * @throws Will throw an error if reading the file fails.
63
+ */
64
+ const parseAgentConfig = (agentConfigFilePath: string): {crew: AgentConfig[]} => {
65
+ try {
66
+ const fileContents = fs.readFileSync(agentConfigFilePath, 'utf8');
67
+ const parsedConfigs = yaml.load(fileContents) as { crew: AgentConfig[] };
68
+ return parsedConfigs;
69
+ } catch (e) {
70
+ console.error('Error reading agent config file:', e);
71
+ throw e;
72
+ }
73
+ };
74
+
75
+ /**
76
+ * Initializes the AI agent using the specified agent name and configuration file path.
77
+ * This function parses the agent's configuration, validates the presence of the necessary API key,
78
+ * and creates an instance of the AI agent with the appropriate settings.
79
+ *
80
+ * @param {string} agentName - The identifier for the AI agent to be initialized.
81
+ * @param {string} agentConfigFilePath - The file path to the YAML configuration for the agent.
82
+ * @param {Object} state - The state object for the agent.
83
+ * @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
84
+ * @throws {Error} Throws an error if the agent configuration is missing, the provider is unsupported,
85
+ * the API key is not found, or the provider key name is not specified in the configuration.
86
+ */
87
+ const getAgentConfigParams = (agentName: string, agentConfigFilePath: string, state: Record<string, any>) => {
88
+ try{
89
+ // Retrieve the parameters for the specified AI agent from a config file in yaml format
90
+ const agentConfig = parseAgentConfig(agentConfigFilePath).crew.find(agent => agent.name === agentName);
91
+ if (!agentConfig) {
92
+ throw new Error(`AI agent with name ${agentName} is not configured`);
93
+ }
94
+
95
+ // Get the constructor for the AI agent's provider
96
+ const AIConstructor = AIConstructors[agentConfig.provider];
97
+ if (!AIConstructor) {
98
+ throw new Error(`AI provider ${agentConfig.provider} is not supported. Did you mean '${agentConfig.provider.toLowerCase()}'?`);
99
+ }
100
+
101
+ // If an API Key property is present, get the API key for the AI agent from the environment variables
102
+ let apiKey = '';
103
+ if (agentConfig.provider_key_name) {
104
+ apiKey = PROVIDER_API_KEYS[agentConfig.provider_key_name] || '';
105
+
106
+ if (!apiKey) {
107
+ throw new Error(`API key for provider ${agentConfig.provider} is not set in environment variables`);
108
+ }
109
+ } else {
110
+ throw new Error(`Provider key name is missing in the agent configuration`);
111
+ }
112
+
113
+ // Create an instance of the AI agent
114
+ const ai = new AIConstructor({
115
+ apiKey,
116
+ config: agentConfig.ai,
117
+ options: {
118
+ debug: agentConfig.debug || false
119
+ }
120
+ });
121
+
122
+ // If an apiURL is provided in the agent config, set it in the AI agent
123
+ if (agentConfig.apiURL) {
124
+ ai.setAPIURL(agentConfig.apiURL);
125
+ }
126
+
127
+ // Set all options from the agent configuration
128
+ ai.setOptions({ ...agentConfig.options });
129
+
130
+ // Prepare functions for the AI agent
131
+ const agentFunctions = (agentConfig.functions || [])
132
+ .map(funcName => {
133
+ const func = functions[funcName];
134
+ if (!func) {
135
+ console.warn(`Warning: Function ${funcName} not found.`);
136
+ return;
137
+ }
138
+ return func;
139
+ })
140
+ .filter(Boolean);
141
+
142
+ // Return AI instance and Agent parameters
143
+ return {
144
+ ai,
145
+ name: agentName,
146
+ description: agentConfig.description,
147
+ signature: agentConfig.signature,
148
+ functions: agentFunctions,
149
+ subAgentNames: agentConfig.agents || []
150
+ };
151
+ } catch (error) {
152
+ console.error(error);
153
+ throw new Error(`Error setting up AI agent`);
154
+ }
155
+ };
156
+
157
+ export { getAgentConfigParams }
@@ -0,0 +1,133 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { getAgentConfigParams } from './agentConfig';
3
+ import { AxAgent, AxAI } from '@ax-llm/ax';
4
+ import type { AxSignature, AxAgentic, AxFunction, AxAIService } from '@ax-llm/ax';
5
+ import { createState, StateInstance } from '../state/createState';
6
+
7
+
8
+ // Define interfaces for the agent configurations and return types
9
+ interface Agents {
10
+ [key: string]: StatefulAxAgent;
11
+ }
12
+
13
+ interface AgentConfigParams {
14
+ ai: AxAI;
15
+ name: string;
16
+ description: string;
17
+ signature: AxSignature;
18
+ functions: (AxFunction | { toFunction: () => AxFunction; } | undefined)[];
19
+ subAgentNames: string[];
20
+ }
21
+
22
+ // Extend the AxAgent class to include shared state functionality
23
+ class StatefulAxAgent extends AxAgent<any, any> {
24
+ state: StateInstance;
25
+ constructor(ai: AxAI, options: Readonly<{ name: string; description: string; signature: string | AxSignature; agents?: AxAgentic[] | undefined; functions?: (AxFunction | (() => AxFunction))[] | undefined; }>, state: StateInstance) {
26
+ const formattedOptions = {
27
+ ...options,
28
+ functions: options.functions?.map(fn => typeof fn === 'function' ? fn() : fn) as AxFunction[] | undefined
29
+ };
30
+ super(ai, formattedOptions);
31
+ this.state = state;
32
+ }
33
+ }
34
+
35
+
36
+ /**
37
+ * Represents a crew of agents with shared state functionality.
38
+ */
39
+ class AgentCrew {
40
+ private configFilePath: string;
41
+ crewId: string;
42
+ agents: Map<string, StatefulAxAgent> | null;
43
+ state: StateInstance;
44
+
45
+ /**
46
+ * Creates an instance of AgentCrew.
47
+ * @param {string} configFilePath - The file path to the agent configuration.
48
+ * @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
49
+ */
50
+ constructor(configFilePath: string, crewId: string = uuidv4()) {
51
+ this.configFilePath = configFilePath;
52
+ this.crewId = crewId;
53
+ this.agents = new Map<string, StatefulAxAgent>();
54
+ this.state = createState(crewId);
55
+ }
56
+
57
+ /**
58
+ * Factory function for creating an agent.
59
+ * @param {string} agentName - The name of the agent to create.
60
+ * @returns {StatefulAxAgent} The created StatefulAxAgent instance.
61
+ * @throws Will throw an error if the agent creation fails.
62
+ */
63
+ createAgent = (agentName: string): StatefulAxAgent => {
64
+ try {
65
+ const agentConfigParams: AgentConfigParams = getAgentConfigParams(agentName, this.configFilePath, this.state);
66
+
67
+ // Destructure with type assertion
68
+ const {
69
+ ai,
70
+ name,
71
+ description,
72
+ signature,
73
+ functions,
74
+ subAgentNames
75
+ } = agentConfigParams;
76
+
77
+ // Get subagents for the AI agent
78
+ const subAgents = subAgentNames.map((subAgentName: string) => {
79
+ if (!this.agents?.get(subAgentName)) {
80
+ throw new Error(`Sub-agent '${subAgentName}' does not exist in available agents.`);
81
+ }
82
+ return this.agents?.get(subAgentName);
83
+ });
84
+
85
+ // Create an instance of StatefulAxAgent
86
+ const agent = new StatefulAxAgent(ai, {
87
+ name,
88
+ description,
89
+ signature,
90
+ functions: functions.filter((fn): fn is AxFunction => fn !== undefined),
91
+ agents: subAgents.filter((agent): agent is StatefulAxAgent => agent !== undefined)
92
+ }, this.state);
93
+
94
+ return agent;
95
+ } catch (error) {
96
+ console.error(`Failed to create agent '${agentName}':`, error);
97
+ throw error;
98
+ }
99
+ };
100
+
101
+ /**
102
+ * Adds an agent to the crew by name.
103
+ * @param {string} agentName - The name of the agent to add.
104
+ */
105
+ addAgent(agentName: string): void {
106
+ if (this.agents && !this.agents.has(agentName)) {
107
+ this.agents.set(agentName, this.createAgent(agentName));
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Sets up agents by their names.
113
+ * For each name provided, it will add the agent to the crew if it's not already present.
114
+ * @param {string[]} agentNames - An array of agent names to set up.
115
+ * @returns {Map<string, StatefulAxAgent> | null} A map of agent names to their corresponding instances.
116
+ */
117
+ setupAgents(agentNames: string[]): Map<string, StatefulAxAgent> | null {
118
+ agentNames.forEach(agentName => {
119
+ this.addAgent(agentName);
120
+ });
121
+ return this.agents;
122
+ }
123
+
124
+ /**
125
+ * Cleans up the crew by dereferencing agents and resetting the state.
126
+ */
127
+ destroy() {
128
+ this.agents = null;
129
+ this.state.reset();
130
+ }
131
+ }
132
+
133
+ export default AgentCrew;
@@ -0,0 +1,44 @@
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+
4
+ const DEBUG: boolean = process.env.DEBUG === 'true';
5
+
6
+ // Weaviate
7
+ const WEAVIATE_SCHEME: string = process.env.WEAVIATE_SCHEME || 'http';
8
+ const WEAVIATE_HOST: string = process.env.WEAVIATE_HOST || 'localhost:8080';
9
+ const WEAVIATE_APIKEY: string = process.env.WEAVIATE_APIKEY || '';
10
+
11
+ // AI API keys
12
+ const ANTHROPIC_API_KEY: string | undefined = process.env.ANTHROPIC_API_KEY;
13
+ const OPENAI_API_KEY: string | undefined = process.env.OPENAI_API_KEY;
14
+ const COHERE_API_KEY: string | undefined = process.env.COHERE_API_KEY;
15
+ const GEMINI_API_KEY: string | undefined = process.env.GEMINI_API_KEY;
16
+
17
+ interface ProviderApiKeys {
18
+ [key: string]: string | undefined;
19
+ }
20
+
21
+ const PROVIDER_API_KEYS: ProviderApiKeys = {
22
+ COHERE_API_KEY,
23
+ GEMINI_API_KEY,
24
+ OPENAI_API_KEY,
25
+ ANTHROPIC_API_KEY,
26
+ };
27
+
28
+ // GCP
29
+ const GCP_PROJECT_ID: string | undefined = process.env.GCP_PROJECT_ID;
30
+ const GCP_LOCATION: string | undefined = process.env.GCP_LOCATION;
31
+ const GCP_RAG_KEYRING: string | undefined = process.env.GCP_RAG_KEYRING;
32
+ const GCP_CRYPTO_SECRET: string | undefined = process.env.GCP_CRYPTO_SECRET;
33
+
34
+ export {
35
+ DEBUG,
36
+ WEAVIATE_SCHEME,
37
+ WEAVIATE_HOST,
38
+ WEAVIATE_APIKEY,
39
+ PROVIDER_API_KEYS,
40
+ GCP_PROJECT_ID,
41
+ GCP_LOCATION,
42
+ GCP_RAG_KEYRING,
43
+ GCP_CRYPTO_SECRET
44
+ };
@@ -0,0 +1,39 @@
1
+ import type { AxFunction } from "@ax-llm/ax";
2
+
3
+ export const CurrentDateTime: AxFunction = {
4
+ name: 'CurrentDateTime',
5
+ description: 'Get the current date and time.',
6
+ parameters: {
7
+ type: 'object',
8
+ properties: {},
9
+ required: []
10
+ },
11
+ func: () => new Date().toISOString()
12
+ }
13
+
14
+ export const DaysBetweenDates: AxFunction = {
15
+ name: 'DaysBetweenDates',
16
+ description: 'Calculate the number of days between two dates in ISO format.',
17
+ parameters: {
18
+ type: 'object',
19
+ properties: {
20
+ startDate: {
21
+ type: 'string',
22
+ description: 'The start date in ISO format.'
23
+ },
24
+ endDate: {
25
+ type: 'string',
26
+ description: 'The end date in ISO format.'
27
+ }
28
+ },
29
+ required: ['startDate', 'endDate']
30
+ },
31
+ func: (args: { startDate: string; endDate: string }) => {
32
+ const { startDate, endDate } = args;
33
+ const start = new Date(startDate);
34
+ const end = new Date(endDate);
35
+ const differenceInTime = end.getTime() - start.getTime();
36
+ const differenceInDays = differenceInTime / (1000 * 3600 * 24);
37
+ return Math.round(differenceInDays);
38
+ }
39
+ }
@@ -0,0 +1,8 @@
1
+ import { CurrentDateTime, DaysBetweenDates } from './dateTime';
2
+
3
+ const functions = {
4
+ CurrentDateTime,
5
+ DaysBetweenDates
6
+ }
7
+
8
+ export { functions };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import AgentCrew from './agents/index';
2
+
3
+ export { AgentCrew };
@@ -0,0 +1,46 @@
1
+ interface StateInstance {
2
+ set: (key: string, value: any) => void;
3
+ get: (key: string) => any;
4
+ getAll: () => { [key: string]: any };
5
+ getId: () => string;
6
+ reset: () => void;
7
+ }
8
+
9
+ const stateInstances: { [id: string]: StateInstance } = {};
10
+
11
+ const createState = (id: string): StateInstance => {
12
+ if (!id) {
13
+ throw new Error('An ID is required to create a new state instance.');
14
+ }
15
+ if (stateInstances[id]) {
16
+ return stateInstances[id];
17
+ }
18
+
19
+ let data: { [key: string]: any } = {};
20
+
21
+ stateInstances[id] = {
22
+ set: (key, value) => {
23
+ data[key] = value;
24
+ },
25
+ get: (key) => {
26
+ return data[key];
27
+ },
28
+ getAll: () => {
29
+ return data;
30
+ },
31
+ getId: () => {
32
+ return id;
33
+ },
34
+ reset: () => {
35
+ data = {};
36
+ }
37
+ };
38
+
39
+ return stateInstances[id];
40
+ };
41
+
42
+ const getState = (id: string): StateInstance | undefined => {
43
+ return stateInstances[id];
44
+ }
45
+
46
+ export { createState, getState, StateInstance };
@@ -0,0 +1,27 @@
1
+ import { AxFunction } from "@ax-llm/ax";
2
+
3
+ /**
4
+ * Checks if a given function is a class constructor.
5
+ *
6
+ * @param {Function} obj - The object to check.
7
+ * @returns {boolean} True if the object is a class constructor, false otherwise.
8
+ */
9
+ export function isClass(obj: AxFunction | ClassDecorator): boolean {
10
+ // A class must be a function
11
+ if (typeof obj !== 'function') {
12
+ return false;
13
+ }
14
+ // The function must have a prototype, but not be an instance of another function
15
+ if (obj.prototype === undefined || obj.prototype.constructor !== obj) {
16
+ return false;
17
+ }
18
+ // The function's prototype must have isPrototypeOf method
19
+ if (!Object.prototype.hasOwnProperty.call(obj.prototype, 'isPrototypeOf')) {
20
+ return false;
21
+ }
22
+ // The function must not have its own 'isPrototypeOf' property
23
+ if (obj.hasOwnProperty('isPrototypeOf')) {
24
+ return false;
25
+ }
26
+ return true;
27
+ }
package/test.js ADDED
@@ -0,0 +1,36 @@
1
+ // Import the AgentCrew class
2
+ import AgentCrew from './dist/agents/index.js';
3
+
4
+ // Create a new instance of AgentCrew
5
+ const configFilePath = './agent_config.example.yaml';
6
+ const crew = new AgentCrew(configFilePath);
7
+
8
+ // Add agents by providing their names
9
+ const agentNames = ['Planner', 'Calculator', 'Manager'];
10
+ const agents = crew.addAgentsToCrew(agentNames);
11
+
12
+ crew.state.set('clientId', '0906-978189-7350');
13
+ console.log(`Set ClientId: ${crew.state.get('clientId')}`);
14
+
15
+ const ManagerAgent = agents.Manager;
16
+ const PlannerAgent = agents['Planner'];
17
+
18
+ console.log(crew.state.getAll())
19
+ PlannerAgent.state.set('plan', 'plan 1 2 3')
20
+ console.log(ManagerAgent.state.getAll())
21
+
22
+ const userQuery = "i referred a friend to active and they were hired and started work on 1st july. But i did not receive my referral bonus. what amount should i have received?";
23
+
24
+ console.log(`\n\nQuestion: ${userQuery}`);
25
+
26
+ const planResponse = await PlannerAgent.forward({ task: userQuery });
27
+ const managerResponse = await ManagerAgent.forward({ question: userQuery, plan: planResponse.plan });
28
+
29
+ const plan = planResponse.plan;
30
+ const answer = managerResponse.answer;
31
+ const reason = managerResponse.reason;
32
+
33
+ console.log(`\n\nPlan: ${plan}`);
34
+ console.log(`\n\nAnswer: ${answer}`);
35
+
36
+ console.log(crew.state.getAll());
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "target": "ES2020",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "allowJs": true,
12
+ "resolveJsonModule": true,
13
+ "typeRoots": ["./node_modules/@types", "./types"]
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "**/*.spec.ts"]
17
+ }