@observyze/sdk 0.1.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/INSTRUMENTATION_SUMMARY.md +184 -0
- package/README.md +198 -0
- package/examples/auto-instrumentation.ts +210 -0
- package/package.json +43 -0
- package/src/client.ts +578 -0
- package/src/index.ts +21 -0
- package/src/instrumentation/README.md +227 -0
- package/src/instrumentation/anthropic.ts +233 -0
- package/src/instrumentation/index.ts +43 -0
- package/src/instrumentation/openai.ts +193 -0
- package/src/trace.ts +242 -0
- package/src/types.ts +102 -0
- package/tsconfig.json +14 -0
package/src/trace.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace and Span classes for capturing AI workflow execution
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SpanType, TraceStatus } from './types'
|
|
6
|
+
import type { Span as SpanData, Trace as TraceData, TokenUsage } from '@observyze/types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generates a unique ID for traces and spans
|
|
10
|
+
*/
|
|
11
|
+
function generateId(): string {
|
|
12
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents an individual operation within a trace
|
|
17
|
+
*/
|
|
18
|
+
export class Span {
|
|
19
|
+
private data: SpanData
|
|
20
|
+
private startTime: number
|
|
21
|
+
|
|
22
|
+
constructor(name: string, type: SpanType, parentSpanId?: string) {
|
|
23
|
+
this.startTime = Date.now()
|
|
24
|
+
this.data = {
|
|
25
|
+
span_id: generateId(),
|
|
26
|
+
parent_span_id: parentSpanId,
|
|
27
|
+
name,
|
|
28
|
+
type,
|
|
29
|
+
start_time: new Date(this.startTime),
|
|
30
|
+
end_time: new Date(this.startTime), // Will be updated on end()
|
|
31
|
+
duration_ms: 0,
|
|
32
|
+
input: null,
|
|
33
|
+
output: null,
|
|
34
|
+
metadata: {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set the input data for this span
|
|
40
|
+
*/
|
|
41
|
+
setInput(input: any): this {
|
|
42
|
+
this.data.input = input
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Set the output data for this span
|
|
48
|
+
*/
|
|
49
|
+
setOutput(output: any): this {
|
|
50
|
+
this.data.output = output
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Record an error that occurred during span execution
|
|
56
|
+
*/
|
|
57
|
+
setError(error: Error): this {
|
|
58
|
+
this.data.error = {
|
|
59
|
+
message: error.message,
|
|
60
|
+
stack: error.stack,
|
|
61
|
+
code: (error as any).code
|
|
62
|
+
}
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set metadata for this span
|
|
68
|
+
*/
|
|
69
|
+
setMetadata(key: string, value: any): this {
|
|
70
|
+
this.data.metadata[key] = value
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set multiple metadata fields at once
|
|
76
|
+
*/
|
|
77
|
+
setMetadataAll(metadata: Record<string, any>): this {
|
|
78
|
+
this.data.metadata = { ...this.data.metadata, ...metadata }
|
|
79
|
+
return this
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set token usage information
|
|
84
|
+
*/
|
|
85
|
+
setTokens(tokens: TokenUsage): this {
|
|
86
|
+
this.data.tokens = tokens
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* End the span and calculate duration
|
|
92
|
+
*/
|
|
93
|
+
end(): void {
|
|
94
|
+
const endTime = Date.now()
|
|
95
|
+
this.data.end_time = new Date(endTime)
|
|
96
|
+
this.data.duration_ms = endTime - this.startTime
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the span ID
|
|
101
|
+
*/
|
|
102
|
+
get id(): string {
|
|
103
|
+
return this.data.span_id
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the span data for serialization
|
|
108
|
+
*/
|
|
109
|
+
toJSON(): SpanData {
|
|
110
|
+
return { ...this.data }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Represents a complete AI workflow execution
|
|
116
|
+
*/
|
|
117
|
+
export class Trace {
|
|
118
|
+
private data: Omit<TraceData, '_id' | 'created_at' | 'updated_at'>
|
|
119
|
+
private startTime: number
|
|
120
|
+
private spans: Span[] = []
|
|
121
|
+
private ended: boolean = false
|
|
122
|
+
|
|
123
|
+
constructor(
|
|
124
|
+
name: string,
|
|
125
|
+
organizationId: string,
|
|
126
|
+
projectId?: string
|
|
127
|
+
) {
|
|
128
|
+
this.startTime = Date.now()
|
|
129
|
+
this.data = {
|
|
130
|
+
trace_id: generateId(),
|
|
131
|
+
organization_id: organizationId,
|
|
132
|
+
project_id: projectId,
|
|
133
|
+
name,
|
|
134
|
+
status: TraceStatus.RUNNING,
|
|
135
|
+
start_time: new Date(this.startTime),
|
|
136
|
+
end_time: new Date(this.startTime), // Will be updated on end()
|
|
137
|
+
duration_ms: 0,
|
|
138
|
+
metadata: {},
|
|
139
|
+
spans: [],
|
|
140
|
+
tags: []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Start a new span within this trace
|
|
146
|
+
*/
|
|
147
|
+
startSpan(name: string, type: SpanType, parentSpanId?: string): Span {
|
|
148
|
+
if (this.ended) {
|
|
149
|
+
throw new Error('Cannot start span on an ended trace')
|
|
150
|
+
}
|
|
151
|
+
const span = new Span(name, type, parentSpanId)
|
|
152
|
+
this.spans.push(span)
|
|
153
|
+
return span
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Add metadata to the trace
|
|
158
|
+
*/
|
|
159
|
+
setMetadata(key: string, value: any): this {
|
|
160
|
+
this.data.metadata[key] = value
|
|
161
|
+
return this
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Set multiple metadata fields at once
|
|
166
|
+
*/
|
|
167
|
+
setMetadataAll(metadata: Record<string, any>): this {
|
|
168
|
+
this.data.metadata = { ...this.data.metadata, ...metadata }
|
|
169
|
+
return this
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Add tags to the trace
|
|
174
|
+
*/
|
|
175
|
+
addTag(tag: string): this {
|
|
176
|
+
if (!this.data.tags.includes(tag)) {
|
|
177
|
+
this.data.tags.push(tag)
|
|
178
|
+
}
|
|
179
|
+
return this
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Add multiple tags at once
|
|
184
|
+
*/
|
|
185
|
+
addTags(tags: string[]): this {
|
|
186
|
+
tags.forEach(tag => this.addTag(tag))
|
|
187
|
+
return this
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Set the user ID associated with this trace
|
|
192
|
+
*/
|
|
193
|
+
setUserId(userId: string): this {
|
|
194
|
+
this.data.user_id = userId
|
|
195
|
+
return this
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Set the session ID associated with this trace
|
|
200
|
+
*/
|
|
201
|
+
setSessionId(sessionId: string): this {
|
|
202
|
+
this.data.session_id = sessionId
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* End the trace with a final status
|
|
208
|
+
*/
|
|
209
|
+
end(status: TraceStatus = TraceStatus.SUCCESS): void {
|
|
210
|
+
if (this.ended) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const endTime = Date.now()
|
|
215
|
+
this.data.end_time = new Date(endTime)
|
|
216
|
+
this.data.duration_ms = endTime - this.startTime
|
|
217
|
+
this.data.status = status
|
|
218
|
+
this.data.spans = this.spans.map(span => span.toJSON())
|
|
219
|
+
this.ended = true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the trace ID
|
|
224
|
+
*/
|
|
225
|
+
get id(): string {
|
|
226
|
+
return this.data.trace_id
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if the trace has ended
|
|
231
|
+
*/
|
|
232
|
+
get isEnded(): boolean {
|
|
233
|
+
return this.ended
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get the trace data for serialization
|
|
238
|
+
*/
|
|
239
|
+
toJSON(): Omit<TraceData, '_id' | 'created_at' | 'updated_at'> {
|
|
240
|
+
return { ...this.data }
|
|
241
|
+
}
|
|
242
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Configuration and Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Re-export types from @observyze/types for convenience
|
|
6
|
+
export { SpanType, TraceStatus } from '@observyze/types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for Observyze SDK client
|
|
10
|
+
*/
|
|
11
|
+
export interface ClientConfig {
|
|
12
|
+
/**
|
|
13
|
+
* API key for authentication with Observyze Ingestion Service
|
|
14
|
+
*/
|
|
15
|
+
apiKey: string
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Endpoint URL for the Ingestion Service
|
|
19
|
+
* @default 'http://localhost:3001'
|
|
20
|
+
*/
|
|
21
|
+
endpoint?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maximum number of traces to buffer before flushing
|
|
25
|
+
* @default 100
|
|
26
|
+
*/
|
|
27
|
+
batchSize?: number
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Time in milliseconds to wait before auto-flushing buffered traces
|
|
31
|
+
* @default 5000
|
|
32
|
+
*/
|
|
33
|
+
flushInterval?: number
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Enable automatic instrumentation of popular LLM libraries
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
enableAutoInstrumentation?: boolean
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Organization ID (optional, can be extracted from API key)
|
|
43
|
+
*/
|
|
44
|
+
organizationId?: string
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project ID for trace attribution
|
|
48
|
+
*/
|
|
49
|
+
projectId?: string
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Enable debug logging
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
debug?: boolean
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Dry run mode - don't send traces to server (useful for testing)
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
dryRun?: boolean
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Phase 5: Automatically scrub PII from trace output before network transmission
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
enablePiiRedaction?: boolean
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Phase 4: Autonomous Circuit Breaker
|
|
71
|
+
* Hallucination score threshold (0-1) above which execution is blocked
|
|
72
|
+
* @default 0.8
|
|
73
|
+
*/
|
|
74
|
+
hallucinationThreshold?: number
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Phase 4: Autonomous Circuit Breaker
|
|
78
|
+
* Safety score threshold (0-1) above which execution is blocked
|
|
79
|
+
* @default 0.9
|
|
80
|
+
*/
|
|
81
|
+
safetyThreshold?: number
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Evaluation service URL for real-time guardrail checks
|
|
85
|
+
* In production, this should be https://api.observyze.com
|
|
86
|
+
* @default 'http://localhost:3000'
|
|
87
|
+
*/
|
|
88
|
+
evalEndpoint?: string
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Enable circuit breaker - block execution on high hallucination/safety scores
|
|
92
|
+
* @default true
|
|
93
|
+
*/
|
|
94
|
+
enableCircuitBreaker?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Internal configuration with defaults applied
|
|
99
|
+
*/
|
|
100
|
+
export interface ResolvedClientConfig extends Required<ClientConfig> {
|
|
101
|
+
// All fields are required after defaults are applied
|
|
102
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"composite": false,
|
|
10
|
+
"incremental": false
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"],
|
|
13
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
14
|
+
}
|