openlayer 0.1.28 → 0.1.30
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/index.d.ts +20 -13
- package/dist/index.js +157 -76
- package/examples/openai_assistant_monitor.mjs +56 -0
- package/examples/openai_monitor.mjs +4 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RequestOptions } from 'openai/core';
|
|
2
2
|
import { ChatCompletion, ChatCompletionChunk, ChatCompletionCreateParams, ChatCompletionMessageParam, Completion, CompletionCreateParams } from 'openai/resources';
|
|
3
|
+
import { Run } from 'openai/resources/beta/threads/runs/runs';
|
|
3
4
|
import { Stream } from 'openai/streaming';
|
|
4
5
|
/**
|
|
5
6
|
* Represents the data structure for a chat completion.
|
|
@@ -71,14 +72,13 @@ interface StreamingDataConfig {
|
|
|
71
72
|
}
|
|
72
73
|
type OpenlayerClientConstructorProps = {
|
|
73
74
|
openlayerApiKey?: string;
|
|
74
|
-
openlayerInferencePipelineName?: string;
|
|
75
|
-
openlayerProjectName?: string;
|
|
76
75
|
openlayerServerUrl?: string;
|
|
77
76
|
};
|
|
78
77
|
type OpenAIMonitorConstructorProps = OpenlayerClientConstructorProps & {
|
|
79
78
|
openAiApiKey: string;
|
|
79
|
+
openlayerInferencePipelineId?: string;
|
|
80
80
|
openlayerInferencePipelineName?: string;
|
|
81
|
-
openlayerProjectName
|
|
81
|
+
openlayerProjectName?: string;
|
|
82
82
|
};
|
|
83
83
|
type OpenlayerInferencePipeline = {
|
|
84
84
|
dataVolumeGraphs?: OpenlayerSampleVolumeGraph;
|
|
@@ -187,24 +187,25 @@ export declare class OpenlayerClient {
|
|
|
187
187
|
streamData: (data: StreamingData, config: StreamingDataConfig, inferencePipelineId: string) => Promise<void>;
|
|
188
188
|
}
|
|
189
189
|
export declare class OpenAIMonitor {
|
|
190
|
-
private inferencePipeline?;
|
|
191
190
|
private openlayerClient;
|
|
192
191
|
private openAIClient;
|
|
193
|
-
private openlayerProjectName
|
|
192
|
+
private openlayerProjectName?;
|
|
193
|
+
private openlayerInferencePipelineId?;
|
|
194
194
|
private openlayerInferencePipelineName;
|
|
195
|
-
private monitoringOn;
|
|
196
|
-
private project?;
|
|
197
195
|
/**
|
|
198
196
|
* Constructs an OpenAIMonitor instance.
|
|
199
197
|
* @param {OpenAIMonitorConstructorProps} props - The configuration properties for the OpenAI and Openlayer clients.
|
|
200
198
|
*/
|
|
201
|
-
constructor({ openAiApiKey, openlayerApiKey, openlayerProjectName, openlayerInferencePipelineName, openlayerServerUrl, }: OpenAIMonitorConstructorProps);
|
|
199
|
+
constructor({ openAiApiKey, openlayerApiKey, openlayerProjectName, openlayerInferencePipelineId, openlayerInferencePipelineName, openlayerServerUrl, }: OpenAIMonitorConstructorProps);
|
|
202
200
|
private cost;
|
|
203
|
-
private
|
|
201
|
+
private chatCompletionPrompt;
|
|
202
|
+
private threadPrompt;
|
|
203
|
+
private inputVariables;
|
|
204
204
|
/**
|
|
205
205
|
* Creates a chat completion using the OpenAI client and streams the result to Openlayer.
|
|
206
206
|
* @param {ChatCompletionCreateParams} body - The parameters for creating a chat completion.
|
|
207
207
|
* @param {RequestOptions} [options] - Optional request options.
|
|
208
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
208
209
|
* @returns {Promise<ChatCompletion | Stream<ChatCompletionChunk>>} Promise of a ChatCompletion or a Stream
|
|
209
210
|
* @throws {Error} Throws errors from the OpenAI client.
|
|
210
211
|
*/
|
|
@@ -213,17 +214,23 @@ export declare class OpenAIMonitor {
|
|
|
213
214
|
* Creates a completion using the OpenAI client and streams the result to Openlayer.
|
|
214
215
|
* @param {CompletionCreateParams} body - The parameters for creating a completion.
|
|
215
216
|
* @param {RequestOptions} [options] - Optional request options.
|
|
217
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
216
218
|
* @returns {Promise<Completion | Stream<Completion>>} Promise that resolves to a Completion or a Stream.
|
|
217
219
|
* @throws {Error} Throws errors from the OpenAI client.
|
|
218
220
|
*/
|
|
219
221
|
createCompletion: (body: CompletionCreateParams, options?: RequestOptions, additionalLogs?: StreamingData) => Promise<Completion | Stream<Completion>>;
|
|
220
222
|
/**
|
|
221
|
-
*
|
|
223
|
+
* Monitor a run from an OpenAI assistant.
|
|
224
|
+
* Once the run is completed, the thread data is published to Openlayer,
|
|
225
|
+
* along with the latency, cost, and number of tokens used.
|
|
226
|
+
* @param {Run} run - The run created by the OpenAI assistant.
|
|
227
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
228
|
+
* @returns {Promise<void>} A promise that resolves when the run data has been successfully published to Openlayer.
|
|
222
229
|
*/
|
|
223
|
-
|
|
230
|
+
monitorThreadRun(run: Run, additionalLogs?: StreamingData): Promise<void>;
|
|
224
231
|
/**
|
|
225
|
-
*
|
|
232
|
+
* Starts monitoring for the OpenAI Monitor instance. If monitoring is already active, a warning is logged.
|
|
226
233
|
*/
|
|
227
|
-
|
|
234
|
+
initialize(): Promise<void>;
|
|
228
235
|
}
|
|
229
236
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -295,9 +295,8 @@ class OpenAIMonitor {
|
|
|
295
295
|
* Constructs an OpenAIMonitor instance.
|
|
296
296
|
* @param {OpenAIMonitorConstructorProps} props - The configuration properties for the OpenAI and Openlayer clients.
|
|
297
297
|
*/
|
|
298
|
-
constructor({ openAiApiKey, openlayerApiKey, openlayerProjectName, openlayerInferencePipelineName, openlayerServerUrl, }) {
|
|
298
|
+
constructor({ openAiApiKey, openlayerApiKey, openlayerProjectName, openlayerInferencePipelineId, openlayerInferencePipelineName, openlayerServerUrl, }) {
|
|
299
299
|
this.openlayerInferencePipelineName = 'production';
|
|
300
|
-
this.monitoringOn = false;
|
|
301
300
|
this.cost = (model, inputTokens, outputTokens) => {
|
|
302
301
|
const pricing = OpenAIPricing[model];
|
|
303
302
|
const inputCost = typeof pricing === 'undefined'
|
|
@@ -310,24 +309,65 @@ class OpenAIMonitor {
|
|
|
310
309
|
? undefined
|
|
311
310
|
: (inputCost !== null && inputCost !== void 0 ? inputCost : 0) + (outputCost !== null && outputCost !== void 0 ? outputCost : 0);
|
|
312
311
|
};
|
|
313
|
-
this.
|
|
312
|
+
this.chatCompletionPrompt = (fromMessages) => fromMessages.map(({ content, role }, i) => ({
|
|
314
313
|
content: role === 'user' ? `{{ message_${i} }}` : content,
|
|
315
314
|
role,
|
|
316
315
|
}));
|
|
316
|
+
this.threadPrompt = (fromMessages) => __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
var _a, e_1, _b, _c;
|
|
318
|
+
const messages = [];
|
|
319
|
+
try {
|
|
320
|
+
for (var _d = true, _e = __asyncValues(fromMessages.iterPages()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
321
|
+
_c = _f.value;
|
|
322
|
+
_d = false;
|
|
323
|
+
const page = _c;
|
|
324
|
+
messages.push(...page.getPaginatedItems());
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
328
|
+
finally {
|
|
329
|
+
try {
|
|
330
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
331
|
+
}
|
|
332
|
+
finally { if (e_1) throw e_1.error; }
|
|
333
|
+
}
|
|
334
|
+
return messages
|
|
335
|
+
.map(({ content, role }) => content.map((item) => ({
|
|
336
|
+
content: (() => {
|
|
337
|
+
switch (item.type) {
|
|
338
|
+
case 'image_file':
|
|
339
|
+
return item.image_file.file_id;
|
|
340
|
+
case 'text':
|
|
341
|
+
default:
|
|
342
|
+
return item.text.value;
|
|
343
|
+
}
|
|
344
|
+
})(),
|
|
345
|
+
role,
|
|
346
|
+
})))
|
|
347
|
+
.flat();
|
|
348
|
+
});
|
|
349
|
+
this.inputVariables = (fromPrompt, andMessages) => {
|
|
350
|
+
const inputVariableNames = fromPrompt
|
|
351
|
+
.filter(({ role }) => role === 'user')
|
|
352
|
+
.map(({ content }) => String(content).replace(/{{\s*|\s*}}/g, ''));
|
|
353
|
+
const inputVariables = andMessages
|
|
354
|
+
.filter(({ role }) => role === 'user')
|
|
355
|
+
.map(({ content }) => content);
|
|
356
|
+
const inputVariablesMap = inputVariableNames.reduce((acc, name, i) => (Object.assign(Object.assign({}, acc), { [name]: inputVariables[i] })), {});
|
|
357
|
+
return { inputVariableNames, inputVariables, inputVariablesMap };
|
|
358
|
+
};
|
|
317
359
|
/**
|
|
318
360
|
* Creates a chat completion using the OpenAI client and streams the result to Openlayer.
|
|
319
361
|
* @param {ChatCompletionCreateParams} body - The parameters for creating a chat completion.
|
|
320
362
|
* @param {RequestOptions} [options] - Optional request options.
|
|
363
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
321
364
|
* @returns {Promise<ChatCompletion | Stream<ChatCompletionChunk>>} Promise of a ChatCompletion or a Stream
|
|
322
365
|
* @throws {Error} Throws errors from the OpenAI client.
|
|
323
366
|
*/
|
|
324
367
|
this.createChatCompletion = (body, options, additionalLogs) => __awaiter(this, void 0, void 0, function* () {
|
|
325
|
-
var
|
|
326
|
-
var
|
|
327
|
-
if (
|
|
328
|
-
console.warn('Monitoring is not active.');
|
|
329
|
-
}
|
|
330
|
-
else if (typeof this.inferencePipeline === 'undefined') {
|
|
368
|
+
var _g, e_2, _h, _j;
|
|
369
|
+
var _k, _l, _m, _o, _p, _q, _r;
|
|
370
|
+
if (typeof this.openlayerInferencePipelineId === 'undefined') {
|
|
331
371
|
console.error('No inference pipeline found.');
|
|
332
372
|
}
|
|
333
373
|
// Start a timer to measure latency
|
|
@@ -336,39 +376,33 @@ class OpenAIMonitor {
|
|
|
336
376
|
let streamedOutput = '';
|
|
337
377
|
const response = yield this.openAIClient.chat.completions.create(body, options);
|
|
338
378
|
try {
|
|
339
|
-
if (
|
|
340
|
-
const prompt = this.
|
|
341
|
-
const inputVariableNames = prompt
|
|
342
|
-
.filter(({ role }) => role === 'user')
|
|
343
|
-
.map(({ content }) => String(content).replace(/{{\s*|\s*}}/g, ''));
|
|
344
|
-
const inputVariables = body.messages
|
|
345
|
-
.filter(({ role }) => role === 'user')
|
|
346
|
-
.map(({ content }) => content);
|
|
347
|
-
const inputVariablesMap = inputVariableNames.reduce((acc, name, i) => (Object.assign(Object.assign({}, acc), { [name]: inputVariables[i] })), {});
|
|
379
|
+
if (typeof this.openlayerInferencePipelineId !== 'undefined') {
|
|
380
|
+
const prompt = this.chatCompletionPrompt(body.messages);
|
|
381
|
+
const { inputVariableNames, inputVariablesMap } = this.inputVariables(prompt, body.messages);
|
|
348
382
|
const config = Object.assign(Object.assign({}, this.openlayerClient.defaultConfig), { inputVariableNames,
|
|
349
383
|
prompt });
|
|
350
384
|
if (body.stream) {
|
|
351
385
|
const streamedResponse = response;
|
|
352
386
|
try {
|
|
353
|
-
for (var
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const chunk =
|
|
387
|
+
for (var _s = true, streamedResponse_1 = __asyncValues(streamedResponse), streamedResponse_1_1; streamedResponse_1_1 = yield streamedResponse_1.next(), _g = streamedResponse_1_1.done, !_g; _s = true) {
|
|
388
|
+
_j = streamedResponse_1_1.value;
|
|
389
|
+
_s = false;
|
|
390
|
+
const chunk = _j;
|
|
357
391
|
// Process each chunk - for example, accumulate input data
|
|
358
|
-
const chunkOutput = (
|
|
392
|
+
const chunkOutput = (_k = chunk.choices[0].delta.content) !== null && _k !== void 0 ? _k : '';
|
|
359
393
|
streamedOutput += chunkOutput;
|
|
360
394
|
}
|
|
361
395
|
}
|
|
362
|
-
catch (
|
|
396
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
363
397
|
finally {
|
|
364
398
|
try {
|
|
365
|
-
if (!
|
|
399
|
+
if (!_s && !_g && (_h = streamedResponse_1.return)) yield _h.call(streamedResponse_1);
|
|
366
400
|
}
|
|
367
|
-
finally { if (
|
|
401
|
+
finally { if (e_2) throw e_2.error; }
|
|
368
402
|
}
|
|
369
403
|
const endTime = Date.now();
|
|
370
404
|
const latency = endTime - startTime;
|
|
371
|
-
this.openlayerClient.streamData(Object.assign(Object.assign({ latency, output: streamedOutput, timestamp: startTime }, inputVariablesMap), additionalLogs), config, this.
|
|
405
|
+
this.openlayerClient.streamData(Object.assign(Object.assign({ latency, output: streamedOutput, timestamp: startTime }, inputVariablesMap), additionalLogs), config, this.openlayerInferencePipelineId);
|
|
372
406
|
}
|
|
373
407
|
else {
|
|
374
408
|
const nonStreamedResponse = response;
|
|
@@ -376,13 +410,13 @@ class OpenAIMonitor {
|
|
|
376
410
|
const endTime = Date.now();
|
|
377
411
|
const latency = endTime - startTime;
|
|
378
412
|
const output = nonStreamedResponse.choices[0].message.content;
|
|
379
|
-
const tokens = (
|
|
380
|
-
const inputTokens = (
|
|
381
|
-
const outputTokens = (
|
|
413
|
+
const tokens = (_m = (_l = nonStreamedResponse.usage) === null || _l === void 0 ? void 0 : _l.total_tokens) !== null && _m !== void 0 ? _m : 0;
|
|
414
|
+
const inputTokens = (_p = (_o = nonStreamedResponse.usage) === null || _o === void 0 ? void 0 : _o.prompt_tokens) !== null && _p !== void 0 ? _p : 0;
|
|
415
|
+
const outputTokens = (_r = (_q = nonStreamedResponse.usage) === null || _q === void 0 ? void 0 : _q.completion_tokens) !== null && _r !== void 0 ? _r : 0;
|
|
382
416
|
const cost = this.cost(nonStreamedResponse.model, inputTokens, outputTokens);
|
|
383
417
|
if (typeof output === 'string') {
|
|
384
418
|
this.openlayerClient.streamData(Object.assign(Object.assign({ cost,
|
|
385
|
-
latency, model: nonStreamedResponse.model, output, timestamp: startTime, tokens }, inputVariablesMap), additionalLogs), config, this.
|
|
419
|
+
latency, model: nonStreamedResponse.model, output, timestamp: startTime, tokens }, inputVariablesMap), additionalLogs), config, this.openlayerInferencePipelineId);
|
|
386
420
|
}
|
|
387
421
|
else {
|
|
388
422
|
console.error('No output received from OpenAI.');
|
|
@@ -399,19 +433,17 @@ class OpenAIMonitor {
|
|
|
399
433
|
* Creates a completion using the OpenAI client and streams the result to Openlayer.
|
|
400
434
|
* @param {CompletionCreateParams} body - The parameters for creating a completion.
|
|
401
435
|
* @param {RequestOptions} [options] - Optional request options.
|
|
436
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
402
437
|
* @returns {Promise<Completion | Stream<Completion>>} Promise that resolves to a Completion or a Stream.
|
|
403
438
|
* @throws {Error} Throws errors from the OpenAI client.
|
|
404
439
|
*/
|
|
405
440
|
this.createCompletion = (body, options, additionalLogs) => __awaiter(this, void 0, void 0, function* () {
|
|
406
|
-
var
|
|
407
|
-
var
|
|
441
|
+
var _t, e_3, _u, _v;
|
|
442
|
+
var _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
|
|
408
443
|
if (!body.prompt) {
|
|
409
444
|
console.error('No prompt provided.');
|
|
410
445
|
}
|
|
411
|
-
if (
|
|
412
|
-
console.warn('Monitoring is not active.');
|
|
413
|
-
}
|
|
414
|
-
else if (typeof this.inferencePipeline === 'undefined') {
|
|
446
|
+
if (typeof this.openlayerInferencePipelineId === 'undefined') {
|
|
415
447
|
console.error('No inference pipeline found.');
|
|
416
448
|
}
|
|
417
449
|
// Start a timer to measure latency
|
|
@@ -424,45 +456,45 @@ class OpenAIMonitor {
|
|
|
424
456
|
let streamedOutputTokens = 0;
|
|
425
457
|
const response = yield this.openAIClient.completions.create(body, options);
|
|
426
458
|
try {
|
|
427
|
-
if (
|
|
459
|
+
if (typeof this.openlayerInferencePipelineId !== 'undefined') {
|
|
428
460
|
const config = Object.assign(Object.assign({}, this.openlayerClient.defaultConfig), { inputVariableNames: ['input'] });
|
|
429
461
|
if (body.stream) {
|
|
430
462
|
const streamedResponse = response;
|
|
431
463
|
try {
|
|
432
|
-
for (var
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const chunk =
|
|
464
|
+
for (var _8 = true, streamedResponse_2 = __asyncValues(streamedResponse), streamedResponse_2_1; streamedResponse_2_1 = yield streamedResponse_2.next(), _t = streamedResponse_2_1.done, !_t; _8 = true) {
|
|
465
|
+
_v = streamedResponse_2_1.value;
|
|
466
|
+
_8 = false;
|
|
467
|
+
const chunk = _v;
|
|
436
468
|
// Process each chunk - for example, accumulate input data
|
|
437
469
|
streamedModel = chunk.model;
|
|
438
470
|
streamedOutput += chunk.choices[0].text.trim();
|
|
439
|
-
streamedTokens += (
|
|
440
|
-
streamedInputTokens += (
|
|
441
|
-
streamedOutputTokens += (
|
|
471
|
+
streamedTokens += (_x = (_w = chunk.usage) === null || _w === void 0 ? void 0 : _w.total_tokens) !== null && _x !== void 0 ? _x : 0;
|
|
472
|
+
streamedInputTokens += (_z = (_y = chunk.usage) === null || _y === void 0 ? void 0 : _y.prompt_tokens) !== null && _z !== void 0 ? _z : 0;
|
|
473
|
+
streamedOutputTokens += (_1 = (_0 = chunk.usage) === null || _0 === void 0 ? void 0 : _0.completion_tokens) !== null && _1 !== void 0 ? _1 : 0;
|
|
442
474
|
}
|
|
443
475
|
}
|
|
444
|
-
catch (
|
|
476
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
445
477
|
finally {
|
|
446
478
|
try {
|
|
447
|
-
if (!
|
|
479
|
+
if (!_8 && !_t && (_u = streamedResponse_2.return)) yield _u.call(streamedResponse_2);
|
|
448
480
|
}
|
|
449
|
-
finally { if (
|
|
481
|
+
finally { if (e_3) throw e_3.error; }
|
|
450
482
|
}
|
|
451
483
|
const endTime = Date.now();
|
|
452
484
|
const latency = endTime - startTime;
|
|
453
485
|
const cost = this.cost(streamedModel, streamedInputTokens, streamedOutputTokens);
|
|
454
|
-
this.openlayerClient.streamData(Object.assign({ cost, input: body.prompt, latency, output: streamedOutput, timestamp: startTime, tokens: streamedTokens }, additionalLogs), config, this.
|
|
486
|
+
this.openlayerClient.streamData(Object.assign({ cost, input: body.prompt, latency, output: streamedOutput, timestamp: startTime, tokens: streamedTokens }, additionalLogs), config, this.openlayerInferencePipelineId);
|
|
455
487
|
}
|
|
456
488
|
else {
|
|
457
489
|
const nonStreamedResponse = response;
|
|
458
490
|
// Handle regular (non-streamed) response
|
|
459
491
|
const endTime = Date.now();
|
|
460
492
|
const latency = endTime - startTime;
|
|
461
|
-
const tokens = (
|
|
462
|
-
const inputTokens = (
|
|
463
|
-
const outputTokens = (
|
|
493
|
+
const tokens = (_3 = (_2 = nonStreamedResponse.usage) === null || _2 === void 0 ? void 0 : _2.total_tokens) !== null && _3 !== void 0 ? _3 : 0;
|
|
494
|
+
const inputTokens = (_5 = (_4 = nonStreamedResponse.usage) === null || _4 === void 0 ? void 0 : _4.prompt_tokens) !== null && _5 !== void 0 ? _5 : 0;
|
|
495
|
+
const outputTokens = (_7 = (_6 = nonStreamedResponse.usage) === null || _6 === void 0 ? void 0 : _6.completion_tokens) !== null && _7 !== void 0 ? _7 : 0;
|
|
464
496
|
const cost = this.cost(nonStreamedResponse.model, inputTokens, outputTokens);
|
|
465
|
-
this.openlayerClient.streamData(Object.assign({ cost, input: body.prompt, latency, output: nonStreamedResponse.choices[0].text, timestamp: startTime, tokens }, additionalLogs), config, this.
|
|
497
|
+
this.openlayerClient.streamData(Object.assign({ cost, input: body.prompt, latency, output: nonStreamedResponse.choices[0].text, timestamp: startTime, tokens }, additionalLogs), config, this.openlayerInferencePipelineId);
|
|
466
498
|
}
|
|
467
499
|
}
|
|
468
500
|
}
|
|
@@ -472,6 +504,7 @@ class OpenAIMonitor {
|
|
|
472
504
|
return response;
|
|
473
505
|
});
|
|
474
506
|
this.openlayerProjectName = openlayerProjectName;
|
|
507
|
+
this.openlayerInferencePipelineId = openlayerInferencePipelineId;
|
|
475
508
|
if (openlayerInferencePipelineName) {
|
|
476
509
|
this.openlayerInferencePipelineName = openlayerInferencePipelineName;
|
|
477
510
|
}
|
|
@@ -484,43 +517,91 @@ class OpenAIMonitor {
|
|
|
484
517
|
dangerouslyAllowBrowser: true,
|
|
485
518
|
});
|
|
486
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Monitor a run from an OpenAI assistant.
|
|
522
|
+
* Once the run is completed, the thread data is published to Openlayer,
|
|
523
|
+
* along with the latency, cost, and number of tokens used.
|
|
524
|
+
* @param {Run} run - The run created by the OpenAI assistant.
|
|
525
|
+
* @param {StreamingData} [additionalLogs] - Optional metadata logs to include with the request sent to Openlayer.
|
|
526
|
+
* @returns {Promise<void>} A promise that resolves when the run data has been successfully published to Openlayer.
|
|
527
|
+
*/
|
|
528
|
+
monitorThreadRun(run, additionalLogs) {
|
|
529
|
+
var _a;
|
|
530
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
531
|
+
if (typeof this.openlayerInferencePipelineId === 'undefined') {
|
|
532
|
+
console.error('No inference pipeline found.');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (run.status !== 'completed') {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const { assistant_id, completed_at, created_at, model, thread_id,
|
|
540
|
+
// @ts-ignore
|
|
541
|
+
usage, } = run;
|
|
542
|
+
// @ts-ignore
|
|
543
|
+
const { completion_tokens, prompt_tokens, total_tokens } = typeof usage === 'undefined' ||
|
|
544
|
+
typeof usage !== 'object' ||
|
|
545
|
+
usage === null
|
|
546
|
+
? {}
|
|
547
|
+
: usage;
|
|
548
|
+
const cost = this.cost(model, prompt_tokens, completion_tokens);
|
|
549
|
+
const latency = completed_at === null ||
|
|
550
|
+
created_at === null ||
|
|
551
|
+
isNaN(completed_at) ||
|
|
552
|
+
isNaN(created_at)
|
|
553
|
+
? undefined
|
|
554
|
+
: (completed_at - created_at) * 1000;
|
|
555
|
+
const messages = yield this.openAIClient.beta.threads.messages.list(thread_id, { order: 'asc' });
|
|
556
|
+
const populatedPrompt = yield this.threadPrompt(messages);
|
|
557
|
+
const prompt = this.chatCompletionPrompt(populatedPrompt);
|
|
558
|
+
const { inputVariableNames, inputVariablesMap } = this.inputVariables(prompt, populatedPrompt);
|
|
559
|
+
const config = Object.assign(Object.assign({}, this.openlayerClient.defaultConfig), { inputVariableNames, prompt: prompt.slice(0, prompt.length - 1) });
|
|
560
|
+
const output = (_a = prompt[prompt.length - 1]) === null || _a === void 0 ? void 0 : _a.content;
|
|
561
|
+
const resolvedOutput = typeof output === 'string'
|
|
562
|
+
? output
|
|
563
|
+
: typeof output === 'undefined' || output === null
|
|
564
|
+
? ''
|
|
565
|
+
: `${output}`;
|
|
566
|
+
this.openlayerClient.streamData(Object.assign(Object.assign({ 'OpenAI Assistant ID': assistant_id, 'OpenAI Thread ID': thread_id, cost,
|
|
567
|
+
latency, output: resolvedOutput, timestamp: run.created_at, tokens: total_tokens }, inputVariablesMap), additionalLogs), config, this.openlayerInferencePipelineId);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
console.error('Error logging thread run:', error);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
487
574
|
/**
|
|
488
575
|
* Starts monitoring for the OpenAI Monitor instance. If monitoring is already active, a warning is logged.
|
|
489
576
|
*/
|
|
490
|
-
|
|
577
|
+
initialize() {
|
|
491
578
|
return __awaiter(this, void 0, void 0, function* () {
|
|
492
|
-
|
|
493
|
-
|
|
579
|
+
console.info('Initializing monitor: creating or loading an Openlayer project and inference pipeline...');
|
|
580
|
+
if (typeof this.openlayerInferencePipelineId !== 'undefined') {
|
|
581
|
+
console.info('Monitor initialized: using inference pipeline ID provided.');
|
|
494
582
|
return;
|
|
495
583
|
}
|
|
496
|
-
console.info('Starting monitor: creating or loading an Openlayer project and inference pipeline...');
|
|
497
584
|
try {
|
|
498
|
-
this.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
585
|
+
if (typeof this.openlayerProjectName === 'undefined') {
|
|
586
|
+
console.error('No project name provided.');
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const project = yield this.openlayerClient.createProject(this.openlayerProjectName, 'llm-base');
|
|
590
|
+
if (typeof project !== 'undefined') {
|
|
591
|
+
const inferencePipeline = yield this.openlayerClient.createInferencePipeline(project.id, this.openlayerInferencePipelineName);
|
|
592
|
+
if (typeof (inferencePipeline === null || inferencePipeline === void 0 ? void 0 : inferencePipeline.id) === 'undefined') {
|
|
593
|
+
console.error('Unable to locate inference pipeline.');
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
this.openlayerInferencePipelineId = inferencePipeline.id;
|
|
597
|
+
}
|
|
503
598
|
}
|
|
504
599
|
console.info('Monitor started');
|
|
505
600
|
}
|
|
506
601
|
catch (error) {
|
|
507
602
|
console.error('An error occurred while starting the monitor:', error);
|
|
508
|
-
this.stopMonitoring();
|
|
509
603
|
}
|
|
510
604
|
});
|
|
511
605
|
}
|
|
512
|
-
/**
|
|
513
|
-
* Stops monitoring for the OpenAI Monitor instance. If monitoring is not active, a warning is logged.
|
|
514
|
-
*/
|
|
515
|
-
stopMonitoring() {
|
|
516
|
-
if (!this.monitoringOn) {
|
|
517
|
-
console.warn('Monitor is not active.');
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
this.monitoringOn = false;
|
|
521
|
-
this.project = undefined;
|
|
522
|
-
this.inferencePipeline = undefined;
|
|
523
|
-
console.info('Monitor stopped.');
|
|
524
|
-
}
|
|
525
606
|
}
|
|
526
607
|
exports.OpenAIMonitor = OpenAIMonitor;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This example shows how to use Openlayer to monitor runs from OpenAI assistants.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import OpenAI from 'openai';
|
|
6
|
+
import { OpenAIMonitor } from 'openlayer';
|
|
7
|
+
|
|
8
|
+
const openai = new OpenAI({
|
|
9
|
+
apiKey: 'YOUR_OPENAI_API_KEY',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Create monitor with your credentials
|
|
13
|
+
const monitor = new OpenAIMonitor({
|
|
14
|
+
openAiApiKey: 'YOUR_OPENAI_API_KEY',
|
|
15
|
+
openlayerApiKey: 'YOUR_OPENLAYER_API_KEY',
|
|
16
|
+
// EITHER specify an existing inference pipeline ID
|
|
17
|
+
openlayerInferencePipelineId: 'YOUR_OPENLAYER_INFERENCE_PIPELINE_ID',
|
|
18
|
+
// OR the project and inference pipeline names to create or load one
|
|
19
|
+
openlayerInferencePipelineName: 'production',
|
|
20
|
+
openlayerProjectName: 'YOUR_OPENLAYER_PROJECT_NAME',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await monitor.initialize();
|
|
24
|
+
|
|
25
|
+
// Create the assistant
|
|
26
|
+
const assistant = await openai.beta.assistants.create({
|
|
27
|
+
description:
|
|
28
|
+
'You are great at creating and explaining beautiful data visualizations.',
|
|
29
|
+
model: 'gpt-4',
|
|
30
|
+
name: 'Data visualizer',
|
|
31
|
+
tools: [{ type: 'code_interpreter' }],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Create a thread
|
|
35
|
+
const thread = await openai.beta.threads.create({
|
|
36
|
+
messages: [
|
|
37
|
+
{
|
|
38
|
+
content: 'Create a data visualization of the american GDP.',
|
|
39
|
+
role: 'user',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Run the assistant on the thread
|
|
45
|
+
const run = await openai.beta.threads.runs.create(thread.id, {
|
|
46
|
+
assistant_id: assistant.id,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Keep polling the run results
|
|
50
|
+
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
51
|
+
while (runStatus.status !== 'completed') {
|
|
52
|
+
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
53
|
+
|
|
54
|
+
// Monitor the run. If complete, it will be sent to Openlayer
|
|
55
|
+
await monitor.monitorThreadRun(runStatus);
|
|
56
|
+
}
|
|
@@ -7,11 +7,14 @@ import { OpenAIMonitor } from 'openlayer';
|
|
|
7
7
|
const monitor = new OpenAIMonitor({
|
|
8
8
|
openAiApiKey: 'YOUR_OPENAI_API_KEY',
|
|
9
9
|
openlayerApiKey: 'YOUR_OPENLAYER_API_KEY',
|
|
10
|
+
// EITHER specify an existing inference pipeline ID
|
|
11
|
+
openlayerInferencePipelineId: 'YOUR_OPENLAYER_INFERENCE_PIPELINE_ID',
|
|
12
|
+
// OR the project and inference pipeline names to create or load one
|
|
10
13
|
openlayerInferencePipelineName: 'production',
|
|
11
14
|
openlayerProjectName: 'YOUR_OPENLAYER_PROJECT_NAME',
|
|
12
15
|
});
|
|
13
16
|
|
|
14
|
-
await monitor.
|
|
17
|
+
await monitor.initialize();
|
|
15
18
|
|
|
16
19
|
const inputs = [
|
|
17
20
|
{
|