@amitdeshmukh/ax-crew 3.11.3 → 4.0.1
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 +24 -0
- package/README.md +31 -62
- package/dist/agents/agentConfig.d.ts +6 -6
- package/dist/agents/agentConfig.js +39 -32
- package/dist/agents/index.d.ts +13 -10
- package/dist/agents/index.js +154 -40
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.js +2 -0
- package/dist/metrics/registry.d.ts +9 -0
- package/dist/metrics/registry.js +138 -0
- package/dist/metrics/types.d.ts +40 -0
- package/dist/metrics/types.js +1 -0
- package/dist/types.d.ts +6 -6
- package/examples/basic-researcher-writer.ts +18 -13
- package/examples/mcp-agent.ts +12 -8
- package/examples/solve-math-problem.ts +7 -5
- package/examples/streaming.ts +7 -6
- package/package.json +5 -3
- package/src/agents/agentConfig.ts +44 -38
- package/src/agents/index.ts +168 -59
- package/src/index.ts +2 -0
- package/src/metrics/index.ts +4 -0
- package/src/metrics/registry.ts +167 -0
- package/src/metrics/types.ts +48 -0
- package/src/types.ts +23 -7
- package/tests/AxCrew.test.ts +22 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [4.0.1] - 2025-08-24
|
|
3
|
+
|
|
4
|
+
### Added
|
|
5
|
+
- Dependency: `big.js` for precise accumulation of estimated costs.
|
|
6
|
+
- Dev Dependency: `@types/big.js` for TypeScript support.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Switched crew cost reporting to metrics-based API. Use `agent.getMetrics()` and `crew.getCrewMetrics()` instead of legacy cost getters.
|
|
10
|
+
- Estimated cost (`estimatedCostUSD`) is now accumulated with Big.js and reported rounded to 5 decimal places.
|
|
11
|
+
- Tests updated to use metrics assertions; added dummy `ANTHROPIC_API_KEY` in test setup to avoid env failures.
|
|
12
|
+
- Test agent signatures updated to new DSL (`query:string -> queryResponse:string`).
|
|
13
|
+
- Aligned with new `@ax-llm/ax` `ai()` and `agent()` factory methods: enforce canonical provider slugs, pass cost tracker via `options.trackers`, and validate `apiURL` when provided.
|
|
14
|
+
- Updated package versions for `@ax-llm/ax` and `@ax-llm/ax-tools`.
|
|
15
|
+
|
|
16
|
+
### Deprecated
|
|
17
|
+
- Legacy cost APIs (`getLastUsageCost`, `getAccumulatedCosts`, `getAggregatedCosts`) are deprecated in favor of metrics.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
- Tests no longer use `getAggregatedCosts`; replaced with metrics assertions.
|
|
21
|
+
|
|
22
|
+
### Updated
|
|
23
|
+
- README now documents metrics usage and 5-decimal cost rounding, de-emphasizing legacy cost APIs.
|
|
24
|
+
- README Features now includes "Metrics and Cost Tracking"; examples updated to use metrics (`getMetrics`, `getCrewMetrics`).
|
|
25
|
+
|
|
2
26
|
|
|
3
27
|
This Changelog format is based on [Keep a Changelog]
|
|
4
28
|
(https://keepachangelog.com/en/1.0.0/), and this project
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ This repo simplifies development of [AxLLM](https://axllm.dev) AI Agents by usin
|
|
|
10
10
|
- **Task Execution**: Plan and execute tasks using agents in the crew.
|
|
11
11
|
- **Streaming Support**: Stream agent responses in real-time for better user experience and faster feedback.
|
|
12
12
|
- **Model Context Protocol (MCP)**: Support for MCP to allow agents to use MCP servers.
|
|
13
|
+
- **Metrics and Cost Tracking**: Built-in per-agent and per-crew metrics, including token usage and estimated USD costs (5-decimal rounding).
|
|
13
14
|
|
|
14
15
|
## Getting Started
|
|
15
16
|
|
|
@@ -92,12 +93,11 @@ if (planner) {
|
|
|
92
93
|
// Sub-agent usage - when used by another agent (AI is ignored and agent's own config is used)
|
|
93
94
|
const subAgentResponse = await planner.forward(ai, { task: "Plan something" });
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
96
|
+
// Metrics (per-agent and per-crew)
|
|
97
|
+
const agentMetrics = (planner as any).getMetrics?.();
|
|
98
|
+
const crewMetrics = crew.getCrewMetrics();
|
|
99
|
+
console.log('Agent metrics:', agentMetrics);
|
|
100
|
+
console.log('Crew metrics:', crewMetrics);
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -514,7 +514,7 @@ For MCP servers that support streamable HTTP communication:
|
|
|
514
514
|
"mcpEndpoint": "http://localhost:3002/stream",
|
|
515
515
|
"options": {
|
|
516
516
|
"authorization": "Bearer ey.JhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..-1234567890.1234567890",
|
|
517
|
-
"headers": {
|
|
517
|
+
"headers": {
|
|
518
518
|
"X-Custom-Header": "custom-value"
|
|
519
519
|
}
|
|
520
520
|
}
|
|
@@ -667,76 +667,45 @@ For new streamable HTTP servers, use:
|
|
|
667
667
|
|
|
668
668
|
### Tracking Usage Costs
|
|
669
669
|
|
|
670
|
-
The package provides
|
|
670
|
+
The package provides metrics for monitoring API usage across individual agents and the entire crew. Estimated costs are accumulated precisely and rounded to 5 decimal places for reporting.
|
|
671
671
|
|
|
672
672
|
```javascript
|
|
673
673
|
// After running an agent's forward method
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// Get individual agent costs
|
|
677
|
-
const agentCost = Planner.getLastUsageCost();
|
|
678
|
-
console.log(agentCost);
|
|
679
|
-
/* Output example:
|
|
680
|
-
{
|
|
681
|
-
promptCost: "0.0003637500000",
|
|
682
|
-
completionCost: "0.0006100000000",
|
|
683
|
-
totalCost: "0.0009737500000",
|
|
684
|
-
tokenMetrics: {
|
|
685
|
-
promptTokens: 291,
|
|
686
|
-
completionTokens: 122,
|
|
687
|
-
totalTokens: 413
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
*/
|
|
674
|
+
await Planner.forward({ task: userQuery });
|
|
691
675
|
|
|
692
|
-
//
|
|
693
|
-
const
|
|
694
|
-
console.log(
|
|
695
|
-
/*
|
|
676
|
+
// Per-agent metrics
|
|
677
|
+
const agentMetrics = (Planner as any).getMetrics?.();
|
|
678
|
+
console.log(agentMetrics);
|
|
679
|
+
/* Example:
|
|
696
680
|
{
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
totalTokens: 413
|
|
704
|
-
}
|
|
681
|
+
provider: "anthropic",
|
|
682
|
+
model: "claude-3-haiku-20240307",
|
|
683
|
+
requests: { totalRequests, totalErrors, errorRate, totalStreamingRequests, durationMsSum, durationCount },
|
|
684
|
+
tokens: { promptTokens, completionTokens, totalTokens },
|
|
685
|
+
estimatedCostUSD: 0.00091, // rounded to 5 decimals
|
|
686
|
+
functions: { totalFunctionCalls, totalFunctionLatencyMs }
|
|
705
687
|
}
|
|
706
688
|
*/
|
|
707
689
|
|
|
708
|
-
//
|
|
709
|
-
const
|
|
710
|
-
console.log(
|
|
711
|
-
/*
|
|
690
|
+
// Crew metrics
|
|
691
|
+
const crewMetrics = crew.getCrewMetrics();
|
|
692
|
+
console.log(crewMetrics);
|
|
693
|
+
/* Example:
|
|
712
694
|
{
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
"Manager": { ... }
|
|
718
|
-
},
|
|
719
|
-
aggregatedMetrics: {
|
|
720
|
-
promptTokens: 850,
|
|
721
|
-
completionTokens: 324,
|
|
722
|
-
totalTokens: 1174,
|
|
723
|
-
promptCost: "0.0010625000000",
|
|
724
|
-
completionCost: "0.0014857500000"
|
|
725
|
-
}
|
|
695
|
+
requests: { ... },
|
|
696
|
+
tokens: { promptTokens, completionTokens, totalTokens },
|
|
697
|
+
estimatedCostUSD: 0.00255, // rounded to 5 decimals
|
|
698
|
+
functions: { ... }
|
|
726
699
|
}
|
|
727
700
|
*/
|
|
728
701
|
|
|
729
|
-
// Reset
|
|
702
|
+
// Reset tracked metrics
|
|
730
703
|
crew.resetCosts();
|
|
731
704
|
```
|
|
732
705
|
|
|
733
|
-
|
|
734
|
-
-
|
|
735
|
-
-
|
|
736
|
-
- Aggregated crew-wide metrics
|
|
737
|
-
- Token usage statistics
|
|
738
|
-
- Support for different pricing tiers per model
|
|
739
|
-
- Persistent cost tracking across multiple agent runs
|
|
706
|
+
Notes:
|
|
707
|
+
- Legacy cost APIs (`getLastUsageCost`, `getAccumulatedCosts`, `getAggregatedCosts`) are superseded by metrics methods.
|
|
708
|
+
- Estimated cost values are numbers rounded to 5 decimal places.
|
|
740
709
|
|
|
741
710
|
## Changelog
|
|
742
711
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { AxDefaultCostTracker } from '@ax-llm/ax';
|
|
1
2
|
import type { AxFunction } from '@ax-llm/ax';
|
|
2
|
-
import type { AgentConfig, CrewConfigInput, FunctionRegistryType, MCPTransportConfig, MCPStdioTransportConfig, MCPHTTPSSETransportConfig,
|
|
3
|
-
declare const AIConstructors: Record<string, any>;
|
|
4
|
-
export type Provider = keyof typeof AIConstructors;
|
|
3
|
+
import type { AgentConfig, CrewConfigInput, FunctionRegistryType, MCPTransportConfig, MCPStdioTransportConfig, MCPHTTPSSETransportConfig, MCPStreamableHTTPTransportConfig } from '../types.js';
|
|
5
4
|
export declare function isStdioTransport(config: MCPTransportConfig): config is MCPStdioTransportConfig;
|
|
6
5
|
export declare function isHTTPSSETransport(config: MCPTransportConfig): config is MCPHTTPSSETransportConfig;
|
|
7
|
-
export declare function isStreambleHTTPTransport(config: MCPTransportConfig): config is
|
|
6
|
+
export declare function isStreambleHTTPTransport(config: MCPTransportConfig): config is MCPStreamableHTTPTransportConfig;
|
|
8
7
|
/**
|
|
9
8
|
* Parses and returns the AxCrew config from either a JSON config file or a direct JSON object.
|
|
10
9
|
* @param {CrewConfigInput} input - Either a path to the agent config json file or a JSON object with crew configuration.
|
|
@@ -27,12 +26,13 @@ declare const parseCrewConfig: (input: CrewConfigInput) => {
|
|
|
27
26
|
* the API key is not found, or the provider key name is not specified in the configuration.
|
|
28
27
|
*/
|
|
29
28
|
declare const parseAgentConfig: (agentName: string, crewConfig: CrewConfigInput, functions: FunctionRegistryType, state: Record<string, any>) => Promise<{
|
|
30
|
-
ai:
|
|
29
|
+
ai: import("@ax-llm/ax").AxAI<string>;
|
|
31
30
|
name: string;
|
|
32
31
|
description: string;
|
|
33
|
-
signature: string | import("@ax-llm/ax").AxSignature
|
|
32
|
+
signature: string | import("@ax-llm/ax").AxSignature<Record<string, any>, Record<string, any>>;
|
|
34
33
|
functions: AxFunction[];
|
|
35
34
|
subAgentNames: string[];
|
|
36
35
|
examples: Record<string, any>[];
|
|
36
|
+
tracker: AxDefaultCostTracker;
|
|
37
37
|
}>;
|
|
38
38
|
export { parseAgentConfig, parseCrewConfig };
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
// Import
|
|
3
|
-
import {
|
|
4
|
-
//
|
|
5
|
-
import { AxMCPClient, AxMCPHTTPSSETransport, AxMCPStreambleHTTPTransport } from '@ax-llm/ax';
|
|
2
|
+
// Import Ax factory and MCP transports (as exported by current package)
|
|
3
|
+
import { ai, AxMCPClient, AxMCPHTTPSSETransport, AxMCPStreambleHTTPTransport, AxDefaultCostTracker } from '@ax-llm/ax';
|
|
4
|
+
// STDIO transport from tools package
|
|
6
5
|
import { AxMCPStdioTransport } from '@ax-llm/ax-tools';
|
|
7
6
|
import { PROVIDER_API_KEYS } from '../config/index.js';
|
|
8
|
-
//
|
|
9
|
-
const
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'ollama'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
|
|
7
|
+
// Canonical provider slugs supported by ai() factory
|
|
8
|
+
const PROVIDER_CANONICAL = new Set([
|
|
9
|
+
'openai',
|
|
10
|
+
'anthropic',
|
|
11
|
+
'google-gemini',
|
|
12
|
+
'mistral',
|
|
13
|
+
'groq',
|
|
14
|
+
'cohere',
|
|
15
|
+
'together',
|
|
16
|
+
'deepseek',
|
|
17
|
+
'ollama',
|
|
18
|
+
'huggingface',
|
|
19
|
+
'openrouter',
|
|
20
|
+
'azure-openai',
|
|
21
|
+
'reka',
|
|
22
|
+
'x-grok'
|
|
23
|
+
]);
|
|
24
|
+
// Provider type lives in src/types.ts
|
|
24
25
|
// Type guard to check if config is stdio transport
|
|
25
26
|
export function isStdioTransport(config) {
|
|
26
27
|
return 'command' in config;
|
|
@@ -173,11 +174,12 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state) => {
|
|
|
173
174
|
if (!agentConfigData) {
|
|
174
175
|
throw new Error(`AI agent with name ${agentName} is not configured`);
|
|
175
176
|
}
|
|
176
|
-
//
|
|
177
|
-
const
|
|
178
|
-
if (!
|
|
179
|
-
throw new Error(`AI provider ${agentConfigData.provider} is not supported.
|
|
177
|
+
// Enforce canonical provider slug
|
|
178
|
+
const lower = agentConfigData.provider.toLowerCase();
|
|
179
|
+
if (!PROVIDER_CANONICAL.has(lower)) {
|
|
180
|
+
throw new Error(`AI provider ${agentConfigData.provider} is not supported. Use one of: ${Array.from(PROVIDER_CANONICAL).join(', ')}`);
|
|
180
181
|
}
|
|
182
|
+
const provider = lower;
|
|
181
183
|
// If an API Key property is present, get the API key for the AI agent from the environment variables
|
|
182
184
|
let apiKey = '';
|
|
183
185
|
if (agentConfigData.providerKeyName) {
|
|
@@ -189,26 +191,30 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state) => {
|
|
|
189
191
|
else {
|
|
190
192
|
throw new Error(`Provider key name is missing in the agent configuration`);
|
|
191
193
|
}
|
|
192
|
-
// Create
|
|
193
|
-
const
|
|
194
|
+
// Create a cost tracker instance and pass to ai()
|
|
195
|
+
const costTracker = new AxDefaultCostTracker();
|
|
196
|
+
// Create an instance of the AI agent via factory
|
|
197
|
+
const aiArgs = {
|
|
198
|
+
name: provider,
|
|
194
199
|
apiKey,
|
|
195
200
|
config: agentConfigData.ai,
|
|
196
201
|
options: {
|
|
197
202
|
debug: agentConfigData.debug || false,
|
|
198
|
-
...agentConfigData.options
|
|
203
|
+
...agentConfigData.options,
|
|
204
|
+
// Attach default cost tracker so usage/costs are recorded by provider layer
|
|
205
|
+
trackers: [costTracker]
|
|
199
206
|
}
|
|
200
|
-
}
|
|
201
|
-
// If an apiURL is provided in the agent config, set it in the AI agent
|
|
207
|
+
};
|
|
202
208
|
if (agentConfigData.apiURL) {
|
|
203
209
|
try {
|
|
204
|
-
// Validate apiURL format
|
|
205
210
|
new URL(agentConfigData.apiURL);
|
|
206
|
-
|
|
211
|
+
aiArgs.apiURL = agentConfigData.apiURL;
|
|
207
212
|
}
|
|
208
213
|
catch (error) {
|
|
209
214
|
throw new Error(`Invalid apiURL provided: ${agentConfigData.apiURL}`);
|
|
210
215
|
}
|
|
211
216
|
}
|
|
217
|
+
const aiInstance = ai(aiArgs);
|
|
212
218
|
// If an mcpServers config is provided in the agent config, convert to functions
|
|
213
219
|
const mcpFunctions = await initializeMCPServers(agentConfigData);
|
|
214
220
|
// Prepare functions for the AI agent
|
|
@@ -229,13 +235,14 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state) => {
|
|
|
229
235
|
];
|
|
230
236
|
// Return AI instance and Agent parameters
|
|
231
237
|
return {
|
|
232
|
-
ai,
|
|
238
|
+
ai: aiInstance,
|
|
233
239
|
name: agentName,
|
|
234
240
|
description: agentConfigData.description,
|
|
235
241
|
signature: agentConfigData.signature,
|
|
236
242
|
functions: agentFunctions,
|
|
237
243
|
subAgentNames: agentConfigData.agents || [],
|
|
238
244
|
examples: agentConfigData.examples || [],
|
|
245
|
+
tracker: costTracker,
|
|
239
246
|
};
|
|
240
247
|
}
|
|
241
248
|
catch (error) {
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { AxAgent, AxAI } from "@ax-llm/ax";
|
|
2
2
|
import type { AxSignature, AxAgentic, AxFunction, AxProgramForwardOptions, AxProgramStreamingForwardOptions, AxGenStreamingOut } from "@ax-llm/ax";
|
|
3
3
|
import type { StateInstance, FunctionRegistryType, UsageCost, CrewConfigInput, MCPTransportConfig } from "../types.js";
|
|
4
|
-
import { StateFulAxAgentUsage } from "./agentUseCosts.js";
|
|
5
4
|
declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
6
5
|
state: StateInstance;
|
|
7
6
|
axai: any;
|
|
8
7
|
private agentName;
|
|
8
|
+
private costTracker?;
|
|
9
|
+
private lastRecordedCostUSD;
|
|
10
|
+
private isAxAIService;
|
|
11
|
+
private isAxAIInstance;
|
|
9
12
|
constructor(ai: AxAI, options: Readonly<{
|
|
10
13
|
name: string;
|
|
11
14
|
description: string;
|
|
@@ -15,12 +18,14 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
15
18
|
examples?: Array<Record<string, any>> | undefined;
|
|
16
19
|
mcpServers?: Record<string, MCPTransportConfig> | undefined;
|
|
17
20
|
}>, state: StateInstance);
|
|
18
|
-
forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions
|
|
19
|
-
forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions
|
|
20
|
-
streamingForward(values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions
|
|
21
|
-
streamingForward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions
|
|
21
|
+
forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
22
|
+
forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
23
|
+
streamingForward(values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
24
|
+
streamingForward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
22
25
|
getLastUsageCost(): UsageCost | null;
|
|
23
26
|
getAccumulatedCosts(): UsageCost | null;
|
|
27
|
+
getMetrics(): import("../metrics/types.js").MetricsSnapshot;
|
|
28
|
+
resetMetrics(): void;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* Represents a crew of agents with shared state functionality.
|
|
@@ -63,14 +68,12 @@ declare class AxCrew {
|
|
|
63
68
|
* Cleans up the crew by dereferencing agents and resetting the state.
|
|
64
69
|
*/
|
|
65
70
|
destroy(): void;
|
|
66
|
-
/**
|
|
67
|
-
* Gets aggregated costs for all agents in the crew
|
|
68
|
-
* @returns Aggregated cost information for all agents
|
|
69
|
-
*/
|
|
70
|
-
getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts>;
|
|
71
71
|
/**
|
|
72
72
|
* Resets all cost tracking for the crew
|
|
73
73
|
*/
|
|
74
74
|
resetCosts(): void;
|
|
75
|
+
getCrewMetrics(): import("../metrics/types.js").MetricsSnapshot;
|
|
76
|
+
resetCrewMetrics(): void;
|
|
75
77
|
}
|
|
76
78
|
export { AxCrew };
|
|
79
|
+
export type { StatefulAxAgent };
|
package/dist/agents/index.js
CHANGED
|
@@ -2,12 +2,20 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
2
2
|
import { AxAgent } from "@ax-llm/ax";
|
|
3
3
|
import { createState } from "../state/index.js";
|
|
4
4
|
import { parseCrewConfig, parseAgentConfig } from "./agentConfig.js";
|
|
5
|
-
import {
|
|
5
|
+
import { MetricsRegistry } from "../metrics/index.js";
|
|
6
6
|
// Extend the AxAgent class from ax-llm
|
|
7
7
|
class StatefulAxAgent extends AxAgent {
|
|
8
8
|
state;
|
|
9
9
|
axai;
|
|
10
10
|
agentName;
|
|
11
|
+
costTracker;
|
|
12
|
+
lastRecordedCostUSD = 0;
|
|
13
|
+
isAxAIService(obj) {
|
|
14
|
+
return !!obj && typeof obj.getName === 'function' && typeof obj.chat === 'function';
|
|
15
|
+
}
|
|
16
|
+
isAxAIInstance(obj) {
|
|
17
|
+
return !!obj && typeof obj === 'object' && ('defaults' in obj || 'modelInfo' in obj);
|
|
18
|
+
}
|
|
11
19
|
constructor(ai, options, state) {
|
|
12
20
|
const { examples, ...restOptions } = options;
|
|
13
21
|
const formattedOptions = {
|
|
@@ -26,9 +34,12 @@ class StatefulAxAgent extends AxAgent {
|
|
|
26
34
|
// Implementation
|
|
27
35
|
async forward(first, second, third) {
|
|
28
36
|
let result;
|
|
37
|
+
const start = performance.now();
|
|
38
|
+
const crewId = this.state?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
39
|
+
const labels = { crewId, agent: this.agentName };
|
|
29
40
|
// Track costs regardless of whether it's a direct or sub-agent call
|
|
30
41
|
// This ensures we capture multiple legitimate calls to the same agent
|
|
31
|
-
if (
|
|
42
|
+
if (this.isAxAIService(first)) {
|
|
32
43
|
// Sub-agent case (called with AI service)
|
|
33
44
|
result = await super.forward(this.axai, second, third);
|
|
34
45
|
}
|
|
@@ -36,17 +47,51 @@ class StatefulAxAgent extends AxAgent {
|
|
|
36
47
|
// Direct call case
|
|
37
48
|
result = await super.forward(this.axai, first, second);
|
|
38
49
|
}
|
|
39
|
-
// Track costs after the call
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
// Track metrics and costs after the call using built-in usage
|
|
51
|
+
const durationMs = performance.now() - start;
|
|
52
|
+
MetricsRegistry.recordRequest(labels, false, durationMs);
|
|
53
|
+
// Always record tokens from built-in usage array if present
|
|
54
|
+
const builtIn = this.getUsage?.();
|
|
55
|
+
if (Array.isArray(builtIn)) {
|
|
56
|
+
const totals = builtIn.reduce((acc, u) => {
|
|
57
|
+
const pt = u.tokens?.promptTokens ?? u.promptTokens ?? 0;
|
|
58
|
+
const ct = u.tokens?.completionTokens ?? u.completionTokens ?? 0;
|
|
59
|
+
acc.promptTokens += typeof pt === 'number' ? pt : 0;
|
|
60
|
+
acc.completionTokens += typeof ct === 'number' ? ct : 0;
|
|
61
|
+
// also aggregate per-model to feed Ax tracker
|
|
62
|
+
const model = u.model || this.axai?.getLastUsedChatModel?.() || this.axai?.defaults?.model;
|
|
63
|
+
if (model) {
|
|
64
|
+
acc.byModel[model] = (acc.byModel[model] || 0) + (pt + ct);
|
|
65
|
+
}
|
|
66
|
+
return acc;
|
|
67
|
+
}, { promptTokens: 0, completionTokens: 0, byModel: {} });
|
|
68
|
+
MetricsRegistry.recordTokens(labels, {
|
|
69
|
+
promptTokens: totals.promptTokens,
|
|
70
|
+
completionTokens: totals.completionTokens,
|
|
71
|
+
totalTokens: totals.promptTokens + totals.completionTokens,
|
|
72
|
+
});
|
|
73
|
+
// Feed Ax's cost tracker with token totals per model; Ax owns pricing
|
|
74
|
+
const costTracker = this.costTracker;
|
|
75
|
+
try {
|
|
76
|
+
for (const [m, count] of Object.entries(totals.byModel)) {
|
|
77
|
+
costTracker?.trackTokens?.(count, m);
|
|
78
|
+
}
|
|
79
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
80
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
81
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
43
85
|
}
|
|
44
86
|
return result;
|
|
45
87
|
}
|
|
46
88
|
// Implementation
|
|
47
89
|
streamingForward(first, second, third) {
|
|
90
|
+
const start = performance.now();
|
|
91
|
+
const crewId = this.state?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
92
|
+
const labels = { crewId, agent: this.agentName };
|
|
48
93
|
let streamingResult;
|
|
49
|
-
if (
|
|
94
|
+
if (this.isAxAIService(first)) {
|
|
50
95
|
streamingResult = super.streamingForward(this.axai, second, third);
|
|
51
96
|
}
|
|
52
97
|
else {
|
|
@@ -60,34 +105,64 @@ class StatefulAxAgent extends AxAgent {
|
|
|
60
105
|
}
|
|
61
106
|
}
|
|
62
107
|
finally {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
108
|
+
const durationMs = performance.now() - start;
|
|
109
|
+
MetricsRegistry.recordRequest(labels, true, durationMs);
|
|
110
|
+
// Record tokens from built-in usage array if present
|
|
111
|
+
const builtIn = this.getUsage?.();
|
|
112
|
+
if (Array.isArray(builtIn)) {
|
|
113
|
+
const totals = builtIn.reduce((acc, u) => {
|
|
114
|
+
const pt = u.tokens?.promptTokens ?? u.promptTokens ?? 0;
|
|
115
|
+
const ct = u.tokens?.completionTokens ?? u.completionTokens ?? 0;
|
|
116
|
+
acc.promptTokens += typeof pt === 'number' ? pt : 0;
|
|
117
|
+
acc.completionTokens += typeof ct === 'number' ? ct : 0;
|
|
118
|
+
const model = u.model || this.axai?.getLastUsedChatModel?.() || this.axai?.defaults?.model;
|
|
119
|
+
if (model) {
|
|
120
|
+
acc.byModel[model] = (acc.byModel[model] || 0) + (pt + ct);
|
|
121
|
+
}
|
|
122
|
+
return acc;
|
|
123
|
+
}, { promptTokens: 0, completionTokens: 0, byModel: {} });
|
|
124
|
+
MetricsRegistry.recordTokens(labels, {
|
|
125
|
+
promptTokens: totals.promptTokens,
|
|
126
|
+
completionTokens: totals.completionTokens,
|
|
127
|
+
totalTokens: totals.promptTokens + totals.completionTokens,
|
|
128
|
+
});
|
|
129
|
+
const costTracker = this.costTracker;
|
|
130
|
+
try {
|
|
131
|
+
for (const [m, count] of Object.entries(totals.byModel)) {
|
|
132
|
+
costTracker?.trackTokens?.(count, m);
|
|
133
|
+
}
|
|
134
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
135
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
136
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
}
|
|
141
|
+
// Record estimated cost (USD) via attached tracker if available
|
|
142
|
+
const costTracker = this.costTracker;
|
|
143
|
+
try {
|
|
144
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
145
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
146
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
147
|
+
}
|
|
66
148
|
}
|
|
149
|
+
catch { }
|
|
67
150
|
}
|
|
68
151
|
}).bind(this)();
|
|
69
152
|
return wrappedGenerator;
|
|
70
153
|
}
|
|
71
|
-
//
|
|
72
|
-
getLastUsageCost() {
|
|
73
|
-
const { modelUsage, modelInfo, defaults } = this.axai;
|
|
74
|
-
// Check if all required properties exist
|
|
75
|
-
if (!modelUsage?.promptTokens || !modelUsage?.completionTokens) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
if (!modelInfo || !defaults?.model) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
const currentModelInfo = modelInfo.find((m) => m.name === defaults.model);
|
|
82
|
-
if (!currentModelInfo?.promptTokenCostPer1M || !currentModelInfo?.completionTokenCostPer1M) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
return StateFulAxAgentUsage.calculateCost(modelUsage, currentModelInfo);
|
|
86
|
-
}
|
|
154
|
+
// Legacy cost API removed: rely on Ax trackers for cost reporting
|
|
155
|
+
getLastUsageCost() { return null; }
|
|
87
156
|
// Get the accumulated costs for all runs of this agent
|
|
88
|
-
getAccumulatedCosts() {
|
|
89
|
-
|
|
90
|
-
|
|
157
|
+
getAccumulatedCosts() { return null; }
|
|
158
|
+
// Metrics API for this agent
|
|
159
|
+
getMetrics() {
|
|
160
|
+
const crewId = this.state?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
161
|
+
return MetricsRegistry.snapshot({ crewId, agent: this.agentName });
|
|
162
|
+
}
|
|
163
|
+
resetMetrics() {
|
|
164
|
+
const crewId = this.state?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
165
|
+
MetricsRegistry.reset({ crewId, agent: this.agentName });
|
|
91
166
|
}
|
|
92
167
|
}
|
|
93
168
|
/**
|
|
@@ -121,6 +196,8 @@ class AxCrew {
|
|
|
121
196
|
this.crewId = crewId;
|
|
122
197
|
this.agents = new Map();
|
|
123
198
|
this.state = createState(crewId);
|
|
199
|
+
// Make crewId discoverable to metrics
|
|
200
|
+
this.state.set('crewId', crewId);
|
|
124
201
|
}
|
|
125
202
|
/**
|
|
126
203
|
* Factory function for creating an agent.
|
|
@@ -132,7 +209,7 @@ class AxCrew {
|
|
|
132
209
|
try {
|
|
133
210
|
const agentConfig = await parseAgentConfig(agentName, this.crewConfig, this.functionsRegistry, this.state);
|
|
134
211
|
// Destructure with type assertion
|
|
135
|
-
const { ai, name, description, signature, functions, subAgentNames, examples } = agentConfig;
|
|
212
|
+
const { ai, name, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
136
213
|
// Get subagents for the AI agent
|
|
137
214
|
const subAgents = subAgentNames.map((subAgentName) => {
|
|
138
215
|
if (!this.agents?.get(subAgentName)) {
|
|
@@ -140,15 +217,39 @@ class AxCrew {
|
|
|
140
217
|
}
|
|
141
218
|
return this.agents?.get(subAgentName);
|
|
142
219
|
});
|
|
220
|
+
// Dedupe sub-agents by name (defensive)
|
|
221
|
+
const subAgentSet = new Map();
|
|
222
|
+
for (const sa of subAgents.filter((agent) => agent !== undefined)) {
|
|
223
|
+
const n = sa?.agentName ?? sa?.name ?? '';
|
|
224
|
+
if (!subAgentSet.has(n))
|
|
225
|
+
subAgentSet.set(n, sa);
|
|
226
|
+
}
|
|
227
|
+
const uniqueSubAgents = Array.from(subAgentSet.values());
|
|
228
|
+
// Dedupe functions by name and avoid collision with sub-agent names
|
|
229
|
+
const subAgentNameSet = new Set(uniqueSubAgents.map((sa) => sa?.agentName ?? sa?.name).filter(Boolean));
|
|
230
|
+
const uniqueFunctions = [];
|
|
231
|
+
const seenFn = new Set();
|
|
232
|
+
for (const fn of functions.filter((fn) => fn !== undefined)) {
|
|
233
|
+
const fnName = fn.name;
|
|
234
|
+
if (subAgentNameSet.has(fnName)) {
|
|
235
|
+
// Skip function that collides with a sub-agent name
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!seenFn.has(fnName)) {
|
|
239
|
+
seenFn.add(fnName);
|
|
240
|
+
uniqueFunctions.push(fn);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
143
243
|
// Create an instance of StatefulAxAgent
|
|
144
244
|
const agent = new StatefulAxAgent(ai, {
|
|
145
245
|
name,
|
|
146
246
|
description,
|
|
147
247
|
signature,
|
|
148
|
-
functions:
|
|
149
|
-
agents:
|
|
248
|
+
functions: uniqueFunctions,
|
|
249
|
+
agents: uniqueSubAgents,
|
|
150
250
|
examples,
|
|
151
251
|
}, this.state);
|
|
252
|
+
agent.costTracker = tracker;
|
|
152
253
|
return agent;
|
|
153
254
|
}
|
|
154
255
|
catch (error) {
|
|
@@ -273,18 +374,31 @@ class AxCrew {
|
|
|
273
374
|
this.agents = null;
|
|
274
375
|
this.state.reset();
|
|
275
376
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Gets aggregated costs for all agents in the crew
|
|
278
|
-
* @returns Aggregated cost information for all agents
|
|
279
|
-
*/
|
|
280
|
-
getAggregatedCosts() {
|
|
281
|
-
return StateFulAxAgentUsage.getAggregatedCosts(this.state);
|
|
282
|
-
}
|
|
283
377
|
/**
|
|
284
378
|
* Resets all cost tracking for the crew
|
|
285
379
|
*/
|
|
286
380
|
resetCosts() {
|
|
287
|
-
|
|
381
|
+
// Reset AxAgent built-in usage and our metrics registry
|
|
382
|
+
if (this.agents) {
|
|
383
|
+
for (const [, agent] of this.agents) {
|
|
384
|
+
try {
|
|
385
|
+
agent.resetUsage?.();
|
|
386
|
+
}
|
|
387
|
+
catch { }
|
|
388
|
+
try {
|
|
389
|
+
agent.resetMetrics?.();
|
|
390
|
+
}
|
|
391
|
+
catch { }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
395
|
+
}
|
|
396
|
+
// Metrics API
|
|
397
|
+
getCrewMetrics() {
|
|
398
|
+
return MetricsRegistry.snapshotCrew(this.crewId);
|
|
399
|
+
}
|
|
400
|
+
resetCrewMetrics() {
|
|
401
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
288
402
|
}
|
|
289
403
|
}
|
|
290
404
|
export { AxCrew };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { AxCrew } from './agents/index.js';
|
|
|
2
2
|
import { AxCrewFunctions } from './functions/index.js';
|
|
3
3
|
import type { CrewConfigInput, AgentConfig } from './types.js';
|
|
4
4
|
import type { UsageCost, AggregatedMetrics, AggregatedCosts, StateInstance, FunctionRegistryType } from './types.js';
|
|
5
|
+
export * from './metrics/index.js';
|
|
6
|
+
export { MetricsRegistry } from './metrics/index.js';
|
|
5
7
|
/**
|
|
6
8
|
* The configuration for an AxCrew.
|
|
7
9
|
*
|