@amitdeshmukh/ax-crew 3.11.1 → 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.
@@ -0,0 +1,167 @@
1
+ import type { LabelKeys, MetricsSnapshot, TokenUsage } from './types.js';
2
+ import Big from 'big.js';
3
+
4
+ type Key = string;
5
+
6
+ type Counters = {
7
+ requests: number;
8
+ errors: number;
9
+ streaming: number;
10
+ durationMsSum: number;
11
+ durationCount: number;
12
+ inputTokens: number;
13
+ outputTokens: number;
14
+ estimatedCostUSD: number;
15
+ functionCalls: number;
16
+ functionLatencyMs: number;
17
+ };
18
+
19
+ const store = new Map<Key, Counters>();
20
+
21
+ function keyOf(labels: LabelKeys): Key {
22
+ const { crewId, agent = '', provider = '', model = '' } = labels;
23
+ return [crewId, agent, provider, model].join('|');
24
+ }
25
+
26
+ function getOrInit(labels: LabelKeys): Counters {
27
+ const k = keyOf(labels);
28
+ let c = store.get(k);
29
+ if (!c) {
30
+ c = {
31
+ requests: 0,
32
+ errors: 0,
33
+ streaming: 0,
34
+ durationMsSum: 0,
35
+ durationCount: 0,
36
+ inputTokens: 0,
37
+ outputTokens: 0,
38
+ estimatedCostUSD: 0,
39
+ functionCalls: 0,
40
+ functionLatencyMs: 0,
41
+ };
42
+ store.set(k, c);
43
+ }
44
+ return c;
45
+ }
46
+
47
+ export function recordRequest(labels: LabelKeys, streaming: boolean, durationMs: number) {
48
+ const c = getOrInit(labels);
49
+ c.requests += 1;
50
+ if (streaming) c.streaming += 1;
51
+ c.durationMsSum += durationMs;
52
+ c.durationCount += 1;
53
+ }
54
+
55
+ export function recordError(labels: LabelKeys) {
56
+ const c = getOrInit(labels);
57
+ c.errors += 1;
58
+ }
59
+
60
+ export function recordTokens(labels: LabelKeys, usage: TokenUsage) {
61
+ const c = getOrInit(labels);
62
+ c.inputTokens += usage.promptTokens || 0;
63
+ c.outputTokens += usage.completionTokens || 0;
64
+ }
65
+
66
+ export function recordEstimatedCost(labels: LabelKeys, usd: number) {
67
+ const c = getOrInit(labels);
68
+ const current = new Big(c.estimatedCostUSD || 0);
69
+ const addition = new Big(usd || 0);
70
+ c.estimatedCostUSD = Number(current.plus(addition));
71
+ }
72
+
73
+ export function recordFunctionCall(labels: LabelKeys, latencyMs: number) {
74
+ const c = getOrInit(labels);
75
+ c.functionCalls += 1;
76
+ c.functionLatencyMs += latencyMs || 0;
77
+ }
78
+
79
+ export function snapshot(labels: LabelKeys): MetricsSnapshot {
80
+ const c = getOrInit(labels);
81
+ const totalTokens = c.inputTokens + c.outputTokens;
82
+ return {
83
+ provider: labels.provider,
84
+ model: labels.model,
85
+ requests: {
86
+ totalRequests: c.requests,
87
+ totalErrors: c.errors,
88
+ errorRate: c.requests > 0 ? c.errors / c.requests : 0,
89
+ totalStreamingRequests: c.streaming,
90
+ durationMsSum: c.durationMsSum,
91
+ durationCount: c.durationCount,
92
+ },
93
+ tokens: {
94
+ promptTokens: c.inputTokens,
95
+ completionTokens: c.outputTokens,
96
+ totalTokens,
97
+ },
98
+ estimatedCostUSD: Number(new Big(c.estimatedCostUSD || 0).round(5)),
99
+ functions: {
100
+ totalFunctionCalls: c.functionCalls,
101
+ totalFunctionLatencyMs: c.functionLatencyMs,
102
+ },
103
+ };
104
+ }
105
+
106
+ export function reset(labels?: LabelKeys) {
107
+ if (!labels) {
108
+ store.clear();
109
+ return;
110
+ }
111
+ const k = keyOf(labels);
112
+ store.delete(k);
113
+ }
114
+
115
+ export function snapshotCrew(crewId: string): MetricsSnapshot {
116
+ const empty: Counters = {
117
+ requests: 0,
118
+ errors: 0,
119
+ streaming: 0,
120
+ durationMsSum: 0,
121
+ durationCount: 0,
122
+ inputTokens: 0,
123
+ outputTokens: 0,
124
+ estimatedCostUSD: 0,
125
+ functionCalls: 0,
126
+ functionLatencyMs: 0,
127
+ };
128
+ const agg = Array.from(store.entries()).reduce((acc, [k, v]) => {
129
+ if (k.startsWith(crewId + '|')) {
130
+ acc.requests += v.requests;
131
+ acc.errors += v.errors;
132
+ acc.streaming += v.streaming;
133
+ acc.durationMsSum += v.durationMsSum;
134
+ acc.durationCount += v.durationCount;
135
+ acc.inputTokens += v.inputTokens;
136
+ acc.outputTokens += v.outputTokens;
137
+ acc.estimatedCostUSD = Number(new Big(acc.estimatedCostUSD || 0).plus(v.estimatedCostUSD || 0));
138
+ acc.functionCalls += v.functionCalls;
139
+ acc.functionLatencyMs += v.functionLatencyMs;
140
+ }
141
+ return acc;
142
+ }, { ...empty });
143
+
144
+ const totalTokens = agg.inputTokens + agg.outputTokens;
145
+ return {
146
+ requests: {
147
+ totalRequests: agg.requests,
148
+ totalErrors: agg.errors,
149
+ errorRate: agg.requests > 0 ? agg.errors / agg.requests : 0,
150
+ totalStreamingRequests: agg.streaming,
151
+ durationMsSum: agg.durationMsSum,
152
+ durationCount: agg.durationCount,
153
+ },
154
+ tokens: {
155
+ promptTokens: agg.inputTokens,
156
+ completionTokens: agg.outputTokens,
157
+ totalTokens,
158
+ },
159
+ estimatedCostUSD: Number(new Big(agg.estimatedCostUSD || 0).round(5)),
160
+ functions: {
161
+ totalFunctionCalls: agg.functionCalls,
162
+ totalFunctionLatencyMs: agg.functionLatencyMs,
163
+ },
164
+ } as MetricsSnapshot;
165
+ }
166
+
167
+
@@ -0,0 +1,48 @@
1
+ export interface TokenUsage {
2
+ promptTokens: number;
3
+ completionTokens: number;
4
+ totalTokens?: number;
5
+ }
6
+
7
+ export interface CostSnapshot {
8
+ usdTotal: number;
9
+ tokenUsage: TokenUsage;
10
+ }
11
+
12
+ export interface RequestStats {
13
+ totalRequests: number;
14
+ totalErrors: number;
15
+ errorRate: number;
16
+ totalStreamingRequests: number;
17
+ durationMsSum: number;
18
+ durationCount: number;
19
+ }
20
+
21
+ export interface FunctionStats {
22
+ totalFunctionCalls: number;
23
+ totalFunctionLatencyMs: number;
24
+ }
25
+
26
+ export interface MetricsSnapshot {
27
+ provider?: string;
28
+ model?: string;
29
+ requests: RequestStats;
30
+ tokens: TokenUsage;
31
+ estimatedCostUSD: number;
32
+ functions: FunctionStats;
33
+ }
34
+
35
+ export interface LabelKeys {
36
+ crewId: string;
37
+ agent?: string;
38
+ provider?: string;
39
+ model?: string;
40
+ }
41
+
42
+ export interface BudgetConfig {
43
+ maxTokens?: number;
44
+ maxCost?: number;
45
+ costPerModel?: Record<string, number>; // USD per 1K tokens override
46
+ }
47
+
48
+
package/src/types.ts CHANGED
@@ -2,10 +2,27 @@ import type {
2
2
  AxFunction,
3
3
  AxSignature,
4
4
  AxModelConfig,
5
- AxMCPStreamableHTTPTransportOptions
5
+ AxMCPStreamableHTTPTransportOptions,
6
+ AxProgramForwardOptions
6
7
  } from '@ax-llm/ax';
7
8
 
8
- import type { Provider } from './agents/agentConfig.js';
9
+ // Canonical provider slugs supported by ai() factory
10
+ export type Provider =
11
+ // Canonical slugs per docs
12
+ | 'openai'
13
+ | 'anthropic'
14
+ | 'google-gemini'
15
+ | 'mistral'
16
+ | 'groq'
17
+ | 'cohere'
18
+ | 'together'
19
+ | 'deepseek'
20
+ | 'ollama'
21
+ | 'huggingface'
22
+ | 'openrouter'
23
+ | 'azure-openai'
24
+ | 'reka'
25
+ | 'x-grok'
9
26
 
10
27
  /**
11
28
  * A state instance that is shared between agents.
@@ -131,7 +148,7 @@ interface MCPHTTPSSETransportConfig {
131
148
  * @property {string} mcpEndpoint - The HTTP endpoint URL for the MCP server.
132
149
  * @property {AxMCPStreamableHTTPTransportOptions} options - Optional transport options.
133
150
  */
134
- interface MCPStreambleHTTPTransportConfig {
151
+ interface MCPStreamableHTTPTransportConfig {
135
152
  mcpEndpoint: string
136
153
  options?: AxMCPStreamableHTTPTransportOptions
137
154
  }
@@ -141,7 +158,7 @@ interface MCPStreambleHTTPTransportConfig {
141
158
  *
142
159
  * @property {MCPStdioTransportConfig | MCPHTTPSSETransportConfig | MCPStreambleHTTPTransportConfig} config - The config for the MCP server. Config can be either stdio, http-sse, or streamable http transport.
143
160
  */
144
- type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig | MCPStreambleHTTPTransportConfig
161
+ type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig | MCPStreamableHTTPTransportConfig
145
162
 
146
163
  /**
147
164
  * The configuration for an agent.
@@ -154,7 +171,7 @@ type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig |
154
171
  * @property {AxModelConfig & { model: string }} ai - The AI model configuration to be passed to the agent.
155
172
  * @property {boolean} debug - Whether to enable debug mode.
156
173
  * @property {string} apiURL - Set this if you are using a custom API URL e.g. ollama on localhost.
157
- * @property {Record<string, any>} options - Agent options. Refer to the Ax documentation for more details.
174
+ * @property {Partial<AxProgramForwardOptions<any>> & Record<string, any>} options - Agent options including thinkingTokenBudget, showThoughts, etc. Also allows arbitrary provider-specific keys.
158
175
  * @property {string[]} functions - Function names to be used by the agent.
159
176
  * @property {string[]} agents - Sub-agent available to the agent.
160
177
  * @property {Record<string, any>[]} examples - DSPy examples for the agent to learn from.
@@ -169,7 +186,7 @@ interface AgentConfig {
169
186
  ai: AxModelConfig & { model: string };
170
187
  debug?: boolean;
171
188
  apiURL?: string;
172
- options?: Record<string, any>;
189
+ options?: Partial<AxProgramForwardOptions<any>> & Record<string, any>;
173
190
  functions?: string[];
174
191
  agents?: string[];
175
192
  examples?: Array<Record<string, any>>;
@@ -189,10 +206,10 @@ export {
189
206
  type FunctionRegistryType,
190
207
  type MCPStdioTransportConfig,
191
208
  type MCPHTTPSSETransportConfig,
192
- type MCPStreambleHTTPTransportConfig,
209
+ type MCPStreamableHTTPTransportConfig,
193
210
  type MCPTransportConfig,
194
211
  type ModelUsage,
195
212
  type ModelInfo,
196
213
  type UsageCost,
197
214
  type AggregatedCosts
198
- };
215
+ }
@@ -1,6 +1,10 @@
1
1
  import { describe, test, expect, beforeEach } from 'vitest';
2
2
  import { AxCrew } from '../src/agents';
3
3
  import { AxCrewFunctions } from '../src/functions';
4
+ import type { AxCrewConfig } from '../src/index.js';
5
+
6
+ // Provide dummy API key for tests
7
+ process.env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || 'test';
4
8
 
5
9
  describe('AxCrew', () => {
6
10
  // Basic initialization tests
@@ -10,7 +14,7 @@ describe('AxCrew', () => {
10
14
  crew: [{
11
15
  name: "ResearchAgent",
12
16
  description: "A research agent that can search the web for information and provide detailed analysis of search results with proper citations",
13
- signature: "input:string -> output:string",
17
+ signature: "query:string -> queryResponse:string",
14
18
  provider: "anthropic",
15
19
  providerKeyName: "ANTHROPIC_API_KEY",
16
20
  ai: {
@@ -19,7 +23,7 @@ describe('AxCrew', () => {
19
23
  }]
20
24
  };
21
25
 
22
- const crew = new AxCrew(config, AxCrewFunctions);
26
+ const crew = new AxCrew(config as AxCrewConfig, AxCrewFunctions);
23
27
  expect(crew).toBeInstanceOf(AxCrew);
24
28
  });
25
29
 
@@ -36,7 +40,7 @@ describe('AxCrew', () => {
36
40
  }
37
41
  }]
38
42
  };
39
- expect(() => new AxCrew(invalidConfig, AxCrewFunctions))
43
+ expect(() => new AxCrew(invalidConfig as AxCrewConfig, AxCrewFunctions))
40
44
  .toThrowError('Agent name cannot be empty');
41
45
  });
42
46
  });
@@ -50,8 +54,8 @@ describe('AxCrew', () => {
50
54
  crew: [
51
55
  {
52
56
  name: "agent1",
53
- description: "A sophisticated agent that processes text input and generates structured analysis with detailed explanations and recommendations",
54
- signature: "input:string -> output:string",
57
+ description: "A sophisticated agent that processes text query and generates structured analysis with detailed explanations and recommendations",
58
+ signature: "query:string -> queryResponse:string",
55
59
  provider: "anthropic",
56
60
  providerKeyName: "ANTHROPIC_API_KEY",
57
61
  ai: { model: "claude-3-haiku-20240307" }
@@ -59,7 +63,7 @@ describe('AxCrew', () => {
59
63
  {
60
64
  name: "agent2",
61
65
  description: "An advanced processing agent that builds upon agent1's output to provide deeper insights and actionable intelligence",
62
- signature: "input:string -> output:string",
66
+ signature: "query:string -> queryResponse:string",
63
67
  provider: "anthropic",
64
68
  providerKeyName: "ANTHROPIC_API_KEY",
65
69
  ai: { model: "claude-3-haiku-20240307" },
@@ -94,7 +98,7 @@ describe('AxCrew', () => {
94
98
  crew: [{
95
99
  name: "testAgent",
96
100
  description: "A comprehensive testing agent that validates inputs, processes data, and ensures output quality through multiple verification steps",
97
- signature: "input:string -> output:string",
101
+ signature: "query:string -> queryResponse:string",
98
102
  provider: "anthropic",
99
103
  providerKeyName: "ANTHROPIC_API_KEY",
100
104
  ai: { model: "claude-3-haiku-20240307" }
@@ -104,19 +108,19 @@ describe('AxCrew', () => {
104
108
  await crew.addAgent('testAgent');
105
109
  });
106
110
 
107
- test('should track costs correctly', async () => {
108
- const costs = crew.getAggregatedCosts();
109
- expect(costs).toBeDefined();
110
- expect(costs).toHaveProperty('totalCost');
111
- expect(typeof costs.totalCost).toBe('string');
111
+ test('should track costs correctly (metrics)', async () => {
112
+ const metrics = crew.getCrewMetrics();
113
+ expect(metrics).toBeDefined();
114
+ expect(metrics).toHaveProperty('estimatedCostUSD');
115
+ expect(typeof metrics.estimatedCostUSD).toBe('number');
112
116
  });
113
117
 
114
- test('should reset costs', () => {
118
+ test('should reset costs (metrics)', () => {
115
119
  crew.resetCosts();
116
- const costs = crew.getAggregatedCosts();
117
- expect(costs).toBeDefined();
118
- expect(costs).toHaveProperty('totalCost');
119
- expect(costs.totalCost).toBe('0'); // Updated to match actual format
120
+ const metrics = crew.getCrewMetrics();
121
+ expect(metrics).toBeDefined();
122
+ expect(metrics).toHaveProperty('estimatedCostUSD');
123
+ expect(metrics.estimatedCostUSD).toBe(0);
120
124
  });
121
125
  });
122
126
 
@@ -127,7 +131,7 @@ describe('AxCrew', () => {
127
131
  crew: [{
128
132
  name: "testAgent",
129
133
  description: "A comprehensive testing agent that validates inputs, processes data, and ensures output quality through multiple verification steps",
130
- signature: "input:string -> output:string",
134
+ signature: "query:string -> queryResponse:string",
131
135
  provider: "anthropic",
132
136
  providerKeyName: "ANTHROPIC_API_KEY",
133
137
  ai: { model: "claude-3-haiku-20240307" }