observa-sdk 0.0.7 → 0.0.9

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/README.md CHANGED
@@ -41,18 +41,58 @@ const observa = init({
41
41
 
42
42
  ## Quick Start
43
43
 
44
- ### JWT-based API Key (Recommended)
44
+ ### Auto-Capture with OpenAI (Recommended)
45
45
 
46
- After signing up, you'll receive a JWT-formatted API key that automatically encodes your tenant and project context:
46
+ The easiest way to track LLM calls is using the `observeOpenAI()` wrapper - it automatically captures 90%+ of your LLM interactions:
47
47
 
48
48
  ```typescript
49
49
  import { init } from "observa-sdk";
50
+ import OpenAI from "openai";
50
51
 
51
- // Initialize with JWT API key from signup (automatically extracts tenant/project context)
52
+ // Initialize Observa
52
53
  const observa = init({
53
54
  apiKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // Your API key from signup
54
55
  });
55
56
 
57
+ // Initialize OpenAI client
58
+ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
59
+
60
+ // Wrap with Observa (automatic tracing)
61
+ const wrappedOpenAI = observa.observeOpenAI(openai, {
62
+ name: 'my-app',
63
+ userId: 'user_123',
64
+ redact: (data) => {
65
+ // Optional: Scrub sensitive data before sending to Observa
66
+ if (data?.messages) {
67
+ return { ...data, messages: '[REDACTED]' };
68
+ }
69
+ return data;
70
+ }
71
+ });
72
+
73
+ // Use wrapped client - automatically tracked!
74
+ const response = await wrappedOpenAI.chat.completions.create({
75
+ model: 'gpt-4',
76
+ messages: [{ role: 'user', content: 'Hello!' }],
77
+ });
78
+
79
+ // Streaming also works automatically
80
+ const stream = await wrappedOpenAI.chat.completions.create({
81
+ model: 'gpt-4',
82
+ messages: [{ role: 'user', content: 'Hello!' }],
83
+ stream: true,
84
+ });
85
+
86
+ for await (const chunk of stream) {
87
+ process.stdout.write(chunk.choices[0]?.delta?.content || '');
88
+ }
89
+ ```
90
+
91
+ ### Legacy Manual Tracking
92
+
93
+ For more control, you can still use the manual `track()` method:
94
+
95
+ ```typescript
56
96
  // Track AI interactions with simple wrapping
57
97
  const response = await observa.track({ query: "What is the weather?" }, () =>
58
98
  fetch("https://api.openai.com/v1/chat/completions", {
@@ -67,18 +107,26 @@ const response = await observa.track({ query: "What is the weather?" }, () =>
67
107
  );
68
108
  ```
69
109
 
70
- ### Legacy API Key Format
110
+ ### Manual Tracking (Advanced)
111
+
112
+ For more control over what gets tracked, use the manual tracking methods:
71
113
 
72
114
  ```typescript
73
- // For backward compatibility, you can still provide tenantId/projectId explicitly
74
- const observa = init({
75
- apiKey: "your-api-key",
76
- tenantId: "acme_corp",
77
- projectId: "prod_app",
78
- environment: "prod", // optional, defaults to "dev"
115
+ // Use trackLLMCall for fine-grained control
116
+ const spanId = observa.trackLLMCall({
117
+ model: 'gpt-4',
118
+ input: 'Hello!',
119
+ output: 'Hi there!',
120
+ inputTokens: 10,
121
+ outputTokens: 5,
122
+ latencyMs: 1200,
123
+ operationName: 'chat',
124
+ providerName: 'openai',
79
125
  });
80
126
  ```
81
127
 
128
+ See the [API Reference](#api-reference) section for all available methods.
129
+
82
130
  ## Multi-Tenant Architecture
83
131
 
84
132
  Observa SDK uses a **multi-tenant shared runtime architecture** for optimal cost, scalability, and operational simplicity.
@@ -141,6 +189,9 @@ interface ObservaInitConfig {
141
189
  projectId?: string;
142
190
  environment?: "dev" | "prod";
143
191
 
192
+ // Observa backend URL (optional, defaults to https://api.observa.ai)
193
+ apiUrl?: string;
194
+
144
195
  // SDK behavior
145
196
  mode?: "development" | "production";
146
197
  sampleRate?: number; // 0..1, default: 1.0
@@ -153,6 +204,7 @@ interface ObservaInitConfig {
153
204
  - **apiKey**: Your Observa API key (JWT format recommended)
154
205
  - **tenantId** / **projectId**: Required only for legacy API keys
155
206
  - **environment**: `"dev"` or `"prod"` (defaults to `"dev"`)
207
+ - **apiUrl**: Observa backend URL (optional, defaults to `https://api.observa.ai`)
156
208
  - **mode**: SDK mode - `"development"` logs traces to console, `"production"` sends to Observa
157
209
  - **sampleRate**: Fraction of traces to record (0.0 to 1.0)
158
210
  - **maxResponseChars**: Maximum response size to capture (prevents huge payloads)
@@ -163,9 +215,246 @@ interface ObservaInitConfig {
163
215
 
164
216
  Initialize the Observa SDK instance.
165
217
 
218
+ **Example:**
219
+ ```typescript
220
+ import { init } from "observa-sdk";
221
+
222
+ const observa = init({
223
+ apiKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // Your JWT API key
224
+ apiUrl: "https://api.observa.ai", // Optional, defaults to https://api.observa.ai
225
+ environment: "prod", // Optional, defaults to "dev"
226
+ mode: "production", // Optional, "development" or "production"
227
+ sampleRate: 1.0, // Optional, 0.0 to 1.0, default: 1.0
228
+ maxResponseChars: 50000, // Optional, default: 50000
229
+ });
230
+ ```
231
+
232
+ ### `observa.observeOpenAI(client, options?)`
233
+
234
+ Wrap an OpenAI client with automatic tracing. This is the **recommended** way to track LLM calls.
235
+
236
+ **Parameters:**
237
+ - `client` (required): OpenAI client instance
238
+ - `options` (optional):
239
+ - `name` (optional): Application/service name
240
+ - `tags` (optional): Array of tags
241
+ - `userId` (optional): User identifier
242
+ - `sessionId` (optional): Session identifier
243
+ - `redact` (optional): Function to sanitize data before sending to Observa
244
+
245
+ **Returns**: Wrapped OpenAI client (use it exactly like the original client)
246
+
247
+ **Example:**
248
+ ```typescript
249
+ import OpenAI from 'openai';
250
+
251
+ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
252
+ const wrapped = observa.observeOpenAI(openai, {
253
+ name: 'my-app',
254
+ userId: 'user_123',
255
+ redact: (data) => {
256
+ // Sanitize sensitive data
257
+ if (data?.messages) {
258
+ return { ...data, messages: '[REDACTED]' };
259
+ }
260
+ return data;
261
+ }
262
+ });
263
+
264
+ // Use wrapped client - automatically tracked!
265
+ const response = await wrapped.chat.completions.create({
266
+ model: 'gpt-4',
267
+ messages: [{ role: 'user', content: 'Hello!' }],
268
+ });
269
+ ```
270
+
271
+ ### `observa.observeAnthropic(client, options?)`
272
+
273
+ Wrap an Anthropic client with automatic tracing. Same API as `observeOpenAI()`.
274
+
275
+ **Example:**
276
+ ```typescript
277
+ import Anthropic from '@anthropic-ai/sdk';
278
+
279
+ const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
280
+ const wrapped = observa.observeAnthropic(anthropic, {
281
+ name: 'my-app',
282
+ redact: (data) => ({ ...data, messages: '[REDACTED]' })
283
+ });
284
+
285
+ // Use wrapped client - automatically tracked!
286
+ const response = await wrapped.messages.create({
287
+ model: 'claude-3-opus-20240229',
288
+ max_tokens: 1024,
289
+ messages: [{ role: 'user', content: 'Hello!' }],
290
+ });
291
+ ```
292
+
293
+ ### `observa.startTrace(options)`
294
+
295
+ Start a new trace for manual trace management. Returns the trace ID.
296
+
297
+ **Parameters:**
298
+ - `options.name` (optional): Trace name
299
+ - `options.metadata` (optional): Custom metadata object
300
+ - `options.conversationId` (optional): Conversation identifier
301
+ - `options.sessionId` (optional): Session identifier
302
+ - `options.userId` (optional): User identifier
303
+
304
+ **Returns**: `string` - The trace ID
305
+
306
+ **Example:**
307
+ ```typescript
308
+ const traceId = observa.startTrace({
309
+ name: "RAG Query",
310
+ conversationId: "conv-123",
311
+ userId: "user-456",
312
+ metadata: { feature: "chat", version: "2.0" }
313
+ });
314
+ ```
315
+
316
+ ### `observa.endTrace(options)`
317
+
318
+ End the current trace and send all buffered events. Must be called after `startTrace()`.
319
+
320
+ **Parameters:**
321
+ - `options.outcome` (optional): `"success"` | `"error"` | `"timeout"` (default: `"success"`)
322
+
323
+ **Returns**: `Promise<string>` - The trace ID
324
+
325
+ **Example:**
326
+ ```typescript
327
+ await observa.endTrace({ outcome: "success" });
328
+ ```
329
+
330
+ ### `observa.trackLLMCall(options)` ⭐ NEW - Full OTEL Support
331
+
332
+ Track an LLM call with complete OTEL compliance. **This is the recommended method** for tracking LLM calls.
333
+
334
+ **Parameters:**
335
+ - `model` (required): Model name
336
+ - `input`, `output`: Input/output text
337
+ - `inputTokens`, `outputTokens`, `totalTokens`: Token counts
338
+ - `latencyMs` (required): Latency in milliseconds
339
+ - `operationName`: OTEL operation name ("chat", "text_completion", "generate_content")
340
+ - `providerName`: Provider name ("openai", "anthropic", etc.) - auto-inferred from model if not provided
341
+ - `responseModel`: Actual model used (vs requested)
342
+ - `topK`, `topP`, `frequencyPenalty`, `presencePenalty`, `stopSequences`, `seed`: Sampling parameters
343
+ - `inputCost`, `outputCost`: Structured cost tracking
344
+ - `inputMessages`, `outputMessages`, `systemInstructions`: Structured message objects
345
+ - `serverAddress`, `serverPort`: Server metadata
346
+ - `conversationIdOtel`: OTEL conversation ID
347
+ - And more... (see SDK_SOTA_IMPLEMENTATION.md for complete list)
348
+
349
+ **Example:**
350
+ ```typescript
351
+ const spanId = observa.trackLLMCall({
352
+ model: "gpt-4-turbo",
353
+ input: "What is AI?",
354
+ output: "AI is...",
355
+ inputTokens: 10,
356
+ outputTokens: 50,
357
+ latencyMs: 1200,
358
+ operationName: "chat",
359
+ providerName: "openai", // Auto-inferred if not provided
360
+ temperature: 0.7,
361
+ topP: 0.9,
362
+ inputCost: 0.00245,
363
+ outputCost: 0.01024
364
+ });
365
+ ```
366
+
367
+ ### `observa.trackEmbedding(options)` ⭐ NEW
368
+
369
+ Track an embedding operation with full OTEL support.
370
+
371
+ **Example:**
372
+ ```typescript
373
+ const spanId = observa.trackEmbedding({
374
+ model: "text-embedding-ada-002",
375
+ dimensionCount: 1536,
376
+ inputTokens: 10,
377
+ outputTokens: 1536,
378
+ latencyMs: 45,
379
+ cost: 0.0001
380
+ });
381
+ ```
382
+
383
+ ### `observa.trackVectorDbOperation(options)` ⭐ NEW
384
+
385
+ Track vector database operations (Pinecone, Weaviate, Qdrant, etc.).
386
+
387
+ **Example:**
388
+ ```typescript
389
+ const spanId = observa.trackVectorDbOperation({
390
+ operationType: "vector_search",
391
+ indexName: "documents",
392
+ vectorDimensions: 1536,
393
+ resultsCount: 10,
394
+ latencyMs: 30,
395
+ cost: 0.0005,
396
+ providerName: "pinecone"
397
+ });
398
+ ```
399
+
400
+ ### `observa.trackCacheOperation(options)` ⭐ NEW
401
+
402
+ Track cache hit/miss operations.
403
+
404
+ **Example:**
405
+ ```typescript
406
+ const spanId = observa.trackCacheOperation({
407
+ cacheBackend: "redis",
408
+ hitStatus: "hit",
409
+ latencyMs: 2,
410
+ savedCost: 0.01269
411
+ });
412
+ ```
413
+
414
+ ### `observa.trackAgentCreate(options)` ⭐ NEW
415
+
416
+ Track agent creation.
417
+
418
+ **Example:**
419
+ ```typescript
420
+ const spanId = observa.trackAgentCreate({
421
+ agentName: "Customer Support Agent",
422
+ toolsBound: ["web_search", "database_query"],
423
+ modelConfig: { model: "gpt-4-turbo", temperature: 0.7 }
424
+ });
425
+ ```
426
+
427
+ ### `observa.trackToolCall(options)` - Enhanced
428
+
429
+ Track a tool call with OTEL standardization.
430
+
431
+ **New Parameters:**
432
+ - `toolType`: "function" | "extension" | "datastore"
433
+ - `toolDescription`: Tool description
434
+ - `toolCallId`: Unique tool invocation ID
435
+ - `errorType`, `errorCategory`: Structured error classification
436
+
437
+ ### `observa.trackRetrieval(options)` - Enhanced
438
+
439
+ Track retrieval operations with vector metadata.
440
+
441
+ **New Parameters:**
442
+ - `embeddingModel`: Model used for embeddings
443
+ - `embeddingDimensions`: Vector dimensions
444
+ - `vectorMetric`: Similarity metric
445
+ - `rerankScore`, `fusionMethod`, `qualityScore`: Quality metrics
446
+
447
+ ### `observa.trackError(options)` - Enhanced
448
+
449
+ Track errors with structured classification.
450
+
451
+ **New Parameters:**
452
+ - `errorCategory`: Error category
453
+ - `errorCode`: Error code
454
+
166
455
  ### `observa.track(event, action)`
167
456
 
168
- Track an AI interaction.
457
+ Track an AI interaction (legacy method, still supported).
169
458
 
170
459
  **Parameters**:
171
460
 
@@ -201,6 +490,166 @@ const response = await observa.track(
201
490
  );
202
491
  ```
203
492
 
493
+ ### `observa.trackFeedback(options)`
494
+
495
+ Track user feedback (likes, dislikes, ratings, corrections) for AI interactions.
496
+
497
+ **Parameters**:
498
+
499
+ - `options.type` (required): Feedback type - `"like"` | `"dislike"` | `"rating"` | `"correction"`
500
+ - `options.rating` (optional): Rating value (1-5 scale, automatically clamped). Required for `"rating"` type.
501
+ - `options.comment` (optional): User comment/feedback text
502
+ - `options.outcome` (optional): Outcome classification - `"success"` | `"failure"` | `"partial"`
503
+ - `options.conversationId` (optional): Conversation identifier for context
504
+ - `options.sessionId` (optional): Session identifier for context
505
+ - `options.userId` (optional): User identifier for context
506
+ - `options.messageIndex` (optional): Position in conversation (1, 2, 3...)
507
+ - `options.parentMessageId` (optional): For threaded conversations
508
+ - `options.agentName` (optional): Agent/application name
509
+ - `options.version` (optional): Application version
510
+ - `options.route` (optional): API route/endpoint
511
+ - `options.parentSpanId` (optional): Attach feedback to a specific span (e.g., LLM call span)
512
+ - `options.spanId` (optional): Custom span ID for feedback (auto-generated if not provided)
513
+
514
+ **Returns**: `string` - The span ID of the feedback event
515
+
516
+ **Examples**:
517
+
518
+ #### Basic Like/Dislike Feedback
519
+
520
+ ```typescript
521
+ // User clicks "like" button after receiving AI response
522
+ const feedbackSpanId = observa.trackFeedback({
523
+ type: "like",
524
+ outcome: "success",
525
+ conversationId: "conv-123",
526
+ userId: "user-456",
527
+ });
528
+
529
+ // User clicks "dislike" button
530
+ observa.trackFeedback({
531
+ type: "dislike",
532
+ outcome: "failure",
533
+ comment: "The answer was incorrect",
534
+ conversationId: "conv-123",
535
+ userId: "user-456",
536
+ });
537
+ ```
538
+
539
+ #### Rating Feedback (1-5 Scale)
540
+
541
+ ```typescript
542
+ // User provides a 5-star rating
543
+ observa.trackFeedback({
544
+ type: "rating",
545
+ rating: 5, // Automatically clamped to 1-5 range
546
+ comment: "Excellent response!",
547
+ outcome: "success",
548
+ conversationId: "conv-123",
549
+ userId: "user-456",
550
+ });
551
+
552
+ // Rating is automatically validated (e.g., 10 becomes 5, -1 becomes 1)
553
+ observa.trackFeedback({
554
+ type: "rating",
555
+ rating: 10, // Will be clamped to 5
556
+ conversationId: "conv-123",
557
+ });
558
+ ```
559
+
560
+ #### Correction Feedback
561
+
562
+ ```typescript
563
+ // User provides correction/feedback
564
+ observa.trackFeedback({
565
+ type: "correction",
566
+ comment: "The capital of France is Paris, not Lyon",
567
+ outcome: "partial",
568
+ conversationId: "conv-123",
569
+ userId: "user-456",
570
+ });
571
+ ```
572
+
573
+ #### Linking Feedback to Specific Spans
574
+
575
+ ```typescript
576
+ // Start a trace and track LLM call
577
+ const traceId = observa.startTrace({
578
+ conversationId: "conv-123",
579
+ userId: "user-456",
580
+ });
581
+
582
+ const llmSpanId = observa.trackLLMCall({
583
+ model: "gpt-4",
584
+ input: "What is the capital of France?",
585
+ output: "The capital of France is Paris.",
586
+ // ... other LLM call data
587
+ });
588
+
589
+ // Link feedback directly to the LLM call span
590
+ observa.trackFeedback({
591
+ type: "like",
592
+ parentSpanId: llmSpanId, // Attach feedback to the specific LLM call
593
+ conversationId: "conv-123",
594
+ userId: "user-456",
595
+ });
596
+ ```
597
+
598
+ #### Full Context Feedback
599
+
600
+ ```typescript
601
+ // Track feedback with complete context for analytics
602
+ observa.trackFeedback({
603
+ type: "rating",
604
+ rating: 4,
605
+ comment: "Good answer, but could be more detailed",
606
+ outcome: "partial",
607
+ conversationId: "conv-123",
608
+ sessionId: "session-789",
609
+ userId: "user-456",
610
+ messageIndex: 3,
611
+ agentName: "customer-support-bot",
612
+ version: "v2.1.0",
613
+ route: "/api/chat",
614
+ });
615
+ ```
616
+
617
+ #### Feedback in Conversation Flow
618
+
619
+ ```typescript
620
+ // Track feedback as part of a conversation
621
+ const traceId = observa.startTrace({
622
+ conversationId: "conv-123",
623
+ sessionId: "session-789",
624
+ userId: "user-456",
625
+ messageIndex: 1,
626
+ });
627
+
628
+ // ... perform AI operations ...
629
+
630
+ // User provides feedback after message 1
631
+ observa.trackFeedback({
632
+ type: "like",
633
+ conversationId: "conv-123",
634
+ sessionId: "session-789",
635
+ userId: "user-456",
636
+ messageIndex: 1, // Link to specific message in conversation
637
+ });
638
+
639
+ await observa.endTrace();
640
+ ```
641
+
642
+ **Best Practices**:
643
+
644
+ 1. **Always include context**: Provide `conversationId`, `userId`, and `sessionId` when available for better analytics
645
+ 2. **Link to spans**: Use `parentSpanId` to attach feedback to specific LLM calls or operations
646
+ 3. **Use appropriate types**:
647
+ - `"like"` / `"dislike"` for binary feedback
648
+ - `"rating"` for 1-5 star ratings
649
+ - `"correction"` for user corrections or detailed feedback
650
+ 4. **Include comments**: Comments provide valuable qualitative feedback for improving AI responses
651
+ 5. **Set outcome**: Use `outcome` to classify feedback (`"success"` for positive, `"failure"` for negative, `"partial"` for mixed)
652
+
204
653
  ## Data Captured
205
654
 
206
655
  The SDK automatically captures: