@observyze/sdk 0.1.3 → 0.1.5

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Observyze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,198 +1,145 @@
1
- # @observyze/sdk
2
-
3
- Node.js SDK for Observyze AI Observability Platform.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @observyze/sdk
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ### Auto-Instrumentation (Recommended)
14
-
15
- The easiest way to get started is with auto-instrumentation:
16
-
17
- ```typescript
18
- import OpenAI from 'openai'
19
- import { ObservyzeClient } from '@observyze/sdk'
20
-
21
- // Initialize Observyze
22
- const nw = new ObservyzeClient({
23
- apiKey: process.env.Observyze_API_KEY!,
24
- organizationId: 'your-org-id',
25
- projectId: 'your-project-id'
26
- })
27
-
28
- // Initialize your LLM client
29
- const openai = new OpenAI({
30
- apiKey: process.env.OPENAI_API_KEY!
31
- })
32
-
33
- // Wrap the client - that's it!
34
- nw.wrap(openai)
35
-
36
- // All calls are now automatically traced
37
- const response = await openai.chat.completions.create({
38
- model: 'gpt-4',
39
- messages: [{ role: 'user', content: 'Hello!' }]
40
- })
41
- ```
42
-
43
- **Supported Providers:**
44
- - OpenAI (`chat.completions.create`)
45
- - Anthropic (`messages.create`)
46
- - Streaming responses fully supported
47
-
48
- See [Auto-Instrumentation Guide](./src/instrumentation/README.md) for more details.
49
-
50
- ### Manual Instrumentation
51
-
52
- For more control, you can manually create traces and spans:
53
-
54
- ```typescript
55
- import { ObservyzeClient, SpanType, TraceStatus } from '@observyze/sdk'
56
-
57
- // Initialize the client
58
- const nw = new ObservyzeClient({
59
- apiKey: 'your-api-key',
60
- endpoint: 'https://api.observyze.com',
61
- projectId: 'your-project-id'
62
- })
63
-
64
- // Create a trace
65
- const trace = nw.startTrace('my-ai-workflow')
66
-
67
- // Add a span for an LLM call
68
- const span = trace.startSpan('openai-completion', SpanType.LLM)
69
- span.setInput({ prompt: 'Hello, world!' })
70
- span.setMetadata('model', 'gpt-4')
71
- span.setMetadata('temperature', 0.7)
72
-
73
- // ... perform your LLM call ...
74
-
75
- span.setOutput({ completion: 'Hello! How can I help you?' })
76
- span.setTokens({ input: 10, output: 15, total: 25 })
77
- span.end()
78
-
79
- // End the trace
80
- trace.end(TraceStatus.SUCCESS)
81
-
82
- // Flush traces (or wait for auto-flush)
83
- await nw.flush()
84
-
85
- // Shutdown when done
86
- await nw.shutdown()
87
- ```
88
-
89
- ## Configuration
90
-
91
- ```typescript
92
- interface ClientConfig {
93
- apiKey: string // Required: Your Observyze API key
94
- endpoint?: string // Optional: Ingestion endpoint (default: http://localhost:3001)
95
- batchSize?: number // Optional: Max traces per batch (default: 100)
96
- flushInterval?: number // Optional: Auto-flush interval in ms (default: 5000)
97
- organizationId?: string // Optional: Organization ID
98
- projectId?: string // Optional: Project ID
99
- debug?: boolean // Optional: Enable debug logging (default: false)
100
- dryRun?: boolean // Optional: Don't send traces (default: false)
101
- }
102
- ```
103
-
104
- ## API Reference
105
-
106
- ### ObservyzeClient
107
-
108
- #### `startTrace(name: string, metadata?: Record<string, any>): Trace`
109
-
110
- Start a new trace for an AI workflow.
111
-
112
- #### `flush(): Promise<void>`
113
-
114
- Manually flush buffered traces to the Ingestion Service.
115
-
116
- #### `shutdown(): Promise<void>`
117
-
118
- Shutdown the SDK and flush remaining traces.
119
-
120
- #### `wrap<T>(client: T): T`
121
-
122
- Wrap an LLM client (OpenAI, Anthropic) to enable auto-instrumentation. Returns the wrapped client.
123
-
124
- ```typescript
125
- const openai = new OpenAI({ apiKey: 'key' })
126
- nw.wrap(openai)
127
- ```
128
-
129
- ### Trace
130
-
131
- #### `startSpan(name: string, type: SpanType, parentSpanId?: string): Span`
132
-
133
- Start a new span within the trace.
134
-
135
- #### `setMetadata(key: string, value: any): this`
136
-
137
- Add metadata to the trace.
138
-
139
- #### `addTag(tag: string): this`
140
-
141
- Add a tag to the trace.
142
-
143
- #### `setUserId(userId: string): this`
144
-
145
- Set the user ID associated with this trace.
146
-
147
- #### `setSessionId(sessionId: string): this`
148
-
149
- Set the session ID associated with this trace.
150
-
151
- #### `end(status?: TraceStatus): void`
152
-
153
- End the trace with a final status.
154
-
155
- ### Span
156
-
157
- #### `setInput(input: any): this`
158
-
159
- Set the input data for the span.
160
-
161
- #### `setOutput(output: any): this`
162
-
163
- Set the output data for the span.
164
-
165
- #### `setError(error: Error): this`
166
-
167
- Record an error that occurred during span execution.
168
-
169
- #### `setMetadata(key: string, value: any): this`
170
-
171
- Add metadata to the span.
172
-
173
- #### `setTokens(tokens: TokenUsage): this`
174
-
175
- Set token usage information.
176
-
177
- #### `end(): void`
178
-
179
- End the span and calculate duration.
180
-
181
- ## Span Types
182
-
183
- - `SpanType.LLM` - LLM API calls (OpenAI, Anthropic, etc.)
184
- - `SpanType.TOOL` - Tool invocations
185
- - `SpanType.AGENT` - Agent executions
186
- - `SpanType.CHAIN` - Chain operations
187
- - `SpanType.RETRIEVAL` - Retrieval operations (RAG)
188
-
189
- ## Trace Status
190
-
191
- - `TraceStatus.SUCCESS` - Workflow completed successfully
192
- - `TraceStatus.ERROR` - Workflow failed with an error
193
- - `TraceStatus.TIMEOUT` - Workflow timed out
194
- - `TraceStatus.RUNNING` - Workflow is still running
195
-
196
- ## License
197
-
198
- MIT
1
+ # Observyze Node.js SDK
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@observyze/sdk.svg?style=flat-square&color=blue)](https://www.npmjs.com/package/@observyze/sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6.svg?style=flat-square)](https://www.typescriptlang.org/)
6
+
7
+ The official TypeScript/Node.js SDK for [Observyze](https://observyze.com), an enterprise AI observability, evaluation, and governance platform.
8
+
9
+ ## Features
10
+
11
+ - 🚀 **1-Line Auto-Instrumentation:** Support for OpenAI, Anthropic, Google Gemini, Vercel AI SDK, LangChain, and LlamaIndex.
12
+ - 🔒 **Client-Side PII Redaction:** Redacts emails, credit cards, SSNs, API keys, passwords, and custom sensitive keys before data leaves your server.
13
+ - ⚡ **Zero-Latency Async Buffering:** Non-blocking background flush with exponential backoff retries.
14
+ - 🛡️ **Execution Budgets:** Limit tokens, API call counts, and execution deadlines for agentic workflows.
15
+ - 🌐 **OpenTelemetry Exporter:** Plug directly into standard OpenTelemetry pipelines with `ObservyzeSpanExporter`.
16
+ - 🔎 **End-to-End Connection Testing:** Built-in `testConnection()` utility.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @observyze/sdk
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Quickstart
29
+
30
+ ### 1. OpenAI
31
+
32
+ ```typescript
33
+ import { ObservyzeClient } from '@observyze/sdk'
34
+ import OpenAI from 'openai'
35
+
36
+ const nw = new ObservyzeClient({
37
+ apiKey: process.env.OBSERVYZE_API_KEY!,
38
+ projectId: 'my-ai-app'
39
+ })
40
+
41
+ const openai = nw.wrap(new OpenAI())
42
+
43
+ // All chat completions are now automatically traced!
44
+ const response = await openai.chat.completions.create({
45
+ model: 'gpt-4o',
46
+ messages: [{ role: 'user', content: 'Explain quantum computing simply.' }]
47
+ })
48
+ ```
49
+
50
+ ### 2. Anthropic
51
+
52
+ ```typescript
53
+ import { ObservyzeClient } from '@observyze/sdk'
54
+ import Anthropic from '@anthropic-ai/sdk'
55
+
56
+ const nw = new ObservyzeClient({ apiKey: process.env.OBSERVYZE_API_KEY! })
57
+ const anthropic = nw.wrap(new Anthropic())
58
+
59
+ const response = await anthropic.messages.create({
60
+ model: 'claude-3-5-sonnet-20241022',
61
+ max_tokens: 1024,
62
+ messages: [{ role: 'user', content: 'Hello, Claude!' }]
63
+ })
64
+ ```
65
+
66
+ ### 3. Google Gemini
67
+
68
+ ```typescript
69
+ import { ObservyzeClient, wrapGemini } from '@observyze/sdk'
70
+ import { GoogleGenerativeAI } from '@google/generative-ai'
71
+
72
+ const nw = new ObservyzeClient({ apiKey: process.env.OBSERVYZE_API_KEY! })
73
+ const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!)
74
+ const model = wrapGemini(genAI.getGenerativeModel({ model: 'gemini-1.5-pro' }), nw)
75
+
76
+ const result = await model.generateContent('Why is the sky blue?')
77
+ ```
78
+
79
+ ### 4. Vercel AI SDK
80
+
81
+ ```typescript
82
+ import { ObservyzeClient, wrapVercelAI } from '@observyze/sdk'
83
+ import { generateText, streamText } from 'ai'
84
+ import { openai } from '@ai-sdk/openai'
85
+
86
+ const nw = new ObservyzeClient({ apiKey: process.env.OBSERVYZE_API_KEY! })
87
+ const ai = wrapVercelAI({ generateText, streamText }, nw)
88
+
89
+ const { text } = await ai.generateText({
90
+ model: openai('gpt-4o'),
91
+ prompt: 'Write a haiku about AI observability.',
92
+ })
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Active Agent Execution Budgets
98
+
99
+ Cap calls, tokens, and wall-clock duration for recursive or multi-step agent loops:
100
+
101
+ ```typescript
102
+ const budget = nw.createExecutionBudget({
103
+ maxCalls: 15,
104
+ maxTokens: 40_000,
105
+ timeoutMs: 60_000,
106
+ })
107
+
108
+ const result = await budget.run(async (signal) => {
109
+ return openai.chat.completions.create({
110
+ model: 'gpt-4o',
111
+ messages: [{ role: 'user', content: 'Run agent step' }]
112
+ }, { signal })
113
+ })
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Configuration
119
+
120
+ | Option | Type | Default | Description |
121
+ |---|---|---|---|
122
+ | `apiKey` | `string` | **Required** | Your Observyze API key |
123
+ | `endpoint` | `string` | `https://api.observyze.com` | API endpoint |
124
+ | `batchSize` | `number` | `100` | Max traces buffered before auto-flush |
125
+ | `flushInterval` | `number` | `5000` | Flush interval in ms |
126
+ | `enablePiiRedaction` | `boolean` | `true` | Client-side PII masking |
127
+ | `captureContent` | `boolean` | `true` | Set to `false` for metadata-only mode |
128
+ | `requestTimeoutMs` | `number` | `10000` | Per-request HTTP timeout in ms |
129
+ | `dryRun` | `boolean` | `false` | Disable network delivery (for testing) |
130
+ | `debug` | `boolean` | `false` | Enable verbose debug logging |
131
+
132
+ ---
133
+
134
+ ## Verify Integration
135
+
136
+ ```typescript
137
+ const result = await nw.testConnection()
138
+ console.log(result.ok, result.message)
139
+ ```
140
+
141
+ ---
142
+
143
+ ## License
144
+
145
+ MIT © [Observyze](https://observyze.com)
@@ -1,20 +1,59 @@
1
1
  // src/types.ts
2
- import { SpanType, TraceStatus } from "@observyze/types";
2
+ var SpanType = /* @__PURE__ */ ((SpanType3) => {
3
+ SpanType3["LLM"] = "llm";
4
+ SpanType3["TOOL"] = "tool";
5
+ SpanType3["AGENT"] = "agent";
6
+ SpanType3["CHAIN"] = "chain";
7
+ SpanType3["RETRIEVAL"] = "retrieval";
8
+ SpanType3["EMBEDDING"] = "embedding";
9
+ SpanType3["CUSTOM"] = "custom";
10
+ return SpanType3;
11
+ })(SpanType || {});
12
+ var TraceStatus = /* @__PURE__ */ ((TraceStatus2) => {
13
+ TraceStatus2["SUCCESS"] = "success";
14
+ TraceStatus2["ERROR"] = "error";
15
+ TraceStatus2["TIMEOUT"] = "timeout";
16
+ TraceStatus2["RUNNING"] = "running";
17
+ return TraceStatus2;
18
+ })(TraceStatus || {});
3
19
 
4
20
  // src/trace.ts
5
21
  import { randomUUID } from "crypto";
6
22
  function generateId() {
7
23
  return `${Date.now()}-${randomUUID().substring(0, 8)}`;
8
24
  }
25
+ var SAFE_OPERATIONAL_METADATA = /* @__PURE__ */ new Set([
26
+ "provider",
27
+ "model",
28
+ "temperature",
29
+ "max_tokens",
30
+ "max_completion_tokens",
31
+ "max_output_tokens",
32
+ "latency_ms",
33
+ "streaming",
34
+ "stream_completed",
35
+ "output_truncated",
36
+ "token_usage_source",
37
+ "cost_source",
38
+ "known_pricing",
39
+ "source",
40
+ "lifecycle",
41
+ "invocation_type"
42
+ ]);
43
+ function isSafeOperationalValue(value) {
44
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
45
+ }
9
46
  var Span = class {
10
47
  data;
11
48
  startTime;
12
- constructor(name, type, parentSpanId) {
49
+ captureContent;
50
+ constructor(name, type, parentSpanId, captureContent = true) {
13
51
  this.startTime = Date.now();
52
+ this.captureContent = captureContent;
14
53
  this.data = {
15
54
  span_id: generateId(),
16
55
  parent_span_id: parentSpanId,
17
- name,
56
+ name: captureContent ? name : "[CONTENT_CAPTURE_DISABLED]",
18
57
  type,
19
58
  start_time: new Date(this.startTime),
20
59
  end_time: new Date(this.startTime),
@@ -29,14 +68,14 @@ var Span = class {
29
68
  * Set the input data for this span
30
69
  */
31
70
  setInput(input) {
32
- this.data.input = input;
71
+ if (this.captureContent) this.data.input = input;
33
72
  return this;
34
73
  }
35
74
  /**
36
75
  * Set the output data for this span
37
76
  */
38
77
  setOutput(output) {
39
- this.data.output = output;
78
+ if (this.captureContent) this.data.output = output;
40
79
  return this;
41
80
  }
42
81
  /**
@@ -44,9 +83,9 @@ var Span = class {
44
83
  */
45
84
  setError(error) {
46
85
  this.data.error = {
47
- message: error.message,
48
- stack: error.stack,
49
- code: error.code
86
+ message: this.captureContent ? error.message : "Error details omitted because content capture is disabled",
87
+ stack: this.captureContent ? error.stack : void 0,
88
+ code: this.captureContent ? error.code : void 0
50
89
  };
51
90
  return this;
52
91
  }
@@ -54,14 +93,16 @@ var Span = class {
54
93
  * Set metadata for this span
55
94
  */
56
95
  setMetadata(key, value) {
57
- this.data.metadata[key] = value;
96
+ if (this.captureContent || SAFE_OPERATIONAL_METADATA.has(key) && isSafeOperationalValue(value)) {
97
+ this.data.metadata[key] = value;
98
+ }
58
99
  return this;
59
100
  }
60
101
  /**
61
102
  * Set multiple metadata fields at once
62
103
  */
63
104
  setMetadataAll(metadata) {
64
- this.data.metadata = { ...this.data.metadata, ...metadata };
105
+ for (const [key, value] of Object.entries(metadata)) this.setMetadata(key, value);
65
106
  return this;
66
107
  }
67
108
  /**
@@ -97,14 +138,16 @@ var Trace = class {
97
138
  startTime;
98
139
  spans = [];
99
140
  ended = false;
100
- constructor(name, organizationId, projectId) {
141
+ captureContent;
142
+ constructor(name, organizationId, projectId, captureContent = true) {
101
143
  this.startTime = Date.now();
144
+ this.captureContent = captureContent;
102
145
  this.data = {
103
146
  trace_id: generateId(),
104
147
  organization_id: organizationId,
105
148
  project_id: projectId,
106
- name,
107
- status: TraceStatus.RUNNING,
149
+ name: captureContent ? name : "[CONTENT_CAPTURE_DISABLED]",
150
+ status: "running" /* RUNNING */,
108
151
  start_time: new Date(this.startTime),
109
152
  end_time: new Date(this.startTime),
110
153
  // Will be updated on end()
@@ -121,7 +164,7 @@ var Trace = class {
121
164
  if (this.ended) {
122
165
  throw new Error("Cannot start span on an ended trace");
123
166
  }
124
- const span = new Span(name, type, parentSpanId);
167
+ const span = new Span(name, type, parentSpanId, this.captureContent);
125
168
  this.spans.push(span);
126
169
  return span;
127
170
  }
@@ -129,20 +172,23 @@ var Trace = class {
129
172
  * Add metadata to the trace
130
173
  */
131
174
  setMetadata(key, value) {
132
- this.data.metadata[key] = value;
175
+ if (this.captureContent || SAFE_OPERATIONAL_METADATA.has(key) && isSafeOperationalValue(value)) {
176
+ this.data.metadata[key] = value;
177
+ }
133
178
  return this;
134
179
  }
135
180
  /**
136
181
  * Set multiple metadata fields at once
137
182
  */
138
183
  setMetadataAll(metadata) {
139
- this.data.metadata = { ...this.data.metadata, ...metadata };
184
+ for (const [key, value] of Object.entries(metadata)) this.setMetadata(key, value);
140
185
  return this;
141
186
  }
142
187
  /**
143
188
  * Add tags to the trace
144
189
  */
145
190
  addTag(tag) {
191
+ if (!this.captureContent) return this;
146
192
  if (!this.data.tags.includes(tag)) {
147
193
  this.data.tags.push(tag);
148
194
  }
@@ -159,20 +205,20 @@ var Trace = class {
159
205
  * Set the user ID associated with this trace
160
206
  */
161
207
  setUserId(userId) {
162
- this.data.user_id = userId;
208
+ if (this.captureContent) this.data.user_id = userId;
163
209
  return this;
164
210
  }
165
211
  /**
166
212
  * Set the session ID associated with this trace
167
213
  */
168
214
  setSessionId(sessionId) {
169
- this.data.session_id = sessionId;
215
+ if (this.captureContent) this.data.session_id = sessionId;
170
216
  return this;
171
217
  }
172
218
  /**
173
219
  * End the trace with a final status
174
220
  */
175
- end(status = TraceStatus.SUCCESS) {
221
+ end(status = "success" /* SUCCESS */) {
176
222
  if (this.ended) {
177
223
  return;
178
224
  }
@@ -273,9 +319,9 @@ var ObservyzeSpanExporter = class {
273
319
  const statusCode = status?.code;
274
320
  if (statusCode === 2) {
275
321
  oSpan.setError(new Error(status?.message || "OTel span error"));
276
- trace.end(TraceStatus.ERROR);
322
+ trace.end("error" /* ERROR */);
277
323
  } else {
278
- trace.end(TraceStatus.SUCCESS);
324
+ trace.end("success" /* SUCCESS */);
279
325
  }
280
326
  }
281
327
  resultCallback({ code: 0 });