@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/src/index.ts
CHANGED
|
@@ -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
|
@@ -6,7 +6,23 @@ import type {
|
|
|
6
6
|
AxProgramForwardOptions
|
|
7
7
|
} from '@ax-llm/ax';
|
|
8
8
|
|
|
9
|
-
|
|
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'
|
|
10
26
|
|
|
11
27
|
/**
|
|
12
28
|
* A state instance that is shared between agents.
|
|
@@ -132,7 +148,7 @@ interface MCPHTTPSSETransportConfig {
|
|
|
132
148
|
* @property {string} mcpEndpoint - The HTTP endpoint URL for the MCP server.
|
|
133
149
|
* @property {AxMCPStreamableHTTPTransportOptions} options - Optional transport options.
|
|
134
150
|
*/
|
|
135
|
-
interface
|
|
151
|
+
interface MCPStreamableHTTPTransportConfig {
|
|
136
152
|
mcpEndpoint: string
|
|
137
153
|
options?: AxMCPStreamableHTTPTransportOptions
|
|
138
154
|
}
|
|
@@ -142,7 +158,7 @@ interface MCPStreambleHTTPTransportConfig {
|
|
|
142
158
|
*
|
|
143
159
|
* @property {MCPStdioTransportConfig | MCPHTTPSSETransportConfig | MCPStreambleHTTPTransportConfig} config - The config for the MCP server. Config can be either stdio, http-sse, or streamable http transport.
|
|
144
160
|
*/
|
|
145
|
-
type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig |
|
|
161
|
+
type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig | MCPStreamableHTTPTransportConfig
|
|
146
162
|
|
|
147
163
|
/**
|
|
148
164
|
* The configuration for an agent.
|
|
@@ -155,7 +171,7 @@ type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig |
|
|
|
155
171
|
* @property {AxModelConfig & { model: string }} ai - The AI model configuration to be passed to the agent.
|
|
156
172
|
* @property {boolean} debug - Whether to enable debug mode.
|
|
157
173
|
* @property {string} apiURL - Set this if you are using a custom API URL e.g. ollama on localhost.
|
|
158
|
-
* @property {Partial<AxProgramForwardOptions>} options - Agent options including thinkingTokenBudget, showThoughts, etc.
|
|
174
|
+
* @property {Partial<AxProgramForwardOptions<any>> & Record<string, any>} options - Agent options including thinkingTokenBudget, showThoughts, etc. Also allows arbitrary provider-specific keys.
|
|
159
175
|
* @property {string[]} functions - Function names to be used by the agent.
|
|
160
176
|
* @property {string[]} agents - Sub-agent available to the agent.
|
|
161
177
|
* @property {Record<string, any>[]} examples - DSPy examples for the agent to learn from.
|
|
@@ -164,13 +180,23 @@ type MCPTransportConfig = MCPStdioTransportConfig | MCPHTTPSSETransportConfig |
|
|
|
164
180
|
interface AgentConfig {
|
|
165
181
|
name: string;
|
|
166
182
|
description: string;
|
|
183
|
+
/**
|
|
184
|
+
* Optional detailed persona/program definition. If provided, becomes the system prompt.
|
|
185
|
+
* Must be at least 100 characters per Ax semantics.
|
|
186
|
+
*/
|
|
187
|
+
definition?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Optional alias for definition for clarity. If provided and definition is omitted,
|
|
190
|
+
* this will be used as the program definition/system prompt.
|
|
191
|
+
*/
|
|
192
|
+
prompt?: string;
|
|
167
193
|
signature: string | AxSignature;
|
|
168
194
|
provider: Provider;
|
|
169
195
|
providerKeyName?: string;
|
|
170
196
|
ai: AxModelConfig & { model: string };
|
|
171
197
|
debug?: boolean;
|
|
172
198
|
apiURL?: string;
|
|
173
|
-
options?: Partial<AxProgramForwardOptions>;
|
|
199
|
+
options?: Partial<AxProgramForwardOptions<any>> & Record<string, any>;
|
|
174
200
|
functions?: string[];
|
|
175
201
|
agents?: string[];
|
|
176
202
|
examples?: Array<Record<string, any>>;
|
|
@@ -190,10 +216,10 @@ export {
|
|
|
190
216
|
type FunctionRegistryType,
|
|
191
217
|
type MCPStdioTransportConfig,
|
|
192
218
|
type MCPHTTPSSETransportConfig,
|
|
193
|
-
type
|
|
219
|
+
type MCPStreamableHTTPTransportConfig,
|
|
194
220
|
type MCPTransportConfig,
|
|
195
221
|
type ModelUsage,
|
|
196
222
|
type ModelInfo,
|
|
197
223
|
type UsageCost,
|
|
198
224
|
type AggregatedCosts
|
|
199
|
-
}
|
|
225
|
+
}
|
package/tests/AxCrew.test.ts
CHANGED
|
@@ -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: "
|
|
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
|
|
54
|
-
signature: "
|
|
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: "
|
|
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: "
|
|
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
|
|
109
|
-
expect(
|
|
110
|
-
expect(
|
|
111
|
-
expect(typeof
|
|
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
|
|
117
|
-
expect(
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
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: "
|
|
134
|
+
signature: "query:string -> queryResponse:string",
|
|
131
135
|
provider: "anthropic",
|
|
132
136
|
providerKeyName: "ANTHROPIC_API_KEY",
|
|
133
137
|
ai: { model: "claude-3-haiku-20240307" }
|