@illuma-ai/observability-node 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/dist/__tests__/client.test.d.ts +9 -0
- package/dist/__tests__/client.test.d.ts.map +1 -0
- package/dist/__tests__/client.test.js +235 -0
- package/dist/__tests__/client.test.js.map +1 -0
- package/dist/__tests__/openai.test.d.ts +9 -0
- package/dist/__tests__/openai.test.d.ts.map +1 -0
- package/dist/__tests__/openai.test.js +370 -0
- package/dist/__tests__/openai.test.js.map +1 -0
- package/dist/client.d.ts +74 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +108 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/openai.d.ts +63 -0
- package/dist/openai.d.ts.map +1 -0
- package/dist/openai.js +190 -0
- package/dist/openai.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Node.js Observability client.
|
|
3
|
+
*
|
|
4
|
+
* Validates environment variable resolution (ILLUMA_* and LANGFUSE_* fallback),
|
|
5
|
+
* class inheritance from ObservabilityCoreClient, fetchWithRetry implementation,
|
|
6
|
+
* and process exit handler registration.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=client.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/client.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Node.js Observability client.
|
|
3
|
+
*
|
|
4
|
+
* Validates environment variable resolution (ILLUMA_* and LANGFUSE_* fallback),
|
|
5
|
+
* class inheritance from ObservabilityCoreClient, fetchWithRetry implementation,
|
|
6
|
+
* and process exit handler registration.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { Observability } from '../client.js';
|
|
10
|
+
import { ObservabilityCoreClient } from '@illuma-ai/observability-core';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Environment variable helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const ENV_KEYS = [
|
|
15
|
+
'ILLUMA_PUBLIC_KEY',
|
|
16
|
+
'ILLUMA_SECRET_KEY',
|
|
17
|
+
'ILLUMA_BASE_URL',
|
|
18
|
+
'LANGFUSE_PUBLIC_KEY',
|
|
19
|
+
'LANGFUSE_SECRET_KEY',
|
|
20
|
+
'LANGFUSE_BASE_URL',
|
|
21
|
+
];
|
|
22
|
+
/** Save and clear all relevant env vars before each test. */
|
|
23
|
+
function clearEnvVars() {
|
|
24
|
+
const saved = {};
|
|
25
|
+
for (const key of ENV_KEYS) {
|
|
26
|
+
saved[key] = process.env[key];
|
|
27
|
+
delete process.env[key];
|
|
28
|
+
}
|
|
29
|
+
return saved;
|
|
30
|
+
}
|
|
31
|
+
/** Restore previously saved env vars. */
|
|
32
|
+
function restoreEnvVars(saved) {
|
|
33
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
34
|
+
if (value === undefined) {
|
|
35
|
+
delete process.env[key];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
process.env[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Tests
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
describe('Observability (Node.js client)', () => {
|
|
46
|
+
let savedEnv;
|
|
47
|
+
let processOnSpy;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
savedEnv = clearEnvVars();
|
|
50
|
+
// Spy on process.on to verify exit handler registration without side effects
|
|
51
|
+
processOnSpy = vi.spyOn(process, 'on');
|
|
52
|
+
});
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
restoreEnvVars(savedEnv);
|
|
55
|
+
processOnSpy.mockRestore();
|
|
56
|
+
});
|
|
57
|
+
// -------------------------------------------------------------------------
|
|
58
|
+
// Env var reading
|
|
59
|
+
// -------------------------------------------------------------------------
|
|
60
|
+
describe('environment variable reading', () => {
|
|
61
|
+
it('should read ILLUMA_PUBLIC_KEY, ILLUMA_SECRET_KEY, ILLUMA_BASE_URL', () => {
|
|
62
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-illuma';
|
|
63
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-illuma';
|
|
64
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.illuma.ai';
|
|
65
|
+
const client = new Observability({ flushInterval: 0 });
|
|
66
|
+
// Client should have been created without errors
|
|
67
|
+
expect(client).toBeInstanceOf(Observability);
|
|
68
|
+
// Verify it produces traces (which proves it read the keys)
|
|
69
|
+
const trace = client.trace({ name: 'env-test' });
|
|
70
|
+
expect(trace).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
it('should fall back to LANGFUSE_PUBLIC_KEY when ILLUMA_PUBLIC_KEY is not set', () => {
|
|
73
|
+
process.env.LANGFUSE_PUBLIC_KEY = 'pk-langfuse';
|
|
74
|
+
process.env.LANGFUSE_SECRET_KEY = 'sk-langfuse';
|
|
75
|
+
process.env.LANGFUSE_BASE_URL = 'https://cloud.langfuse.com';
|
|
76
|
+
const client = new Observability({ flushInterval: 0 });
|
|
77
|
+
expect(client).toBeInstanceOf(Observability);
|
|
78
|
+
});
|
|
79
|
+
it('should prefer ILLUMA_* over LANGFUSE_* when both are set', () => {
|
|
80
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-illuma';
|
|
81
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-illuma';
|
|
82
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.illuma.ai';
|
|
83
|
+
process.env.LANGFUSE_PUBLIC_KEY = 'pk-langfuse';
|
|
84
|
+
process.env.LANGFUSE_SECRET_KEY = 'sk-langfuse';
|
|
85
|
+
process.env.LANGFUSE_BASE_URL = 'https://cloud.langfuse.com';
|
|
86
|
+
// We can't directly inspect which key was used, but the constructor
|
|
87
|
+
// should succeed and prefer ILLUMA_* (verified by the code path)
|
|
88
|
+
const client = new Observability({ flushInterval: 0 });
|
|
89
|
+
expect(client).toBeInstanceOf(Observability);
|
|
90
|
+
});
|
|
91
|
+
it('should prefer explicit constructor args over env vars', () => {
|
|
92
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-env';
|
|
93
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-env';
|
|
94
|
+
process.env.ILLUMA_BASE_URL = 'https://env.example.com';
|
|
95
|
+
const client = new Observability({
|
|
96
|
+
publicKey: 'pk-explicit',
|
|
97
|
+
secretKey: 'sk-explicit',
|
|
98
|
+
baseUrl: 'https://explicit.example.com',
|
|
99
|
+
flushInterval: 0,
|
|
100
|
+
});
|
|
101
|
+
expect(client).toBeInstanceOf(Observability);
|
|
102
|
+
});
|
|
103
|
+
it('should throw when publicKey is not available from any source', () => {
|
|
104
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
105
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
106
|
+
expect(() => new Observability()).toThrow('publicKey is required');
|
|
107
|
+
});
|
|
108
|
+
it('should throw when secretKey is not available from any source', () => {
|
|
109
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
110
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
111
|
+
expect(() => new Observability()).toThrow('secretKey is required');
|
|
112
|
+
});
|
|
113
|
+
it('should throw when baseUrl is not available from any source', () => {
|
|
114
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
115
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
116
|
+
expect(() => new Observability()).toThrow('baseUrl is required');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
// Extends core correctly
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
describe('class inheritance', () => {
|
|
123
|
+
it('should extend ObservabilityCoreClient', () => {
|
|
124
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
125
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
126
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
127
|
+
const client = new Observability({ flushInterval: 0 });
|
|
128
|
+
expect(client).toBeInstanceOf(ObservabilityCoreClient);
|
|
129
|
+
});
|
|
130
|
+
it('should expose trace() method from the core client', () => {
|
|
131
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
132
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
133
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
134
|
+
const client = new Observability({ flushInterval: 0 });
|
|
135
|
+
expect(typeof client.trace).toBe('function');
|
|
136
|
+
expect(typeof client.flush).toBe('function');
|
|
137
|
+
expect(typeof client.shutdown).toBe('function');
|
|
138
|
+
expect(typeof client.score).toBe('function');
|
|
139
|
+
});
|
|
140
|
+
it('should support the full trace -> span -> generation chain', () => {
|
|
141
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
142
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
143
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
144
|
+
const client = new Observability({ flushInterval: 0 });
|
|
145
|
+
const trace = client.trace({ name: 'test-trace' });
|
|
146
|
+
const span = trace.span({ name: 'test-span' });
|
|
147
|
+
const gen = span.generation({ name: 'test-gen', model: 'gpt-4o' });
|
|
148
|
+
gen.end({ output: 'done' });
|
|
149
|
+
span.end();
|
|
150
|
+
// All operations should have enqueued events
|
|
151
|
+
expect(client.getQueue().length).toBeGreaterThan(0);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// -------------------------------------------------------------------------
|
|
155
|
+
// fetchWithRetry with mocked fetch
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
describe('fetchWithRetry', () => {
|
|
158
|
+
it('should send events to the server via native fetch', async () => {
|
|
159
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
160
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
161
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
162
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
163
|
+
status: 200,
|
|
164
|
+
statusText: 'OK',
|
|
165
|
+
ok: true,
|
|
166
|
+
json: async () => ({ successes: [], errors: [] }),
|
|
167
|
+
text: async () => '',
|
|
168
|
+
});
|
|
169
|
+
// Replace global fetch for this test
|
|
170
|
+
const originalFetch = globalThis.fetch;
|
|
171
|
+
globalThis.fetch = mockFetch;
|
|
172
|
+
try {
|
|
173
|
+
const client = new Observability({ flushInterval: 0 });
|
|
174
|
+
client.trace({ name: 'fetch-test' });
|
|
175
|
+
await client.flush();
|
|
176
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
177
|
+
const [url, options] = mockFetch.mock.calls[0];
|
|
178
|
+
expect(url).toBe('https://observe.test/api/public/ingestion');
|
|
179
|
+
expect(options.method).toBe('POST');
|
|
180
|
+
expect(options.headers['Content-Type']).toBe('application/json');
|
|
181
|
+
expect(options.headers['Authorization']).toMatch(/^Basic /);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
globalThis.fetch = originalFetch;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
it('should handle fetch errors gracefully', async () => {
|
|
188
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
189
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
190
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
191
|
+
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
192
|
+
const originalFetch = globalThis.fetch;
|
|
193
|
+
globalThis.fetch = mockFetch;
|
|
194
|
+
// Suppress error output during test
|
|
195
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
196
|
+
try {
|
|
197
|
+
const client = new Observability({
|
|
198
|
+
flushInterval: 0,
|
|
199
|
+
maxRetries: 0,
|
|
200
|
+
});
|
|
201
|
+
client.trace({ name: 'error-test' });
|
|
202
|
+
await client.flush();
|
|
203
|
+
// Events should be re-queued after failure
|
|
204
|
+
expect(client.getQueue().length).toBeGreaterThan(0);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
globalThis.fetch = originalFetch;
|
|
208
|
+
consoleSpy.mockRestore();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
// -------------------------------------------------------------------------
|
|
213
|
+
// Process exit handler registration
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
describe('process exit handler', () => {
|
|
216
|
+
it('should register a beforeExit handler on construction', () => {
|
|
217
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
218
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
219
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
220
|
+
new Observability({ flushInterval: 0 });
|
|
221
|
+
expect(processOnSpy).toHaveBeenCalledWith('beforeExit', expect.any(Function));
|
|
222
|
+
});
|
|
223
|
+
it('should only register one exit handler even if multiple instances are created', () => {
|
|
224
|
+
process.env.ILLUMA_PUBLIC_KEY = 'pk-test';
|
|
225
|
+
process.env.ILLUMA_SECRET_KEY = 'sk-test';
|
|
226
|
+
process.env.ILLUMA_BASE_URL = 'https://observe.test';
|
|
227
|
+
// Each instance registers its own handler (per-instance guard)
|
|
228
|
+
const client1 = new Observability({ flushInterval: 0 });
|
|
229
|
+
// Creating the same instance again would still register (separate instance)
|
|
230
|
+
// But the guard prevents re-registration on the same instance
|
|
231
|
+
expect(processOnSpy).toHaveBeenCalledWith('beforeExit', expect.any(Function));
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/__tests__/client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the observeOpenAI wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the OpenAI client Proxy intercepts chat.completions.create,
|
|
5
|
+
* records generation events with correct model/usage data, and handles
|
|
6
|
+
* errors by creating ERROR-level generation events.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=openai.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/openai.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the observeOpenAI wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the OpenAI client Proxy intercepts chat.completions.create,
|
|
5
|
+
* records generation events with correct model/usage data, and handles
|
|
6
|
+
* errors by creating ERROR-level generation events.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
+
import { observeOpenAI } from '../openai.js';
|
|
10
|
+
import { Observability } from '../client.js';
|
|
11
|
+
import { ObservationLevel } from '@illuma-ai/observability-core';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mock OpenAI client
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function createMockOpenAI(overrides = {}) {
|
|
16
|
+
const defaultChatResponse = {
|
|
17
|
+
id: 'chatcmpl-abc123',
|
|
18
|
+
model: 'gpt-4o-2024-08-06',
|
|
19
|
+
choices: [
|
|
20
|
+
{
|
|
21
|
+
message: { role: 'assistant', content: 'Hello! How can I help you?' },
|
|
22
|
+
finish_reason: 'stop',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
usage: {
|
|
26
|
+
prompt_tokens: 10,
|
|
27
|
+
completion_tokens: 8,
|
|
28
|
+
total_tokens: 18,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const defaultCompletionResponse = {
|
|
32
|
+
id: 'cmpl-xyz789',
|
|
33
|
+
model: 'gpt-3.5-turbo-instruct',
|
|
34
|
+
choices: [
|
|
35
|
+
{
|
|
36
|
+
text: 'The answer is 42.',
|
|
37
|
+
finish_reason: 'stop',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
usage: {
|
|
41
|
+
prompt_tokens: 5,
|
|
42
|
+
completion_tokens: 6,
|
|
43
|
+
total_tokens: 11,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
chat: {
|
|
48
|
+
completions: {
|
|
49
|
+
create: vi.fn().mockImplementation(async () => {
|
|
50
|
+
if (overrides.chatError)
|
|
51
|
+
throw overrides.chatError;
|
|
52
|
+
return overrides.chatResponse ?? defaultChatResponse;
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
completions: {
|
|
57
|
+
create: vi.fn().mockImplementation(async () => {
|
|
58
|
+
if (overrides.completionError)
|
|
59
|
+
throw overrides.completionError;
|
|
60
|
+
return overrides.completionResponse ?? defaultCompletionResponse;
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Mock Observability client (avoid real network + env var requirements)
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
function createMockObservability() {
|
|
69
|
+
// Mock global fetch so the Observability constructor's fetchWithRetry works
|
|
70
|
+
const originalFetch = globalThis.fetch;
|
|
71
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
72
|
+
status: 200,
|
|
73
|
+
statusText: 'OK',
|
|
74
|
+
ok: true,
|
|
75
|
+
json: async () => ({ successes: [], errors: [] }),
|
|
76
|
+
text: async () => '',
|
|
77
|
+
});
|
|
78
|
+
const client = new Observability({
|
|
79
|
+
publicKey: 'pk-test',
|
|
80
|
+
secretKey: 'sk-test',
|
|
81
|
+
baseUrl: 'https://observe.test',
|
|
82
|
+
flushInterval: 0,
|
|
83
|
+
flushAt: 1000, // High threshold to prevent auto-flush during tests
|
|
84
|
+
});
|
|
85
|
+
// Restore fetch after construction
|
|
86
|
+
globalThis.fetch = originalFetch;
|
|
87
|
+
// Expose getQueue with a different name to avoid confusion
|
|
88
|
+
return Object.assign(client, {
|
|
89
|
+
_getQueueDirect: () => client.getQueue(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Tests
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
describe('observeOpenAI', () => {
|
|
96
|
+
let observability;
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
observability = createMockObservability();
|
|
99
|
+
});
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
// Wrapping
|
|
102
|
+
// -------------------------------------------------------------------------
|
|
103
|
+
describe('wrapping', () => {
|
|
104
|
+
it('should return a proxied client with the same interface', () => {
|
|
105
|
+
const openai = createMockOpenAI();
|
|
106
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
107
|
+
// Should have the same top-level properties
|
|
108
|
+
expect(wrapped.chat).toBeDefined();
|
|
109
|
+
expect(wrapped.chat.completions).toBeDefined();
|
|
110
|
+
expect(typeof wrapped.chat.completions.create).toBe('function');
|
|
111
|
+
expect(wrapped.completions).toBeDefined();
|
|
112
|
+
expect(typeof wrapped.completions.create).toBe('function');
|
|
113
|
+
});
|
|
114
|
+
it('should preserve non-intercepted properties via proxy pass-through', () => {
|
|
115
|
+
const openai = createMockOpenAI();
|
|
116
|
+
openai.models = { list: vi.fn().mockResolvedValue([]) };
|
|
117
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
118
|
+
// Non-intercepted properties should pass through
|
|
119
|
+
expect(wrapped.models).toBeDefined();
|
|
120
|
+
expect(wrapped.models.list).toBe(openai.models.list);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
// chat.completions.create interception
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
describe('chat.completions.create interception', () => {
|
|
127
|
+
it('should call the original create method and return its result', async () => {
|
|
128
|
+
const openai = createMockOpenAI();
|
|
129
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
130
|
+
const result = await wrapped.chat.completions.create({
|
|
131
|
+
model: 'gpt-4o',
|
|
132
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
133
|
+
});
|
|
134
|
+
// Should return the original response
|
|
135
|
+
expect(result).toMatchObject({
|
|
136
|
+
id: 'chatcmpl-abc123',
|
|
137
|
+
choices: expect.any(Array),
|
|
138
|
+
});
|
|
139
|
+
// Should have called the original function
|
|
140
|
+
expect(openai.chat.completions.create).toHaveBeenCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
it('should create trace and generation events', async () => {
|
|
143
|
+
const openai = createMockOpenAI();
|
|
144
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
145
|
+
await wrapped.chat.completions.create({
|
|
146
|
+
model: 'gpt-4o',
|
|
147
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
148
|
+
});
|
|
149
|
+
const queue = observability._getQueueDirect();
|
|
150
|
+
// Should have at least a trace-create and generation-create
|
|
151
|
+
const types = queue.map((e) => e.type);
|
|
152
|
+
expect(types).toContain('trace-create');
|
|
153
|
+
expect(types).toContain('generation-create');
|
|
154
|
+
});
|
|
155
|
+
it('should record correct model in generation event', async () => {
|
|
156
|
+
const openai = createMockOpenAI();
|
|
157
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
158
|
+
await wrapped.chat.completions.create({
|
|
159
|
+
model: 'gpt-4o',
|
|
160
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
161
|
+
});
|
|
162
|
+
const queue = observability._getQueueDirect();
|
|
163
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
164
|
+
expect(genEvent.body.model).toBe('gpt-4o');
|
|
165
|
+
expect(genEvent.body.name).toBe('chat.completions.create');
|
|
166
|
+
});
|
|
167
|
+
it('should record correct usage in generation event', async () => {
|
|
168
|
+
const openai = createMockOpenAI();
|
|
169
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
170
|
+
await wrapped.chat.completions.create({
|
|
171
|
+
model: 'gpt-4o',
|
|
172
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
173
|
+
});
|
|
174
|
+
const queue = observability._getQueueDirect();
|
|
175
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
176
|
+
expect(genEvent.body.usage).toMatchObject({
|
|
177
|
+
input: 10,
|
|
178
|
+
output: 8,
|
|
179
|
+
total: 18,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
it('should record input messages and output message', async () => {
|
|
183
|
+
const openai = createMockOpenAI();
|
|
184
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
185
|
+
const messages = [{ role: 'user', content: 'What is 2+2?' }];
|
|
186
|
+
await wrapped.chat.completions.create({
|
|
187
|
+
model: 'gpt-4o',
|
|
188
|
+
messages,
|
|
189
|
+
});
|
|
190
|
+
const queue = observability._getQueueDirect();
|
|
191
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
192
|
+
expect(genEvent.body.input).toEqual(messages);
|
|
193
|
+
expect(genEvent.body.output).toMatchObject({
|
|
194
|
+
role: 'assistant',
|
|
195
|
+
content: 'Hello! How can I help you?',
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
it('should extract model parameters (temperature, max_tokens)', async () => {
|
|
199
|
+
const openai = createMockOpenAI();
|
|
200
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
201
|
+
await wrapped.chat.completions.create({
|
|
202
|
+
model: 'gpt-4o',
|
|
203
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
204
|
+
temperature: 0.5,
|
|
205
|
+
max_tokens: 200,
|
|
206
|
+
top_p: 0.9,
|
|
207
|
+
});
|
|
208
|
+
const queue = observability._getQueueDirect();
|
|
209
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
210
|
+
expect(genEvent.body.modelParameters).toMatchObject({
|
|
211
|
+
temperature: 0.5,
|
|
212
|
+
max_tokens: 200,
|
|
213
|
+
top_p: 0.9,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
it('should record OpenAI response ID and finish_reason in metadata', async () => {
|
|
217
|
+
const openai = createMockOpenAI();
|
|
218
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
219
|
+
await wrapped.chat.completions.create({
|
|
220
|
+
model: 'gpt-4o',
|
|
221
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
222
|
+
});
|
|
223
|
+
const queue = observability._getQueueDirect();
|
|
224
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
225
|
+
expect(genEvent.body.metadata).toMatchObject({
|
|
226
|
+
openai_id: 'chatcmpl-abc123',
|
|
227
|
+
finish_reason: 'stop',
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
it('should set startTime and endTime on the generation', async () => {
|
|
231
|
+
const openai = createMockOpenAI();
|
|
232
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
233
|
+
await wrapped.chat.completions.create({
|
|
234
|
+
model: 'gpt-4o',
|
|
235
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
236
|
+
});
|
|
237
|
+
const queue = observability._getQueueDirect();
|
|
238
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
239
|
+
expect(genEvent.body.startTime).toBeDefined();
|
|
240
|
+
expect(genEvent.body.endTime).toBeDefined();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// -------------------------------------------------------------------------
|
|
244
|
+
// Options: traceName, userId, sessionId
|
|
245
|
+
// -------------------------------------------------------------------------
|
|
246
|
+
describe('options', () => {
|
|
247
|
+
it('should use custom traceName', async () => {
|
|
248
|
+
const openai = createMockOpenAI();
|
|
249
|
+
const wrapped = observeOpenAI(openai, observability, {
|
|
250
|
+
traceName: 'my-custom-trace',
|
|
251
|
+
});
|
|
252
|
+
await wrapped.chat.completions.create({
|
|
253
|
+
model: 'gpt-4o',
|
|
254
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
255
|
+
});
|
|
256
|
+
const queue = observability._getQueueDirect();
|
|
257
|
+
const traceEvent = queue.find((e) => e.type === 'trace-create');
|
|
258
|
+
expect(traceEvent.body.name).toBe('my-custom-trace');
|
|
259
|
+
});
|
|
260
|
+
it('should attach userId and sessionId to the trace', async () => {
|
|
261
|
+
const openai = createMockOpenAI();
|
|
262
|
+
const wrapped = observeOpenAI(openai, observability, {
|
|
263
|
+
userId: 'user-42',
|
|
264
|
+
sessionId: 'session-abc',
|
|
265
|
+
});
|
|
266
|
+
await wrapped.chat.completions.create({
|
|
267
|
+
model: 'gpt-4o',
|
|
268
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
269
|
+
});
|
|
270
|
+
const queue = observability._getQueueDirect();
|
|
271
|
+
const traceEvent = queue.find((e) => e.type === 'trace-create');
|
|
272
|
+
expect(traceEvent.body.userId).toBe('user-42');
|
|
273
|
+
expect(traceEvent.body.sessionId).toBe('session-abc');
|
|
274
|
+
});
|
|
275
|
+
it('should merge generationMetadata into generation metadata', async () => {
|
|
276
|
+
const openai = createMockOpenAI();
|
|
277
|
+
const wrapped = observeOpenAI(openai, observability, {
|
|
278
|
+
generationMetadata: { env: 'test', feature: 'chat' },
|
|
279
|
+
});
|
|
280
|
+
await wrapped.chat.completions.create({
|
|
281
|
+
model: 'gpt-4o',
|
|
282
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
283
|
+
});
|
|
284
|
+
const queue = observability._getQueueDirect();
|
|
285
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
286
|
+
const metadata = genEvent.body.metadata;
|
|
287
|
+
expect(metadata.env).toBe('test');
|
|
288
|
+
expect(metadata.feature).toBe('chat');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
// -------------------------------------------------------------------------
|
|
292
|
+
// Error handling
|
|
293
|
+
// -------------------------------------------------------------------------
|
|
294
|
+
describe('error handling', () => {
|
|
295
|
+
it('should create an ERROR-level generation when OpenAI throws', async () => {
|
|
296
|
+
const apiError = new Error('Rate limit exceeded');
|
|
297
|
+
const openai = createMockOpenAI({ chatError: apiError });
|
|
298
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
299
|
+
await expect(wrapped.chat.completions.create({
|
|
300
|
+
model: 'gpt-4o',
|
|
301
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
302
|
+
})).rejects.toThrow('Rate limit exceeded');
|
|
303
|
+
const queue = observability._getQueueDirect();
|
|
304
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
305
|
+
expect(genEvent.body.level).toBe(ObservationLevel.ERROR);
|
|
306
|
+
expect(genEvent.body.statusMessage).toBe('Rate limit exceeded');
|
|
307
|
+
expect(genEvent.body.model).toBe('gpt-4o');
|
|
308
|
+
});
|
|
309
|
+
it('should still re-throw the original error', async () => {
|
|
310
|
+
const apiError = new Error('API key invalid');
|
|
311
|
+
const openai = createMockOpenAI({ chatError: apiError });
|
|
312
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
313
|
+
await expect(wrapped.chat.completions.create({
|
|
314
|
+
model: 'gpt-4o',
|
|
315
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
316
|
+
})).rejects.toThrow('API key invalid');
|
|
317
|
+
});
|
|
318
|
+
it('should record the error generation with startTime and endTime', async () => {
|
|
319
|
+
const apiError = new Error('Timeout');
|
|
320
|
+
const openai = createMockOpenAI({ chatError: apiError });
|
|
321
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
322
|
+
await expect(wrapped.chat.completions.create({
|
|
323
|
+
model: 'gpt-4o',
|
|
324
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
325
|
+
})).rejects.toThrow();
|
|
326
|
+
const queue = observability._getQueueDirect();
|
|
327
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
328
|
+
expect(genEvent.body.startTime).toBeDefined();
|
|
329
|
+
expect(genEvent.body.endTime).toBeDefined();
|
|
330
|
+
});
|
|
331
|
+
it('should handle non-Error thrown values', async () => {
|
|
332
|
+
const openai = createMockOpenAI();
|
|
333
|
+
// Override to throw a string
|
|
334
|
+
openai.chat.completions.create = vi.fn().mockRejectedValue('string error');
|
|
335
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
336
|
+
await expect(wrapped.chat.completions.create({
|
|
337
|
+
model: 'gpt-4o',
|
|
338
|
+
messages: [{ role: 'user', content: 'Test' }],
|
|
339
|
+
})).rejects.toBe('string error');
|
|
340
|
+
const queue = observability._getQueueDirect();
|
|
341
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
342
|
+
expect(genEvent.body.level).toBe(ObservationLevel.ERROR);
|
|
343
|
+
expect(genEvent.body.statusMessage).toBe('string error');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
// -------------------------------------------------------------------------
|
|
347
|
+
// completions.create (legacy)
|
|
348
|
+
// -------------------------------------------------------------------------
|
|
349
|
+
describe('completions.create (legacy)', () => {
|
|
350
|
+
it('should intercept legacy completions.create calls', async () => {
|
|
351
|
+
const openai = createMockOpenAI();
|
|
352
|
+
const wrapped = observeOpenAI(openai, observability);
|
|
353
|
+
const result = await wrapped.completions.create({
|
|
354
|
+
model: 'gpt-3.5-turbo-instruct',
|
|
355
|
+
prompt: 'Say hello',
|
|
356
|
+
});
|
|
357
|
+
expect(result).toMatchObject({ id: 'cmpl-xyz789' });
|
|
358
|
+
const queue = observability._getQueueDirect();
|
|
359
|
+
const genEvent = queue.find((e) => e.type === 'generation-create');
|
|
360
|
+
expect(genEvent.body.name).toBe('completions.create');
|
|
361
|
+
expect(genEvent.body.model).toBe('gpt-3.5-turbo-instruct');
|
|
362
|
+
expect(genEvent.body.usage).toMatchObject({
|
|
363
|
+
input: 5,
|
|
364
|
+
output: 6,
|
|
365
|
+
total: 11,
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
//# sourceMappingURL=openai.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.test.js","sourceRoot":"","sources":["../../src/__tests__/openai.test.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ObservabilityCoreClient, type FetchOptions, type FetchResponse } from '@illuma-ai/observability-core';
|
|
2
|
+
/**
|
|
3
|
+
* Options for the Node.js Observability client.
|
|
4
|
+
* All fields are optional — values are read from environment variables when omitted.
|
|
5
|
+
*
|
|
6
|
+
* Env var resolution order (first match wins):
|
|
7
|
+
* - `ILLUMA_PUBLIC_KEY` / `LANGFUSE_PUBLIC_KEY`
|
|
8
|
+
* - `ILLUMA_SECRET_KEY` / `LANGFUSE_SECRET_KEY`
|
|
9
|
+
* - `ILLUMA_BASE_URL` / `LANGFUSE_BASE_URL`
|
|
10
|
+
*/
|
|
11
|
+
export interface ObservabilityOptions {
|
|
12
|
+
/** Public API key. Falls back to ILLUMA_PUBLIC_KEY or LANGFUSE_PUBLIC_KEY env var. */
|
|
13
|
+
publicKey?: string;
|
|
14
|
+
/** Secret API key. Falls back to ILLUMA_SECRET_KEY or LANGFUSE_SECRET_KEY env var. */
|
|
15
|
+
secretKey?: string;
|
|
16
|
+
/** Base URL of the Illuma Observe instance. Falls back to ILLUMA_BASE_URL or LANGFUSE_BASE_URL env var. */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/** Number of events to batch before auto-flushing (default: 15) */
|
|
19
|
+
flushAt?: number;
|
|
20
|
+
/** Interval in ms between auto-flushes (default: 5000) */
|
|
21
|
+
flushInterval?: number;
|
|
22
|
+
/** Max retries for failed ingestion requests (default: 3) */
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Node.js SDK client for Illuma Observability.
|
|
27
|
+
*
|
|
28
|
+
* Extends the core client with:
|
|
29
|
+
* - **Environment variable auto-detection** for credentials and base URL
|
|
30
|
+
* (compatible with both `ILLUMA_*` and `LANGFUSE_*` env vars for drop-in migration)
|
|
31
|
+
* - **Native Node.js `fetch`** (Node 18+)
|
|
32
|
+
* - **Process exit handler** that auto-flushes pending events before shutdown
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // Reads ILLUMA_PUBLIC_KEY, ILLUMA_SECRET_KEY, ILLUMA_BASE_URL from env
|
|
37
|
+
* const observability = new Observability();
|
|
38
|
+
*
|
|
39
|
+
* const trace = observability.trace({ name: 'my-chain' });
|
|
40
|
+
* trace.generation({ model: 'gpt-4o', input: 'Hello', output: 'Hi!' });
|
|
41
|
+
*
|
|
42
|
+
* await observability.shutdown();
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Explicit configuration (overrides env vars)
|
|
48
|
+
* const observability = new Observability({
|
|
49
|
+
* publicKey: 'pk-...',
|
|
50
|
+
* secretKey: 'sk-...',
|
|
51
|
+
* baseUrl: 'https://observe.illuma.ai',
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare class Observability extends ObservabilityCoreClient {
|
|
56
|
+
private exitHandlerRegistered;
|
|
57
|
+
constructor(options?: ObservabilityOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Send an HTTP request using native Node.js fetch.
|
|
60
|
+
* Implements the abstract method from ObservabilityCoreClient.
|
|
61
|
+
* Retry logic is handled by the core client.
|
|
62
|
+
*
|
|
63
|
+
* @param url - The full endpoint URL
|
|
64
|
+
* @param options - Fetch options (method, headers, body, signal)
|
|
65
|
+
* @returns Simplified response object
|
|
66
|
+
*/
|
|
67
|
+
protected fetchWithRetry(url: string, options: FetchOptions): Promise<FetchResponse>;
|
|
68
|
+
/**
|
|
69
|
+
* Register a handler that flushes pending events when the Node.js process exits.
|
|
70
|
+
* Uses `beforeExit` so the event loop can wait for the async flush to complete.
|
|
71
|
+
*/
|
|
72
|
+
private registerExitHandler;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EAEvB,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,+BAA+B,CAAC;AAKvC;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2GAA2G;IAC3G,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,aAAc,SAAQ,uBAAuB;IACxD,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,OAAO,GAAE,oBAAyB;IAkD9C;;;;;;;;OAQG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,aAAa,CAAC;IAiBzB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CAW5B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ObservabilityCoreClient, } from '@illuma-ai/observability-core';
|
|
2
|
+
const SDK_NAME = '@illuma-ai/observability-node';
|
|
3
|
+
const SDK_VERSION = '0.1.0';
|
|
4
|
+
/**
|
|
5
|
+
* Node.js SDK client for Illuma Observability.
|
|
6
|
+
*
|
|
7
|
+
* Extends the core client with:
|
|
8
|
+
* - **Environment variable auto-detection** for credentials and base URL
|
|
9
|
+
* (compatible with both `ILLUMA_*` and `LANGFUSE_*` env vars for drop-in migration)
|
|
10
|
+
* - **Native Node.js `fetch`** (Node 18+)
|
|
11
|
+
* - **Process exit handler** that auto-flushes pending events before shutdown
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // Reads ILLUMA_PUBLIC_KEY, ILLUMA_SECRET_KEY, ILLUMA_BASE_URL from env
|
|
16
|
+
* const observability = new Observability();
|
|
17
|
+
*
|
|
18
|
+
* const trace = observability.trace({ name: 'my-chain' });
|
|
19
|
+
* trace.generation({ model: 'gpt-4o', input: 'Hello', output: 'Hi!' });
|
|
20
|
+
*
|
|
21
|
+
* await observability.shutdown();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Explicit configuration (overrides env vars)
|
|
27
|
+
* const observability = new Observability({
|
|
28
|
+
* publicKey: 'pk-...',
|
|
29
|
+
* secretKey: 'sk-...',
|
|
30
|
+
* baseUrl: 'https://observe.illuma.ai',
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class Observability extends ObservabilityCoreClient {
|
|
35
|
+
exitHandlerRegistered = false;
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
const publicKey = options.publicKey ??
|
|
38
|
+
process.env.ILLUMA_PUBLIC_KEY ??
|
|
39
|
+
process.env.LANGFUSE_PUBLIC_KEY;
|
|
40
|
+
const secretKey = options.secretKey ??
|
|
41
|
+
process.env.ILLUMA_SECRET_KEY ??
|
|
42
|
+
process.env.LANGFUSE_SECRET_KEY;
|
|
43
|
+
const baseUrl = options.baseUrl ??
|
|
44
|
+
process.env.ILLUMA_BASE_URL ??
|
|
45
|
+
process.env.LANGFUSE_BASE_URL;
|
|
46
|
+
if (!publicKey) {
|
|
47
|
+
throw new Error('[observability-node] publicKey is required. ' +
|
|
48
|
+
'Pass it in the constructor or set ILLUMA_PUBLIC_KEY / LANGFUSE_PUBLIC_KEY env var.');
|
|
49
|
+
}
|
|
50
|
+
if (!secretKey) {
|
|
51
|
+
throw new Error('[observability-node] secretKey is required. ' +
|
|
52
|
+
'Pass it in the constructor or set ILLUMA_SECRET_KEY / LANGFUSE_SECRET_KEY env var.');
|
|
53
|
+
}
|
|
54
|
+
if (!baseUrl) {
|
|
55
|
+
throw new Error('[observability-node] baseUrl is required. ' +
|
|
56
|
+
'Pass it in the constructor or set ILLUMA_BASE_URL / LANGFUSE_BASE_URL env var.');
|
|
57
|
+
}
|
|
58
|
+
const coreConfig = {
|
|
59
|
+
publicKey,
|
|
60
|
+
secretKey,
|
|
61
|
+
baseUrl,
|
|
62
|
+
flushAt: options.flushAt,
|
|
63
|
+
flushInterval: options.flushInterval,
|
|
64
|
+
maxRetries: options.maxRetries,
|
|
65
|
+
};
|
|
66
|
+
super(coreConfig);
|
|
67
|
+
// Register process exit handler to auto-flush pending events
|
|
68
|
+
this.registerExitHandler();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Send an HTTP request using native Node.js fetch.
|
|
72
|
+
* Implements the abstract method from ObservabilityCoreClient.
|
|
73
|
+
* Retry logic is handled by the core client.
|
|
74
|
+
*
|
|
75
|
+
* @param url - The full endpoint URL
|
|
76
|
+
* @param options - Fetch options (method, headers, body, signal)
|
|
77
|
+
* @returns Simplified response object
|
|
78
|
+
*/
|
|
79
|
+
async fetchWithRetry(url, options) {
|
|
80
|
+
const response = await fetch(url, {
|
|
81
|
+
method: options.method,
|
|
82
|
+
headers: options.headers,
|
|
83
|
+
body: options.body,
|
|
84
|
+
signal: options.signal,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
status: response.status,
|
|
88
|
+
statusText: response.statusText,
|
|
89
|
+
ok: response.ok,
|
|
90
|
+
json: () => response.json(),
|
|
91
|
+
text: () => response.text(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Register a handler that flushes pending events when the Node.js process exits.
|
|
96
|
+
* Uses `beforeExit` so the event loop can wait for the async flush to complete.
|
|
97
|
+
*/
|
|
98
|
+
registerExitHandler() {
|
|
99
|
+
if (this.exitHandlerRegistered) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.exitHandlerRegistered = true;
|
|
103
|
+
process.on('beforeExit', () => {
|
|
104
|
+
void this.flush();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,GAIxB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AACjD,MAAM,WAAW,GAAG,OAAO,CAAC;AA0B5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,OAAO,aAAc,SAAQ,uBAAuB;IAChD,qBAAqB,GAAG,KAAK,CAAC;IAEtC,YAAY,UAAgC,EAAE;QAC5C,MAAM,SAAS,GACb,OAAO,CAAC,SAAS;YACjB,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7B,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAElC,MAAM,SAAS,GACb,OAAO,CAAC,SAAS;YACjB,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7B,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAElC,MAAM,OAAO,GACX,OAAO,CAAC,OAAO;YACf,OAAO,CAAC,GAAG,CAAC,eAAe;YAC3B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAEhC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8CAA8C;gBAC5C,oFAAoF,CACvF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8CAA8C;gBAC5C,oFAAoF,CACvF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,4CAA4C;gBAC1C,gFAAgF,CACnF,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAA8B;YAC5C,SAAS;YACT,SAAS;YACT,OAAO;YACP,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC;QAEF,KAAK,CAAC,UAAU,CAAC,CAAC;QAElB,6DAA6D;QAC7D,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAED;;;;;;;;OAQG;IACO,KAAK,CAAC,cAAc,CAC5B,GAAW,EACX,OAAqB;QAErB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC3B,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;SAC5B,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,mBAAmB;QACzB,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;QAElC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;YAC5B,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @illuma-ai/observability-node
|
|
3
|
+
*
|
|
4
|
+
* Node.js SDK for Illuma Observability — LLM tracing and analytics.
|
|
5
|
+
* Drop-in replacement for the `langfuse` npm package.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Observability, observeOpenAI } from '@illuma-ai/observability-node';
|
|
10
|
+
*
|
|
11
|
+
* // Reads ILLUMA_PUBLIC_KEY, ILLUMA_SECRET_KEY, ILLUMA_BASE_URL from env
|
|
12
|
+
* // (also supports LANGFUSE_* env vars for easy migration)
|
|
13
|
+
* const observability = new Observability();
|
|
14
|
+
*
|
|
15
|
+
* const trace = observability.trace({ name: 'my-workflow' });
|
|
16
|
+
* trace.generation({ model: 'gpt-4o', input: 'Hello', output: 'Hi!' });
|
|
17
|
+
*
|
|
18
|
+
* await observability.shutdown();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
export { Observability, type ObservabilityOptions } from './client.js';
|
|
24
|
+
export { observeOpenAI, type ObserveOpenAIOptions } from './openai.js';
|
|
25
|
+
export type { ObservabilityClientConfig, TraceParams, GenerationParams, SpanParams, EventParams, ScoreParams, IngestionEvent, IngestionEventType, IngestionBatchRequest, } from '@illuma-ai/observability-core';
|
|
26
|
+
export { ObservabilityCoreClient, TraceClient } from '@illuma-ai/observability-core';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGvE,YAAY,EACV,yBAAyB,EACzB,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @illuma-ai/observability-node
|
|
3
|
+
*
|
|
4
|
+
* Node.js SDK for Illuma Observability — LLM tracing and analytics.
|
|
5
|
+
* Drop-in replacement for the `langfuse` npm package.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Observability, observeOpenAI } from '@illuma-ai/observability-node';
|
|
10
|
+
*
|
|
11
|
+
* // Reads ILLUMA_PUBLIC_KEY, ILLUMA_SECRET_KEY, ILLUMA_BASE_URL from env
|
|
12
|
+
* // (also supports LANGFUSE_* env vars for easy migration)
|
|
13
|
+
* const observability = new Observability();
|
|
14
|
+
*
|
|
15
|
+
* const trace = observability.trace({ name: 'my-workflow' });
|
|
16
|
+
* trace.generation({ model: 'gpt-4o', input: 'Hello', output: 'Hi!' });
|
|
17
|
+
*
|
|
18
|
+
* await observability.shutdown();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
export { Observability } from './client.js';
|
|
24
|
+
export { observeOpenAI } from './openai.js';
|
|
25
|
+
export { ObservabilityCoreClient, TraceClient } from '@illuma-ai/observability-core';
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,aAAa,EAA6B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,aAAa,EAA6B,MAAM,aAAa,CAAC;AAevE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/openai.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Observability } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Represents the minimal shape of an OpenAI client we need to instrument.
|
|
4
|
+
* We avoid importing the `openai` package directly to keep it as an optional peer dependency.
|
|
5
|
+
*/
|
|
6
|
+
interface OpenAILike {
|
|
7
|
+
chat: {
|
|
8
|
+
completions: {
|
|
9
|
+
create: (...args: unknown[]) => Promise<unknown>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
completions: {
|
|
13
|
+
create: (...args: unknown[]) => Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for configuring the OpenAI observation wrapper.
|
|
18
|
+
*/
|
|
19
|
+
export interface ObserveOpenAIOptions {
|
|
20
|
+
/** Parent trace name. Defaults to "openai-call". */
|
|
21
|
+
traceName?: string;
|
|
22
|
+
/** Additional metadata to attach to each generation event */
|
|
23
|
+
generationMetadata?: Record<string, unknown>;
|
|
24
|
+
/** User ID to associate with traces */
|
|
25
|
+
userId?: string;
|
|
26
|
+
/** Session ID to group related traces */
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Wraps an OpenAI client instance with automatic observability instrumentation.
|
|
31
|
+
*
|
|
32
|
+
* Uses ES Proxy to intercept calls to `chat.completions.create()` and
|
|
33
|
+
* `completions.create()`, automatically creating trace and generation events
|
|
34
|
+
* with model, usage, and timing information.
|
|
35
|
+
*
|
|
36
|
+
* The returned client has the same interface as the original — existing code
|
|
37
|
+
* works unchanged.
|
|
38
|
+
*
|
|
39
|
+
* @param openai - An OpenAI client instance (from the `openai` npm package)
|
|
40
|
+
* @param observability - An initialized Observability client
|
|
41
|
+
* @param options - Optional configuration for trace naming and metadata
|
|
42
|
+
* @returns A proxied OpenAI client with the same interface
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import OpenAI from 'openai';
|
|
47
|
+
* import { Observability, observeOpenAI } from '@illuma-ai/observability-node';
|
|
48
|
+
*
|
|
49
|
+
* const client = new OpenAI();
|
|
50
|
+
* const observability = new Observability(); // reads env vars
|
|
51
|
+
*
|
|
52
|
+
* const traced = observeOpenAI(client, observability);
|
|
53
|
+
*
|
|
54
|
+
* // Works exactly like the original client, but now all calls are traced
|
|
55
|
+
* const response = await traced.chat.completions.create({
|
|
56
|
+
* model: 'gpt-4o',
|
|
57
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function observeOpenAI<T extends OpenAILike>(openai: T, observability: Observability, options?: ObserveOpenAIOptions): T;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;GAGG;AACH,UAAU,UAAU;IAClB,IAAI,EAAE;QACJ,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;SAClD,CAAC;KACH,CAAC;IACF,WAAW,EAAE;QACX,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAClD,CAAC;CACH;AAwCD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAChD,MAAM,EAAE,CAAC,EACT,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,oBAAyB,GACjC,CAAC,CAwLH"}
|
package/dist/openai.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ObservationLevel } from '@illuma-ai/observability-core';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an OpenAI client instance with automatic observability instrumentation.
|
|
4
|
+
*
|
|
5
|
+
* Uses ES Proxy to intercept calls to `chat.completions.create()` and
|
|
6
|
+
* `completions.create()`, automatically creating trace and generation events
|
|
7
|
+
* with model, usage, and timing information.
|
|
8
|
+
*
|
|
9
|
+
* The returned client has the same interface as the original — existing code
|
|
10
|
+
* works unchanged.
|
|
11
|
+
*
|
|
12
|
+
* @param openai - An OpenAI client instance (from the `openai` npm package)
|
|
13
|
+
* @param observability - An initialized Observability client
|
|
14
|
+
* @param options - Optional configuration for trace naming and metadata
|
|
15
|
+
* @returns A proxied OpenAI client with the same interface
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import OpenAI from 'openai';
|
|
20
|
+
* import { Observability, observeOpenAI } from '@illuma-ai/observability-node';
|
|
21
|
+
*
|
|
22
|
+
* const client = new OpenAI();
|
|
23
|
+
* const observability = new Observability(); // reads env vars
|
|
24
|
+
*
|
|
25
|
+
* const traced = observeOpenAI(client, observability);
|
|
26
|
+
*
|
|
27
|
+
* // Works exactly like the original client, but now all calls are traced
|
|
28
|
+
* const response = await traced.chat.completions.create({
|
|
29
|
+
* model: 'gpt-4o',
|
|
30
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function observeOpenAI(openai, observability, options = {}) {
|
|
35
|
+
const { traceName = 'openai-call', generationMetadata, userId, sessionId, } = options;
|
|
36
|
+
/**
|
|
37
|
+
* Wrap a create function to auto-instrument it with tracing.
|
|
38
|
+
* Handles both chat.completions.create and completions.create.
|
|
39
|
+
*/
|
|
40
|
+
function wrapCreate(originalFn, kind) {
|
|
41
|
+
return async function wrappedCreate(...args) {
|
|
42
|
+
const params = (args[0] ?? {});
|
|
43
|
+
const startTime = new Date().toISOString();
|
|
44
|
+
// Create a trace for this call
|
|
45
|
+
const trace = observability.trace({
|
|
46
|
+
name: traceName,
|
|
47
|
+
input: params,
|
|
48
|
+
userId,
|
|
49
|
+
sessionId,
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
// Call the original OpenAI method
|
|
53
|
+
const result = await originalFn.apply(openai, args);
|
|
54
|
+
const endTime = new Date().toISOString();
|
|
55
|
+
if (kind === 'chat') {
|
|
56
|
+
const response = result;
|
|
57
|
+
trace.generation({
|
|
58
|
+
name: `chat.completions.create`,
|
|
59
|
+
model: params.model ?? response.model,
|
|
60
|
+
input: params.messages,
|
|
61
|
+
output: response.choices?.[0]?.message,
|
|
62
|
+
startTime,
|
|
63
|
+
endTime,
|
|
64
|
+
modelParameters: extractModelParams(params),
|
|
65
|
+
usage: response.usage
|
|
66
|
+
? {
|
|
67
|
+
input: response.usage.prompt_tokens,
|
|
68
|
+
output: response.usage.completion_tokens,
|
|
69
|
+
total: response.usage.total_tokens,
|
|
70
|
+
}
|
|
71
|
+
: undefined,
|
|
72
|
+
metadata: {
|
|
73
|
+
...generationMetadata,
|
|
74
|
+
openai_id: response.id,
|
|
75
|
+
finish_reason: response.choices?.[0]?.finish_reason,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const response = result;
|
|
81
|
+
trace.generation({
|
|
82
|
+
name: `completions.create`,
|
|
83
|
+
model: params.model ?? response.model,
|
|
84
|
+
input: params.prompt,
|
|
85
|
+
output: response.choices?.[0]?.text,
|
|
86
|
+
startTime,
|
|
87
|
+
endTime,
|
|
88
|
+
modelParameters: extractModelParams(params),
|
|
89
|
+
usage: response.usage
|
|
90
|
+
? {
|
|
91
|
+
input: response.usage.prompt_tokens,
|
|
92
|
+
output: response.usage.completion_tokens,
|
|
93
|
+
total: response.usage.total_tokens,
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
metadata: {
|
|
97
|
+
...generationMetadata,
|
|
98
|
+
openai_id: response.id,
|
|
99
|
+
finish_reason: response.choices?.[0]?.finish_reason,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const endTime = new Date().toISOString();
|
|
107
|
+
// Record the failed generation
|
|
108
|
+
trace.generation({
|
|
109
|
+
name: kind === 'chat' ? 'chat.completions.create' : 'completions.create',
|
|
110
|
+
model: params.model,
|
|
111
|
+
input: kind === 'chat' ? params.messages : params.prompt,
|
|
112
|
+
startTime,
|
|
113
|
+
endTime,
|
|
114
|
+
modelParameters: extractModelParams(params),
|
|
115
|
+
level: ObservationLevel.ERROR,
|
|
116
|
+
statusMessage: err instanceof Error ? err.message : String(err),
|
|
117
|
+
metadata: generationMetadata,
|
|
118
|
+
});
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Extract common model parameters from OpenAI request params.
|
|
125
|
+
*/
|
|
126
|
+
function extractModelParams(params) {
|
|
127
|
+
const modelParams = {};
|
|
128
|
+
const keys = [
|
|
129
|
+
'temperature',
|
|
130
|
+
'top_p',
|
|
131
|
+
'max_tokens',
|
|
132
|
+
'max_completion_tokens',
|
|
133
|
+
'frequency_penalty',
|
|
134
|
+
'presence_penalty',
|
|
135
|
+
'stop',
|
|
136
|
+
'seed',
|
|
137
|
+
'response_format',
|
|
138
|
+
'tools',
|
|
139
|
+
'tool_choice',
|
|
140
|
+
];
|
|
141
|
+
for (const key of keys) {
|
|
142
|
+
if (params[key] !== undefined) {
|
|
143
|
+
modelParams[key] = params[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return modelParams;
|
|
147
|
+
}
|
|
148
|
+
// Create proxied chat.completions object with instrumented create method
|
|
149
|
+
const wrappedChatCreate = wrapCreate(openai.chat.completions.create.bind(openai.chat.completions), 'chat');
|
|
150
|
+
const proxiedChatCompletions = new Proxy(openai.chat.completions, {
|
|
151
|
+
get(target, prop, receiver) {
|
|
152
|
+
if (prop === 'create') {
|
|
153
|
+
return wrappedChatCreate;
|
|
154
|
+
}
|
|
155
|
+
return Reflect.get(target, prop, receiver);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// Create proxied chat object
|
|
159
|
+
const proxiedChat = new Proxy(openai.chat, {
|
|
160
|
+
get(target, prop, receiver) {
|
|
161
|
+
if (prop === 'completions') {
|
|
162
|
+
return proxiedChatCompletions;
|
|
163
|
+
}
|
|
164
|
+
return Reflect.get(target, prop, receiver);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
// Create proxied completions object with instrumented create method
|
|
168
|
+
const wrappedCompletionsCreate = wrapCreate(openai.completions.create.bind(openai.completions), 'completion');
|
|
169
|
+
const proxiedCompletions = new Proxy(openai.completions, {
|
|
170
|
+
get(target, prop, receiver) {
|
|
171
|
+
if (prop === 'create') {
|
|
172
|
+
return wrappedCompletionsCreate;
|
|
173
|
+
}
|
|
174
|
+
return Reflect.get(target, prop, receiver);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
// Create the top-level proxy
|
|
178
|
+
return new Proxy(openai, {
|
|
179
|
+
get(target, prop, receiver) {
|
|
180
|
+
if (prop === 'chat') {
|
|
181
|
+
return proxiedChat;
|
|
182
|
+
}
|
|
183
|
+
if (prop === 'completions') {
|
|
184
|
+
return proxiedCompletions;
|
|
185
|
+
}
|
|
186
|
+
return Reflect.get(target, prop, receiver);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@illuma-ai/observability-node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js SDK for Illuma Observability - LLM tracing and analytics",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@illuma-ai/observability-core": "^0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.4.5",
|
|
26
|
+
"@types/node": "^20.14.2",
|
|
27
|
+
"vitest": "^1.6.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/illuma-ai/observability.git",
|
|
35
|
+
"directory": "packages/observability-node"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"keywords": ["llm", "observability", "tracing", "illuma", "node"]
|
|
39
|
+
}
|