@amitdeshmukh/ax-crew 3.11.3 → 4.1.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 +34 -0
- package/README.md +83 -82
- package/dist/agents/agentConfig.d.ts +7 -6
- package/dist/agents/agentConfig.js +40 -32
- package/dist/agents/index.d.ts +14 -10
- package/dist/agents/index.js +155 -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 +16 -6
- package/examples/basic-researcher-writer.ts +18 -13
- package/examples/mcp-agent.ts +14 -9
- package/examples/solve-math-problem.ts +7 -5
- package/examples/streaming.ts +7 -6
- package/package.json +5 -3
- package/src/agents/agentConfig.ts +45 -38
- package/src/agents/index.ts +171 -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 +33 -7
- package/tests/AxCrew.test.ts +22 -18
package/examples/mcp-agent.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AxCrew } from "../dist/index.js";
|
|
1
|
+
import { AxCrew, AxCrewConfig } from "../dist/index.js";
|
|
2
2
|
|
|
3
3
|
import dotenv from "dotenv";
|
|
4
4
|
dotenv.config();
|
|
@@ -9,15 +9,18 @@ const config = {
|
|
|
9
9
|
{
|
|
10
10
|
name: "MapsAgent",
|
|
11
11
|
description: "A specialized agent with access to Google Maps APIs that can: geocode addresses to coordinates and vice versa, search for and get details about places, calculate travel distances and times between multiple locations, provide elevation data, and generate navigation directions between points.",
|
|
12
|
+
prompt: "You are a precise geospatial assistant with expert knowledge of Google Maps APIs. Answer user queries by combining place search, geocoding, distance matrix, elevation, and directions. Provide concise, actionable answers, include travel mode assumptions, and cite uncertainties. When location is ambiguous, ask a brief clarifying question before proceeding.",
|
|
12
13
|
signature: 'userQuery:string "a question to be answered" -> answer:string "the answer to the question"',
|
|
13
14
|
provider: "anthropic",
|
|
14
15
|
providerKeyName: "ANTHROPIC_API_KEY",
|
|
15
16
|
ai: {
|
|
16
17
|
model: "claude-3-5-sonnet-latest",
|
|
17
18
|
temperature: 0,
|
|
19
|
+
maxTokens: 1000,
|
|
20
|
+
stream: true
|
|
18
21
|
},
|
|
19
22
|
options: {
|
|
20
|
-
debug: true
|
|
23
|
+
debug: true
|
|
21
24
|
},
|
|
22
25
|
"mcpServers": {
|
|
23
26
|
"google-maps": {
|
|
@@ -35,6 +38,7 @@ const config = {
|
|
|
35
38
|
{
|
|
36
39
|
name: "ManagerAgent",
|
|
37
40
|
description: "Completes a user specified task",
|
|
41
|
+
prompt: "You are a manager agent that orchestrates tools and sub-agents. Read the user's objective, optionally produce a short plan, then call the MapsAgent when geospatial knowledge is needed. Keep answers direct and avoid extraneous commentary.",
|
|
38
42
|
signature:
|
|
39
43
|
'question:string "a question to be answered" -> answer:string "the answer to the question"',
|
|
40
44
|
provider: "openai",
|
|
@@ -43,6 +47,7 @@ const config = {
|
|
|
43
47
|
model: "gpt-4o-mini",
|
|
44
48
|
maxTokens: 1000,
|
|
45
49
|
temperature: 0,
|
|
50
|
+
stream: true
|
|
46
51
|
},
|
|
47
52
|
options: {
|
|
48
53
|
debug: true,
|
|
@@ -59,17 +64,17 @@ const config = {
|
|
|
59
64
|
ai: {
|
|
60
65
|
model: "gemini-1.5-pro",
|
|
61
66
|
temperature: 0,
|
|
67
|
+
stream: true
|
|
62
68
|
},
|
|
63
69
|
options: {
|
|
64
70
|
debug: false,
|
|
65
|
-
codeExecution: true,
|
|
66
71
|
},
|
|
67
72
|
},
|
|
68
73
|
],
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
// Create a new instance of AxCrew with the config
|
|
72
|
-
const crew = new AxCrew(config);
|
|
77
|
+
const crew = new AxCrew(config as AxCrewConfig);
|
|
73
78
|
|
|
74
79
|
// Add the agents to the crew
|
|
75
80
|
await crew.addAllAgents();
|
|
@@ -89,11 +94,11 @@ const main = async (): Promise<void> => {
|
|
|
89
94
|
|
|
90
95
|
console.log(`\nAnswer: ${JSON.stringify(managerResponse?.answer, null, 2)}`);
|
|
91
96
|
|
|
92
|
-
// Print
|
|
93
|
-
console.log("\
|
|
94
|
-
console.log("Manager Agent
|
|
95
|
-
console.log("Maps Agent
|
|
96
|
-
console.log("
|
|
97
|
+
// Print metrics
|
|
98
|
+
console.log("\nMetrics:\n+++++++++++++++++++++++++++++++++");
|
|
99
|
+
console.log("Manager Agent Metrics:", JSON.stringify((managerAgent as any)?.getMetrics?.(), null, 2));
|
|
100
|
+
console.log("Maps Agent Metrics:", JSON.stringify((mapsAgent as any)?.getMetrics?.(), null, 2));
|
|
101
|
+
console.log("Crew Metrics:", JSON.stringify((crew as any)?.getCrewMetrics?.(), null, 2));
|
|
97
102
|
};
|
|
98
103
|
|
|
99
104
|
main()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AxCrew } from "../dist/index.js";
|
|
2
|
+
import type { AxCrewConfig } from "../src/index.js";
|
|
2
3
|
|
|
3
4
|
// Define the crew configuration
|
|
4
|
-
const config = {
|
|
5
|
+
const config: AxCrewConfig = {
|
|
5
6
|
crew: [
|
|
6
7
|
{
|
|
7
8
|
name: "MathAgent",
|
|
@@ -62,10 +63,11 @@ const main = async (): Promise<void> => {
|
|
|
62
63
|
|
|
63
64
|
console.log(`\nAnswer: ${JSON.stringify(managerResponse.answer, null, 2)}`);
|
|
64
65
|
|
|
65
|
-
// Print
|
|
66
|
-
console.log("\
|
|
67
|
-
console.log("Manager Agent:", JSON.stringify(managerAgent.
|
|
68
|
-
console.log("
|
|
66
|
+
// Print metrics
|
|
67
|
+
console.log("\nMetrics:\n+++++++++++++++++++++++++++++++++");
|
|
68
|
+
console.log("Manager Agent Metrics:", JSON.stringify((managerAgent as any).getMetrics?.(), null, 2));
|
|
69
|
+
console.log("Math Agent Metrics:", JSON.stringify((mathAgent as any).getMetrics?.(), null, 2));
|
|
70
|
+
console.log("Crew Metrics:", JSON.stringify((crew as any).getCrewMetrics?.(), null, 2));
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
|
package/examples/streaming.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AxCrew } from "../dist/index.js";
|
|
2
|
+
import type { AxCrewConfig } from "../src/index.js";
|
|
2
3
|
|
|
3
4
|
import dotenv from "dotenv";
|
|
4
5
|
dotenv.config();
|
|
5
6
|
|
|
6
7
|
// Define the crew configuration
|
|
7
|
-
const config = {
|
|
8
|
+
const config: AxCrewConfig = {
|
|
8
9
|
crew: [
|
|
9
10
|
{
|
|
10
11
|
name: "ManagerAgent",
|
|
@@ -75,11 +76,11 @@ const main = async (): Promise<void> => {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
// Print
|
|
79
|
-
console.log("\
|
|
80
|
-
console.log("Manager Agent
|
|
81
|
-
console.log("Math Agent
|
|
82
|
-
console.log("
|
|
79
|
+
// Print metrics
|
|
80
|
+
console.log("\nMetrics:\n+++++++++++++++++++++++++++++++++");
|
|
81
|
+
console.log("Manager Agent Metrics:", JSON.stringify((managerAgent as any)?.getMetrics?.(), null, 2));
|
|
82
|
+
console.log("Math Agent Metrics:", JSON.stringify((mathAgent as any)?.getMetrics?.(), null, 2));
|
|
83
|
+
console.log("Crew Metrics:", JSON.stringify((crew as any)?.getCrewMetrics?.(), null, 2));
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
main()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@amitdeshmukh/ax-crew",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.1.1",
|
|
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",
|
|
@@ -17,17 +17,19 @@
|
|
|
17
17
|
"test:ui": "vitest --ui"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"big.js": "^7.0.1",
|
|
20
21
|
"decimal.js": "^10.5.0",
|
|
21
22
|
"dotenv": "^16.4.5",
|
|
22
23
|
"upgrade": "^1.1.0",
|
|
23
24
|
"uuid": "^10.0.0"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
|
-
"@ax-llm/ax": "
|
|
27
|
-
"@ax-llm/ax-tools": "
|
|
27
|
+
"@ax-llm/ax": "14.0.16",
|
|
28
|
+
"@ax-llm/ax-tools": "14.0.16"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@testing-library/jest-dom": "^6.6.3",
|
|
32
|
+
"@types/big.js": "^6.2.2",
|
|
31
33
|
"@types/node": "^20.14.9",
|
|
32
34
|
"@types/uuid": "^10.0.0",
|
|
33
35
|
"@vitest/coverage-v8": "^3.0.9",
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
// Import
|
|
3
|
-
import {
|
|
4
|
-
// Import Ax types
|
|
2
|
+
// Import Ax factory and MCP transports (as exported by current package)
|
|
3
|
+
import { ai, AxMCPClient, AxMCPHTTPSSETransport, AxMCPStreambleHTTPTransport, AxDefaultCostTracker } from '@ax-llm/ax'
|
|
5
4
|
import type { AxFunction } from '@ax-llm/ax';
|
|
6
|
-
//
|
|
7
|
-
import { AxMCPClient, AxMCPHTTPSSETransport, AxMCPStreambleHTTPTransport } from '@ax-llm/ax'
|
|
5
|
+
// STDIO transport from tools package
|
|
8
6
|
import { AxMCPStdioTransport } from '@ax-llm/ax-tools'
|
|
9
7
|
import { PROVIDER_API_KEYS } from '../config/index.js';
|
|
10
8
|
import type {
|
|
@@ -14,28 +12,29 @@ import type {
|
|
|
14
12
|
MCPTransportConfig,
|
|
15
13
|
MCPStdioTransportConfig,
|
|
16
14
|
MCPHTTPSSETransportConfig,
|
|
17
|
-
|
|
15
|
+
MCPStreamableHTTPTransportConfig
|
|
18
16
|
} from '../types.js';
|
|
17
|
+
import type { Provider } from '../types.js';
|
|
19
18
|
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'ollama'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
|
|
19
|
+
// Canonical provider slugs supported by ai() factory
|
|
20
|
+
const PROVIDER_CANONICAL = new Set([
|
|
21
|
+
'openai',
|
|
22
|
+
'anthropic',
|
|
23
|
+
'google-gemini',
|
|
24
|
+
'mistral',
|
|
25
|
+
'groq',
|
|
26
|
+
'cohere',
|
|
27
|
+
'together',
|
|
28
|
+
'deepseek',
|
|
29
|
+
'ollama',
|
|
30
|
+
'huggingface',
|
|
31
|
+
'openrouter',
|
|
32
|
+
'azure-openai',
|
|
33
|
+
'reka',
|
|
34
|
+
'x-grok'
|
|
35
|
+
]);
|
|
36
36
|
|
|
37
|
-
// Provider type
|
|
38
|
-
export type Provider = keyof typeof AIConstructors;
|
|
37
|
+
// Provider type lives in src/types.ts
|
|
39
38
|
|
|
40
39
|
// Type guard to check if config is stdio transport
|
|
41
40
|
export function isStdioTransport(config: MCPTransportConfig): config is MCPStdioTransportConfig {
|
|
@@ -48,7 +47,7 @@ export function isHTTPSSETransport(config: MCPTransportConfig): config is MCPHTT
|
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// Type guard to check if config is streamable HTTP transport
|
|
51
|
-
export function isStreambleHTTPTransport(config: MCPTransportConfig): config is
|
|
50
|
+
export function isStreambleHTTPTransport(config: MCPTransportConfig): config is MCPStreamableHTTPTransportConfig {
|
|
52
51
|
return 'mcpEndpoint' in config;
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -205,11 +204,12 @@ const parseAgentConfig = async (
|
|
|
205
204
|
throw new Error(`AI agent with name ${agentName} is not configured`);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
//
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
211
|
-
throw new Error(`AI provider ${agentConfigData.provider} is not supported.
|
|
207
|
+
// Enforce canonical provider slug
|
|
208
|
+
const lower = agentConfigData.provider.toLowerCase();
|
|
209
|
+
if (!PROVIDER_CANONICAL.has(lower)) {
|
|
210
|
+
throw new Error(`AI provider ${agentConfigData.provider} is not supported. Use one of: ${Array.from(PROVIDER_CANONICAL).join(', ')}`);
|
|
212
211
|
}
|
|
212
|
+
const provider = lower as Provider;
|
|
213
213
|
|
|
214
214
|
// If an API Key property is present, get the API key for the AI agent from the environment variables
|
|
215
215
|
let apiKey = '';
|
|
@@ -223,25 +223,30 @@ const parseAgentConfig = async (
|
|
|
223
223
|
throw new Error(`Provider key name is missing in the agent configuration`);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
// Create
|
|
227
|
-
const
|
|
226
|
+
// Create a cost tracker instance and pass to ai()
|
|
227
|
+
const costTracker = new AxDefaultCostTracker(((agentConfigData as any).options?.costTracking) || undefined);
|
|
228
|
+
|
|
229
|
+
// Create an instance of the AI agent via factory
|
|
230
|
+
const aiArgs: any = {
|
|
231
|
+
name: provider,
|
|
228
232
|
apiKey,
|
|
229
233
|
config: agentConfigData.ai,
|
|
230
234
|
options: {
|
|
231
235
|
debug: agentConfigData.debug || false,
|
|
232
|
-
...agentConfigData.options
|
|
236
|
+
...agentConfigData.options,
|
|
237
|
+
// Attach default cost tracker so usage/costs are recorded by provider layer
|
|
238
|
+
trackers: [costTracker]
|
|
233
239
|
}
|
|
234
|
-
}
|
|
235
|
-
// If an apiURL is provided in the agent config, set it in the AI agent
|
|
240
|
+
};
|
|
236
241
|
if (agentConfigData.apiURL) {
|
|
237
242
|
try {
|
|
238
|
-
// Validate apiURL format
|
|
239
243
|
new URL(agentConfigData.apiURL);
|
|
240
|
-
|
|
244
|
+
aiArgs.apiURL = agentConfigData.apiURL;
|
|
241
245
|
} catch (error) {
|
|
242
246
|
throw new Error(`Invalid apiURL provided: ${agentConfigData.apiURL}`);
|
|
243
|
-
}
|
|
247
|
+
}
|
|
244
248
|
}
|
|
249
|
+
const aiInstance = ai(aiArgs);
|
|
245
250
|
|
|
246
251
|
// If an mcpServers config is provided in the agent config, convert to functions
|
|
247
252
|
const mcpFunctions = await initializeMCPServers(agentConfigData);
|
|
@@ -265,13 +270,15 @@ const parseAgentConfig = async (
|
|
|
265
270
|
|
|
266
271
|
// Return AI instance and Agent parameters
|
|
267
272
|
return {
|
|
268
|
-
ai,
|
|
273
|
+
ai: aiInstance,
|
|
269
274
|
name: agentName,
|
|
270
275
|
description: agentConfigData.description,
|
|
276
|
+
definition: (agentConfigData as any).definition ?? (agentConfigData as any).prompt,
|
|
271
277
|
signature: agentConfigData.signature,
|
|
272
278
|
functions: agentFunctions,
|
|
273
279
|
subAgentNames: agentConfigData.agents || [],
|
|
274
280
|
examples: agentConfigData.examples || [],
|
|
281
|
+
tracker: costTracker,
|
|
275
282
|
};
|
|
276
283
|
} catch (error) {
|
|
277
284
|
if (error instanceof Error) {
|
package/src/agents/index.ts
CHANGED
|
@@ -20,13 +20,14 @@ import type {
|
|
|
20
20
|
|
|
21
21
|
import { createState } from "../state/index.js";
|
|
22
22
|
import { parseCrewConfig, parseAgentConfig } from "./agentConfig.js";
|
|
23
|
-
import {
|
|
23
|
+
import { MetricsRegistry } from "../metrics/index.js";
|
|
24
24
|
|
|
25
25
|
// Define the interface for the agent configuration
|
|
26
26
|
interface ParsedAgentConfig {
|
|
27
27
|
ai: AxAI;
|
|
28
28
|
name: string;
|
|
29
29
|
description: string;
|
|
30
|
+
definition?: string;
|
|
30
31
|
signature: string | AxSignature;
|
|
31
32
|
functions: (
|
|
32
33
|
| AxFunction
|
|
@@ -36,6 +37,7 @@ interface ParsedAgentConfig {
|
|
|
36
37
|
mcpServers?: Record<string, MCPTransportConfig>;
|
|
37
38
|
subAgentNames: string[];
|
|
38
39
|
examples?: Array<Record<string, any>>;
|
|
40
|
+
tracker?: any;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Extend the AxAgent class from ax-llm
|
|
@@ -43,12 +45,21 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
43
45
|
state: StateInstance;
|
|
44
46
|
axai: any;
|
|
45
47
|
private agentName: string;
|
|
48
|
+
private costTracker?: any;
|
|
49
|
+
private lastRecordedCostUSD: number = 0;
|
|
50
|
+
private isAxAIService(obj: any): obj is AxAI {
|
|
51
|
+
return !!obj && typeof obj.getName === 'function' && typeof obj.chat === 'function';
|
|
52
|
+
}
|
|
53
|
+
private isAxAIInstance(obj: any): obj is AxAI {
|
|
54
|
+
return !!obj && typeof obj === 'object' && ('defaults' in obj || 'modelInfo' in obj);
|
|
55
|
+
}
|
|
46
56
|
|
|
47
57
|
constructor(
|
|
48
58
|
ai: AxAI,
|
|
49
59
|
options: Readonly<{
|
|
50
60
|
name: string;
|
|
51
61
|
description: string;
|
|
62
|
+
definition?: string;
|
|
52
63
|
signature: string | AxSignature;
|
|
53
64
|
agents?: AxAgentic<any, any>[] | undefined;
|
|
54
65
|
functions?: (AxFunction | (() => AxFunction))[] | undefined;
|
|
@@ -76,52 +87,92 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
// Function overloads for forward method
|
|
79
|
-
async forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions
|
|
80
|
-
async forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions
|
|
90
|
+
async forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
91
|
+
async forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
81
92
|
|
|
82
93
|
// Implementation
|
|
83
94
|
async forward(
|
|
84
95
|
first: Record<string, any> | AxAI,
|
|
85
|
-
second?: Record<string, any> | Readonly<AxProgramForwardOptions
|
|
86
|
-
third?: Readonly<AxProgramForwardOptions
|
|
96
|
+
second?: Record<string, any> | Readonly<AxProgramForwardOptions<any>>,
|
|
97
|
+
third?: Readonly<AxProgramForwardOptions<any>>
|
|
87
98
|
): Promise<Record<string, any>> {
|
|
88
99
|
let result;
|
|
89
100
|
|
|
101
|
+
const start = performance.now();
|
|
102
|
+
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
103
|
+
const labels = { crewId, agent: this.agentName } as any;
|
|
104
|
+
|
|
90
105
|
// Track costs regardless of whether it's a direct or sub-agent call
|
|
91
106
|
// This ensures we capture multiple legitimate calls to the same agent
|
|
92
|
-
if (
|
|
107
|
+
if (this.isAxAIService(first)) {
|
|
93
108
|
// Sub-agent case (called with AI service)
|
|
94
109
|
result = await super.forward(this.axai, second as Record<string, any>, third);
|
|
95
110
|
} else {
|
|
96
111
|
// Direct call case
|
|
97
|
-
result = await super.forward(this.axai, first, second as Readonly<AxProgramForwardOptions
|
|
112
|
+
result = await super.forward(this.axai, first, second as Readonly<AxProgramForwardOptions<any>>);
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
// Track costs after the call
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
// Track metrics and costs after the call using built-in usage
|
|
116
|
+
const durationMs = performance.now() - start;
|
|
117
|
+
MetricsRegistry.recordRequest(labels, false, durationMs);
|
|
118
|
+
// Always record tokens from built-in usage array if present
|
|
119
|
+
const builtIn = (this as any).getUsage?.();
|
|
120
|
+
if (Array.isArray(builtIn)) {
|
|
121
|
+
const totals = builtIn.reduce(
|
|
122
|
+
(acc: any, u: any) => {
|
|
123
|
+
const pt = u.tokens?.promptTokens ?? u.promptTokens ?? 0;
|
|
124
|
+
const ct = u.tokens?.completionTokens ?? u.completionTokens ?? 0;
|
|
125
|
+
acc.promptTokens += typeof pt === 'number' ? pt : 0;
|
|
126
|
+
acc.completionTokens += typeof ct === 'number' ? ct : 0;
|
|
127
|
+
// also aggregate per-model to feed Ax tracker
|
|
128
|
+
const model = u.model || (this.axai as any)?.getLastUsedChatModel?.() || (this.axai as any)?.defaults?.model;
|
|
129
|
+
if (model) {
|
|
130
|
+
acc.byModel[model] = (acc.byModel[model] || 0) + (pt + ct);
|
|
131
|
+
}
|
|
132
|
+
return acc;
|
|
133
|
+
},
|
|
134
|
+
{ promptTokens: 0, completionTokens: 0, byModel: {} as Record<string, number> }
|
|
135
|
+
);
|
|
136
|
+
MetricsRegistry.recordTokens(labels, {
|
|
137
|
+
promptTokens: totals.promptTokens,
|
|
138
|
+
completionTokens: totals.completionTokens,
|
|
139
|
+
totalTokens: totals.promptTokens + totals.completionTokens,
|
|
140
|
+
});
|
|
141
|
+
// Feed Ax's cost tracker with token totals per model; Ax owns pricing
|
|
142
|
+
const costTracker = (this as any).costTracker;
|
|
143
|
+
try {
|
|
144
|
+
for (const [m, count] of Object.entries(totals.byModel)) {
|
|
145
|
+
costTracker?.trackTokens?.(count, m);
|
|
146
|
+
}
|
|
147
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
148
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
149
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
150
|
+
}
|
|
151
|
+
} catch {}
|
|
104
152
|
}
|
|
105
153
|
|
|
106
154
|
return result;
|
|
107
155
|
}
|
|
108
156
|
|
|
109
157
|
// Add streaming forward method overloads
|
|
110
|
-
streamingForward(values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions
|
|
111
|
-
streamingForward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions
|
|
158
|
+
streamingForward(values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
159
|
+
streamingForward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
112
160
|
|
|
113
161
|
// Implementation
|
|
114
162
|
streamingForward(
|
|
115
163
|
first: Record<string, any> | AxAI,
|
|
116
|
-
second?: Record<string, any> | Readonly<AxProgramStreamingForwardOptions
|
|
117
|
-
third?: Readonly<AxProgramStreamingForwardOptions
|
|
164
|
+
second?: Record<string, any> | Readonly<AxProgramStreamingForwardOptions<any>>,
|
|
165
|
+
third?: Readonly<AxProgramStreamingForwardOptions<any>>
|
|
118
166
|
): AxGenStreamingOut<any> {
|
|
167
|
+
const start = performance.now();
|
|
168
|
+
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
169
|
+
const labels = { crewId, agent: this.agentName } as any;
|
|
119
170
|
let streamingResult: AxGenStreamingOut<any>;
|
|
120
171
|
|
|
121
|
-
if (
|
|
172
|
+
if (this.isAxAIService(first)) {
|
|
122
173
|
streamingResult = super.streamingForward(this.axai, second as Record<string, any>, third);
|
|
123
174
|
} else {
|
|
124
|
-
streamingResult = super.streamingForward(this.axai, first, second as Readonly<AxProgramStreamingForwardOptions
|
|
175
|
+
streamingResult = super.streamingForward(this.axai, first, second as Readonly<AxProgramStreamingForwardOptions<any>>);
|
|
125
176
|
}
|
|
126
177
|
|
|
127
178
|
// Create a new async generator that tracks costs after completion
|
|
@@ -131,43 +182,71 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
131
182
|
yield chunk;
|
|
132
183
|
}
|
|
133
184
|
} finally {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
185
|
+
const durationMs = performance.now() - start;
|
|
186
|
+
MetricsRegistry.recordRequest(labels, true, durationMs);
|
|
187
|
+
// Record tokens from built-in usage array if present
|
|
188
|
+
const builtIn = (this as any).getUsage?.();
|
|
189
|
+
if (Array.isArray(builtIn)) {
|
|
190
|
+
const totals = builtIn.reduce(
|
|
191
|
+
(acc: any, u: any) => {
|
|
192
|
+
const pt = u.tokens?.promptTokens ?? u.promptTokens ?? 0;
|
|
193
|
+
const ct = u.tokens?.completionTokens ?? u.completionTokens ?? 0;
|
|
194
|
+
acc.promptTokens += typeof pt === 'number' ? pt : 0;
|
|
195
|
+
acc.completionTokens += typeof ct === 'number' ? ct : 0;
|
|
196
|
+
const model = u.model || (this.axai as any)?.getLastUsedChatModel?.() || (this.axai as any)?.defaults?.model;
|
|
197
|
+
if (model) {
|
|
198
|
+
acc.byModel[model] = (acc.byModel[model] || 0) + (pt + ct);
|
|
199
|
+
}
|
|
200
|
+
return acc;
|
|
201
|
+
},
|
|
202
|
+
{ promptTokens: 0, completionTokens: 0, byModel: {} as Record<string, number> }
|
|
203
|
+
);
|
|
204
|
+
MetricsRegistry.recordTokens(labels, {
|
|
205
|
+
promptTokens: totals.promptTokens,
|
|
206
|
+
completionTokens: totals.completionTokens,
|
|
207
|
+
totalTokens: totals.promptTokens + totals.completionTokens,
|
|
208
|
+
});
|
|
209
|
+
const costTracker = (this as any).costTracker;
|
|
210
|
+
try {
|
|
211
|
+
for (const [m, count] of Object.entries(totals.byModel)) {
|
|
212
|
+
costTracker?.trackTokens?.(count, m);
|
|
213
|
+
}
|
|
214
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
215
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
216
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
217
|
+
}
|
|
218
|
+
} catch {}
|
|
137
219
|
}
|
|
220
|
+
// Record estimated cost (USD) via attached tracker if available
|
|
221
|
+
const costTracker = (this as any).costTracker;
|
|
222
|
+
try {
|
|
223
|
+
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
224
|
+
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
225
|
+
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
226
|
+
}
|
|
227
|
+
} catch {}
|
|
138
228
|
}
|
|
139
229
|
}).bind(this)();
|
|
140
230
|
|
|
141
231
|
return wrappedGenerator as AxGenStreamingOut<any>;
|
|
142
232
|
}
|
|
143
233
|
|
|
144
|
-
//
|
|
145
|
-
getLastUsageCost(): UsageCost | null {
|
|
146
|
-
const { modelUsage, modelInfo, defaults } = this.axai;
|
|
147
|
-
|
|
148
|
-
// Check if all required properties exist
|
|
149
|
-
if (!modelUsage?.promptTokens || !modelUsage?.completionTokens) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
234
|
+
// Legacy cost API removed: rely on Ax trackers for cost reporting
|
|
235
|
+
getLastUsageCost(): UsageCost | null { return null; }
|
|
152
236
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const currentModelInfo = modelInfo.find((m: { name: string }) => m.name === defaults.model);
|
|
158
|
-
|
|
159
|
-
if (!currentModelInfo?.promptTokenCostPer1M || !currentModelInfo?.completionTokenCostPer1M) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
237
|
+
// Get the accumulated costs for all runs of this agent
|
|
238
|
+
getAccumulatedCosts(): UsageCost | null { return null; }
|
|
162
239
|
|
|
163
|
-
|
|
240
|
+
// Metrics API for this agent
|
|
241
|
+
getMetrics() {
|
|
242
|
+
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
243
|
+
return MetricsRegistry.snapshot({ crewId, agent: this.agentName } as any);
|
|
164
244
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const stateKey = `${StateFulAxAgentUsage.STATE_KEY_PREFIX}${this.agentName}`;
|
|
169
|
-
return this.state.get(stateKey) as UsageCost | null;
|
|
245
|
+
resetMetrics(): void {
|
|
246
|
+
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
247
|
+
MetricsRegistry.reset({ crewId, agent: this.agentName } as any);
|
|
170
248
|
}
|
|
249
|
+
|
|
171
250
|
}
|
|
172
251
|
|
|
173
252
|
/**
|
|
@@ -208,6 +287,8 @@ class AxCrew {
|
|
|
208
287
|
this.crewId = crewId;
|
|
209
288
|
this.agents = new Map<string, StatefulAxAgent>();
|
|
210
289
|
this.state = createState(crewId);
|
|
290
|
+
// Make crewId discoverable to metrics
|
|
291
|
+
this.state.set('crewId', crewId);
|
|
211
292
|
}
|
|
212
293
|
|
|
213
294
|
/**
|
|
@@ -226,7 +307,7 @@ class AxCrew {
|
|
|
226
307
|
);
|
|
227
308
|
|
|
228
309
|
// Destructure with type assertion
|
|
229
|
-
const { ai, name, description, signature, functions, subAgentNames, examples } = agentConfig;
|
|
310
|
+
const { ai, name, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
230
311
|
|
|
231
312
|
// Get subagents for the AI agent
|
|
232
313
|
const subAgents = subAgentNames.map((subAgentName: string) => {
|
|
@@ -238,23 +319,45 @@ class AxCrew {
|
|
|
238
319
|
return this.agents?.get(subAgentName);
|
|
239
320
|
});
|
|
240
321
|
|
|
322
|
+
// Dedupe sub-agents by name (defensive)
|
|
323
|
+
const subAgentSet = new Map<string, StatefulAxAgent>();
|
|
324
|
+
for (const sa of subAgents.filter((agent): agent is StatefulAxAgent => agent !== undefined)) {
|
|
325
|
+
const n = (sa as any)?.agentName ?? (sa as any)?.name ?? '';
|
|
326
|
+
if (!subAgentSet.has(n)) subAgentSet.set(n, sa);
|
|
327
|
+
}
|
|
328
|
+
const uniqueSubAgents = Array.from(subAgentSet.values());
|
|
329
|
+
|
|
330
|
+
// Dedupe functions by name and avoid collision with sub-agent names
|
|
331
|
+
const subAgentNameSet = new Set(uniqueSubAgents.map((sa: any) => sa?.agentName ?? sa?.name).filter(Boolean));
|
|
332
|
+
const uniqueFunctions: AxFunction[] = [];
|
|
333
|
+
const seenFn = new Set<string>();
|
|
334
|
+
for (const fn of functions.filter((fn): fn is AxFunction => fn !== undefined)) {
|
|
335
|
+
const fnName = fn.name;
|
|
336
|
+
if (subAgentNameSet.has(fnName)) {
|
|
337
|
+
// Skip function that collides with a sub-agent name
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (!seenFn.has(fnName)) {
|
|
341
|
+
seenFn.add(fnName);
|
|
342
|
+
uniqueFunctions.push(fn);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
241
346
|
// Create an instance of StatefulAxAgent
|
|
242
347
|
const agent = new StatefulAxAgent(
|
|
243
348
|
ai,
|
|
244
349
|
{
|
|
245
350
|
name,
|
|
246
351
|
description,
|
|
352
|
+
definition: (agentConfig as any).definition,
|
|
247
353
|
signature,
|
|
248
|
-
functions:
|
|
249
|
-
|
|
250
|
-
),
|
|
251
|
-
agents: subAgents.filter(
|
|
252
|
-
(agent): agent is StatefulAxAgent => agent !== undefined
|
|
253
|
-
),
|
|
354
|
+
functions: uniqueFunctions,
|
|
355
|
+
agents: uniqueSubAgents,
|
|
254
356
|
examples,
|
|
255
357
|
},
|
|
256
358
|
this.state
|
|
257
359
|
);
|
|
360
|
+
(agent as any).costTracker = tracker;
|
|
258
361
|
|
|
259
362
|
return agent;
|
|
260
363
|
} catch (error) {
|
|
@@ -394,20 +497,29 @@ class AxCrew {
|
|
|
394
497
|
this.state.reset();
|
|
395
498
|
}
|
|
396
499
|
|
|
397
|
-
/**
|
|
398
|
-
* Gets aggregated costs for all agents in the crew
|
|
399
|
-
* @returns Aggregated cost information for all agents
|
|
400
|
-
*/
|
|
401
|
-
getAggregatedCosts(): ReturnType<typeof StateFulAxAgentUsage.getAggregatedCosts> {
|
|
402
|
-
return StateFulAxAgentUsage.getAggregatedCosts(this.state);
|
|
403
|
-
}
|
|
404
500
|
|
|
405
501
|
/**
|
|
406
502
|
* Resets all cost tracking for the crew
|
|
407
503
|
*/
|
|
408
504
|
resetCosts(): void {
|
|
409
|
-
|
|
505
|
+
// Reset AxAgent built-in usage and our metrics registry
|
|
506
|
+
if (this.agents) {
|
|
507
|
+
for (const [, agent] of this.agents) {
|
|
508
|
+
try { (agent as any).resetUsage?.(); } catch {}
|
|
509
|
+
try { (agent as any).resetMetrics?.(); } catch {}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Metrics API
|
|
516
|
+
getCrewMetrics() {
|
|
517
|
+
return MetricsRegistry.snapshotCrew(this.crewId);
|
|
518
|
+
}
|
|
519
|
+
resetCrewMetrics(): void {
|
|
520
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
410
521
|
}
|
|
411
522
|
}
|
|
412
523
|
|
|
413
|
-
export { AxCrew };
|
|
524
|
+
export { AxCrew };
|
|
525
|
+
export type { StatefulAxAgent };
|