@observyze/sdk 0.1.2 → 0.1.3

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
@@ -1,6 +1,6 @@
1
- # Observyze Node.js SDK
1
+ # @observyze/sdk
2
2
 
3
- The official Node.js SDK for Observyze.
3
+ Node.js SDK for Observyze AI Observability Platform.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,26 +8,191 @@ The official Node.js SDK for Observyze.
8
8
  npm install @observyze/sdk
9
9
  ```
10
10
 
11
- ## Basic Usage
11
+ ## Quick Start
12
+
13
+ ### Auto-Instrumentation (Recommended)
14
+
15
+ The easiest way to get started is with auto-instrumentation:
12
16
 
13
17
  ```typescript
14
- import { ObservyzeClient } from '@observyze/sdk'
15
18
  import OpenAI from 'openai'
19
+ import { ObservyzeClient } from '@observyze/sdk'
16
20
 
21
+ // Initialize Observyze
17
22
  const nw = new ObservyzeClient({
18
- apiKey: process.env.OBSERVYZE_API_KEY,
19
- projectId: 'my-project'
23
+ apiKey: process.env.Observyze_API_KEY!,
24
+ organizationId: 'your-org-id',
25
+ projectId: 'your-project-id'
20
26
  })
21
27
 
22
- const openai = nw.wrap(new OpenAI())
28
+ // Initialize your LLM client
29
+ const openai = new OpenAI({
30
+ apiKey: process.env.OPENAI_API_KEY!
31
+ })
23
32
 
24
- // All calls are now automatically traced!
33
+ // Wrap the client - that's it!
34
+ nw.wrap(openai)
35
+
36
+ // All calls are now automatically traced
25
37
  const response = await openai.chat.completions.create({
26
38
  model: 'gpt-4',
27
39
  messages: [{ role: 'user', content: 'Hello!' }]
28
40
  })
29
41
  ```
30
42
 
31
- ## Documentation
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
32
197
 
33
- Full documentation is available at [observyze.com/docs](https://observyze.com/docs).
198
+ MIT
@@ -0,0 +1,316 @@
1
+ // src/types.ts
2
+ import { SpanType, TraceStatus } from "@observyze/types";
3
+
4
+ // src/trace.ts
5
+ import { randomUUID } from "crypto";
6
+ function generateId() {
7
+ return `${Date.now()}-${randomUUID().substring(0, 8)}`;
8
+ }
9
+ var Span = class {
10
+ data;
11
+ startTime;
12
+ constructor(name, type, parentSpanId) {
13
+ this.startTime = Date.now();
14
+ this.data = {
15
+ span_id: generateId(),
16
+ parent_span_id: parentSpanId,
17
+ name,
18
+ type,
19
+ start_time: new Date(this.startTime),
20
+ end_time: new Date(this.startTime),
21
+ // Will be updated on end()
22
+ duration_ms: 0,
23
+ input: null,
24
+ output: null,
25
+ metadata: {}
26
+ };
27
+ }
28
+ /**
29
+ * Set the input data for this span
30
+ */
31
+ setInput(input) {
32
+ this.data.input = input;
33
+ return this;
34
+ }
35
+ /**
36
+ * Set the output data for this span
37
+ */
38
+ setOutput(output) {
39
+ this.data.output = output;
40
+ return this;
41
+ }
42
+ /**
43
+ * Record an error that occurred during span execution
44
+ */
45
+ setError(error) {
46
+ this.data.error = {
47
+ message: error.message,
48
+ stack: error.stack,
49
+ code: error.code
50
+ };
51
+ return this;
52
+ }
53
+ /**
54
+ * Set metadata for this span
55
+ */
56
+ setMetadata(key, value) {
57
+ this.data.metadata[key] = value;
58
+ return this;
59
+ }
60
+ /**
61
+ * Set multiple metadata fields at once
62
+ */
63
+ setMetadataAll(metadata) {
64
+ this.data.metadata = { ...this.data.metadata, ...metadata };
65
+ return this;
66
+ }
67
+ /**
68
+ * Set token usage information
69
+ */
70
+ setTokens(tokens) {
71
+ this.data.tokens = tokens;
72
+ return this;
73
+ }
74
+ /**
75
+ * End the span and calculate duration
76
+ */
77
+ end() {
78
+ const endTime = Date.now();
79
+ this.data.end_time = new Date(endTime);
80
+ this.data.duration_ms = endTime - this.startTime;
81
+ }
82
+ /**
83
+ * Get the span ID
84
+ */
85
+ get id() {
86
+ return this.data.span_id;
87
+ }
88
+ /**
89
+ * Get the span data for serialization
90
+ */
91
+ toJSON() {
92
+ return { ...this.data };
93
+ }
94
+ };
95
+ var Trace = class {
96
+ data;
97
+ startTime;
98
+ spans = [];
99
+ ended = false;
100
+ constructor(name, organizationId, projectId) {
101
+ this.startTime = Date.now();
102
+ this.data = {
103
+ trace_id: generateId(),
104
+ organization_id: organizationId,
105
+ project_id: projectId,
106
+ name,
107
+ status: TraceStatus.RUNNING,
108
+ start_time: new Date(this.startTime),
109
+ end_time: new Date(this.startTime),
110
+ // Will be updated on end()
111
+ duration_ms: 0,
112
+ metadata: {},
113
+ spans: [],
114
+ tags: []
115
+ };
116
+ }
117
+ /**
118
+ * Start a new span within this trace
119
+ */
120
+ startSpan(name, type, parentSpanId) {
121
+ if (this.ended) {
122
+ throw new Error("Cannot start span on an ended trace");
123
+ }
124
+ const span = new Span(name, type, parentSpanId);
125
+ this.spans.push(span);
126
+ return span;
127
+ }
128
+ /**
129
+ * Add metadata to the trace
130
+ */
131
+ setMetadata(key, value) {
132
+ this.data.metadata[key] = value;
133
+ return this;
134
+ }
135
+ /**
136
+ * Set multiple metadata fields at once
137
+ */
138
+ setMetadataAll(metadata) {
139
+ this.data.metadata = { ...this.data.metadata, ...metadata };
140
+ return this;
141
+ }
142
+ /**
143
+ * Add tags to the trace
144
+ */
145
+ addTag(tag) {
146
+ if (!this.data.tags.includes(tag)) {
147
+ this.data.tags.push(tag);
148
+ }
149
+ return this;
150
+ }
151
+ /**
152
+ * Add multiple tags at once
153
+ */
154
+ addTags(tags) {
155
+ tags.forEach((tag) => this.addTag(tag));
156
+ return this;
157
+ }
158
+ /**
159
+ * Set the user ID associated with this trace
160
+ */
161
+ setUserId(userId) {
162
+ this.data.user_id = userId;
163
+ return this;
164
+ }
165
+ /**
166
+ * Set the session ID associated with this trace
167
+ */
168
+ setSessionId(sessionId) {
169
+ this.data.session_id = sessionId;
170
+ return this;
171
+ }
172
+ /**
173
+ * End the trace with a final status
174
+ */
175
+ end(status = TraceStatus.SUCCESS) {
176
+ if (this.ended) {
177
+ return;
178
+ }
179
+ const endTime = Date.now();
180
+ this.data.end_time = new Date(endTime);
181
+ this.data.duration_ms = endTime - this.startTime;
182
+ this.data.status = status;
183
+ this.data.spans = this.spans.map((span) => span.toJSON());
184
+ this.ended = true;
185
+ }
186
+ /**
187
+ * Get the trace ID
188
+ */
189
+ get id() {
190
+ return this.data.trace_id;
191
+ }
192
+ /**
193
+ * Check if the trace has ended
194
+ */
195
+ get isEnded() {
196
+ return this.ended;
197
+ }
198
+ /**
199
+ * Get the trace data for serialization
200
+ */
201
+ toJSON() {
202
+ return { ...this.data };
203
+ }
204
+ };
205
+
206
+ // src/opentelemetry/exporter.ts
207
+ var ObservyzeSpanExporter = class {
208
+ client;
209
+ config;
210
+ constructor(client, config) {
211
+ this.client = client;
212
+ this.config = {
213
+ serviceName: config?.serviceName || "unknown-service",
214
+ projectId: config?.projectId || "",
215
+ defaultSpanType: config?.defaultSpanType || "llm",
216
+ headers: config?.headers || {}
217
+ };
218
+ }
219
+ /**
220
+ * Export spans — called by OTel SDK when spans are ready.
221
+ * Converts OTel spans to Observyze traces and buffers them.
222
+ */
223
+ async export(spans, resultCallback) {
224
+ if (!spans || spans.length === 0) {
225
+ resultCallback({ code: 0 });
226
+ return;
227
+ }
228
+ try {
229
+ const organizationId = this.client.config?.organizationId || "";
230
+ const projectId = this.config.projectId || this.client.config?.projectId || "";
231
+ for (const span of spans) {
232
+ const spanContext = span.spanContext();
233
+ const traceId = spanContext?.traceId || span.spanId();
234
+ const trace = new Trace(
235
+ span.name || "otel-span",
236
+ organizationId,
237
+ projectId
238
+ );
239
+ trace.setMetadata("source", "opentelemetry");
240
+ trace.setMetadata("otel.trace_id", traceId);
241
+ trace.setMetadata("otel.span_id", spanContext?.spanId || "");
242
+ trace.setMetadata("service.name", this.config.serviceName);
243
+ trace.setMetadataAll(span.attributes || {});
244
+ if (span.resource?.attributes) {
245
+ trace.setMetadataAll(span.resource.attributes);
246
+ }
247
+ const input = span.attributes?.["gen_ai.prompt.0.content"] || span.attributes?.["gen_ai.completion.0.content"] || span.attributes?.["llm.input"] || void 0;
248
+ const output = span.attributes?.["gen_ai.completion.0.content"] || span.attributes?.["llm.output"] || void 0;
249
+ const model = span.attributes?.["gen_ai.request.model"] || span.attributes?.["llm.model"] || void 0;
250
+ const provider = span.attributes?.["gen_ai.request.provider"] || span.attributes?.["llm.provider"] || void 0;
251
+ const inputTokens = typeof span.attributes?.["gen_ai.usage.input_tokens"] === "number" ? span.attributes["gen_ai.usage.input_tokens"] : void 0;
252
+ const outputTokens = typeof span.attributes?.["gen_ai.usage.output_tokens"] === "number" ? span.attributes["gen_ai.usage.output_tokens"] : void 0;
253
+ trace.setMetadata("provider", provider || "unknown");
254
+ trace.setMetadata("model", model || "unknown");
255
+ const nsDuration = span.duration;
256
+ const durationMs = nsDuration ? Math.round(nsDuration / 1e6) : 0;
257
+ trace.setMetadata("latency_ms", durationMs);
258
+ trace.setMetadata("otel.duration_ns", nsDuration);
259
+ const oSpan = trace.startSpan(span.name || "otel-operation", this.config.defaultSpanType);
260
+ if (input) oSpan.setInput(input);
261
+ if (output) oSpan.setOutput(output);
262
+ if (inputTokens || outputTokens) {
263
+ oSpan.setTokens({
264
+ input: inputTokens || 0,
265
+ output: outputTokens || 0,
266
+ total: (inputTokens || 0) + (outputTokens || 0)
267
+ });
268
+ }
269
+ if (model) oSpan.setMetadata("model", model);
270
+ if (provider) oSpan.setMetadata("provider", provider);
271
+ if (span.attributes) oSpan.setMetadataAll(span.attributes);
272
+ const status = span.status;
273
+ const statusCode = status?.code;
274
+ if (statusCode === 2) {
275
+ oSpan.setError(new Error(status?.message || "OTel span error"));
276
+ trace.end(TraceStatus.ERROR);
277
+ } else {
278
+ trace.end(TraceStatus.SUCCESS);
279
+ }
280
+ }
281
+ resultCallback({ code: 0 });
282
+ } catch (error) {
283
+ resultCallback({
284
+ code: 1,
285
+ error: error instanceof Error ? error : new Error(String(error))
286
+ });
287
+ }
288
+ }
289
+ /**
290
+ * Called when the exporter is shut down.
291
+ * Flushes any remaining buffered traces via the SDK client.
292
+ */
293
+ async shutdown() {
294
+ try {
295
+ await this.client.flush();
296
+ } catch {
297
+ }
298
+ }
299
+ /**
300
+ * Called by the OTel SDK to force-export buffered spans.
301
+ */
302
+ async forceFlush() {
303
+ try {
304
+ await this.client.flush();
305
+ } catch {
306
+ }
307
+ }
308
+ };
309
+
310
+ export {
311
+ SpanType,
312
+ TraceStatus,
313
+ Span,
314
+ Trace,
315
+ ObservyzeSpanExporter
316
+ };
@@ -26,25 +26,10 @@ var __copyProps = (to, from, except, desc) => {
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
28
  // src/types.ts
29
- var SpanType, TraceStatus;
29
+ import { SpanType, TraceStatus } from "@observyze/types";
30
30
  var init_types = __esm({
31
31
  "src/types.ts"() {
32
32
  "use strict";
33
- SpanType = /* @__PURE__ */ ((SpanType3) => {
34
- SpanType3["LLM"] = "llm";
35
- SpanType3["TOOL"] = "tool";
36
- SpanType3["AGENT"] = "agent";
37
- SpanType3["CHAIN"] = "chain";
38
- SpanType3["RETRIEVAL"] = "retrieval";
39
- return SpanType3;
40
- })(SpanType || {});
41
- TraceStatus = /* @__PURE__ */ ((TraceStatus2) => {
42
- TraceStatus2["SUCCESS"] = "success";
43
- TraceStatus2["ERROR"] = "error";
44
- TraceStatus2["TIMEOUT"] = "timeout";
45
- TraceStatus2["RUNNING"] = "running";
46
- return TraceStatus2;
47
- })(TraceStatus || {});
48
33
  }
49
34
  });
50
35
 
@@ -155,7 +140,7 @@ var Trace = class {
155
140
  organization_id: organizationId,
156
141
  project_id: projectId,
157
142
  name,
158
- status: "running" /* RUNNING */,
143
+ status: TraceStatus.RUNNING,
159
144
  start_time: new Date(this.startTime),
160
145
  end_time: new Date(this.startTime),
161
146
  // Will be updated on end()
@@ -223,7 +208,7 @@ var Trace = class {
223
208
  /**
224
209
  * End the trace with a final status
225
210
  */
226
- end(status = "success" /* SUCCESS */) {
211
+ end(status = TraceStatus.SUCCESS) {
227
212
  if (this.ended) {
228
213
  return;
229
214
  }
@@ -324,9 +309,9 @@ var ObservyzeSpanExporter = class {
324
309
  const statusCode = status?.code;
325
310
  if (statusCode === 2) {
326
311
  oSpan.setError(new Error(status?.message || "OTel span error"));
327
- trace.end("error" /* ERROR */);
312
+ trace.end(TraceStatus.ERROR);
328
313
  } else {
329
- trace.end("success" /* SUCCESS */);
314
+ trace.end(TraceStatus.SUCCESS);
330
315
  }
331
316
  }
332
317
  resultCallback({ code: 0 });