@amitdeshmukh/ax-crew 2.0.7 → 3.1.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/CHANGELOG.md CHANGED
@@ -1,12 +1,33 @@
1
1
  # Changelog
2
2
 
3
- This Changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3
+ This Changelog format is based on [Keep a Changelog]
4
+ (https://keepachangelog.com/en/1.0.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/
6
+ v2.0.0.html).
4
7
 
5
- ## [Unreleased]
8
+ ## [3.1.0] - 2024-12-10
6
9
 
7
10
  ### Added
8
- - Change [agent_config.example.yaml](agent_config.example.yaml) to JSON format
9
- - Add support for providing examples via `setExamples` method
11
+ - Support for direct JSON object configuration in addition to configuration files
12
+ - New configuration option to pass agent configuration as a JavaScript object
13
+ - Updated documentation in README.md with examples of both configuration methods
14
+
15
+ ### Enhanced
16
+ - Configuration flexibility allowing runtime configuration modifications
17
+ - Support for dynamic agent configuration generation
18
+
19
+ ## [3.0.0] - 2024-12-10
20
+
21
+ ### Changed
22
+ - Switched from YAML to JSON format for agent configuration files
23
+ - Renamed `provider_key_name` to `providerKeyName` in agent configuration to align with JSON naming conventions
24
+ - Improved error handling for JSON parsing with detailed error message and troubleshooting hints
25
+
26
+ ### Added
27
+ - Better error messages for configuration file parsing issues, including:
28
+ - Exact line and column numbers for JSON syntax errors
29
+ - Context showing surrounding lines of code
30
+ - Common troubleshooting tips for JSON configuration issues
10
31
 
11
32
  ## [2.0.7] - 2024-11-23
12
33
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  This repo simplifies development of [AxLLM](https://axllm.dev) AI Agents by using config to instantiate agents. This means you can write a library of functions, and quickly invoke AI agents to use them using a simple configuration file.
6
6
 
7
7
  ## Features
8
- - **Crew Configuration**: Define a crew of agents in a YAML file. (see [agent_config.example.yaml](agent_config.example.yaml))
8
+ - **Crew Configuration**: Define a crew of agents in a JSON file. (see [agentConfig.json](agentConfig.json) as an example)
9
9
  - **State Management**: Share state across agents in a crew, as well as with functions used by those agents.
10
10
  - **Task Execution**: Plan and execute tasks using agents in the crew.
11
11
 
@@ -28,21 +28,65 @@ Refer to the [.env.example](.env.example) file for the required environment vari
28
28
  ## Usage
29
29
 
30
30
  ### Initializing a Crew
31
- 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 individual configurations.
31
+ A Crew is a team of agents that work together to achieve a common goal. You can configure your crew in two ways:
32
32
 
33
- See [agent_config.example.yaml](agent_config.example.yaml) for an example.
33
+ 1. Using a JSON configuration file that defines the agents in the crew, along with their individual configurations.
34
+ 2. Directly passing a JSON object with the crew configuration.
34
35
 
35
- To initialize a crew of agents, pass a config file to the `AxCrew` constructor.
36
+ #### Using a Configuration File
37
+ See [agentConfig.json](agentConfig.json) for an example configuration file.
36
38
 
37
39
  ```javascript
38
40
  // Import the AxCrew class
39
41
  import { AxCrew } from '@amitdeshmukh/ax-crew';
40
42
 
41
- // Create a new instance of AxCrew
42
- const configFilePath = './agent_config.example.yaml';
43
+ // Create a new instance of AxCrew using a config file
44
+ const configFilePath = './agentConfig.json';
43
45
  const crew = new AxCrew(configFilePath);
44
46
  ```
45
47
 
48
+ #### Using a Direct Configuration Object
49
+ You can also pass the configuration directly as a JSON object:
50
+
51
+ ```javascript
52
+ // Import the AxCrew class
53
+ import { AxCrew } from '@amitdeshmukh/ax-crew';
54
+
55
+ // Create the configuration object
56
+ const config = {
57
+ crew: [
58
+ {
59
+ name: "Planner",
60
+ description: "Creates a plan to complete a task",
61
+ signature: "task:string \"a task to be completed\" -> plan:string \"a plan to execute the task in 5 steps or less\"",
62
+ provider: "google-gemini",
63
+ providerKeyName: "GEMINI_API_KEY",
64
+ ai: {
65
+ model: "gemini-1.5-flash",
66
+ temperature: 0
67
+ },
68
+ options: {
69
+ debug: false
70
+ }
71
+ }
72
+ // ... more agents
73
+ ]
74
+ };
75
+
76
+ // Create a new instance of AxCrew using the config object
77
+ const crew = new AxCrew(config);
78
+ ```
79
+
80
+ Both methods support the same configuration structure and options. Choose the one that best fits your use case:
81
+ - Use a configuration file when you want to:
82
+ - Keep your configuration separate from your code
83
+ - Share configurations across different projects
84
+ - Version control your configurations
85
+ - Use a direct configuration object when you want to:
86
+ - Generate configurations dynamically
87
+ - Modify configurations at runtime
88
+ - Keep everything in one file for simpler projects
89
+
46
90
  ### Function Registry
47
91
  Functions (aka Tools) are the building blocks of agents. They are used to perform specific tasks, such as calling external APIs, databases, or other services.
48
92
 
@@ -122,7 +166,7 @@ An example of how to complete a task using the agents is shown below. The `Plann
122
166
  import { AxCrew, AxCrewFunctions } from '@amitdeshmukh/ax-crew';
123
167
 
124
168
  // Create a new instance of AxCrew
125
- const crew = new AxCrew('./agent_config.example.yaml', AxCrewFunctions);
169
+ const crew = new AxCrew('./agentConfig.json', AxCrewFunctions);
126
170
  crew.addAgentsToCrew(['Planner', 'Calculator', 'Manager']);
127
171
 
128
172
  // Get agent instances
@@ -0,0 +1,65 @@
1
+ {
2
+ "crew": [
3
+ {
4
+ "name": "Planner",
5
+ "description": "Creates a plan to complete a task",
6
+ "signature": "task:string \"a task to be completed\" -> plan:string \"a plan to execute the task in 5 steps or less\"",
7
+ "provider": "google-gemini",
8
+ "providerKeyName": "GEMINI_API_KEY",
9
+ "ai": {
10
+ "model": "gemini-1.5-flash",
11
+ "temperature": 0
12
+ },
13
+ "options": {
14
+ "debug": false
15
+ }
16
+ },
17
+ {
18
+ "name": "Calculator",
19
+ "description": "Solves math problems",
20
+ "signature": "mathProblem:string \"a math problem to be solved using Python code\" -> solution:string \"the solution to the math problem\"",
21
+ "provider": "google-gemini",
22
+ "providerKeyName": "GEMINI_API_KEY",
23
+ "ai": {
24
+ "model": "gemini-1.5-pro",
25
+ "temperature": 0
26
+ },
27
+ "options": {
28
+ "debug": true,
29
+ "codeExecution": true
30
+ },
31
+ "functions": ["CurrentDateTime", "DaysBetweenDates"]
32
+ },
33
+ {
34
+ "name": "WebSearch",
35
+ "description": "Searches the web for the latest information using Google search",
36
+ "signature": "webSearchQuery:string \"a query for Google search\" -> webSearchResponse:string \"the result of the search\"",
37
+ "provider": "google-gemini",
38
+ "providerKeyName": "GEMINI_API_KEY",
39
+ "ai": {
40
+ "model": "gemini-1.5-pro",
41
+ "temperature": 0
42
+ },
43
+ "options": {
44
+ "debug": true,
45
+ "googleSearchRetrieval": true
46
+ },
47
+ "functions": ["CurrentDateTime", "DaysBetweenDates"]
48
+ },
49
+ {
50
+ "name": "Manager",
51
+ "description": "Answers questions from the user",
52
+ "signature": "question:string \"a question from a user\", plan:string \"a suggested plan to answer the question\" -> answer:string \"the answer\"",
53
+ "provider": "openai",
54
+ "providerKeyName": "OPENAI_API_KEY",
55
+ "ai": {
56
+ "model": "gpt-4o-mini",
57
+ "temperature": 0
58
+ },
59
+ "options": {
60
+ "debug": true
61
+ },
62
+ "functions": ["CurrentDateTime", "DaysBetweenDates"]
63
+ }
64
+ ]
65
+ }
package/axllm.js ADDED
@@ -0,0 +1,66 @@
1
+ import { AxAgent, AxAI } from "@ax-llm/ax";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+
5
+ const googleAI = new AxAI({
6
+ name: "google-gemini",
7
+ apiKey: process.env.GEMINI_API_KEY,
8
+ config: {
9
+ model: "gemini-1.5-pro",
10
+ temperature: 0,
11
+ },
12
+ options: {
13
+ debug: true,
14
+ codeExecution: true,
15
+ },
16
+ });
17
+
18
+ const openAI = new AxAI({
19
+ name: "openai",
20
+ apiKey: process.env.OPENAI_API_KEY,
21
+ config: {
22
+ model: "gpt-4-turbo",
23
+ maxTokens: 1000,
24
+ temperature: 0,
25
+ },
26
+ options: { debug: true },
27
+ });
28
+
29
+ const MathAgent = new AxAgent({
30
+ name: "MathAgent",
31
+ description: "Solves math problems",
32
+ signature: `mathProblem:string "a sentence describing a math problem to be solved using Python code" -> solution:string "a precise answer to the math problem"`,
33
+ });
34
+
35
+ const ManagerAgent = new AxAgent({
36
+ name: "ManagerAgent",
37
+ description: "Completes a user specified task.",
38
+ signature: `question:string "a question to be answered" -> answer:string "the answer to the question"`,
39
+ agents: [MathAgent],
40
+ });
41
+
42
+ const userQuery =
43
+ "who is considered as the father of the iphone and what is the 7th root of their year of birth";
44
+ console.log(`\n\nQuestion: ${userQuery}`);
45
+
46
+ const managerAgentResponse = await ManagerAgent.forward(
47
+ openAI,
48
+ {
49
+ question: userQuery,
50
+ }
51
+ );
52
+
53
+ console.log(`\nManagerAgentResponse: ${JSON.stringify(managerAgentResponse, null, 2)}\n`);
54
+
55
+ console.log("❌ This is the wrong answer ☝️");
56
+
57
+ const MathAgentResponse = await MathAgent.forward(
58
+ googleAI,
59
+ {
60
+ mathProblem: userQuery
61
+ }
62
+ );
63
+
64
+ console.log(`\nMathAgentResponse: ${JSON.stringify(MathAgentResponse, null, 2)}\n`);
65
+ console.log(" ✅ Correct answer ☝️");
66
+
@@ -1,5 +1,4 @@
1
1
  import fs from 'fs';
2
- import yaml from 'js-yaml';
3
2
  import { AxAIAnthropic, AxAIOpenAI, AxAIAzureOpenAI, AxAICohere, AxAIDeepSeek, AxAIGoogleGemini, AxAIGroq, AxAIHuggingFace, AxAIMistral, AxAIOllama, AxAITogether } from '@ax-llm/ax';
4
3
  import { PROVIDER_API_KEYS } from '../config/index.js';
5
4
  // Define a mapping from provider names to their respective constructors
@@ -27,53 +26,108 @@ function isConstructor(func) {
27
26
  return typeof func === 'function' && 'prototype' in func && 'toFunction' in func.prototype;
28
27
  }
29
28
  /**
30
- * Reads the AI parameters from the YAML configuration file.
31
- * @param {string} agentConfigFilePath - The path to the agent_config.yaml file.
32
- * @returns {Object} The parsed agent configs from the config.yaml file.
33
- * @throws Will throw an error if reading the file fails.
29
+ * Provides a user-friendly error message for JSON parsing errors
34
30
  */
35
- const parseAgentConfig = (agentConfigFilePath) => {
31
+ const getFormattedJSONError = (error, fileContents) => {
32
+ if (error instanceof SyntaxError) {
33
+ const match = error.message.match(/position (\d+)/);
34
+ const position = match ? parseInt(match[1]) : -1;
35
+ if (position !== -1) {
36
+ const lines = fileContents.split('\n');
37
+ let currentPos = 0;
38
+ let errorLine = 0;
39
+ let errorColumn = 0;
40
+ // Find the line and column of the error
41
+ for (let i = 0; i < lines.length; i++) {
42
+ if (currentPos + lines[i].length >= position) {
43
+ errorLine = i + 1;
44
+ errorColumn = position - currentPos + 1;
45
+ break;
46
+ }
47
+ currentPos += lines[i].length + 1; // +1 for the newline character
48
+ }
49
+ const contextLines = lines.slice(Math.max(0, errorLine - 3), errorLine + 2)
50
+ .map((line, idx) => `${errorLine - 2 + idx}: ${line}`).join('\n');
51
+ return `JSON Parse Error in your agent configuration:
52
+
53
+ Error near line ${errorLine}, column ${errorColumn}
54
+
55
+ Context:
56
+ ${contextLines}
57
+
58
+ Common issues to check:
59
+ - Missing or extra commas between properties
60
+ - Missing quotes around property names
61
+ - Unmatched brackets or braces
62
+ - Invalid JSON values
63
+ - Trailing commas (not allowed in JSON)
64
+
65
+ Original error: ${error.message}`;
66
+ }
67
+ }
68
+ return `Error parsing agent configuration: ${error.message}`;
69
+ };
70
+ /**
71
+ * Reads the AI parameters from either a JSON configuration file or a direct JSON object.
72
+ * @param {AgentConfigInput} input - Either a path to the agent_config.json file or a JSON object with crew configuration.
73
+ * @returns {Object} The parsed agent configs.
74
+ * @throws Will throw an error if reading/parsing fails.
75
+ */
76
+ const parseAgentConfig = (input) => {
36
77
  try {
37
- const fileContents = fs.readFileSync(agentConfigFilePath, 'utf8');
38
- const parsedConfigs = yaml.load(fileContents);
39
- return parsedConfigs;
78
+ if (typeof input === 'string') {
79
+ // Handle file path input
80
+ const fileContents = fs.readFileSync(input, 'utf8');
81
+ const parsedConfigs = JSON.parse(fileContents);
82
+ return parsedConfigs;
83
+ }
84
+ else {
85
+ // Handle direct JSON object input
86
+ return input;
87
+ }
40
88
  }
41
89
  catch (e) {
42
- console.error('Error reading agent config file:', e);
90
+ if (e instanceof Error) {
91
+ if (typeof input === 'string') {
92
+ const formattedError = getFormattedJSONError(e, fs.readFileSync(input, 'utf8'));
93
+ throw new Error(formattedError);
94
+ }
95
+ throw new Error(`Error parsing agent configuration: ${e.message}`);
96
+ }
43
97
  throw e;
44
98
  }
45
99
  };
46
100
  /**
47
- * Initializes the AI agent using the specified agent name and configuration file path.
101
+ * Initializes the AI agent using the specified agent name and configuration.
48
102
  * This function parses the agent's configuration, validates the presence of the necessary API key,
49
103
  * and creates an instance of the AI agent with the appropriate settings.
50
104
  *
51
105
  * @param {string} agentName - The identifier for the AI agent to be initialized.
52
- * @param {string} agentConfigFilePath - The file path to the YAML configuration for the agent.
106
+ * @param {AgentConfigInput} agentConfig - Either a file path to the JSON configuration or a JSON object with crew configuration.
53
107
  * @param {FunctionRegistryType} functions - The functions available to the agent.
54
108
  * @param {Object} state - The state object for the agent.
55
109
  * @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
56
110
  * @throws {Error} Throws an error if the agent configuration is missing, the provider is unsupported,
57
111
  * the API key is not found, or the provider key name is not specified in the configuration.
58
112
  */
59
- const getAgentConfigParams = (agentName, agentConfigFilePath, functions, state) => {
113
+ const getAgentConfigParams = (agentName, agentConfig, functions, state) => {
60
114
  try {
61
- // Retrieve the parameters for the specified AI agent from a config file in yaml format
62
- const agentConfig = parseAgentConfig(agentConfigFilePath).crew.find(agent => agent.name === agentName);
63
- if (!agentConfig) {
115
+ // Retrieve the parameters for the specified AI agent from config
116
+ const agentConfigData = parseAgentConfig(agentConfig).crew.find(agent => agent.name === agentName);
117
+ if (!agentConfigData) {
64
118
  throw new Error(`AI agent with name ${agentName} is not configured`);
65
119
  }
66
120
  // Get the constructor for the AI agent's provider
67
- const AIConstructor = AIConstructors[agentConfig.provider];
121
+ const AIConstructor = AIConstructors[agentConfigData.provider];
68
122
  if (!AIConstructor) {
69
- throw new Error(`AI provider ${agentConfig.provider} is not supported. Did you mean '${agentConfig.provider.toLowerCase()}'?`);
123
+ throw new Error(`AI provider ${agentConfigData.provider} is not supported. Did you mean '${agentConfigData.provider.toLowerCase()}'?`);
70
124
  }
71
125
  // If an API Key property is present, get the API key for the AI agent from the environment variables
72
126
  let apiKey = '';
73
- if (agentConfig.provider_key_name) {
74
- apiKey = PROVIDER_API_KEYS[agentConfig.provider_key_name] || '';
127
+ if (agentConfigData.providerKeyName) {
128
+ apiKey = PROVIDER_API_KEYS[agentConfigData.providerKeyName] || '';
75
129
  if (!apiKey) {
76
- throw new Error(`API key for provider ${agentConfig.provider} is not set in environment variables`);
130
+ throw new Error(`API key for provider ${agentConfigData.provider} is not set in environment variables`);
77
131
  }
78
132
  }
79
133
  else {
@@ -82,19 +136,19 @@ const getAgentConfigParams = (agentName, agentConfigFilePath, functions, state)
82
136
  // Create an instance of the AI agent
83
137
  const ai = new AIConstructor({
84
138
  apiKey,
85
- config: agentConfig.ai,
139
+ config: agentConfigData.ai,
86
140
  options: {
87
- debug: agentConfig.debug || false
141
+ debug: agentConfigData.debug || false
88
142
  }
89
143
  });
90
144
  // If an apiURL is provided in the agent config, set it in the AI agent
91
- if (agentConfig.apiURL) {
92
- ai.setAPIURL(agentConfig.apiURL);
145
+ if (agentConfigData.apiURL) {
146
+ ai.setAPIURL(agentConfigData.apiURL);
93
147
  }
94
148
  // Set all options from the agent configuration
95
- ai.setOptions({ ...agentConfig.options });
149
+ ai.setOptions({ ...agentConfigData.options });
96
150
  // Prepare functions for the AI agent
97
- const agentFunctions = (agentConfig.functions || [])
151
+ const agentFunctions = (agentConfigData.functions || [])
98
152
  .map(funcName => {
99
153
  const func = functions[funcName];
100
154
  if (!func) {
@@ -113,15 +167,17 @@ const getAgentConfigParams = (agentName, agentConfigFilePath, functions, state)
113
167
  return {
114
168
  ai,
115
169
  name: agentName,
116
- description: agentConfig.description,
117
- signature: agentConfig.signature,
170
+ description: agentConfigData.description,
171
+ signature: agentConfigData.signature,
118
172
  functions: agentFunctions,
119
- subAgentNames: agentConfig.agents || []
173
+ subAgentNames: agentConfigData.agents || []
120
174
  };
121
175
  }
122
176
  catch (error) {
123
- console.error(error);
124
- throw new Error(`Error setting up AI agent`);
177
+ if (error instanceof Error) {
178
+ throw error;
179
+ }
180
+ throw new Error(`Error setting up AI agent: ${error}`);
125
181
  }
126
182
  };
127
183
  export { getAgentConfigParams };
@@ -23,11 +23,11 @@ class StatefulAxAgent extends AxAgent {
23
23
  class AxCrew {
24
24
  /**
25
25
  * Creates an instance of AxCrew.
26
- * @param {string} configFilePath - Path to the agent config file.
26
+ * @param {AgentConfigInput} agentConfig - Either a path to the agent config file or a JSON object with crew configuration.
27
27
  * @param {FunctionRegistryType} [functionsRegistry={}] - The registry of functions to use in the crew.
28
28
  * @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
29
29
  */
30
- constructor(configFilePath, functionsRegistry = {}, crewId = uuidv4()) {
30
+ constructor(agentConfig, functionsRegistry = {}, crewId = uuidv4()) {
31
31
  this.functionsRegistry = {};
32
32
  /**
33
33
  * Factory function for creating an agent.
@@ -37,7 +37,7 @@ class AxCrew {
37
37
  */
38
38
  this.createAgent = (agentName) => {
39
39
  try {
40
- const agentConfigParams = getAgentConfigParams(agentName, this.configFilePath, this.functionsRegistry, this.state);
40
+ const agentConfigParams = getAgentConfigParams(agentName, this.agentConfig, this.functionsRegistry, this.state);
41
41
  // Destructure with type assertion
42
42
  const { ai, name, description, signature, functions, subAgentNames } = agentConfigParams;
43
43
  // Get subagents for the AI agent
@@ -58,11 +58,10 @@ class AxCrew {
58
58
  return agent;
59
59
  }
60
60
  catch (error) {
61
- console.error(`Failed to create agent '${agentName}':`, error);
62
61
  throw error;
63
62
  }
64
63
  };
65
- this.configFilePath = configFilePath;
64
+ this.agentConfig = agentConfig;
66
65
  this.functionsRegistry = functionsRegistry;
67
66
  this.crewId = crewId;
68
67
  this.agents = new Map();
@@ -73,8 +72,14 @@ class AxCrew {
73
72
  * @param {string} agentName - The name of the agent to add.
74
73
  */
75
74
  addAgent(agentName) {
76
- if (this.agents && !this.agents.has(agentName)) {
77
- this.agents.set(agentName, this.createAgent(agentName));
75
+ try {
76
+ if (this.agents && !this.agents.has(agentName)) {
77
+ this.agents.set(agentName, this.createAgent(agentName));
78
+ }
79
+ }
80
+ catch (error) {
81
+ console.error(`Failed to create agent '${agentName}':`);
82
+ throw error;
78
83
  }
79
84
  }
80
85
  /**
@@ -85,10 +90,15 @@ class AxCrew {
85
90
  * @returns {Map<string, StatefulAxAgent> | null} A map of agent names to their corresponding instances.
86
91
  */
87
92
  addAgentsToCrew(agentNames) {
88
- agentNames.forEach((agentName) => {
89
- this.addAgent(agentName);
90
- });
91
- return this.agents;
93
+ try {
94
+ agentNames.forEach((agentName) => {
95
+ this.addAgent(agentName);
96
+ });
97
+ return this.agents;
98
+ }
99
+ catch (error) {
100
+ throw error;
101
+ }
92
102
  }
93
103
  /**
94
104
  * Cleans up the crew by dereferencing agents and resetting the state.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@amitdeshmukh/ax-crew",
4
- "version": "2.0.7",
4
+ "version": "3.1.0",
5
5
  "description": "Build and launch a crew of AI agents with shared state. Built with axllm.dev",
6
6
  "main": "dist/index.js",
7
7
  "engines": {
@@ -18,7 +18,7 @@
18
18
  "uuid": "^10.0.0"
19
19
  },
20
20
  "peerDependencies": {
21
- "@ax-llm/ax": "^10.0.0"
21
+ "@ax-llm/ax": "^10.0.9"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/js-yaml": "^4.0.9",
@@ -33,6 +33,6 @@
33
33
  },
34
34
  "bugs": {
35
35
  "url": "https://github.com/amitdeshmukh/ax-crew/issues"
36
- },
36
+ },
37
37
  "license": "MIT"
38
38
  }
@@ -1,6 +1,4 @@
1
1
  import fs from 'fs';
2
- import yaml from 'js-yaml';
3
-
4
2
  import { AxAIAnthropic, AxAIOpenAI, AxAIAzureOpenAI, AxAICohere, AxAIDeepSeek, AxAIGoogleGemini, AxAIGroq, AxAIHuggingFace, AxAIMistral, AxAIOllama, AxAITogether } from '@ax-llm/ax';
5
3
  import type { AxModelConfig, AxFunction, AxSignature } from '@ax-llm/ax';
6
4
 
@@ -31,7 +29,7 @@ interface AgentConfig {
31
29
  description: string;
32
30
  signature: AxSignature;
33
31
  provider: string;
34
- provider_key_name?: string;
32
+ providerKeyName?: string;
35
33
  ai: ExtendedAxModelConfig;
36
34
  debug?: boolean;
37
35
  apiURL?: string;
@@ -51,31 +49,92 @@ function isConstructor<T>(func: any): func is { new (...args: any[]): T } {
51
49
  return typeof func === 'function' && 'prototype' in func && 'toFunction' in func.prototype;
52
50
  }
53
51
 
52
+ /**
53
+ * Provides a user-friendly error message for JSON parsing errors
54
+ */
55
+ const getFormattedJSONError = (error: Error, fileContents: string): string => {
56
+ if (error instanceof SyntaxError) {
57
+ const match = error.message.match(/position (\d+)/);
58
+ const position = match ? parseInt(match[1]) : -1;
59
+
60
+ if (position !== -1) {
61
+ const lines = fileContents.split('\n');
62
+ let currentPos = 0;
63
+ let errorLine = 0;
64
+ let errorColumn = 0;
65
+
66
+ // Find the line and column of the error
67
+ for (let i = 0; i < lines.length; i++) {
68
+ if (currentPos + lines[i].length >= position) {
69
+ errorLine = i + 1;
70
+ errorColumn = position - currentPos + 1;
71
+ break;
72
+ }
73
+ currentPos += lines[i].length + 1; // +1 for the newline character
74
+ }
75
+
76
+ const contextLines = lines.slice(Math.max(0, errorLine - 3), errorLine + 2)
77
+ .map((line, idx) => `${errorLine - 2 + idx}: ${line}`).join('\n');
78
+
79
+ return `JSON Parse Error in your agent configuration:
80
+
81
+ Error near line ${errorLine}, column ${errorColumn}
82
+
83
+ Context:
84
+ ${contextLines}
85
+
86
+ Common issues to check:
87
+ - Missing or extra commas between properties
88
+ - Missing quotes around property names
89
+ - Unmatched brackets or braces
90
+ - Invalid JSON values
91
+ - Trailing commas (not allowed in JSON)
92
+
93
+ Original error: ${error.message}`;
94
+ }
95
+ }
96
+
97
+ return `Error parsing agent configuration: ${error.message}`;
98
+ };
99
+
100
+ type AgentConfigInput = string | { crew: AgentConfig[] };
54
101
 
55
102
  /**
56
- * Reads the AI parameters from the YAML configuration file.
57
- * @param {string} agentConfigFilePath - The path to the agent_config.yaml file.
58
- * @returns {Object} The parsed agent configs from the config.yaml file.
59
- * @throws Will throw an error if reading the file fails.
103
+ * Reads the AI parameters from either a JSON configuration file or a direct JSON object.
104
+ * @param {AgentConfigInput} input - Either a path to the agent_config.json file or a JSON object with crew configuration.
105
+ * @returns {Object} The parsed agent configs.
106
+ * @throws Will throw an error if reading/parsing fails.
60
107
  */
61
- const parseAgentConfig = (agentConfigFilePath: string): {crew: AgentConfig[]} => {
108
+ const parseAgentConfig = (input: AgentConfigInput): { crew: AgentConfig[] } => {
62
109
  try {
63
- const fileContents = fs.readFileSync(agentConfigFilePath, 'utf8');
64
- const parsedConfigs = yaml.load(fileContents) as { crew: AgentConfig[] };
65
- return parsedConfigs;
110
+ if (typeof input === 'string') {
111
+ // Handle file path input
112
+ const fileContents = fs.readFileSync(input, 'utf8');
113
+ const parsedConfigs = JSON.parse(fileContents) as { crew: AgentConfig[] };
114
+ return parsedConfigs;
115
+ } else {
116
+ // Handle direct JSON object input
117
+ return input;
118
+ }
66
119
  } catch (e) {
67
- console.error('Error reading agent config file:', e);
120
+ if (e instanceof Error) {
121
+ if (typeof input === 'string') {
122
+ const formattedError = getFormattedJSONError(e, fs.readFileSync(input, 'utf8'));
123
+ throw new Error(formattedError);
124
+ }
125
+ throw new Error(`Error parsing agent configuration: ${e.message}`);
126
+ }
68
127
  throw e;
69
128
  }
70
129
  };
71
130
 
72
131
  /**
73
- * Initializes the AI agent using the specified agent name and configuration file path.
132
+ * Initializes the AI agent using the specified agent name and configuration.
74
133
  * This function parses the agent's configuration, validates the presence of the necessary API key,
75
134
  * and creates an instance of the AI agent with the appropriate settings.
76
135
  *
77
136
  * @param {string} agentName - The identifier for the AI agent to be initialized.
78
- * @param {string} agentConfigFilePath - The file path to the YAML configuration for the agent.
137
+ * @param {AgentConfigInput} agentConfig - Either a file path to the JSON configuration or a JSON object with crew configuration.
79
138
  * @param {FunctionRegistryType} functions - The functions available to the agent.
80
139
  * @param {Object} state - The state object for the agent.
81
140
  * @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
@@ -84,30 +143,30 @@ const parseAgentConfig = (agentConfigFilePath: string): {crew: AgentConfig[]} =>
84
143
  */
85
144
  const getAgentConfigParams = (
86
145
  agentName: string,
87
- agentConfigFilePath: string,
146
+ agentConfig: AgentConfigInput,
88
147
  functions: FunctionRegistryType,
89
148
  state: Record<string, any>
90
149
  ) => {
91
- try{
92
- // Retrieve the parameters for the specified AI agent from a config file in yaml format
93
- const agentConfig = parseAgentConfig(agentConfigFilePath).crew.find(agent => agent.name === agentName);
94
- if (!agentConfig) {
150
+ try {
151
+ // Retrieve the parameters for the specified AI agent from config
152
+ const agentConfigData = parseAgentConfig(agentConfig).crew.find(agent => agent.name === agentName);
153
+ if (!agentConfigData) {
95
154
  throw new Error(`AI agent with name ${agentName} is not configured`);
96
155
  }
97
156
 
98
157
  // Get the constructor for the AI agent's provider
99
- const AIConstructor = AIConstructors[agentConfig.provider];
158
+ const AIConstructor = AIConstructors[agentConfigData.provider];
100
159
  if (!AIConstructor) {
101
- throw new Error(`AI provider ${agentConfig.provider} is not supported. Did you mean '${agentConfig.provider.toLowerCase()}'?`);
160
+ throw new Error(`AI provider ${agentConfigData.provider} is not supported. Did you mean '${agentConfigData.provider.toLowerCase()}'?`);
102
161
  }
103
162
 
104
163
  // If an API Key property is present, get the API key for the AI agent from the environment variables
105
164
  let apiKey = '';
106
- if (agentConfig.provider_key_name) {
107
- apiKey = PROVIDER_API_KEYS[agentConfig.provider_key_name] || '';
165
+ if (agentConfigData.providerKeyName) {
166
+ apiKey = PROVIDER_API_KEYS[agentConfigData.providerKeyName] || '';
108
167
 
109
168
  if (!apiKey) {
110
- throw new Error(`API key for provider ${agentConfig.provider} is not set in environment variables`);
169
+ throw new Error(`API key for provider ${agentConfigData.provider} is not set in environment variables`);
111
170
  }
112
171
  } else {
113
172
  throw new Error(`Provider key name is missing in the agent configuration`);
@@ -116,22 +175,21 @@ const getAgentConfigParams = (
116
175
  // Create an instance of the AI agent
117
176
  const ai = new AIConstructor({
118
177
  apiKey,
119
- config: agentConfig.ai,
178
+ config: agentConfigData.ai,
120
179
  options: {
121
- debug: agentConfig.debug || false
180
+ debug: agentConfigData.debug || false
122
181
  }
123
182
  });
124
-
125
183
  // If an apiURL is provided in the agent config, set it in the AI agent
126
- if (agentConfig.apiURL) {
127
- ai.setAPIURL(agentConfig.apiURL);
184
+ if (agentConfigData.apiURL) {
185
+ ai.setAPIURL(agentConfigData.apiURL);
128
186
  }
129
187
 
130
188
  // Set all options from the agent configuration
131
- ai.setOptions({ ...agentConfig.options });
189
+ ai.setOptions({ ...agentConfigData.options });
132
190
 
133
191
  // Prepare functions for the AI agent
134
- const agentFunctions = (agentConfig.functions || [])
192
+ const agentFunctions = (agentConfigData.functions || [])
135
193
  .map(funcName => {
136
194
  const func = functions[funcName];
137
195
  if (!func) {
@@ -153,15 +211,17 @@ const getAgentConfigParams = (
153
211
  return {
154
212
  ai,
155
213
  name: agentName,
156
- description: agentConfig.description,
157
- signature: agentConfig.signature,
214
+ description: agentConfigData.description,
215
+ signature: agentConfigData.signature,
158
216
  functions: agentFunctions,
159
- subAgentNames: agentConfig.agents || []
217
+ subAgentNames: agentConfigData.agents || []
160
218
  };
161
219
  } catch (error) {
162
- console.error(error);
163
- throw new Error(`Error setting up AI agent`);
220
+ if (error instanceof Error) {
221
+ throw error;
222
+ }
223
+ throw new Error(`Error setting up AI agent: ${error}`);
164
224
  }
165
225
  };
166
226
 
167
- export { getAgentConfigParams }
227
+ export { getAgentConfigParams, AgentConfigInput };
@@ -7,6 +7,7 @@ import type {
7
7
  AxProgramForwardOptions,
8
8
  } from "@ax-llm/ax";
9
9
  import { getAgentConfigParams } from "./agentConfig.js";
10
+ import type { AgentConfigInput } from "./agentConfig.js";
10
11
  import { FunctionRegistryType } from "../functions/index.js";
11
12
  import { createState, StateInstance } from "../state/index.js";
12
13
 
@@ -62,7 +63,7 @@ class StatefulAxAgent extends AxAgent<any, any> {
62
63
  * Represents a crew of agents with shared state functionality.
63
64
  */
64
65
  class AxCrew {
65
- private configFilePath: string;
66
+ private agentConfig: AgentConfigInput;
66
67
  functionsRegistry: FunctionRegistryType = {};
67
68
  crewId: string;
68
69
  agents: Map<string, StatefulAxAgent> | null;
@@ -70,16 +71,16 @@ class AxCrew {
70
71
 
71
72
  /**
72
73
  * Creates an instance of AxCrew.
73
- * @param {string} configFilePath - Path to the agent config file.
74
+ * @param {AgentConfigInput} agentConfig - Either a path to the agent config file or a JSON object with crew configuration.
74
75
  * @param {FunctionRegistryType} [functionsRegistry={}] - The registry of functions to use in the crew.
75
76
  * @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
76
77
  */
77
78
  constructor(
78
- configFilePath: string,
79
+ agentConfig: AgentConfigInput,
79
80
  functionsRegistry: FunctionRegistryType = {},
80
81
  crewId: string = uuidv4()
81
82
  ) {
82
- this.configFilePath = configFilePath;
83
+ this.agentConfig = agentConfig;
83
84
  this.functionsRegistry = functionsRegistry;
84
85
  this.crewId = crewId;
85
86
  this.agents = new Map<string, StatefulAxAgent>();
@@ -96,7 +97,7 @@ class AxCrew {
96
97
  try {
97
98
  const agentConfigParams: AgentConfigParams = getAgentConfigParams(
98
99
  agentName,
99
- this.configFilePath,
100
+ this.agentConfig,
100
101
  this.functionsRegistry,
101
102
  this.state
102
103
  );
@@ -134,7 +135,6 @@ class AxCrew {
134
135
 
135
136
  return agent;
136
137
  } catch (error) {
137
- console.error(`Failed to create agent '${agentName}':`, error);
138
138
  throw error;
139
139
  }
140
140
  };
@@ -144,8 +144,13 @@ class AxCrew {
144
144
  * @param {string} agentName - The name of the agent to add.
145
145
  */
146
146
  addAgent(agentName: string): void {
147
- if (this.agents && !this.agents.has(agentName)) {
148
- this.agents.set(agentName, this.createAgent(agentName));
147
+ try {
148
+ if (this.agents && !this.agents.has(agentName)) {
149
+ this.agents.set(agentName, this.createAgent(agentName));
150
+ }
151
+ } catch (error) {
152
+ console.error(`Failed to create agent '${agentName}':`);
153
+ throw error;
149
154
  }
150
155
  }
151
156
 
@@ -157,10 +162,14 @@ class AxCrew {
157
162
  * @returns {Map<string, StatefulAxAgent> | null} A map of agent names to their corresponding instances.
158
163
  */
159
164
  addAgentsToCrew(agentNames: string[]): Map<string, StatefulAxAgent> | null {
160
- agentNames.forEach((agentName) => {
161
- this.addAgent(agentName);
162
- });
163
- return this.agents;
165
+ try {
166
+ agentNames.forEach((agentName) => {
167
+ this.addAgent(agentName);
168
+ });
169
+ return this.agents;
170
+ } catch (error) {
171
+ throw error;
172
+ }
164
173
  }
165
174
 
166
175
  /**
package/test.js CHANGED
@@ -1,65 +1,30 @@
1
1
  import { AxCrew, AxCrewFunctions } from "./dist/index.js";
2
2
 
3
- // Create a new instance of AxCrew
4
- const crew = new AxCrew("./agent_config.example.yaml", AxCrewFunctions);
5
- crew.addAgentsToCrew(["Planner", "Calculator", "Manager"]);
3
+ const crew = new AxCrew("./agentConfig.json", AxCrewFunctions);
4
+ crew.addAgentsToCrew(["Planner", "Calculator", "WebSearch", "Manager"]);
6
5
 
7
- // Get agent instances
8
- const Planner = crew.agents.get("Planner");
9
- const Manager = crew.agents.get("Manager");
6
+ const calculateAndPrintAgentCost = (agent, agentName) => {
7
+ const { models, modelUsage, modelInfo } = agent.axai;
10
8
 
11
- // Calculate costs
12
- const calculateAgentCost = (agent) => {
13
- const modelUsed = agent.aiInstance.models.model;
14
- const usedTokens = agent.aiInstance.modelUsage;
15
- const modelInfo = agent.aiInstance.modelInfo?.find(
16
- (model) => model.name === modelUsed);
17
-
18
- const promptCost =
19
- (usedTokens.promptTokens / 1000000) * modelInfo?.promptTokenCostPer1M;
20
- const completionCost =
21
- (usedTokens.completionTokens / 1000000) *
22
- modelInfo?.completionTokenCostPer1M;
23
- const totalCost = promptCost + completionCost;
9
+ const { promptTokens, completionTokens } = modelUsage;
10
+ const modelDetails = modelInfo?.find((m) => m.name === models.model);
11
+ const totalCost = ((promptTokens / 1000000) * modelDetails?.promptTokenCostPer1M) + ((completionTokens / 1000000) * modelDetails?.completionTokenCostPer1M);
24
12
 
25
- return {
26
- totalCost,
27
- promptTokens: usedTokens.promptTokens,
28
- completionTokens: usedTokens.completionTokens,
29
- };
13
+ console.log(`\n${agentName} Usage:\nPrompt Token Cost: $${(promptTokens / 1000000 * modelDetails?.promptTokenCostPer1M).toFixed(6)}\nCompletion Token Cost: $${(completionTokens / 1000000 * modelDetails?.completionTokenCostPer1M).toFixed(6)}\nTotal Cost: $${totalCost.toFixed(6)}\nPrompt Tokens: ${promptTokens}\nCompletion Tokens: ${completionTokens}\nTotal Tokens: ${promptTokens + completionTokens}`);
30
14
  };
31
15
 
32
- // User query
33
- const userQuery = "whats the square root of the number of days between now and Christmas";
16
+ const userQuery = "what is the cube root of the number of days between now and Christmas";
34
17
  console.log(`\n\nQuestion: ${userQuery}`);
35
18
 
36
- // Forward the user query to the agents
37
- const planResponse = await Planner.forward({
38
- task: userQuery
39
- });
40
-
41
- const planner = calculateAgentCost(Planner);
42
- console.log('\nPlanner Usage:');
43
- console.log(`Cost: $${planner.totalCost.toFixed(6)}`);
44
- console.log(`Prompt Tokens: ${planner.promptTokens}`);
45
- console.log(`Completion Tokens: ${planner.completionTokens}`);
46
- console.log(`Total Tokens: ${planner.promptTokens + planner.completionTokens}`);
47
-
48
- const managerResponse = await Manager.forward({
49
- question: userQuery,
50
- plan: planResponse.plan,
51
- });
19
+ const Planner = crew.agents.get("Planner");
20
+ const plannerResponse = await Planner.forward({ task: userQuery });
21
+ console.log(`\n\nPlanner Response: ${plannerResponse.plan}`);
52
22
 
53
- const manager = calculateAgentCost(Manager);
54
- console.log('\nManager Usage:');
55
- console.log(`Cost: $${manager.totalCost.toFixed(4)}`);
56
- console.log(`Prompt Tokens: ${manager.promptTokens}`);
57
- console.log(`Completion Tokens: ${manager.completionTokens}`);
58
- console.log(`Total Tokens: ${manager.promptTokens + manager.completionTokens}`);
23
+ const Manager = crew.agents.get("Manager");
24
+ const managerResponse = await Manager.forward({ question: userQuery, plan: plannerResponse.plan });
25
+ console.log(`\n\nManager Response: ${managerResponse.answer}`);
59
26
 
60
- // Get and print the plan and answer from the agents
61
- const plan = planResponse.plan;
62
- const answer = managerResponse.answer;
27
+ // const calculatorResponse = await crew.agents.get("Calculator").forward({ mathProblem: userQuery });
28
+ // calculateAndPrintAgentCost(crew.agents.get("Calculator"), "Calculator");
63
29
 
64
- console.log(`\n\nPlan: ${plan}`);
65
- console.log(`\n\nAnswer: ${answer}`);
30
+ // console.log(`\n\nAnswer: ${calculatorResponse.solution}`);
package/test1.js ADDED
@@ -0,0 +1,98 @@
1
+ import { AxCrew, AxCrewFunctions } from "./dist/index.js";
2
+
3
+ // Define the configuration object directly
4
+ const config = {
5
+ crew: [
6
+ {
7
+ name: "Planner",
8
+ description: "Creates a plan to complete a task",
9
+ signature: "task:string \"a task to be completed\" -> plan:string \"a plan to execute the task in 5 steps or less\"",
10
+ provider: "google-gemini",
11
+ providerKeyName: "GEMINI_API_KEY",
12
+ ai: {
13
+ model: "gemini-1.5-flash",
14
+ temperature: 0
15
+ },
16
+ options: {
17
+ debug: false
18
+ }
19
+ },
20
+ {
21
+ name: "Calculator",
22
+ description: "Solves math problems",
23
+ signature: "mathProblem:string \"a math problem to be solved using Python code\" -> solution:string \"the solution to the math problem\"",
24
+ provider: "google-gemini",
25
+ providerKeyName: "GEMINI_API_KEY",
26
+ ai: {
27
+ model: "gemini-1.5-pro",
28
+ temperature: 0
29
+ },
30
+ options: {
31
+ debug: true,
32
+ codeExecution: true
33
+ },
34
+ functions: ["CurrentDateTime", "DaysBetweenDates"]
35
+ },
36
+ {
37
+ name: "WebSearch",
38
+ description: "Searches the web for the latest information using Google search",
39
+ signature: "webSearchQuery:string \"a query for Google search\" -> webSearchResponse:string \"the result of the search\"",
40
+ provider: "google-gemini",
41
+ providerKeyName: "GEMINI_API_KEY",
42
+ ai: {
43
+ model: "gemini-1.5-pro",
44
+ temperature: 0
45
+ },
46
+ options: {
47
+ debug: true,
48
+ googleSearchRetrieval: true
49
+ },
50
+ functions: ["CurrentDateTime", "DaysBetweenDates"]
51
+ },
52
+ {
53
+ name: "Manager",
54
+ description: "Answers questions from the user",
55
+ signature: "question:string \"a question from a user\", plan:string \"a suggested plan to answer the question\" -> answer:string \"the answer\"",
56
+ provider: "openai",
57
+ providerKeyName: "OPENAI_API_KEY",
58
+ ai: {
59
+ model: "gpt-4o-mini",
60
+ temperature: 0
61
+ },
62
+ options: {
63
+ debug: true
64
+ },
65
+ functions: ["CurrentDateTime", "DaysBetweenDates"]
66
+ }
67
+ ]
68
+ };
69
+
70
+ // Create crew instance with the direct configuration
71
+ const crew = new AxCrew(config, AxCrewFunctions);
72
+ crew.addAgentsToCrew(["Planner", "Calculator", "WebSearch", "Manager"]);
73
+
74
+ const calculateAndPrintAgentCost = (agent, agentName) => {
75
+ const { models, modelUsage, modelInfo } = agent.axai;
76
+
77
+ const { promptTokens, completionTokens } = modelUsage;
78
+ const modelDetails = modelInfo?.find((m) => m.name === models.model);
79
+ const totalCost = ((promptTokens / 1000000) * modelDetails?.promptTokenCostPer1M) + ((completionTokens / 1000000) * modelDetails?.completionTokenCostPer1M);
80
+
81
+ console.log(`\n${agentName} Usage:\nPrompt Token Cost: $${(promptTokens / 1000000 * modelDetails?.promptTokenCostPer1M).toFixed(6)}\nCompletion Token Cost: $${(completionTokens / 1000000 * modelDetails?.completionTokenCostPer1M).toFixed(6)}\nTotal Cost: $${totalCost.toFixed(6)}\nPrompt Tokens: ${promptTokens}\nCompletion Tokens: ${completionTokens}\nTotal Tokens: ${promptTokens + completionTokens}`);
82
+ };
83
+
84
+ const userQuery = "what is the cube root of the number of days between now and Christmas";
85
+ console.log(`\n\nQuestion: ${userQuery}`);
86
+
87
+ const Planner = crew.agents.get("Planner");
88
+ const plannerResponse = await Planner.forward({ task: userQuery });
89
+ console.log(`\n\nPlanner Response: ${plannerResponse.plan}`);
90
+
91
+ const Manager = crew.agents.get("Manager");
92
+ const managerResponse = await Manager.forward({ question: userQuery, plan: plannerResponse.plan });
93
+ console.log(`\n\nManager Response: ${managerResponse.answer}`);
94
+
95
+ // const calculatorResponse = await crew.agents.get("Calculator").forward({ mathProblem: userQuery });
96
+ // calculateAndPrintAgentCost(crew.agents.get("Calculator"), "Calculator");
97
+
98
+ // console.log(`\n\nAnswer: ${calculatorResponse.solution}`);
@@ -1,41 +0,0 @@
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 in 5 steps or less"
6
- provider: google-gemini
7
- provider_key_name: GEMINI_API_KEY
8
- ai:
9
- model: gemini-1.5-flash
10
- temperature: 0
11
- options:
12
- debug: false
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 from the user
28
- signature: question:string "a question from a user", plan:string "a suggested plan to answer the question" -> answer:string "the answer"
29
- provider: openai
30
- provider_key_name: OPENAI_API_KEY
31
- ai:
32
- model: gpt-4o-mini
33
- temperature: 0
34
- options:
35
- debug: true
36
- functions:
37
- - CurrentDateTime
38
- - DaysBetweenDates
39
- agents:
40
- - Calculator
41
-