@amitdeshmukh/ax-crew 3.5.1 → 3.6.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
@@ -5,6 +5,20 @@ This Changelog format is based on [Keep a Changelog]
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/
6
6
  v2.0.0.html).
7
7
 
8
+ ## [3.5.3] - 2025-02-20
9
+
10
+ ### Added
11
+ - New methods for crew level cost tracking:
12
+ - `getAggregatedCosts()` for crew-wide metrics
13
+ - `resetCosts()` for resetting cost tracking
14
+ - Enhanced `forward()` method with automatic cost tracking
15
+ - New TypeScript interfaces for better type safety in cost tracking
16
+ - Improved token metrics aggregation with detailed per-agent breakdowns
17
+
18
+ ### Fixed
19
+ - Improved handling of cost calculations for sub-agent calls
20
+ - Updated [examples](./examples/) to demonstrate the new cost tracking methods
21
+
8
22
  ## [3.5.2] - 2025-02-03
9
23
 
10
24
  ### Added
package/README.md CHANGED
@@ -16,10 +16,12 @@ Install this package:
16
16
  ```bash
17
17
  npm install @amitdeshmukh/ax-crew
18
18
  ```
19
- AxLLM is a peer dependency, so you will need to install it separately.
19
+ AxLLM is a peer dependency, so you will need to install it separately.
20
+
21
+ **Note:** Currently, we support AxLLM v10.0.9. We are working on updating this package to support the latest version of AxLLM as it introduces some breaking changes.
20
22
 
21
23
  ```bash
22
- npm install @ax-llm/ax@latest
24
+ npm install @ax-llm/ax@10.0.9
23
25
  ```
24
26
 
25
27
  ### TypeScript Support
@@ -323,7 +325,7 @@ The package provides precise cost tracking capabilities for monitoring API usage
323
325
  const response = await Planner.forward({ task: userQuery });
324
326
 
325
327
  // Get individual agent costs
326
- const agentCost = Planner.getUsageCost();
328
+ const agentCost = Planner.getLastUsageCost();
327
329
  console.log(agentCost);
328
330
  /* Output example:
329
331
  {
@@ -338,6 +340,22 @@ console.log(agentCost);
338
340
  }
339
341
  */
340
342
 
343
+ // Get cumulative costs for the agent
344
+ const cumulativeCost = Planner.getAccumulatedCosts();
345
+ console.log(cumulativeCost);
346
+ /* Output example:
347
+ {
348
+ promptCost: "0.0003637500000",
349
+ completionCost: "0.0006100000000",
350
+ totalCost: "0.0009737500000",
351
+ tokenMetrics: {
352
+ promptTokens: 291,
353
+ completionTokens: 122,
354
+ totalTokens: 413
355
+ }
356
+ }
357
+ */
358
+
341
359
  // Get aggregated costs for all agents in the crew
342
360
  const crewCosts = crew.getAggregatedCosts();
343
361
  console.log(crewCosts);
@@ -17,20 +17,22 @@ export interface UsageCost {
17
17
  totalTokens: number;
18
18
  };
19
19
  }
20
+ export interface AggregatedMetrics {
21
+ promptTokens: number;
22
+ completionTokens: number;
23
+ totalTokens: number;
24
+ promptCost: string;
25
+ completionCost: string;
26
+ }
27
+ export interface AggregatedCosts {
28
+ totalCost: string;
29
+ byAgent: Record<string, UsageCost>;
30
+ aggregatedMetrics: AggregatedMetrics;
31
+ }
20
32
  export declare class StateFulAxAgentUsage {
21
33
  static STATE_KEY_PREFIX: string;
22
34
  static calculateCost(modelUsage: ModelUsage, modelInfo: ModelInfo): UsageCost;
23
35
  static trackCostInState(agentName: string, cost: UsageCost, state: StateInstance): void;
24
- static getAggregatedCosts(state: StateInstance): {
25
- totalCost: string;
26
- byAgent: Record<string, UsageCost>;
27
- aggregatedMetrics: {
28
- promptTokens: number;
29
- completionTokens: number;
30
- totalTokens: number;
31
- promptCost: string;
32
- completionCost: string;
33
- };
34
- };
36
+ static getAggregatedCosts(state: StateInstance): AggregatedCosts;
35
37
  static resetCosts(state: StateInstance): void;
36
38
  }
@@ -18,9 +18,8 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
18
18
  }>, state: StateInstance);
19
19
  forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions>): Promise<Record<string, any>>;
20
20
  forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions>): Promise<Record<string, any>>;
21
- getUsageCost(): UsageCost | null;
22
- getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts>;
23
- resetCosts(): void;
21
+ getLastUsageCost(): UsageCost | null;
22
+ getAccumulatedCosts(): UsageCost | null;
24
23
  }
25
24
  /**
26
25
  * Represents a crew of agents with shared state functionality.
@@ -62,5 +61,14 @@ declare class AxCrew {
62
61
  * Cleans up the crew by dereferencing agents and resetting the state.
63
62
  */
64
63
  destroy(): void;
64
+ /**
65
+ * Gets aggregated costs for all agents in the crew
66
+ * @returns Aggregated cost information for all agents
67
+ */
68
+ getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts>;
69
+ /**
70
+ * Resets all cost tracking for the crew
71
+ */
72
+ resetCosts(): void;
65
73
  }
66
74
  export { AxCrew };
@@ -17,43 +17,42 @@ class StatefulAxAgent extends AxAgent {
17
17
  this.agentName = options.name;
18
18
  // Set examples if provided
19
19
  if (examples && examples.length > 0) {
20
- this.setExamples(examples);
20
+ super.setExamples(examples);
21
21
  }
22
22
  }
23
23
  // Implementation
24
24
  async forward(first, second, third) {
25
- // Sub-agent case (called with AI service)
26
25
  let result;
26
+ // Track costs regardless of whether it's a direct or sub-agent call
27
+ // This ensures we capture multiple legitimate calls to the same agent
27
28
  if ('apiURL' in first) {
29
+ // Sub-agent case (called with AI service)
28
30
  result = await super.forward(this.axai, second, third);
29
31
  }
30
32
  else {
31
33
  // Direct call case
32
34
  result = await super.forward(this.axai, first, second);
33
35
  }
34
- // Track costs in state after the call
35
- const cost = this.getUsageCost();
36
+ // Track costs after the call
37
+ const cost = this.getLastUsageCost();
36
38
  if (cost) {
37
39
  StateFulAxAgentUsage.trackCostInState(this.agentName, cost, this.state);
38
40
  }
39
41
  return result;
40
42
  }
41
- // Get the usage cost for a run of the agent
42
- getUsageCost() {
43
- const { modelUsage, modelInfo, models } = this.axai;
44
- const currentModelInfo = modelInfo?.find((m) => m.name === models.model);
43
+ // Get the usage cost for the most recent run of the agent
44
+ getLastUsageCost() {
45
+ const { modelUsage, modelInfo, defaults } = this.axai;
46
+ const currentModelInfo = modelInfo?.find((m) => m.name === defaults.model);
45
47
  if (!currentModelInfo || !modelUsage) {
46
48
  return null;
47
49
  }
48
50
  return StateFulAxAgentUsage.calculateCost(modelUsage, currentModelInfo);
49
51
  }
50
- // Get aggregated costs for all agents in the crew
51
- getAggregatedCosts() {
52
- return StateFulAxAgentUsage.getAggregatedCosts(this.state);
53
- }
54
- // Reset all cost tracking
55
- resetCosts() {
56
- StateFulAxAgentUsage.resetCosts(this.state);
52
+ // Get the accumulated costs for all runs of this agent
53
+ getAccumulatedCosts() {
54
+ const stateKey = `${StateFulAxAgentUsage.STATE_KEY_PREFIX}${this.agentName}`;
55
+ return this.state.get(stateKey);
57
56
  }
58
57
  }
59
58
  /**
@@ -147,5 +146,18 @@ class AxCrew {
147
146
  this.agents = null;
148
147
  this.state.reset();
149
148
  }
149
+ /**
150
+ * Gets aggregated costs for all agents in the crew
151
+ * @returns Aggregated cost information for all agents
152
+ */
153
+ getAggregatedCosts() {
154
+ return StateFulAxAgentUsage.getAggregatedCosts(this.state);
155
+ }
156
+ /**
157
+ * Resets all cost tracking for the crew
158
+ */
159
+ resetCosts() {
160
+ StateFulAxAgentUsage.resetCosts(this.state);
161
+ }
150
162
  }
151
163
  export { AxCrew };
@@ -135,13 +135,16 @@ A basic example showing core AxCrew features:
135
135
  All examples include cost tracking. You can monitor API usage costs:
136
136
 
137
137
  ```typescript
138
- // After running an agent
138
+ // Get individual agent costs
139
139
  const cost = agent.getUsageCost();
140
140
  console.log("Usage cost:", cost);
141
141
 
142
- // Get aggregated costs for all agents
143
- const totalCosts = agent.getAggregatedCosts();
142
+ // Get aggregated costs for all agents in the crew
143
+ const totalCosts = crew.getAggregatedCosts();
144
144
  console.log("Total costs:", totalCosts);
145
+
146
+ // Reset cost tracking if needed
147
+ crew.resetCosts();
145
148
  ```
146
149
 
147
150
  ## Best Practices
@@ -1,4 +1,4 @@
1
- ;import { AxCrew } from "../dist/index.js";
1
+ import { AxCrew } from "@amitdeshmukh/ax-crew";
2
2
 
3
3
  // Example agent configuration
4
4
  const agentConfig = {
@@ -10,18 +10,24 @@ const agentConfig = {
10
10
  provider: "anthropic",
11
11
  providerKeyName: "ANTHROPIC_API_KEY",
12
12
  ai: {
13
- model: "claude-3-opus-20240229"
14
- }
13
+ model: "claude-3-sonnet-20240229"
14
+ },
15
+ options: {
16
+ debug: true,
17
+ },
15
18
  },
16
19
  {
17
20
  name: "writer",
18
21
  description: "A writing agent that creates content",
19
- signature: "topic:string, research:string -> article:string",
22
+ signature: "topic:string -> article:string",
20
23
  provider: "anthropic",
21
24
  providerKeyName: "ANTHROPIC_API_KEY",
22
25
  ai: {
23
26
  model: "claude-3-sonnet-20240229"
24
27
  },
28
+ options: {
29
+ debug: true,
30
+ },
25
31
  agents: ["researcher"] // This makes writer use researcher as a sub-agent
26
32
  }
27
33
  ]
@@ -44,30 +50,23 @@ async function main() {
44
50
  }
45
51
 
46
52
  try {
47
- // Use the researcher agent
48
- const { research } = await researcher.forward({
49
- query: "What are the key benefits of quantum computing?"
50
- });
51
-
52
53
  // Use the writer agent (which will internally use the researcher)
53
- const article = await writer.forward({
54
+ const { article } = await writer.forward({
54
55
  topic: "Quantum Computing Benefits",
55
- research: research
56
56
  });
57
57
 
58
58
  // Print the article
59
59
  console.log("Article:", article);
60
-
61
- // Get costs for individual agents
62
- const researcherCost = researcher.getUsageCost();
63
- console.log("Researcher cost:", researcherCost);
64
60
 
65
- // Get aggregated costs for all agents (includes both direct calls and sub-agent calls)
66
- const totalCosts = writer.getAggregatedCosts();
67
- console.log("Total cost breakdown:", JSON.stringify(totalCosts, null, 2));
61
+ // Print usage costs
62
+ console.log("\nUsage:\n+++++++++++++++++++++++++++++++++");
63
+ console.log("Writer Agent:", JSON.stringify(writer.getAccumulatedCosts(), null, 2));
64
+ console.log("Researcher Agent Last Usage:", JSON.stringify(researcher.getLastUsageCost(), null, 2));
65
+ console.log("Researcher Agent Accumulated:", JSON.stringify(researcher.getAccumulatedCosts(), null, 2));
66
+ console.log("Total Cost:", JSON.stringify(crew.getAggregatedCosts(), null, 2));
68
67
 
69
68
  // If you want to start fresh with cost tracking
70
- writer.resetCosts();
69
+ crew.resetCosts();
71
70
 
72
71
  } finally {
73
72
  // Clean up
@@ -1,4 +1,4 @@
1
- import { AxCrew } from "../dist/index.js";
1
+ import { AxCrew } from "@amitdeshmukh/ax-crew";
2
2
 
3
3
  // Define the crew configuration
4
4
  const config = {
@@ -32,7 +32,7 @@ const config = {
32
32
  temperature: 0,
33
33
  },
34
34
  options: {
35
- debug: true,
35
+ debug: false,
36
36
  },
37
37
  agents: ["MathAgent"]
38
38
  },
@@ -47,27 +47,26 @@ const agents = crew.addAgentsToCrew(["MathAgent", "ManagerAgent"]);
47
47
 
48
48
  // Get agent instances
49
49
  const managerAgent = agents?.get("ManagerAgent");
50
+ const mathAgent = agents?.get("MathAgent");
50
51
 
51
52
  const userQuery: string =
52
53
  "who is considered as the father of the iphone and what is the 7th root of their year of birth (precision to minimum 5 decimal places)";
53
54
  console.log(`\n\nQuestion: ${userQuery}`);
54
55
 
55
56
  const main = async (): Promise<void> => {
56
- if (managerAgent) {
57
- // Try with manager agent first
57
+ if (managerAgent && mathAgent) {
58
58
  const managerResponse = await managerAgent.forward({
59
59
  question: userQuery,
60
60
  });
61
61
 
62
- console.log(
63
- `\nManager's Answer: ${JSON.stringify(managerResponse.answer, null, 2)}\n`
64
- );
62
+ console.log(`\nAnswer: ${JSON.stringify(managerResponse.answer, null, 2)}`);
65
63
 
66
64
  // Print usage costs
67
- const managerCost = managerAgent.getUsageCost();
68
-
69
- console.log("\nUsage Costs:");
70
- console.log("Manager Agent:", managerCost);
65
+ console.log("\nUsage:\n+++++++++++++++++++++++++++++++++");
66
+ console.log("Manager Agent:", JSON.stringify(managerAgent.getAccumulatedCosts(), null, 2));
67
+ console.log("Math Agent:", JSON.stringify(mathAgent.getLastUsageCost(), null, 2));
68
+ console.log("Math Agent Accumulated Costs:", JSON.stringify(mathAgent.getAccumulatedCosts(), null, 2));
69
+ console.log("Total Cost:", JSON.stringify(crew.getAggregatedCosts(), null, 2));
71
70
  }
72
71
  };
73
72
 
@@ -1,6 +1,8 @@
1
- import { AxCrew } from "../dist/index.js";
2
- import fetch from 'node-fetch';
3
- import https from 'https';
1
+ // NOTE: For a more complete example of a crew that can research a topic, write a post and publish to WordPress, please see ./write-post-and-publish-to-wordpress.ts
2
+
3
+ import { AxCrew } from "@amitdeshmukh/ax-crew";
4
+ import type { FunctionRegistryType } from "@amitdeshmukh/ax-crew";
5
+ import { WordPressPost } from "@ax-crew/tools-wordpress";
4
6
 
5
7
  // AxCrew configuration
6
8
  const config = {
@@ -8,7 +10,7 @@ const config = {
8
10
  {
9
11
  name: "SearchQueryGenerator",
10
12
  description: "Generates a list of google search queries to help research the topic",
11
- signature: "topic:string \"The topic of the blog post\", guidance:string \"guidance from the user on what the blog post should be about\" -> googleSearchQueries:string[] \"an array of google search queries to help research the topic\"",
13
+ signature: "topic:string \"The topic of the blog post\", guidance:string \"guidance from the user on what the blog post should be about\" -> googleSearchQueries:string[] \"an array of upto 5 google search queries to help research the topic\"",
12
14
  provider: "anthropic",
13
15
  providerKeyName: "ANTHROPIC_API_KEY",
14
16
  ai: {
@@ -27,12 +29,15 @@ const config = {
27
29
  provider: "google-gemini",
28
30
  providerKeyName: "GEMINI_API_KEY",
29
31
  ai: {
30
- model: "gemini-2.0-flash-exp",
32
+ model: "gemini-1.5-pro",
31
33
  temperature: 0.5
32
34
  },
33
35
  options: {
34
36
  debug: true,
35
- stream: false
37
+ stream: false,
38
+ googleSearchRetrieval: {
39
+ mode: "MODE_UNSPECIFIED"
40
+ }
36
41
  }
37
42
  },
38
43
  {
@@ -49,93 +54,83 @@ const config = {
49
54
  debug: true,
50
55
  stream: false
51
56
  }
57
+ },
58
+ {
59
+ name: "WordPressPoster",
60
+ description: "Creates a post on WordPress with the given title, content and status",
61
+ signature: "blogPostTitle:string \"the title of the blog post\", blogPostContent:string \"the content of the blog post.\", status:string \"the status of the post (draft, publish, private)\" -> postResponse:string \"the response from the WordPress API\"",
62
+ provider: "anthropic",
63
+ providerKeyName: "ANTHROPIC_API_KEY",
64
+ ai: {
65
+ model: "claude-3-5-sonnet-20240620",
66
+ temperature: 0
67
+ },
68
+ options: {
69
+ debug: true,
70
+ stream: false
71
+ },
72
+ functions: ["WordPressPost"]
52
73
  }
53
74
  ]
54
75
  };
55
76
 
56
- // Function to post to WordPress
57
- async function postToWordPress(title: string, content: string, status: 'draft' | 'publish' = 'draft') {
58
- const wpUrl = process.env.WORDPRESS_URL;
59
- const wpUsername = process.env.WORDPRESS_USERNAME;
60
- const wpPassword = process.env.WORDPRESS_PASSWORD;
61
-
62
- if (!wpUrl) {
63
- throw new Error('WordPress credentials not found in environment variables');
64
- }
65
-
66
- const auth = Buffer.from(`${wpUsername}:${wpPassword}`).toString('base64');
67
-
68
- const httpsAgent = new https.Agent({
69
- rejectUnauthorized: false
70
- });
71
-
72
- try {
73
- const response = await fetch(`${wpUrl}/wp-json/wp/v2/posts`, {
74
- method: 'POST',
75
- headers: {
76
- 'Authorization': `Basic ${auth}`,
77
- 'Content-Type': 'application/json'
78
- },
79
- body: JSON.stringify({
80
- title,
81
- content,
82
- status
83
- }),
84
- agent: httpsAgent
85
- });
86
-
87
- if (!response.ok) {
88
- const error = await response.text();
89
- throw new Error(`WordPress API error: ${error}`);
90
- }
91
-
92
- const post = await response.json();
93
- return {
94
- id: post.id,
95
- url: post.link,
96
- status: post.status
97
- };
98
- } catch (error) {
99
- console.error('Error details:', error);
100
- throw new Error(`Failed to post to WordPress: ${error.message}`);
101
- }
102
- }
103
-
104
77
  const main = async () => {
105
78
  // Create crew with type checking
106
- const crew = new AxCrew(config);
79
+ const customFunctions: FunctionRegistryType = {
80
+ WordPressPost: WordPressPost
81
+ };
82
+ const crew = new AxCrew(config, customFunctions);
83
+
84
+ // Add agents to crew
85
+ const agents = crew.addAgentsToCrew([
86
+ 'SearchQueryGenerator',
87
+ 'GoogleSearch',
88
+ 'BlogPostWriter',
89
+ 'WordPressPoster'
90
+ ]);
91
+
92
+ // NOTE: Your Wordpress site needs to have the Basic Auth plugin installed and enabled for the automatic posting to work
93
+ // Refer to https://github.com/WP-API/Basic-Auth?tab=readme-ov-file
94
+
95
+ // Set environment variables
96
+ crew.state.set("env", {
97
+ WORDPRESS_URL: "http://my-wordpress-site.com",
98
+ WORDPRESS_USERNAME: "my-username",
99
+ WORDPRESS_PASSWORD: "my-password"
100
+ });
107
101
 
108
- // Type-safe agent management
109
- const agents = crew.addAgentsToCrew(['SearchQueryGenerator', 'GoogleSearch', 'BlogPostWriter']);
102
+ // Get agents from crew
110
103
  const planner = agents?.get('SearchQueryGenerator');
111
104
  const googleSearch = agents?.get('GoogleSearch');
112
105
  const writer = agents?.get('BlogPostWriter');
106
+ const poster = agents?.get('WordPressPoster');
113
107
 
114
- const topic = "How to tell what your cat is thinking";
115
- const guidance = "The article should be a fun and engaging article that will help the reader to understand their cat better.";
108
+ // Define the topic and guidance
109
+ const topic = "How to tell what your dog is thinking";
110
+ const guidance = "The article should be a fun and engaging article and less that 500 words long. It should help the reader to understand their dog better.";
116
111
 
117
- if (planner && googleSearch && writer) {
118
- // Type-safe agent usage
112
+ if (planner && googleSearch && writer && poster) {
113
+ // Generate search queries
119
114
  const plannerResponse = await planner.forward({ topic, guidance });
120
115
  const { googleSearchQueries } = plannerResponse;
121
116
 
117
+ // Research the topic
122
118
  const googleSearchResults: string[] = [];
123
119
  for (const query of googleSearchQueries) {
124
120
  const googleSearchResponse = await googleSearch.forward({ googleSearchQuery: query });
125
121
  const { googleSearchResult } = googleSearchResponse;
126
122
  googleSearchResults.push(googleSearchResult);
123
+ // Wait for 3 seconds between queries to avoid rate limiting
124
+ await new Promise(resolve => setTimeout(resolve, 3000));
127
125
  }
128
126
 
129
-
127
+ // Write the blog post
130
128
  const writerResponse = await writer.forward({ topic, guidance, googleSearchResults });
131
129
  const { blogPostTitle, blogPostContent } = writerResponse;
132
130
 
133
- console.log(blogPostTitle);
134
- console.log(blogPostContent);
135
-
136
131
  // Post to WordPress
137
132
  try {
138
- const postResponse = await postToWordPress(blogPostTitle, blogPostContent, 'publish');
133
+ const postResponse = await poster.forward({ blogPostTitle, blogPostContent, status: "publish" });
139
134
  console.log('Successfully posted to WordPress:', postResponse);
140
135
  } catch (error) {
141
136
  console.error('Failed to post to WordPress:', error);
package/index.d.ts CHANGED
@@ -1,15 +1,7 @@
1
1
  import { AxAI, AxAgentic, AxFunction, AxSignature, AxProgramForwardOptions, AxModelConfig, AxAgent } from "@ax-llm/ax";
2
+ import type { UsageCost, AggregatedCosts } from "./src/agents/agentUseCosts.js";
2
3
 
3
- export interface UsageCost {
4
- promptCost: string;
5
- completionCost: string;
6
- totalCost: string;
7
- tokenMetrics: {
8
- promptTokens: number;
9
- completionTokens: number;
10
- totalTokens: number;
11
- };
12
- }
4
+ export { UsageCost, AggregatedCosts };
13
5
 
14
6
  export interface StateInstance {
15
7
  reset(): void;
@@ -83,6 +75,8 @@ export class AxCrew {
83
75
  createAgent(agentName: string): StatefulAxAgent;
84
76
  addAgent(agentName: string): void;
85
77
  addAgentsToCrew(agentNames: string[]): Map<string, StatefulAxAgent> | null;
78
+ getAggregatedCosts(): AggregatedCosts;
79
+ resetCosts(): void;
86
80
  destroy(): void;
87
81
  }
88
82
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@amitdeshmukh/ax-crew",
4
- "version": "3.5.1",
4
+ "version": "3.6.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
  "types": "dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "uuid": "^10.0.0"
20
20
  },
21
21
  "peerDependencies": {
22
- "@ax-llm/ax": "^10.0.9"
22
+ "@ax-llm/ax": "^11.0.21"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.14.9",
@@ -22,6 +22,20 @@ export interface UsageCost {
22
22
  }
23
23
  }
24
24
 
25
+ export interface AggregatedMetrics {
26
+ promptTokens: number;
27
+ completionTokens: number;
28
+ totalTokens: number;
29
+ promptCost: string;
30
+ completionCost: string;
31
+ }
32
+
33
+ export interface AggregatedCosts {
34
+ totalCost: string;
35
+ byAgent: Record<string, UsageCost>;
36
+ aggregatedMetrics: AggregatedMetrics;
37
+ }
38
+
25
39
  // Utility class to handle usage related functionality
26
40
  export class StateFulAxAgentUsage {
27
41
  static STATE_KEY_PREFIX = 'agent_usage_';
@@ -78,17 +92,7 @@ export class StateFulAxAgentUsage {
78
92
  }
79
93
  }
80
94
 
81
- static getAggregatedCosts(state: StateInstance): {
82
- totalCost: string;
83
- byAgent: Record<string, UsageCost>;
84
- aggregatedMetrics: {
85
- promptTokens: number;
86
- completionTokens: number;
87
- totalTokens: number;
88
- promptCost: string;
89
- completionCost: string;
90
- };
91
- } {
95
+ static getAggregatedCosts(state: StateInstance): AggregatedCosts {
92
96
  const allState = state.getAll();
93
97
  const agentCosts: Record<string, UsageCost> = {};
94
98
  let totalPromptTokens = 0;
@@ -59,7 +59,7 @@ class StatefulAxAgent extends AxAgent<any, any> {
59
59
 
60
60
  // Set examples if provided
61
61
  if (examples && examples.length > 0) {
62
- this.setExamples(examples);
62
+ super.setExamples(examples);
63
63
  }
64
64
  }
65
65
 
@@ -73,17 +73,20 @@ class StatefulAxAgent extends AxAgent<any, any> {
73
73
  second?: Record<string, any> | Readonly<AxProgramForwardOptions>,
74
74
  third?: Readonly<AxProgramForwardOptions>
75
75
  ): Promise<Record<string, any>> {
76
- // Sub-agent case (called with AI service)
77
76
  let result;
77
+
78
+ // Track costs regardless of whether it's a direct or sub-agent call
79
+ // This ensures we capture multiple legitimate calls to the same agent
78
80
  if ('apiURL' in first) {
81
+ // Sub-agent case (called with AI service)
79
82
  result = await super.forward(this.axai, second as Record<string, any>, third);
80
83
  } else {
81
84
  // Direct call case
82
85
  result = await super.forward(this.axai, first, second as Readonly<AxProgramForwardOptions>);
83
86
  }
84
87
 
85
- // Track costs in state after the call
86
- const cost = this.getUsageCost();
88
+ // Track costs after the call
89
+ const cost = this.getLastUsageCost();
87
90
  if (cost) {
88
91
  StateFulAxAgentUsage.trackCostInState(this.agentName, cost, this.state);
89
92
  }
@@ -91,10 +94,10 @@ class StatefulAxAgent extends AxAgent<any, any> {
91
94
  return result;
92
95
  }
93
96
 
94
- // Get the usage cost for a run of the agent
95
- getUsageCost(): UsageCost | null {
96
- const { modelUsage, modelInfo, models } = this.axai;
97
- const currentModelInfo = modelInfo?.find((m: { name: string }) => m.name === models.model);
97
+ // Get the usage cost for the most recent run of the agent
98
+ getLastUsageCost(): UsageCost | null {
99
+ const { modelUsage, modelInfo, defaults } = this.axai;
100
+ const currentModelInfo = modelInfo?.find((m: { name: string }) => m.name === defaults.model);
98
101
 
99
102
  if (!currentModelInfo || !modelUsage) {
100
103
  return null;
@@ -103,14 +106,10 @@ class StatefulAxAgent extends AxAgent<any, any> {
103
106
  return StateFulAxAgentUsage.calculateCost(modelUsage, currentModelInfo);
104
107
  }
105
108
 
106
- // Get aggregated costs for all agents in the crew
107
- getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts> {
108
- return StateFulAxAgentUsage.getAggregatedCosts(this.state);
109
- }
110
-
111
- // Reset all cost tracking
112
- resetCosts(): void {
113
- StateFulAxAgentUsage.resetCosts(this.state);
109
+ // Get the accumulated costs for all runs of this agent
110
+ getAccumulatedCosts(): UsageCost | null {
111
+ const stateKey = `${StateFulAxAgentUsage.STATE_KEY_PREFIX}${this.agentName}`;
112
+ return this.state.get(stateKey) as UsageCost | null;
114
113
  }
115
114
  }
116
115
 
@@ -235,6 +234,21 @@ class AxCrew {
235
234
  this.agents = null;
236
235
  this.state.reset();
237
236
  }
237
+
238
+ /**
239
+ * Gets aggregated costs for all agents in the crew
240
+ * @returns Aggregated cost information for all agents
241
+ */
242
+ getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts> {
243
+ return StateFulAxAgentUsage.getAggregatedCosts(this.state);
244
+ }
245
+
246
+ /**
247
+ * Resets all cost tracking for the crew
248
+ */
249
+ resetCosts(): void {
250
+ StateFulAxAgentUsage.resetCosts(this.state);
251
+ }
238
252
  }
239
253
 
240
254
  export { AxCrew };