@respan/tracing 1.0.45
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/LICENSE +201 -0
- package/README.md +860 -0
- package/dist/constants/index.d.ts +8 -0
- package/dist/constants/index.js +9 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/contexts/index.d.ts +1 -0
- package/dist/contexts/index.js +2 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/contexts/span.d.ts +9 -0
- package/dist/contexts/span.js +39 -0
- package/dist/contexts/span.js.map +1 -0
- package/dist/decorators/base.d.ts +32 -0
- package/dist/decorators/base.js +242 -0
- package/dist/decorators/base.js.map +1 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +2 -0
- package/dist/instrumentation/index.js +3 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/instrumentation/loader.d.ts +5 -0
- package/dist/instrumentation/loader.js +104 -0
- package/dist/instrumentation/loader.js.map +1 -0
- package/dist/instrumentation/manager.d.ts +29 -0
- package/dist/instrumentation/manager.js +564 -0
- package/dist/instrumentation/manager.js.map +1 -0
- package/dist/main.d.ts +162 -0
- package/dist/main.js +212 -0
- package/dist/main.js.map +1 -0
- package/dist/processor/composite.d.ts +29 -0
- package/dist/processor/composite.js +106 -0
- package/dist/processor/composite.js.map +1 -0
- package/dist/processor/filtering.d.ts +19 -0
- package/dist/processor/filtering.js +78 -0
- package/dist/processor/filtering.js.map +1 -0
- package/dist/processor/index.d.ts +3 -0
- package/dist/processor/index.js +4 -0
- package/dist/processor/index.js.map +1 -0
- package/dist/processor/manager.d.ts +61 -0
- package/dist/processor/manager.js +111 -0
- package/dist/processor/manager.js.map +1 -0
- package/dist/types/clientTypes.d.ts +188 -0
- package/dist/types/clientTypes.js +22 -0
- package/dist/types/clientTypes.js.map +1 -0
- package/dist/types/decoratorTypes.d.ts +6 -0
- package/dist/types/decoratorTypes.js +2 -0
- package/dist/types/decoratorTypes.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/instrumentationTypes.d.ts +25 -0
- package/dist/types/instrumentationTypes.js +82 -0
- package/dist/types/instrumentationTypes.js.map +1 -0
- package/dist/utils/client.d.ts +168 -0
- package/dist/utils/client.js +151 -0
- package/dist/utils/client.js.map +1 -0
- package/dist/utils/context.d.ts +28 -0
- package/dist/utils/context.js +44 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/span.d.ts +65 -0
- package/dist/utils/span.js +269 -0
- package/dist/utils/span.js.map +1 -0
- package/dist/utils/spanBuffer.d.ts +94 -0
- package/dist/utils/spanBuffer.js +147 -0
- package/dist/utils/spanBuffer.js.map +1 -0
- package/dist/utils/tracing.d.ts +31 -0
- package/dist/utils/tracing.js +239 -0
- package/dist/utils/tracing.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
# KeywordsAI Tracing SDK
|
|
2
|
+
|
|
3
|
+
A lightweight OpenTelemetry-based tracing SDK for KeywordsAI, built with minimal dependencies and optional instrumentation support.
|
|
4
|
+
Inspired by [Openllmetry](https://github.com/traceloop/openllmetry-js)
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Lightweight Core**: Minimal dependencies for browser and Node.js compatibility
|
|
9
|
+
- **Optional Instrumentations**: Install only the instrumentations you need
|
|
10
|
+
- **OpenTelemetry Native**: Built directly on OpenTelemetry without wrapper dependencies
|
|
11
|
+
- **Decorator Pattern**: Easy-to-use decorators for workflows, tasks, agents, and tools
|
|
12
|
+
- **Dynamic Loading**: Instrumentations are loaded on-demand
|
|
13
|
+
- **Manual Instrumentation**: Support for manual instrumentation (Next.js compatible)
|
|
14
|
+
- **Span Management**: Full control over spans with `getClient()` API
|
|
15
|
+
- **Multi-Processor Routing**: Route spans to multiple destinations
|
|
16
|
+
- **Span Buffering**: Manual control over span export timing
|
|
17
|
+
- **KeywordsAI Parameters**: Add customer identifiers and trace group identifiers
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### Core Package
|
|
22
|
+
```bash
|
|
23
|
+
npm install @keywordsai/tracing
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Optional Instrumentations
|
|
27
|
+
Install only the instrumentations you need:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# OpenAI
|
|
31
|
+
npm install @traceloop/instrumentation-openai
|
|
32
|
+
|
|
33
|
+
# Anthropic
|
|
34
|
+
npm install @traceloop/instrumentation-anthropic
|
|
35
|
+
|
|
36
|
+
# Azure OpenAI
|
|
37
|
+
npm install @traceloop/instrumentation-azure
|
|
38
|
+
|
|
39
|
+
# AWS Bedrock
|
|
40
|
+
npm install @traceloop/instrumentation-bedrock
|
|
41
|
+
|
|
42
|
+
# Cohere
|
|
43
|
+
npm install @traceloop/instrumentation-cohere
|
|
44
|
+
|
|
45
|
+
# LangChain
|
|
46
|
+
npm install @traceloop/instrumentation-langchain
|
|
47
|
+
|
|
48
|
+
# LlamaIndex
|
|
49
|
+
npm install @traceloop/instrumentation-llamaindex
|
|
50
|
+
|
|
51
|
+
# Vector Databases
|
|
52
|
+
npm install @traceloop/instrumentation-pinecone
|
|
53
|
+
npm install @traceloop/instrumentation-chromadb
|
|
54
|
+
npm install @traceloop/instrumentation-qdrant
|
|
55
|
+
|
|
56
|
+
# Other providers
|
|
57
|
+
npm install @traceloop/instrumentation-together
|
|
58
|
+
npm install @traceloop/instrumentation-vertexai
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### Method 1: Dynamic Instrumentation (Recommended for Node.js)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
67
|
+
import OpenAI from 'openai';
|
|
68
|
+
|
|
69
|
+
// Initialize the SDK
|
|
70
|
+
const keywordsAi = new KeywordsAITelemetry({
|
|
71
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
72
|
+
baseURL: process.env.KEYWORDSAI_BASE_URL,
|
|
73
|
+
appName: 'my-app'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Enable instrumentations you need
|
|
77
|
+
await keywordsAi.enableInstrumentation('openai');
|
|
78
|
+
|
|
79
|
+
const openai = new OpenAI();
|
|
80
|
+
|
|
81
|
+
// Use decorators to trace your functions
|
|
82
|
+
const generateJoke = async () => {
|
|
83
|
+
return await keywordsAi.withTask(
|
|
84
|
+
{ name: 'joke_generation' },
|
|
85
|
+
async () => {
|
|
86
|
+
const completion = await openai.chat.completions.create({
|
|
87
|
+
messages: [{ role: 'user', content: 'Tell me a joke' }],
|
|
88
|
+
model: 'gpt-3.5-turbo'
|
|
89
|
+
});
|
|
90
|
+
return completion.choices[0].message.content;
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Method 2: Manual Instrumentation (Recommended for Next.js)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
100
|
+
import OpenAI from 'openai';
|
|
101
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
102
|
+
|
|
103
|
+
// Manual instrumentation - pass the actual imported modules
|
|
104
|
+
const keywordsAi = new KeywordsAITelemetry({
|
|
105
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
106
|
+
baseURL: process.env.KEYWORDSAI_BASE_URL,
|
|
107
|
+
appName: 'my-app',
|
|
108
|
+
// Specify modules to instrument manually
|
|
109
|
+
instrumentModules: {
|
|
110
|
+
openAI: OpenAI,
|
|
111
|
+
anthropic: Anthropic,
|
|
112
|
+
// Add other modules as needed
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Wait for initialization (optional but recommended)
|
|
117
|
+
await keywordsAi.initialize();
|
|
118
|
+
|
|
119
|
+
// Create clients - they will be automatically instrumented
|
|
120
|
+
const openai = new OpenAI();
|
|
121
|
+
const anthropic = new Anthropic();
|
|
122
|
+
|
|
123
|
+
// Use decorators to trace your functions
|
|
124
|
+
const generateContent = async () => {
|
|
125
|
+
return await keywordsAi.withWorkflow(
|
|
126
|
+
{ name: 'content_generation', version: 1 },
|
|
127
|
+
async () => {
|
|
128
|
+
const result = await openai.chat.completions.create({
|
|
129
|
+
messages: [{ role: 'user', content: 'Generate content' }],
|
|
130
|
+
model: 'gpt-3.5-turbo'
|
|
131
|
+
});
|
|
132
|
+
return result.choices[0].message.content;
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## When to Use Each Method
|
|
139
|
+
|
|
140
|
+
### Dynamic Instrumentation
|
|
141
|
+
- **Best for**: Standard Node.js applications, serverless functions
|
|
142
|
+
- **Pros**: Simple setup, automatic loading
|
|
143
|
+
- **Cons**: May not work in all bundling environments
|
|
144
|
+
|
|
145
|
+
### Manual Instrumentation
|
|
146
|
+
- **Best for**: Next.js, Webpack bundled apps, environments with import restrictions
|
|
147
|
+
- **Pros**: Works in all environments, explicit control, better for tree-shaking
|
|
148
|
+
- **Cons**: Requires importing modules explicitly
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### KeywordsAITelemetry
|
|
153
|
+
|
|
154
|
+
#### Constructor Options
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
interface KeywordsAIOptions {
|
|
158
|
+
appName?: string; // App name for traces
|
|
159
|
+
apiKey?: string; // KeywordsAI API key
|
|
160
|
+
baseURL?: string; // KeywordsAI base URL
|
|
161
|
+
disableBatch?: boolean; // Disable batching for development
|
|
162
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
163
|
+
traceContent?: boolean; // Log prompts and completions
|
|
164
|
+
tracingEnabled?: boolean; // Enable/disable tracing
|
|
165
|
+
silenceInitializationMessage?: boolean;
|
|
166
|
+
|
|
167
|
+
// Advanced options
|
|
168
|
+
resourceAttributes?: Record<string, string>; // Custom resource attributes
|
|
169
|
+
spanPostprocessCallback?: (span: any) => void; // Span postprocessing callback
|
|
170
|
+
|
|
171
|
+
// Manual instrumentation modules
|
|
172
|
+
instrumentModules?: {
|
|
173
|
+
openAI?: typeof OpenAI;
|
|
174
|
+
anthropic?: typeof Anthropic;
|
|
175
|
+
azureOpenAI?: typeof AzureOpenAI;
|
|
176
|
+
cohere?: typeof Cohere;
|
|
177
|
+
bedrock?: typeof BedrockRuntime;
|
|
178
|
+
google_vertexai?: typeof VertexAI;
|
|
179
|
+
google_aiplatform?: typeof AIPlatform;
|
|
180
|
+
pinecone?: typeof Pinecone;
|
|
181
|
+
together?: typeof Together;
|
|
182
|
+
langchain?: {
|
|
183
|
+
chainsModule?: typeof ChainsModule;
|
|
184
|
+
agentsModule?: typeof AgentsModule;
|
|
185
|
+
toolsModule?: typeof ToolsModule;
|
|
186
|
+
runnablesModule?: typeof RunnableModule;
|
|
187
|
+
vectorStoreModule?: typeof VectorStoreModule;
|
|
188
|
+
};
|
|
189
|
+
llamaIndex?: typeof LlamaIndex;
|
|
190
|
+
chromadb?: typeof ChromaDB;
|
|
191
|
+
qdrant?: typeof Qdrant;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Methods
|
|
197
|
+
|
|
198
|
+
- `initialize()` - Manually initialize tracing (returns Promise)
|
|
199
|
+
- `isInitialized()` - Check if tracing has been initialized
|
|
200
|
+
- `enableInstrumentation(name: string)` - Enable a specific instrumentation (dynamic method)
|
|
201
|
+
- `enableInstrumentations(names: string[])` - Enable multiple instrumentations (dynamic method)
|
|
202
|
+
- `addProcessor(config: ProcessorConfig)` - Add a processor for routing spans
|
|
203
|
+
- `getClient()` - Get the client API for span management
|
|
204
|
+
- `getSpanBufferManager()` - Get the span buffer manager
|
|
205
|
+
- `shutdown()` - Flush and shutdown tracing
|
|
206
|
+
|
|
207
|
+
### Decorators
|
|
208
|
+
|
|
209
|
+
#### withWorkflow
|
|
210
|
+
Trace high-level workflows:
|
|
211
|
+
```typescript
|
|
212
|
+
await keywordsAi.withWorkflow(
|
|
213
|
+
{ name: 'my_workflow', version: 1 },
|
|
214
|
+
async () => {
|
|
215
|
+
// Your workflow logic
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### withTask
|
|
221
|
+
Trace individual tasks:
|
|
222
|
+
```typescript
|
|
223
|
+
await keywordsAi.withTask(
|
|
224
|
+
{ name: 'my_task' },
|
|
225
|
+
async () => {
|
|
226
|
+
// Your task logic
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### withAgent
|
|
232
|
+
Trace agent operations:
|
|
233
|
+
```typescript
|
|
234
|
+
await keywordsAi.withAgent(
|
|
235
|
+
{ name: 'my_agent', associationProperties: { type: 'assistant' } },
|
|
236
|
+
async () => {
|
|
237
|
+
// Your agent logic
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### withTool
|
|
243
|
+
Trace tool usage:
|
|
244
|
+
```typescript
|
|
245
|
+
await keywordsAi.withTool(
|
|
246
|
+
{ name: 'my_tool' },
|
|
247
|
+
async () => {
|
|
248
|
+
// Your tool logic
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Decorator Configuration
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
interface DecoratorConfig {
|
|
257
|
+
name: string; // Required: Name of the operation
|
|
258
|
+
version?: number; // Optional: Version number
|
|
259
|
+
associationProperties?: Record<string, string>; // Optional: Additional metadata
|
|
260
|
+
traceContent?: boolean; // Optional: Override trace content setting
|
|
261
|
+
inputParameters?: unknown[]; // Optional: Custom input parameters
|
|
262
|
+
suppressTracing?: boolean; // Optional: Suppress tracing for this operation
|
|
263
|
+
processors?: string | string[]; // Optional: Route to specific processor(s)
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Advanced Features
|
|
268
|
+
|
|
269
|
+
### Span Management with getClient()
|
|
270
|
+
|
|
271
|
+
Get full control over your spans with the client API:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { KeywordsAITelemetry, getClient } from '@keywordsai/tracing';
|
|
275
|
+
|
|
276
|
+
const kai = new KeywordsAITelemetry({ apiKey: 'your-key' });
|
|
277
|
+
await kai.initialize();
|
|
278
|
+
|
|
279
|
+
await kai.withTask({ name: 'process_data' }, async () => {
|
|
280
|
+
const client = getClient();
|
|
281
|
+
|
|
282
|
+
// Get current trace and span IDs
|
|
283
|
+
const traceId = client.getCurrentTraceId();
|
|
284
|
+
const spanId = client.getCurrentSpanId();
|
|
285
|
+
console.log(`Trace: ${traceId}, Span: ${spanId}`);
|
|
286
|
+
|
|
287
|
+
// Update span with KeywordsAI parameters
|
|
288
|
+
client.updateCurrentSpan({
|
|
289
|
+
keywordsaiParams: {
|
|
290
|
+
customerIdentifier: 'user-123',
|
|
291
|
+
traceGroupIdentifier: 'data-pipeline',
|
|
292
|
+
metadata: {
|
|
293
|
+
version: '1.0',
|
|
294
|
+
environment: 'production'
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Add events to track progress
|
|
300
|
+
client.addEvent('validation_started', { records: 1000 });
|
|
301
|
+
|
|
302
|
+
// Your processing logic here
|
|
303
|
+
|
|
304
|
+
client.addEvent('validation_completed', { status: 'success' });
|
|
305
|
+
|
|
306
|
+
// Record exceptions
|
|
307
|
+
try {
|
|
308
|
+
// risky operation
|
|
309
|
+
} catch (error) {
|
|
310
|
+
client.recordException(error as Error);
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Available Client Methods:**
|
|
317
|
+
- `getCurrentTraceId()` - Get the current trace ID
|
|
318
|
+
- `getCurrentSpanId()` - Get the current span ID
|
|
319
|
+
- `updateCurrentSpan(options)` - Update span attributes, name, status, or KeywordsAI params
|
|
320
|
+
- `addEvent(name, attributes?)` - Add an event to the current span
|
|
321
|
+
- `recordException(exception)` - Record an exception on the current span
|
|
322
|
+
- `isRecording()` - Check if the span is recording
|
|
323
|
+
- `getTracer()` - Get the tracer for manual span creation
|
|
324
|
+
- `flush()` - Force flush all pending spans
|
|
325
|
+
|
|
326
|
+
### Multi-Processor Routing
|
|
327
|
+
|
|
328
|
+
Route spans to different destinations based on processor names:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
332
|
+
|
|
333
|
+
const kai = new KeywordsAITelemetry({ apiKey: 'your-key' });
|
|
334
|
+
|
|
335
|
+
// Add a debug processor (in addition to default KeywordsAI processor)
|
|
336
|
+
kai.addProcessor({
|
|
337
|
+
exporter: new YourCustomExporter(),
|
|
338
|
+
name: 'debug',
|
|
339
|
+
filter: (span) => span.attributes['environment'] === 'development'
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Route specific spans to debug processor
|
|
343
|
+
await kai.withTask(
|
|
344
|
+
{ name: 'debug_task', processors: 'debug' },
|
|
345
|
+
async () => {
|
|
346
|
+
// This span goes to the debug processor
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Route to multiple processors
|
|
351
|
+
await kai.withTask(
|
|
352
|
+
{ name: 'important_task', processors: ['debug', 'analytics'] },
|
|
353
|
+
async () => {
|
|
354
|
+
// This span goes to both processors
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Default behavior - no processors attribute
|
|
359
|
+
await kai.withTask(
|
|
360
|
+
{ name: 'normal_task' },
|
|
361
|
+
async () => {
|
|
362
|
+
// This span goes to the default KeywordsAI processor
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Processor Configuration:**
|
|
368
|
+
```typescript
|
|
369
|
+
interface ProcessorConfig {
|
|
370
|
+
exporter: SpanExporter; // The span exporter to use
|
|
371
|
+
name: string; // Processor identifier for routing
|
|
372
|
+
filter?: (span: ReadableSpan) => boolean; // Optional custom filter
|
|
373
|
+
priority?: number; // Optional priority (higher = processed first)
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Span Buffering for Manual Control
|
|
378
|
+
|
|
379
|
+
Buffer spans and control when they're exported:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
383
|
+
|
|
384
|
+
const kai = new KeywordsAITelemetry({ apiKey: 'your-key' });
|
|
385
|
+
const manager = kai.getSpanBufferManager();
|
|
386
|
+
|
|
387
|
+
// Create a buffer (spans won't be auto-exported)
|
|
388
|
+
const buffer = manager.createBuffer('workflow-123');
|
|
389
|
+
|
|
390
|
+
// Add spans to the buffer
|
|
391
|
+
buffer.createSpan('validation', {
|
|
392
|
+
status: 'success',
|
|
393
|
+
duration_ms: 10
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
buffer.createSpan('processing', {
|
|
397
|
+
status: 'success',
|
|
398
|
+
duration_ms: 100
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Get buffered spans (they're transportable!)
|
|
402
|
+
const spans = buffer.getAllSpans();
|
|
403
|
+
console.log(`Collected ${spans.length} spans`);
|
|
404
|
+
|
|
405
|
+
// Conditionally process based on business logic
|
|
406
|
+
const isSuccessful = true; // Your business logic
|
|
407
|
+
const isPremiumUser = true; // Your business logic
|
|
408
|
+
|
|
409
|
+
if (isSuccessful && isPremiumUser) {
|
|
410
|
+
// Export to KeywordsAI
|
|
411
|
+
await manager.processSpans(spans);
|
|
412
|
+
} else {
|
|
413
|
+
// Discard spans
|
|
414
|
+
buffer.clearSpans();
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Use Cases for Span Buffering:**
|
|
419
|
+
- Backend systems that need delayed span export
|
|
420
|
+
- Conditional export based on business logic
|
|
421
|
+
- Batch processing of spans
|
|
422
|
+
- Async span creation (create spans after execution)
|
|
423
|
+
- Experiment tracking with selective export
|
|
424
|
+
|
|
425
|
+
**SpanBuffer Methods:**
|
|
426
|
+
- `createSpan(name, attributes?, kind?)` - Create a span in the buffer
|
|
427
|
+
- `getAllSpans()` - Get all buffered spans as a transportable array
|
|
428
|
+
- `getSpanCount()` - Get the number of buffered spans
|
|
429
|
+
- `clearSpans()` - Discard all buffered spans without exporting
|
|
430
|
+
|
|
431
|
+
### KeywordsAI-Specific Parameters
|
|
432
|
+
|
|
433
|
+
Add customer and trace group identifiers to your spans:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { getClient } from '@keywordsai/tracing';
|
|
437
|
+
|
|
438
|
+
await kai.withWorkflow({ name: 'user_workflow' }, async () => {
|
|
439
|
+
const client = getClient();
|
|
440
|
+
|
|
441
|
+
client.updateCurrentSpan({
|
|
442
|
+
keywordsaiParams: {
|
|
443
|
+
// Group traces by customer
|
|
444
|
+
customerIdentifier: 'user-123',
|
|
445
|
+
|
|
446
|
+
// Organize traces into groups
|
|
447
|
+
traceGroupIdentifier: 'onboarding-flow',
|
|
448
|
+
|
|
449
|
+
// Add custom metadata
|
|
450
|
+
metadata: {
|
|
451
|
+
plan: 'premium',
|
|
452
|
+
region: 'us-east-1',
|
|
453
|
+
version: '2.1.0'
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
These parameters help you:
|
|
461
|
+
- Group traces by customer for user-level analytics
|
|
462
|
+
- Organize traces into logical groups (experiments, features, etc.)
|
|
463
|
+
- Add custom metadata for filtering and analysis
|
|
464
|
+
|
|
465
|
+
## Available Instrumentations
|
|
466
|
+
|
|
467
|
+
The following instrumentations can be enabled dynamically:
|
|
468
|
+
|
|
469
|
+
- `openai` - OpenAI API calls
|
|
470
|
+
- `anthropic` - Anthropic API calls
|
|
471
|
+
- `azure` - Azure OpenAI API calls
|
|
472
|
+
- `bedrock` - AWS Bedrock API calls
|
|
473
|
+
- `cohere` - Cohere API calls
|
|
474
|
+
- `langchain` - LangChain operations
|
|
475
|
+
- `llamaindex` - LlamaIndex operations
|
|
476
|
+
- `pinecone` - Pinecone vector database
|
|
477
|
+
- `chromadb` - ChromaDB vector database
|
|
478
|
+
- `qdrant` - Qdrant vector database
|
|
479
|
+
- `together` - Together AI API calls
|
|
480
|
+
- `vertexai` - Google Vertex AI API calls
|
|
481
|
+
|
|
482
|
+
## Troubleshooting Instrumentation
|
|
483
|
+
|
|
484
|
+
### Error: "instrumentation failed to initialize"
|
|
485
|
+
|
|
486
|
+
This usually means the instrumentation package is missing. Install it:
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
# For Anthropic
|
|
490
|
+
npm install @traceloop/instrumentation-anthropic
|
|
491
|
+
|
|
492
|
+
# For OpenAI
|
|
493
|
+
npm install @traceloop/instrumentation-openai
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Error: "Cannot read properties of undefined (reading 'prototype')"
|
|
497
|
+
|
|
498
|
+
This means you're using manual instrumentation but passed the wrong module. Make sure to:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// ✅ CORRECT - Pass the class/module itself
|
|
502
|
+
instrumentModules: {
|
|
503
|
+
anthropic: Anthropic // The imported class
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ❌ WRONG - Don't pass an instance
|
|
507
|
+
instrumentModules: {
|
|
508
|
+
anthropic: new Anthropic() // This won't work
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Dynamic instrumentation not working in Next.js/Webpack?
|
|
513
|
+
|
|
514
|
+
Use manual instrumentation instead:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// Instead of this:
|
|
518
|
+
await kai.enableInstrumentation('anthropic');
|
|
519
|
+
|
|
520
|
+
// Use this:
|
|
521
|
+
const kai = new KeywordsAITelemetry({
|
|
522
|
+
instrumentModules: {
|
|
523
|
+
anthropic: Anthropic
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Anthropic spans not appearing?
|
|
529
|
+
|
|
530
|
+
**Known Issue**: `@traceloop/instrumentation-anthropic@0.22.2` doesn't work with `@anthropic-ai/sdk@0.71+`
|
|
531
|
+
|
|
532
|
+
**Tested Working Versions:**
|
|
533
|
+
- ✅ `@anthropic-ai/sdk@^0.20.0` to `@^0.41.0` - Full tracing support with all metrics
|
|
534
|
+
- ❌ `@anthropic-ai/sdk@0.50.0+` - No spans created (breaking changes)
|
|
535
|
+
|
|
536
|
+
**Official Support**: The instrumentation package officially supports SDK `0.9.1 and later`, but breaking changes in SDK `v0.50.0` prevent spans from being created. Last confirmed working version: `0.41.0`.
|
|
537
|
+
|
|
538
|
+
**Solutions:**
|
|
539
|
+
|
|
540
|
+
1. **Use a compatible version** (recommended - use latest working version):
|
|
541
|
+
```bash
|
|
542
|
+
npm install @anthropic-ai/sdk@^0.41.0
|
|
543
|
+
# or
|
|
544
|
+
yarn add @anthropic-ai/sdk@^0.41.0
|
|
545
|
+
```
|
|
546
|
+
Or use any version between `0.20.0` and `0.41.0`
|
|
547
|
+
|
|
548
|
+
2. **Use OpenAI instead** - Fully supported with latest SDK versions
|
|
549
|
+
|
|
550
|
+
**What Works with 0.20.x:**
|
|
551
|
+
- ✅ All token metrics (`gen_ai.usage.prompt_tokens`, `gen_ai.usage.completion_tokens`)
|
|
552
|
+
- ✅ Full request/response content tracing
|
|
553
|
+
- ✅ Model information and metadata
|
|
554
|
+
- ✅ Proper span hierarchy within workflows
|
|
555
|
+
|
|
556
|
+
**Status**: SDK v0.50.0 introduced breaking changes that broke instrumentation compatibility. Waiting for `@traceloop` to update their package.
|
|
557
|
+
|
|
558
|
+
### Spans not showing up?
|
|
559
|
+
|
|
560
|
+
1. Check that you're using decorators (`withTask`, `withWorkflow`, etc.)
|
|
561
|
+
2. Verify API key is set: `process.env.KEYWORDSAI_API_KEY`
|
|
562
|
+
3. Enable debug logging: `logLevel: 'debug'`
|
|
563
|
+
4. Check network requests to KeywordsAI endpoint
|
|
564
|
+
|
|
565
|
+
## Environment Variables
|
|
566
|
+
|
|
567
|
+
- `KEYWORDSAI_API_KEY`: Your KeywordsAI API key
|
|
568
|
+
- `KEYWORDSAI_BASE_URL`: KeywordsAI base URL (default: https://api.keywordsai.co)
|
|
569
|
+
- `KEYWORDSAI_APP_NAME`: Default app name
|
|
570
|
+
- `KEYWORDSAI_TRACE_CONTENT`: Enable/disable content tracing (default: true)
|
|
571
|
+
|
|
572
|
+
## Provider-Specific Examples
|
|
573
|
+
|
|
574
|
+
### OpenAI
|
|
575
|
+
|
|
576
|
+
**Method 1: Dynamic Instrumentation (Simple)**
|
|
577
|
+
```typescript
|
|
578
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
579
|
+
import OpenAI from 'openai';
|
|
580
|
+
|
|
581
|
+
const kai = new KeywordsAITelemetry({
|
|
582
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
583
|
+
appName: 'openai-app'
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Enable OpenAI instrumentation dynamically
|
|
587
|
+
await kai.enableInstrumentation('openai');
|
|
588
|
+
|
|
589
|
+
const openai = new OpenAI();
|
|
590
|
+
|
|
591
|
+
await kai.withTask({ name: 'chat' }, async () => {
|
|
592
|
+
const response = await openai.chat.completions.create({
|
|
593
|
+
model: 'gpt-4',
|
|
594
|
+
messages: [{ role: 'user', content: 'Hello!' }]
|
|
595
|
+
});
|
|
596
|
+
console.log(response.choices[0].message.content);
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
**Method 2: Manual Instrumentation (Next.js/Webpack)**
|
|
601
|
+
```typescript
|
|
602
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
603
|
+
import OpenAI from 'openai';
|
|
604
|
+
|
|
605
|
+
const kai = new KeywordsAITelemetry({
|
|
606
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
607
|
+
appName: 'openai-app',
|
|
608
|
+
instrumentModules: {
|
|
609
|
+
openAI: OpenAI // Pass the OpenAI class
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
await kai.initialize();
|
|
614
|
+
|
|
615
|
+
const openai = new OpenAI();
|
|
616
|
+
|
|
617
|
+
await kai.withTask({ name: 'chat' }, async () => {
|
|
618
|
+
const response = await openai.chat.completions.create({
|
|
619
|
+
model: 'gpt-4',
|
|
620
|
+
messages: [{ role: 'user', content: 'Hello!' }]
|
|
621
|
+
});
|
|
622
|
+
console.log(response.choices[0].message.content);
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Anthropic (Claude)
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
630
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
631
|
+
|
|
632
|
+
// Initialize with Anthropic instrumentation
|
|
633
|
+
const kai = new KeywordsAITelemetry({
|
|
634
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
635
|
+
appName: 'anthropic-app',
|
|
636
|
+
instrumentModules: {
|
|
637
|
+
anthropic: Anthropic // Pass the Anthropic class
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
await kai.initialize();
|
|
642
|
+
|
|
643
|
+
// Create Anthropic client (will be auto-instrumented)
|
|
644
|
+
const anthropic = new Anthropic({
|
|
645
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Use with decorators
|
|
649
|
+
await kai.withTask({ name: 'generate_text' }, async () => {
|
|
650
|
+
const response = await anthropic.messages.create({
|
|
651
|
+
model: 'claude-3-haiku-20240307',
|
|
652
|
+
max_tokens: 1024,
|
|
653
|
+
messages: [{ role: 'user', content: 'Hello, Claude!' }]
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
console.log(response.content[0].text);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Streaming example
|
|
660
|
+
await kai.withTask({ name: 'stream_text' }, async () => {
|
|
661
|
+
const stream = await anthropic.messages.create({
|
|
662
|
+
model: 'claude-3-haiku-20240307',
|
|
663
|
+
max_tokens: 1024,
|
|
664
|
+
messages: [{ role: 'user', content: 'Count to 10' }],
|
|
665
|
+
stream: true
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
for await (const event of stream) {
|
|
669
|
+
if (event.type === 'content_block_delta' &&
|
|
670
|
+
event.delta.type === 'text_delta') {
|
|
671
|
+
process.stdout.write(event.delta.text);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
**Note**: Make sure to install the Anthropic instrumentation:
|
|
678
|
+
```bash
|
|
679
|
+
npm install @anthropic-ai/sdk @traceloop/instrumentation-anthropic
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
## Complete Examples
|
|
683
|
+
|
|
684
|
+
### Example 1: Full Workflow with Span Management
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import { KeywordsAITelemetry, getClient } from '@keywordsai/tracing';
|
|
688
|
+
import OpenAI from 'openai';
|
|
689
|
+
|
|
690
|
+
const kai = new KeywordsAITelemetry({
|
|
691
|
+
apiKey: process.env.KEYWORDSAI_API_KEY,
|
|
692
|
+
appName: 'my-app',
|
|
693
|
+
resourceAttributes: {
|
|
694
|
+
environment: 'production',
|
|
695
|
+
version: '1.0.0'
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
await kai.initialize();
|
|
700
|
+
const openai = new OpenAI();
|
|
701
|
+
|
|
702
|
+
await kai.withWorkflow({ name: 'process_user_request', version: 1 }, async () => {
|
|
703
|
+
const client = getClient();
|
|
704
|
+
|
|
705
|
+
// Set customer context
|
|
706
|
+
client.updateCurrentSpan({
|
|
707
|
+
keywordsaiParams: {
|
|
708
|
+
customerIdentifier: 'user-123',
|
|
709
|
+
traceGroupIdentifier: 'onboarding'
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Track progress with events
|
|
714
|
+
client.addEvent('validation_started');
|
|
715
|
+
|
|
716
|
+
// Nested task
|
|
717
|
+
await kai.withTask({ name: 'validate_input' }, async () => {
|
|
718
|
+
// Validation logic
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
client.addEvent('ai_processing_started');
|
|
722
|
+
|
|
723
|
+
// LLM call (auto-instrumented)
|
|
724
|
+
const response = await openai.chat.completions.create({
|
|
725
|
+
model: 'gpt-3.5-turbo',
|
|
726
|
+
messages: [{ role: 'user', content: 'Process this' }]
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
client.addEvent('ai_processing_completed', {
|
|
730
|
+
tokens: response.usage?.total_tokens
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
return response.choices[0].message.content;
|
|
734
|
+
});
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Example 2: Backend Workflow with Span Buffering
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
741
|
+
|
|
742
|
+
const kai = new KeywordsAITelemetry({ apiKey: 'your-key' });
|
|
743
|
+
const manager = kai.getSpanBufferManager();
|
|
744
|
+
|
|
745
|
+
// Ingest workflow results from backend
|
|
746
|
+
async function ingestWorkflow(workflowResult: any, orgId: string) {
|
|
747
|
+
const buffer = manager.createBuffer(`workflow-${workflowResult.id}`);
|
|
748
|
+
|
|
749
|
+
// Create spans from workflow results
|
|
750
|
+
buffer.createSpan('workflow_execution', {
|
|
751
|
+
organization_id: orgId,
|
|
752
|
+
input: workflowResult.input,
|
|
753
|
+
output: workflowResult.output,
|
|
754
|
+
duration_ms: workflowResult.duration
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
for (const step of workflowResult.steps) {
|
|
758
|
+
buffer.createSpan(`step_${step.name}`, {
|
|
759
|
+
input: step.input,
|
|
760
|
+
output: step.output,
|
|
761
|
+
duration_ms: step.duration
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Get transportable spans
|
|
766
|
+
const spans = buffer.getAllSpans();
|
|
767
|
+
|
|
768
|
+
// Conditionally export based on business logic
|
|
769
|
+
const isPremium = orgId.includes('premium');
|
|
770
|
+
|
|
771
|
+
if (isPremium) {
|
|
772
|
+
await manager.processSpans(spans);
|
|
773
|
+
console.log('Exported spans for premium org');
|
|
774
|
+
} else {
|
|
775
|
+
buffer.clearSpans();
|
|
776
|
+
console.log('Skipped spans for free org');
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Example 3: Multi-Destination Routing
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
import { KeywordsAITelemetry } from '@keywordsai/tracing';
|
|
785
|
+
import { FileExporter, AnalyticsExporter } from './exporters';
|
|
786
|
+
|
|
787
|
+
const kai = new KeywordsAITelemetry({ apiKey: 'your-key' });
|
|
788
|
+
|
|
789
|
+
// Add debug file exporter
|
|
790
|
+
kai.addProcessor({
|
|
791
|
+
exporter: new FileExporter('./debug-spans.jsonl'),
|
|
792
|
+
name: 'debug'
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Add analytics exporter with filter
|
|
796
|
+
kai.addProcessor({
|
|
797
|
+
exporter: new AnalyticsExporter(),
|
|
798
|
+
name: 'analytics',
|
|
799
|
+
filter: (span) => !span.name.includes('test')
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
// Route to default KeywordsAI processor
|
|
803
|
+
await kai.withTask(
|
|
804
|
+
{ name: 'production_task' },
|
|
805
|
+
async () => { /* goes to KeywordsAI */ }
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
// Route to debug processor
|
|
809
|
+
await kai.withTask(
|
|
810
|
+
{ name: 'debug_task', processors: 'debug' },
|
|
811
|
+
async () => { /* goes to file */ }
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
// Route to multiple processors
|
|
815
|
+
await kai.withTask(
|
|
816
|
+
{ name: 'important_task', processors: ['debug', 'analytics'] },
|
|
817
|
+
async () => { /* goes to file + analytics */ }
|
|
818
|
+
);
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
## Browser Compatibility
|
|
822
|
+
|
|
823
|
+
The core package is designed to work in both Node.js and browser environments. However, some instrumentations may be Node.js only.
|
|
824
|
+
|
|
825
|
+
## Testing Builds
|
|
826
|
+
|
|
827
|
+
Before publishing, test the built package:
|
|
828
|
+
|
|
829
|
+
```bash
|
|
830
|
+
npm run test:build
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
This builds, packs, installs, and tests the package exactly as users will receive it.
|
|
834
|
+
|
|
835
|
+
## Examples Directory
|
|
836
|
+
|
|
837
|
+
Check out the `examples/` directory for more comprehensive examples:
|
|
838
|
+
- `span-management-example.ts` - Full span management with getClient()
|
|
839
|
+
- `multi-processor-example.ts` - Multi-processor routing examples
|
|
840
|
+
- `span-buffer-example.ts` - Span buffering patterns
|
|
841
|
+
- `basic-usage.ts` - Basic usage patterns
|
|
842
|
+
- `advanced-tracing-example.ts` - Advanced tracing patterns
|
|
843
|
+
|
|
844
|
+
## Migration from v1.0.x
|
|
845
|
+
|
|
846
|
+
All new features are **backward compatible**. Existing code will continue to work without modifications:
|
|
847
|
+
|
|
848
|
+
- Default processor is automatically configured
|
|
849
|
+
- New methods are additive (optional)
|
|
850
|
+
- No breaking changes to existing APIs
|
|
851
|
+
|
|
852
|
+
To use new features, simply import and use them:
|
|
853
|
+
```typescript
|
|
854
|
+
import { getClient } from '@keywordsai/tracing'; // New in v1.1.0
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
## License
|
|
858
|
+
|
|
859
|
+
Apache-2.0
|
|
860
|
+
|