@pauly4010/evalai-sdk 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +289 -0
- package/LICENSE +21 -0
- package/README.md +565 -0
- package/dist/assertions.d.ts +189 -0
- package/dist/assertions.js +596 -0
- package/dist/batch.d.ts +68 -0
- package/dist/batch.js +178 -0
- package/dist/cache.d.ts +65 -0
- package/dist/cache.js +135 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +181 -0
- package/dist/client.d.ts +358 -0
- package/dist/client.js +802 -0
- package/dist/context.d.ts +134 -0
- package/dist/context.js +215 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.js +285 -0
- package/dist/export.d.ts +195 -0
- package/dist/export.js +334 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +111 -0
- package/dist/integrations/anthropic.d.ts +72 -0
- package/dist/integrations/anthropic.js +159 -0
- package/dist/integrations/openai.d.ts +69 -0
- package/dist/integrations/openai.js +156 -0
- package/dist/local.d.ts +39 -0
- package/dist/local.js +146 -0
- package/dist/logger.d.ts +128 -0
- package/dist/logger.js +227 -0
- package/dist/pagination.d.ts +74 -0
- package/dist/pagination.js +135 -0
- package/dist/snapshot.d.ts +176 -0
- package/dist/snapshot.js +322 -0
- package/dist/streaming.d.ts +173 -0
- package/dist/streaming.js +268 -0
- package/dist/testing.d.ts +204 -0
- package/dist/testing.js +252 -0
- package/dist/types.d.ts +715 -0
- package/dist/types.js +54 -0
- package/dist/workflows.d.ts +378 -0
- package/dist/workflows.js +628 -0
- package/package.json +102 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow Tracer SDK
|
|
4
|
+
* Multi-agent workflow instrumentation, decision tracking, and cost capture
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { WorkflowTracer } from '@pauly4010/evalai-sdk';
|
|
9
|
+
*
|
|
10
|
+
* const tracer = new WorkflowTracer(client, { organizationId: 123 });
|
|
11
|
+
*
|
|
12
|
+
* // Start a workflow
|
|
13
|
+
* const workflow = await tracer.startWorkflow('Customer Support Pipeline');
|
|
14
|
+
*
|
|
15
|
+
* // Record agent spans and handoffs
|
|
16
|
+
* const span1 = await tracer.startAgentSpan('RouterAgent', { input: query });
|
|
17
|
+
* await tracer.recordDecision({
|
|
18
|
+
* agent: 'RouterAgent',
|
|
19
|
+
* chosen: 'delegate_to_technical',
|
|
20
|
+
* alternatives: [{ action: 'delegate_to_billing', confidence: 0.3 }],
|
|
21
|
+
* reasoning: 'Query contains technical keywords'
|
|
22
|
+
* });
|
|
23
|
+
* await tracer.recordHandoff('RouterAgent', 'TechnicalAgent', { issue: 'API error' });
|
|
24
|
+
* await tracer.endAgentSpan(span1, { result: 'delegated' });
|
|
25
|
+
*
|
|
26
|
+
* // End workflow with final output
|
|
27
|
+
* await tracer.endWorkflow({ resolution: 'Issue resolved' });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.WorkflowTracer = void 0;
|
|
32
|
+
exports.traceLangChainAgent = traceLangChainAgent;
|
|
33
|
+
exports.traceCrewAI = traceCrewAI;
|
|
34
|
+
exports.traceAutoGen = traceAutoGen;
|
|
35
|
+
exports.createWorkflowTracer = createWorkflowTracer;
|
|
36
|
+
exports.traceWorkflowStep = traceWorkflowStep;
|
|
37
|
+
const context_1 = require("./context");
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// MAIN CLASS - WorkflowTracer
|
|
40
|
+
// ============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* WorkflowTracer - Instrument multi-agent workflows with tracing, decision auditing, and cost tracking
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const tracer = new WorkflowTracer(client, { organizationId: 123 });
|
|
47
|
+
*
|
|
48
|
+
* // Simple workflow
|
|
49
|
+
* await tracer.startWorkflow('Data Processing Pipeline');
|
|
50
|
+
*
|
|
51
|
+
* const agentSpan = await tracer.startAgentSpan('DataAgent', { source: 'api' });
|
|
52
|
+
* // ... agent work ...
|
|
53
|
+
* await tracer.endAgentSpan(agentSpan, { processed: 100 });
|
|
54
|
+
*
|
|
55
|
+
* await tracer.endWorkflow({ status: 'success' });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
class WorkflowTracer {
|
|
59
|
+
constructor(client, options = {}) {
|
|
60
|
+
this.currentWorkflow = null;
|
|
61
|
+
this.activeSpans = new Map();
|
|
62
|
+
this.handoffs = [];
|
|
63
|
+
this.decisions = [];
|
|
64
|
+
this.costs = [];
|
|
65
|
+
this.spanCounter = 0;
|
|
66
|
+
this.client = client;
|
|
67
|
+
this.options = {
|
|
68
|
+
organizationId: options.organizationId || client.getOrganizationId() || 0,
|
|
69
|
+
autoCalculateCost: options.autoCalculateCost ?? true,
|
|
70
|
+
tracePrefix: options.tracePrefix || 'workflow',
|
|
71
|
+
captureFullPayloads: options.captureFullPayloads ?? true,
|
|
72
|
+
debug: options.debug ?? false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// ==========================================================================
|
|
76
|
+
// WORKFLOW LIFECYCLE
|
|
77
|
+
// ==========================================================================
|
|
78
|
+
/**
|
|
79
|
+
* Start a new workflow
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const workflow = await tracer.startWorkflow('Customer Support Flow', {
|
|
84
|
+
* nodes: [
|
|
85
|
+
* { id: 'router', type: 'agent', name: 'RouterAgent' },
|
|
86
|
+
* { id: 'technical', type: 'agent', name: 'TechnicalAgent' },
|
|
87
|
+
* ],
|
|
88
|
+
* edges: [{ from: 'router', to: 'technical', condition: 'is_technical' }],
|
|
89
|
+
* entrypoint: 'router'
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
async startWorkflow(name, definition, metadata) {
|
|
94
|
+
if (this.currentWorkflow) {
|
|
95
|
+
throw new Error('A workflow is already active. Call endWorkflow() first.');
|
|
96
|
+
}
|
|
97
|
+
const traceId = `${this.options.tracePrefix}-${Date.now()}-${this.generateId()}`;
|
|
98
|
+
const startedAt = new Date().toISOString();
|
|
99
|
+
// Create the trace
|
|
100
|
+
const trace = await this.client.traces.create({
|
|
101
|
+
name: `Workflow: ${name}`,
|
|
102
|
+
traceId,
|
|
103
|
+
organizationId: this.options.organizationId,
|
|
104
|
+
status: 'pending',
|
|
105
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
106
|
+
workflowName: name,
|
|
107
|
+
definition,
|
|
108
|
+
...metadata,
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
this.currentWorkflow = {
|
|
112
|
+
id: 0, // Will be set after API call returns
|
|
113
|
+
traceId: trace.id,
|
|
114
|
+
name,
|
|
115
|
+
startedAt,
|
|
116
|
+
definition,
|
|
117
|
+
metadata,
|
|
118
|
+
};
|
|
119
|
+
// Reset state
|
|
120
|
+
this.handoffs = [];
|
|
121
|
+
this.decisions = [];
|
|
122
|
+
this.costs = [];
|
|
123
|
+
this.activeSpans.clear();
|
|
124
|
+
this.spanCounter = 0;
|
|
125
|
+
this.log('Started workflow', { name, traceId: trace.id });
|
|
126
|
+
return this.currentWorkflow;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* End the current workflow
|
|
130
|
+
*/
|
|
131
|
+
async endWorkflow(output, status = 'completed') {
|
|
132
|
+
if (!this.currentWorkflow) {
|
|
133
|
+
throw new Error('No active workflow. Call startWorkflow() first.');
|
|
134
|
+
}
|
|
135
|
+
const durationMs = Date.now() - new Date(this.currentWorkflow.startedAt).getTime();
|
|
136
|
+
// Calculate total cost
|
|
137
|
+
const totalCost = this.costs.reduce((sum, cost) => sum + parseFloat(cost.totalCost), 0);
|
|
138
|
+
// Update the trace with final status
|
|
139
|
+
// Note: We create a new trace entry with the same ID pattern to update status
|
|
140
|
+
const traceId = `${this.options.tracePrefix}-complete-${this.currentWorkflow.traceId}`;
|
|
141
|
+
await this.client.traces.create({
|
|
142
|
+
name: `Workflow: ${this.currentWorkflow.name}`,
|
|
143
|
+
traceId,
|
|
144
|
+
organizationId: this.options.organizationId,
|
|
145
|
+
status: status === 'completed' ? 'success' : 'error',
|
|
146
|
+
durationMs,
|
|
147
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
148
|
+
workflowName: this.currentWorkflow.name,
|
|
149
|
+
output,
|
|
150
|
+
status,
|
|
151
|
+
totalCost: totalCost.toFixed(6),
|
|
152
|
+
handoffCount: this.handoffs.length,
|
|
153
|
+
decisionCount: this.decisions.length,
|
|
154
|
+
agentCount: new Set(this.handoffs.map(h => h.toAgent)).size + 1,
|
|
155
|
+
retryCount: this.costs.filter(c => c.isRetry).length,
|
|
156
|
+
handoffs: this.handoffs,
|
|
157
|
+
decisions: this.decisions,
|
|
158
|
+
costs: this.costs,
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
this.log('Ended workflow', {
|
|
162
|
+
name: this.currentWorkflow.name,
|
|
163
|
+
status,
|
|
164
|
+
durationMs,
|
|
165
|
+
totalCost: totalCost.toFixed(6),
|
|
166
|
+
});
|
|
167
|
+
this.currentWorkflow = null;
|
|
168
|
+
}
|
|
169
|
+
// ==========================================================================
|
|
170
|
+
// AGENT SPANS
|
|
171
|
+
// ==========================================================================
|
|
172
|
+
/**
|
|
173
|
+
* Start an agent span within the workflow
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const span = await tracer.startAgentSpan('RouterAgent', {
|
|
178
|
+
* input: userQuery
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
async startAgentSpan(agentName, input, parentSpanId) {
|
|
183
|
+
if (!this.currentWorkflow) {
|
|
184
|
+
throw new Error('No active workflow. Call startWorkflow() first.');
|
|
185
|
+
}
|
|
186
|
+
const spanId = `span-${++this.spanCounter}-${this.generateId()}`;
|
|
187
|
+
const startTime = new Date().toISOString();
|
|
188
|
+
const spanContext = {
|
|
189
|
+
spanId,
|
|
190
|
+
agentName,
|
|
191
|
+
startTime,
|
|
192
|
+
parentSpanId,
|
|
193
|
+
metadata: input,
|
|
194
|
+
};
|
|
195
|
+
this.activeSpans.set(spanId, spanContext);
|
|
196
|
+
// Create span via API
|
|
197
|
+
await this.client.traces.createSpan(this.currentWorkflow.traceId, {
|
|
198
|
+
name: `Agent: ${agentName}`,
|
|
199
|
+
spanId,
|
|
200
|
+
parentSpanId,
|
|
201
|
+
startTime,
|
|
202
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
203
|
+
agentName,
|
|
204
|
+
...(this.options.captureFullPayloads ? { input } : {}),
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
this.log('Started agent span', { agentName, spanId });
|
|
208
|
+
return spanContext;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* End an agent span
|
|
212
|
+
*/
|
|
213
|
+
async endAgentSpan(span, output, error) {
|
|
214
|
+
if (!this.currentWorkflow) {
|
|
215
|
+
throw new Error('No active workflow.');
|
|
216
|
+
}
|
|
217
|
+
const endTime = new Date().toISOString();
|
|
218
|
+
const durationMs = new Date(endTime).getTime() - new Date(span.startTime).getTime();
|
|
219
|
+
// Update span via API (create completion record)
|
|
220
|
+
await this.client.traces.createSpan(this.currentWorkflow.traceId, {
|
|
221
|
+
name: `Agent: ${span.agentName}`,
|
|
222
|
+
spanId: `${span.spanId}-end`,
|
|
223
|
+
parentSpanId: span.spanId,
|
|
224
|
+
startTime: span.startTime,
|
|
225
|
+
endTime,
|
|
226
|
+
durationMs,
|
|
227
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
228
|
+
agentName: span.agentName,
|
|
229
|
+
...(this.options.captureFullPayloads ? { output } : {}),
|
|
230
|
+
...(error ? { error } : {}),
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
this.activeSpans.delete(span.spanId);
|
|
234
|
+
this.log('Ended agent span', { agentName: span.agentName, spanId: span.spanId, durationMs });
|
|
235
|
+
}
|
|
236
|
+
// ==========================================================================
|
|
237
|
+
// HANDOFFS
|
|
238
|
+
// ==========================================================================
|
|
239
|
+
/**
|
|
240
|
+
* Record a handoff between agents
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* await tracer.recordHandoff(
|
|
245
|
+
* 'RouterAgent',
|
|
246
|
+
* 'TechnicalAgent',
|
|
247
|
+
* { issueType: 'api_error', priority: 'high' },
|
|
248
|
+
* 'delegation'
|
|
249
|
+
* );
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
async recordHandoff(fromAgent, toAgent, context, handoffType = 'delegation') {
|
|
253
|
+
if (!this.currentWorkflow) {
|
|
254
|
+
throw new Error('No active workflow. Call startWorkflow() first.');
|
|
255
|
+
}
|
|
256
|
+
const handoff = {
|
|
257
|
+
fromAgent,
|
|
258
|
+
toAgent,
|
|
259
|
+
handoffType,
|
|
260
|
+
context,
|
|
261
|
+
timestamp: new Date().toISOString(),
|
|
262
|
+
};
|
|
263
|
+
this.handoffs.push(handoff);
|
|
264
|
+
// Also create a span for the handoff
|
|
265
|
+
const spanId = `handoff-${this.handoffs.length}-${this.generateId()}`;
|
|
266
|
+
await this.client.traces.createSpan(this.currentWorkflow.traceId, {
|
|
267
|
+
name: `Handoff: ${fromAgent || 'start'} → ${toAgent}`,
|
|
268
|
+
spanId,
|
|
269
|
+
startTime: handoff.timestamp,
|
|
270
|
+
endTime: handoff.timestamp,
|
|
271
|
+
durationMs: 0,
|
|
272
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
273
|
+
handoffType,
|
|
274
|
+
fromAgent,
|
|
275
|
+
toAgent,
|
|
276
|
+
context,
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
this.log('Recorded handoff', { fromAgent, toAgent, handoffType });
|
|
280
|
+
}
|
|
281
|
+
// ==========================================================================
|
|
282
|
+
// DECISION AUDITING
|
|
283
|
+
// ==========================================================================
|
|
284
|
+
/**
|
|
285
|
+
* Record a decision made by an agent
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```typescript
|
|
289
|
+
* await tracer.recordDecision({
|
|
290
|
+
* agent: 'RouterAgent',
|
|
291
|
+
* type: 'route',
|
|
292
|
+
* chosen: 'technical_support',
|
|
293
|
+
* alternatives: [
|
|
294
|
+
* { action: 'billing_support', confidence: 0.3, reasoning: 'No billing keywords' },
|
|
295
|
+
* { action: 'general_support', confidence: 0.1, reasoning: 'Fallback option' }
|
|
296
|
+
* ],
|
|
297
|
+
* reasoning: 'Query contains technical terms like "API", "error", "endpoint"',
|
|
298
|
+
* confidence: 85,
|
|
299
|
+
* contextFactors: ['keyword_match', 'user_history']
|
|
300
|
+
* });
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
async recordDecision(params) {
|
|
304
|
+
if (!this.currentWorkflow) {
|
|
305
|
+
throw new Error('No active workflow. Call startWorkflow() first.');
|
|
306
|
+
}
|
|
307
|
+
this.decisions.push(params);
|
|
308
|
+
// Create a span for the decision
|
|
309
|
+
const spanId = `decision-${this.decisions.length}-${this.generateId()}`;
|
|
310
|
+
const timestamp = new Date().toISOString();
|
|
311
|
+
await this.client.traces.createSpan(this.currentWorkflow.traceId, {
|
|
312
|
+
name: `Decision: ${params.agent} chose ${params.chosen}`,
|
|
313
|
+
spanId,
|
|
314
|
+
startTime: timestamp,
|
|
315
|
+
endTime: timestamp,
|
|
316
|
+
durationMs: 0,
|
|
317
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
318
|
+
isDecisionPoint: true,
|
|
319
|
+
agentName: params.agent,
|
|
320
|
+
decisionType: params.type,
|
|
321
|
+
chosen: params.chosen,
|
|
322
|
+
alternatives: params.alternatives,
|
|
323
|
+
reasoning: params.reasoning,
|
|
324
|
+
confidence: params.confidence,
|
|
325
|
+
contextFactors: params.contextFactors,
|
|
326
|
+
inputContext: params.inputContext,
|
|
327
|
+
}),
|
|
328
|
+
});
|
|
329
|
+
this.log('Recorded decision', {
|
|
330
|
+
agent: params.agent,
|
|
331
|
+
type: params.type,
|
|
332
|
+
chosen: params.chosen,
|
|
333
|
+
confidence: params.confidence,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// ==========================================================================
|
|
337
|
+
// COST TRACKING
|
|
338
|
+
// ==========================================================================
|
|
339
|
+
/**
|
|
340
|
+
* Record cost for an LLM call or operation
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* await tracer.recordCost({
|
|
345
|
+
* provider: 'openai',
|
|
346
|
+
* model: 'gpt-4',
|
|
347
|
+
* inputTokens: 500,
|
|
348
|
+
* outputTokens: 200,
|
|
349
|
+
* category: 'llm',
|
|
350
|
+
* isRetry: false
|
|
351
|
+
* });
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
async recordCost(params) {
|
|
355
|
+
const totalTokens = params.inputTokens + params.outputTokens;
|
|
356
|
+
// Calculate cost based on known pricing (can be extended)
|
|
357
|
+
const pricing = this.getModelPricing(params.provider, params.model);
|
|
358
|
+
const inputCost = (params.inputTokens / 1000000) * pricing.inputPricePerMillion;
|
|
359
|
+
const outputCost = (params.outputTokens / 1000000) * pricing.outputPricePerMillion;
|
|
360
|
+
const totalCost = inputCost + outputCost;
|
|
361
|
+
const costRecord = {
|
|
362
|
+
...params,
|
|
363
|
+
totalTokens,
|
|
364
|
+
category: params.category || 'llm',
|
|
365
|
+
inputCost: inputCost.toFixed(6),
|
|
366
|
+
outputCost: outputCost.toFixed(6),
|
|
367
|
+
totalCost: totalCost.toFixed(6),
|
|
368
|
+
};
|
|
369
|
+
this.costs.push(costRecord);
|
|
370
|
+
// Also record as a span if in an active workflow
|
|
371
|
+
if (this.currentWorkflow) {
|
|
372
|
+
const spanId = `cost-${this.costs.length}-${this.generateId()}`;
|
|
373
|
+
const timestamp = new Date().toISOString();
|
|
374
|
+
await this.client.traces.createSpan(this.currentWorkflow.traceId, {
|
|
375
|
+
name: `Cost: ${params.provider}/${params.model}`,
|
|
376
|
+
spanId,
|
|
377
|
+
startTime: timestamp,
|
|
378
|
+
endTime: timestamp,
|
|
379
|
+
durationMs: 0,
|
|
380
|
+
metadata: (0, context_1.mergeWithContext)({
|
|
381
|
+
costRecord,
|
|
382
|
+
}),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
this.log('Recorded cost', {
|
|
386
|
+
provider: params.provider,
|
|
387
|
+
model: params.model,
|
|
388
|
+
totalTokens,
|
|
389
|
+
totalCost: costRecord.totalCost,
|
|
390
|
+
});
|
|
391
|
+
return costRecord;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get total cost for the current workflow
|
|
395
|
+
*/
|
|
396
|
+
getTotalCost() {
|
|
397
|
+
return this.costs.reduce((sum, cost) => sum + parseFloat(cost.totalCost), 0);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get cost breakdown by category
|
|
401
|
+
*/
|
|
402
|
+
getCostBreakdown() {
|
|
403
|
+
const breakdown = {
|
|
404
|
+
llm: 0,
|
|
405
|
+
tool: 0,
|
|
406
|
+
embedding: 0,
|
|
407
|
+
other: 0,
|
|
408
|
+
};
|
|
409
|
+
for (const cost of this.costs) {
|
|
410
|
+
const category = cost.category || 'other';
|
|
411
|
+
breakdown[category] += parseFloat(cost.totalCost);
|
|
412
|
+
}
|
|
413
|
+
return breakdown;
|
|
414
|
+
}
|
|
415
|
+
// ==========================================================================
|
|
416
|
+
// UTILITY METHODS
|
|
417
|
+
// ==========================================================================
|
|
418
|
+
/**
|
|
419
|
+
* Get known pricing for a model (can be extended or fetched from API)
|
|
420
|
+
*/
|
|
421
|
+
getModelPricing(provider, model) {
|
|
422
|
+
// Default pricing (can be extended with API lookup)
|
|
423
|
+
const knownPricing = {
|
|
424
|
+
// OpenAI
|
|
425
|
+
'openai/gpt-4': { inputPricePerMillion: 30.00, outputPricePerMillion: 60.00 },
|
|
426
|
+
'openai/gpt-4-turbo': { inputPricePerMillion: 10.00, outputPricePerMillion: 30.00 },
|
|
427
|
+
'openai/gpt-4o': { inputPricePerMillion: 5.00, outputPricePerMillion: 15.00 },
|
|
428
|
+
'openai/gpt-4o-mini': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.60 },
|
|
429
|
+
'openai/gpt-3.5-turbo': { inputPricePerMillion: 0.50, outputPricePerMillion: 1.50 },
|
|
430
|
+
// Anthropic
|
|
431
|
+
'anthropic/claude-3-opus': { inputPricePerMillion: 15.00, outputPricePerMillion: 75.00 },
|
|
432
|
+
'anthropic/claude-3-sonnet': { inputPricePerMillion: 3.00, outputPricePerMillion: 15.00 },
|
|
433
|
+
'anthropic/claude-3-haiku': { inputPricePerMillion: 0.25, outputPricePerMillion: 1.25 },
|
|
434
|
+
'anthropic/claude-3.5-sonnet': { inputPricePerMillion: 3.00, outputPricePerMillion: 15.00 },
|
|
435
|
+
// Google
|
|
436
|
+
'google/gemini-pro': { inputPricePerMillion: 0.50, outputPricePerMillion: 1.50 },
|
|
437
|
+
'google/gemini-1.5-pro': { inputPricePerMillion: 3.50, outputPricePerMillion: 10.50 },
|
|
438
|
+
'google/gemini-1.5-flash': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.30 },
|
|
439
|
+
};
|
|
440
|
+
const key = `${provider}/${model}`;
|
|
441
|
+
return knownPricing[key] || { inputPricePerMillion: 1.00, outputPricePerMillion: 3.00 };
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Generate a unique ID
|
|
445
|
+
*/
|
|
446
|
+
generateId() {
|
|
447
|
+
return Math.random().toString(36).substring(2, 11);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Log if debug mode is enabled
|
|
451
|
+
*/
|
|
452
|
+
log(message, data) {
|
|
453
|
+
if (this.options.debug) {
|
|
454
|
+
console.log(`[WorkflowTracer] ${message}`, data || '');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get current workflow context
|
|
459
|
+
*/
|
|
460
|
+
getCurrentWorkflow() {
|
|
461
|
+
return this.currentWorkflow;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Check if a workflow is active
|
|
465
|
+
*/
|
|
466
|
+
isWorkflowActive() {
|
|
467
|
+
return this.currentWorkflow !== null;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get all recorded handoffs
|
|
471
|
+
*/
|
|
472
|
+
getHandoffs() {
|
|
473
|
+
return [...this.handoffs];
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Get all recorded decisions
|
|
477
|
+
*/
|
|
478
|
+
getDecisions() {
|
|
479
|
+
return [...this.decisions];
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get all recorded costs
|
|
483
|
+
*/
|
|
484
|
+
getCosts() {
|
|
485
|
+
return [...this.costs];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
exports.WorkflowTracer = WorkflowTracer;
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// FRAMEWORK INTEGRATIONS
|
|
491
|
+
// ============================================================================
|
|
492
|
+
/**
|
|
493
|
+
* Wrap a LangChain agent for automatic workflow tracing
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* import { AgentExecutor } from 'langchain/agents';
|
|
498
|
+
*
|
|
499
|
+
* const executor = new AgentExecutor({ ... });
|
|
500
|
+
* const tracedExecutor = traceLangChainAgent(executor, tracer);
|
|
501
|
+
*
|
|
502
|
+
* const result = await tracedExecutor.invoke({ input: 'Hello' });
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
function traceLangChainAgent(executor, tracer, options = {}) {
|
|
506
|
+
const agentName = options.agentName || 'LangChainAgent';
|
|
507
|
+
const originalInvoke = executor.invoke?.bind(executor);
|
|
508
|
+
const originalCall = executor.call?.bind(executor);
|
|
509
|
+
if (originalInvoke) {
|
|
510
|
+
executor.invoke = async (input, config) => {
|
|
511
|
+
const span = await tracer.startAgentSpan(agentName, { input });
|
|
512
|
+
try {
|
|
513
|
+
const result = await originalInvoke(input, config);
|
|
514
|
+
await tracer.endAgentSpan(span, { output: result });
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (originalCall) {
|
|
524
|
+
executor.call = async (input, config) => {
|
|
525
|
+
const span = await tracer.startAgentSpan(agentName, { input });
|
|
526
|
+
try {
|
|
527
|
+
const result = await originalCall(input, config);
|
|
528
|
+
await tracer.endAgentSpan(span, { output: result });
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
return executor;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Create a traced wrapper for CrewAI crews
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```typescript
|
|
544
|
+
* const tracedCrew = traceCrewAI(crew, tracer, {
|
|
545
|
+
* crewName: 'ResearchCrew'
|
|
546
|
+
* });
|
|
547
|
+
*
|
|
548
|
+
* const result = await tracedCrew.kickoff({ topic: 'AI Safety' });
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
function traceCrewAI(crew, tracer, options = {}) {
|
|
552
|
+
const crewName = options.crewName || 'CrewAI';
|
|
553
|
+
const originalKickoff = crew.kickoff?.bind(crew);
|
|
554
|
+
if (originalKickoff) {
|
|
555
|
+
crew.kickoff = async (input) => {
|
|
556
|
+
await tracer.startWorkflow(`${crewName} Execution`);
|
|
557
|
+
const span = await tracer.startAgentSpan(crewName, { input });
|
|
558
|
+
try {
|
|
559
|
+
const result = await originalKickoff(input);
|
|
560
|
+
await tracer.endAgentSpan(span, { output: result });
|
|
561
|
+
await tracer.endWorkflow({ result }, 'completed');
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
|
|
566
|
+
await tracer.endWorkflow({ error: error instanceof Error ? error.message : String(error) }, 'failed');
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return crew;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Create a traced wrapper for AutoGen conversations
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```typescript
|
|
578
|
+
* const tracedConversation = traceAutoGen(conversation, tracer, {
|
|
579
|
+
* conversationName: 'CodeReview'
|
|
580
|
+
* });
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
function traceAutoGen(conversation, tracer, options = {}) {
|
|
584
|
+
const conversationName = options.conversationName || 'AutoGenConversation';
|
|
585
|
+
const originalInitiateChat = conversation.initiate_chat?.bind(conversation);
|
|
586
|
+
if (originalInitiateChat) {
|
|
587
|
+
conversation.initiate_chat = async (...args) => {
|
|
588
|
+
await tracer.startWorkflow(`${conversationName}`);
|
|
589
|
+
const span = await tracer.startAgentSpan(conversationName, { args });
|
|
590
|
+
try {
|
|
591
|
+
const result = await originalInitiateChat(...args);
|
|
592
|
+
await tracer.endAgentSpan(span, { output: result });
|
|
593
|
+
await tracer.endWorkflow({ result }, 'completed');
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
|
|
598
|
+
await tracer.endWorkflow({ error: error instanceof Error ? error.message : String(error) }, 'failed');
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return conversation;
|
|
604
|
+
}
|
|
605
|
+
// ============================================================================
|
|
606
|
+
// UTILITY FUNCTIONS
|
|
607
|
+
// ============================================================================
|
|
608
|
+
/**
|
|
609
|
+
* Create a workflow tracer from an existing client
|
|
610
|
+
*/
|
|
611
|
+
function createWorkflowTracer(client, options) {
|
|
612
|
+
return new WorkflowTracer(client, options);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Helper to trace an async function as a workflow step
|
|
616
|
+
*/
|
|
617
|
+
async function traceWorkflowStep(tracer, agentName, fn, input) {
|
|
618
|
+
const span = await tracer.startAgentSpan(agentName, input);
|
|
619
|
+
try {
|
|
620
|
+
const result = await fn();
|
|
621
|
+
await tracer.endAgentSpan(span, { result });
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
}
|