@arizeai/openinference-instrumentation-openai 0.0.4 → 0.1.1

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.
Files changed (36) hide show
  1. package/dist/esm/instrumentation.d.ts.map +1 -1
  2. package/dist/esm/instrumentation.js +438 -44
  3. package/dist/esm/instrumentation.js.map +1 -1
  4. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  5. package/dist/esm/typeUtils.d.ts +9 -0
  6. package/dist/esm/typeUtils.d.ts.map +1 -0
  7. package/dist/esm/typeUtils.js +13 -0
  8. package/dist/esm/typeUtils.js.map +1 -0
  9. package/dist/esm/version.d.ts +1 -1
  10. package/dist/esm/version.js +1 -1
  11. package/dist/esnext/instrumentation.d.ts.map +1 -1
  12. package/dist/esnext/instrumentation.js +300 -43
  13. package/dist/esnext/instrumentation.js.map +1 -1
  14. package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
  15. package/dist/esnext/typeUtils.d.ts +9 -0
  16. package/dist/esnext/typeUtils.d.ts.map +1 -0
  17. package/dist/esnext/typeUtils.js +13 -0
  18. package/dist/esnext/typeUtils.js.map +1 -0
  19. package/dist/esnext/version.d.ts +1 -1
  20. package/dist/esnext/version.js +1 -1
  21. package/dist/src/instrumentation.d.ts.map +1 -1
  22. package/dist/src/instrumentation.js +312 -43
  23. package/dist/src/instrumentation.js.map +1 -1
  24. package/dist/src/typeUtils.d.ts +9 -0
  25. package/dist/src/typeUtils.d.ts.map +1 -0
  26. package/dist/src/typeUtils.js +18 -0
  27. package/dist/src/typeUtils.js.map +1 -0
  28. package/dist/src/version.d.ts +1 -1
  29. package/dist/src/version.js +1 -1
  30. package/dist/test/openai.test.js +607 -1
  31. package/dist/test/openai.test.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +2 -2
  34. package/src/instrumentation.ts +368 -65
  35. package/src/typeUtils.ts +13 -0
  36. package/src/version.ts +1 -1
@@ -31,6 +31,26 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31
31
  step((generator = generator.apply(thisArg, _arguments || [])).next());
32
32
  });
33
33
  };
34
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
35
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
36
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
37
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
38
+ return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
39
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
40
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
41
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
42
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
43
+ function fulfill(value) { resume("next", value); }
44
+ function reject(value) { resume("throw", value); }
45
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
46
+ };
47
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
48
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
49
+ var m = o[Symbol.asyncIterator], i;
50
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
51
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
52
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
53
+ };
34
54
  Object.defineProperty(exports, "__esModule", { value: true });
35
55
  const src_1 = require("../src");
36
56
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
@@ -40,6 +60,18 @@ tracerProvider.register();
40
60
  const instrumentation = new src_1.OpenAIInstrumentation();
41
61
  instrumentation.disable();
42
62
  const OpenAI = __importStar(require("openai"));
63
+ const streaming_1 = require("openai/streaming");
64
+ // Function tools
65
+ function getCurrentLocation() {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ return "Boston"; // Simulate lookup
68
+ });
69
+ }
70
+ function getWeather(_args) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ return { temperature: 52, precipitation: "rainy" };
73
+ });
74
+ }
43
75
  describe("OpenAIInstrumentation", () => {
44
76
  let openai;
45
77
  const memoryExporter = new sdk_trace_base_1.InMemorySpanExporter();
@@ -52,7 +84,7 @@ describe("OpenAIInstrumentation", () => {
52
84
  beforeAll(() => {
53
85
  instrumentation.enable();
54
86
  openai = new OpenAI.OpenAI({
55
- apiKey: `fake-api-key`,
87
+ apiKey: "fake-api-key",
56
88
  });
57
89
  });
58
90
  afterAll(() => {
@@ -123,6 +155,51 @@ describe("OpenAIInstrumentation", () => {
123
155
  }
124
156
  `);
125
157
  }));
158
+ it("creates a span for completions", () => __awaiter(void 0, void 0, void 0, function* () {
159
+ const response = {
160
+ id: "cmpl-8fZu1H3VijJUWev9asnxaYyQvJTC9",
161
+ object: "text_completion",
162
+ created: 1704920149,
163
+ model: "gpt-3.5-turbo-instruct",
164
+ choices: [
165
+ {
166
+ text: "This is a test",
167
+ index: 0,
168
+ logprobs: null,
169
+ finish_reason: "stop",
170
+ },
171
+ ],
172
+ usage: { prompt_tokens: 12, completion_tokens: 5, total_tokens: 17 },
173
+ };
174
+ // Mock out the completions endpoint
175
+ jest.spyOn(openai, "post").mockImplementation(
176
+ // @ts-expect-error the response type is not correct - this is just for testing
177
+ () => __awaiter(void 0, void 0, void 0, function* () {
178
+ return response;
179
+ }));
180
+ yield openai.completions.create({
181
+ prompt: "Say this is a test",
182
+ model: "gpt-3.5-turbo-instruct",
183
+ });
184
+ const spans = memoryExporter.getFinishedSpans();
185
+ expect(spans.length).toBe(1);
186
+ const span = spans[0];
187
+ expect(span.name).toBe("OpenAI Completions");
188
+ expect(span.attributes).toMatchInlineSnapshot(`
189
+ {
190
+ "input.mime_type": "text/plain",
191
+ "input.value": "Say this is a test",
192
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo-instruct"}",
193
+ "llm.model_name": "gpt-3.5-turbo-instruct",
194
+ "llm.token_count.completion": 5,
195
+ "llm.token_count.prompt": 12,
196
+ "llm.token_count.total": 17,
197
+ "openinference.span.kind": "llm",
198
+ "output.mime_type": "text/plain",
199
+ "output.value": "This is a test",
200
+ }
201
+ `);
202
+ }));
126
203
  it("creates a span for embedding create", () => __awaiter(void 0, void 0, void 0, function* () {
127
204
  const response = {
128
205
  object: "list",
@@ -157,5 +234,534 @@ describe("OpenAIInstrumentation", () => {
157
234
  }
158
235
  `);
159
236
  }));
237
+ it("can handle streaming responses", () => __awaiter(void 0, void 0, void 0, function* () {
238
+ var _a, e_1, _b, _c;
239
+ // Mock out the post endpoint to return a stream
240
+ jest.spyOn(openai, "post").mockImplementation(
241
+ // @ts-expect-error the response type is not correct - this is just for testing
242
+ () => __awaiter(void 0, void 0, void 0, function* () {
243
+ const iterator = () => (function () {
244
+ return __asyncGenerator(this, arguments, function* () {
245
+ yield yield __await({ choices: [{ delta: { content: "This is " } }] });
246
+ yield yield __await({ choices: [{ delta: { content: "a test." } }] });
247
+ yield yield __await({ choices: [{ delta: { finish_reason: "stop" } }] });
248
+ });
249
+ })();
250
+ const controller = new AbortController();
251
+ return new streaming_1.Stream(iterator, controller);
252
+ }));
253
+ const stream = yield openai.chat.completions.create({
254
+ messages: [{ role: "user", content: "Say this is a test" }],
255
+ model: "gpt-3.5-turbo",
256
+ stream: true,
257
+ });
258
+ let response = "";
259
+ try {
260
+ for (var _d = true, stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = yield stream_1.next(), _a = stream_1_1.done, !_a; _d = true) {
261
+ _c = stream_1_1.value;
262
+ _d = false;
263
+ const chunk = _c;
264
+ if (chunk.choices[0].delta.content)
265
+ response += chunk.choices[0].delta.content;
266
+ }
267
+ }
268
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
269
+ finally {
270
+ try {
271
+ if (!_d && !_a && (_b = stream_1.return)) yield _b.call(stream_1);
272
+ }
273
+ finally { if (e_1) throw e_1.error; }
274
+ }
275
+ expect(response).toBe("This is a test.");
276
+ const spans = memoryExporter.getFinishedSpans();
277
+ expect(spans.length).toBe(1);
278
+ const span = spans[0];
279
+ expect(span.name).toBe("OpenAI Chat Completions");
280
+ expect(span.attributes).toMatchInlineSnapshot(`
281
+ {
282
+ "input.mime_type": "application/json",
283
+ "input.value": "{"messages":[{"role":"user","content":"Say this is a test"}],"model":"gpt-3.5-turbo","stream":true}",
284
+ "llm.input_messages.0.message.content": "Say this is a test",
285
+ "llm.input_messages.0.message.role": "user",
286
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","stream":true}",
287
+ "llm.model_name": "gpt-3.5-turbo",
288
+ "llm.output_messages.0.message.content": "This is a test.",
289
+ "llm.output_messages.0.message.role": "assistant",
290
+ "openinference.span.kind": "llm",
291
+ "output.mime_type": "text/plain",
292
+ "output.value": "This is a test.",
293
+ }
294
+ `);
295
+ }));
296
+ it("should capture tool calls", () => __awaiter(void 0, void 0, void 0, function* () {
297
+ // Mock out the embedding create endpoint
298
+ const response1 = {
299
+ id: "chatcmpl-8hhqZDFTRD0vzExhqWnMLE7viVl7E",
300
+ object: "chat.completion",
301
+ created: 1705427343,
302
+ model: "gpt-3.5-turbo-0613",
303
+ choices: [
304
+ {
305
+ index: 0,
306
+ message: {
307
+ role: "assistant",
308
+ content: null,
309
+ tool_calls: [
310
+ {
311
+ id: "call_5ERYvu4iTGSvDlcDQjDP3g3J",
312
+ type: "function",
313
+ function: { name: "getCurrentLocation", arguments: "{}" },
314
+ },
315
+ ],
316
+ },
317
+ logprobs: null,
318
+ finish_reason: "tool_calls",
319
+ },
320
+ ],
321
+ usage: { prompt_tokens: 70, completion_tokens: 7, total_tokens: 77 },
322
+ system_fingerprint: null,
323
+ };
324
+ const response2 = {
325
+ id: "chatcmpl-8hhsP9eAplUFYB3mHUJxBkq7IwnjZ",
326
+ object: "chat.completion",
327
+ created: 1705427457,
328
+ model: "gpt-3.5-turbo-0613",
329
+ choices: [
330
+ {
331
+ index: 0,
332
+ message: {
333
+ role: "assistant",
334
+ content: null,
335
+ tool_calls: [
336
+ {
337
+ id: "call_0LCdYLkdRUt3rV3dawoIFHBf",
338
+ type: "function",
339
+ function: {
340
+ name: "getWeather",
341
+ arguments: '{\n "location": "Boston"\n}',
342
+ },
343
+ },
344
+ ],
345
+ },
346
+ logprobs: null,
347
+ finish_reason: "tool_calls",
348
+ },
349
+ ],
350
+ usage: { prompt_tokens: 86, completion_tokens: 15, total_tokens: 101 },
351
+ system_fingerprint: null,
352
+ };
353
+ const response3 = {
354
+ id: "chatcmpl-8hhtfzSD33tsG7XJiBg4F9MqnXKDp",
355
+ object: "chat.completion",
356
+ created: 1705427535,
357
+ model: "gpt-3.5-turbo-0613",
358
+ choices: [
359
+ {
360
+ index: 0,
361
+ message: {
362
+ role: "assistant",
363
+ content: "The weather in Boston this week is expected to be rainy with a temperature of 52 degrees.",
364
+ },
365
+ logprobs: null,
366
+ finish_reason: "stop",
367
+ },
368
+ ],
369
+ usage: { prompt_tokens: 121, completion_tokens: 20, total_tokens: 141 },
370
+ system_fingerprint: null,
371
+ };
372
+ jest
373
+ .spyOn(openai, "post")
374
+ .mockImplementationOnce(
375
+ // @ts-expect-error the response type is not correct - this is just for testing
376
+ () => __awaiter(void 0, void 0, void 0, function* () {
377
+ return response1;
378
+ }))
379
+ .mockImplementationOnce(
380
+ // @ts-expect-error the response type is not correct - this is just for testing
381
+ () => __awaiter(void 0, void 0, void 0, function* () {
382
+ return response2;
383
+ }))
384
+ .mockImplementationOnce(
385
+ // @ts-expect-error the response type is not correct - this is just for testing
386
+ () => __awaiter(void 0, void 0, void 0, function* () {
387
+ return response3;
388
+ }));
389
+ const messages = [];
390
+ const runner = openai.beta.chat.completions
391
+ .runTools({
392
+ model: "gpt-3.5-turbo",
393
+ messages: [{ role: "user", content: "How is the weather this week?" }],
394
+ tools: [
395
+ {
396
+ type: "function",
397
+ function: {
398
+ function: getCurrentLocation,
399
+ parameters: { type: "object", properties: {} },
400
+ description: "Get the current location of the user.",
401
+ },
402
+ },
403
+ {
404
+ type: "function",
405
+ function: {
406
+ function: getWeather,
407
+ parse: JSON.parse, // or use a validation library like zod for typesafe parsing.
408
+ description: "Get the weather for a location.",
409
+ parameters: {
410
+ type: "object",
411
+ properties: {
412
+ location: { type: "string" },
413
+ },
414
+ },
415
+ },
416
+ },
417
+ ],
418
+ })
419
+ .on("message", (message) => messages.push(message));
420
+ const _finalContent = yield runner.finalContent();
421
+ const spans = memoryExporter.getFinishedSpans();
422
+ expect(spans.length).toBe(3);
423
+ const [span1, span2, span3] = spans;
424
+ expect(span1.name).toBe("OpenAI Chat Completions");
425
+ expect(span1.attributes).toMatchInlineSnapshot(`
426
+ {
427
+ "input.mime_type": "application/json",
428
+ "input.value": "{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"How is the weather this week?"}],"tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
429
+ "llm.input_messages.0.message.content": "How is the weather this week?",
430
+ "llm.input_messages.0.message.role": "user",
431
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
432
+ "llm.model_name": "gpt-3.5-turbo-0613",
433
+ "llm.output_messages.0.message.role": "assistant",
434
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.arguments": "{}",
435
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.name": "getCurrentLocation",
436
+ "llm.token_count.completion": 7,
437
+ "llm.token_count.prompt": 70,
438
+ "llm.token_count.total": 77,
439
+ "openinference.span.kind": "llm",
440
+ "output.mime_type": "application/json",
441
+ "output.value": "{"id":"chatcmpl-8hhqZDFTRD0vzExhqWnMLE7viVl7E","object":"chat.completion","created":1705427343,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_5ERYvu4iTGSvDlcDQjDP3g3J","type":"function","function":{"name":"getCurrentLocation","arguments":"{}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":70,"completion_tokens":7,"total_tokens":77},"system_fingerprint":null}",
442
+ }
443
+ `);
444
+ expect(span2.name).toBe("OpenAI Chat Completions");
445
+ expect(span2.attributes).toMatchInlineSnapshot(`
446
+ {
447
+ "input.mime_type": "application/json",
448
+ "input.value": "{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"How is the weather this week?"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_5ERYvu4iTGSvDlcDQjDP3g3J","type":"function","function":{"name":"getCurrentLocation","arguments":"{}"}}]},{"role":"tool","tool_call_id":"call_5ERYvu4iTGSvDlcDQjDP3g3J","content":"Boston"}],"tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
449
+ "llm.input_messages.0.message.content": "How is the weather this week?",
450
+ "llm.input_messages.0.message.role": "user",
451
+ "llm.input_messages.1.message.role": "assistant",
452
+ "llm.input_messages.1.message.tool_calls.0.tool_call.function.arguments": "{}",
453
+ "llm.input_messages.1.message.tool_calls.0.tool_call.function.name": "getCurrentLocation",
454
+ "llm.input_messages.2.message.content": "Boston",
455
+ "llm.input_messages.2.message.role": "tool",
456
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
457
+ "llm.model_name": "gpt-3.5-turbo-0613",
458
+ "llm.output_messages.0.message.role": "assistant",
459
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.arguments": "{
460
+ "location": "Boston"
461
+ }",
462
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.name": "getWeather",
463
+ "llm.token_count.completion": 15,
464
+ "llm.token_count.prompt": 86,
465
+ "llm.token_count.total": 101,
466
+ "openinference.span.kind": "llm",
467
+ "output.mime_type": "application/json",
468
+ "output.value": "{"id":"chatcmpl-8hhsP9eAplUFYB3mHUJxBkq7IwnjZ","object":"chat.completion","created":1705427457,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_0LCdYLkdRUt3rV3dawoIFHBf","type":"function","function":{"name":"getWeather","arguments":"{\\n \\"location\\": \\"Boston\\"\\n}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":86,"completion_tokens":15,"total_tokens":101},"system_fingerprint":null}",
469
+ }
470
+ `);
471
+ expect(span3.name).toBe("OpenAI Chat Completions");
472
+ expect(span3.attributes).toMatchInlineSnapshot(`
473
+ {
474
+ "input.mime_type": "application/json",
475
+ "input.value": "{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"How is the weather this week?"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_5ERYvu4iTGSvDlcDQjDP3g3J","type":"function","function":{"name":"getCurrentLocation","arguments":"{}"}}]},{"role":"tool","tool_call_id":"call_5ERYvu4iTGSvDlcDQjDP3g3J","content":"Boston"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_0LCdYLkdRUt3rV3dawoIFHBf","type":"function","function":{"name":"getWeather","arguments":"{\\n \\"location\\": \\"Boston\\"\\n}"}}]},{"role":"tool","tool_call_id":"call_0LCdYLkdRUt3rV3dawoIFHBf","content":"{\\"temperature\\":52,\\"precipitation\\":\\"rainy\\"}"}],"tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
476
+ "llm.input_messages.0.message.content": "How is the weather this week?",
477
+ "llm.input_messages.0.message.role": "user",
478
+ "llm.input_messages.1.message.role": "assistant",
479
+ "llm.input_messages.1.message.tool_calls.0.tool_call.function.arguments": "{}",
480
+ "llm.input_messages.1.message.tool_calls.0.tool_call.function.name": "getCurrentLocation",
481
+ "llm.input_messages.2.message.content": "Boston",
482
+ "llm.input_messages.2.message.role": "tool",
483
+ "llm.input_messages.3.message.role": "assistant",
484
+ "llm.input_messages.3.message.tool_calls.0.tool_call.function.arguments": "{
485
+ "location": "Boston"
486
+ }",
487
+ "llm.input_messages.3.message.tool_calls.0.tool_call.function.name": "getWeather",
488
+ "llm.input_messages.4.message.content": "{"temperature":52,"precipitation":"rainy"}",
489
+ "llm.input_messages.4.message.role": "tool",
490
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","parameters":{"type":"object","properties":{"location":{"type":"string"}}},"description":"Get the weather for a location."}}],"tool_choice":"auto","stream":false}",
491
+ "llm.model_name": "gpt-3.5-turbo-0613",
492
+ "llm.output_messages.0.message.content": "The weather in Boston this week is expected to be rainy with a temperature of 52 degrees.",
493
+ "llm.output_messages.0.message.role": "assistant",
494
+ "llm.token_count.completion": 20,
495
+ "llm.token_count.prompt": 121,
496
+ "llm.token_count.total": 141,
497
+ "openinference.span.kind": "llm",
498
+ "output.mime_type": "application/json",
499
+ "output.value": "{"id":"chatcmpl-8hhtfzSD33tsG7XJiBg4F9MqnXKDp","object":"chat.completion","created":1705427535,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"message":{"role":"assistant","content":"The weather in Boston this week is expected to be rainy with a temperature of 52 degrees."},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":121,"completion_tokens":20,"total_tokens":141},"system_fingerprint":null}",
500
+ }
501
+ `);
502
+ }));
503
+ it("should capture tool calls with streaming", () => __awaiter(void 0, void 0, void 0, function* () {
504
+ var _e, e_2, _f, _g;
505
+ jest.spyOn(openai, "post").mockImplementation(
506
+ // @ts-expect-error the response type is not correct - this is just for testing
507
+ () => __awaiter(void 0, void 0, void 0, function* () {
508
+ const iterator = () => (function () {
509
+ return __asyncGenerator(this, arguments, function* () {
510
+ yield yield __await({
511
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
512
+ object: "chat.completion.chunk",
513
+ created: 1705535755,
514
+ model: "gpt-3.5-turbo-0613",
515
+ system_fingerprint: null,
516
+ choices: [
517
+ {
518
+ index: 0,
519
+ delta: {
520
+ role: "assistant",
521
+ content: null,
522
+ tool_calls: [
523
+ {
524
+ index: 0,
525
+ id: "call_PGkcUg2u6vYrCpTn0e9ofykY",
526
+ type: "function",
527
+ function: { name: "getWeather", arguments: "" },
528
+ },
529
+ ],
530
+ },
531
+ logprobs: null,
532
+ finish_reason: null,
533
+ },
534
+ ],
535
+ });
536
+ yield yield __await({
537
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
538
+ object: "chat.completion.chunk",
539
+ created: 1705535755,
540
+ model: "gpt-3.5-turbo-0613",
541
+ system_fingerprint: null,
542
+ choices: [
543
+ {
544
+ index: 0,
545
+ delta: {
546
+ tool_calls: [{ index: 0, function: { arguments: "{}" } }],
547
+ },
548
+ logprobs: null,
549
+ finish_reason: null,
550
+ },
551
+ ],
552
+ });
553
+ yield yield __await({
554
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
555
+ object: "chat.completion.chunk",
556
+ created: 1705535755,
557
+ model: "gpt-3.5-turbo-0613",
558
+ system_fingerprint: null,
559
+ choices: [
560
+ {
561
+ index: 0,
562
+ delta: {},
563
+ logprobs: null,
564
+ finish_reason: "tool_calls",
565
+ },
566
+ ],
567
+ });
568
+ });
569
+ })();
570
+ const controller = new AbortController();
571
+ return new streaming_1.Stream(iterator, controller);
572
+ }));
573
+ const stream = yield openai.chat.completions.create({
574
+ messages: [{ role: "user", content: "What's the weather today?" }],
575
+ model: "gpt-3.5-turbo",
576
+ tools: [
577
+ {
578
+ type: "function",
579
+ function: {
580
+ name: "getCurrentLocation",
581
+ parameters: { type: "object", properties: {} },
582
+ description: "Get the current location of the user.",
583
+ },
584
+ },
585
+ {
586
+ type: "function",
587
+ function: {
588
+ name: "getWeather",
589
+ description: "Get the weather for a location.",
590
+ parameters: {
591
+ type: "object",
592
+ properties: {
593
+ location: { type: "string" },
594
+ },
595
+ },
596
+ },
597
+ },
598
+ ],
599
+ stream: true,
600
+ });
601
+ let response = "";
602
+ try {
603
+ for (var _h = true, stream_2 = __asyncValues(stream), stream_2_1; stream_2_1 = yield stream_2.next(), _e = stream_2_1.done, !_e; _h = true) {
604
+ _g = stream_2_1.value;
605
+ _h = false;
606
+ const chunk = _g;
607
+ if (chunk.choices[0].delta.content)
608
+ response += chunk.choices[0].delta.content;
609
+ }
610
+ }
611
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
612
+ finally {
613
+ try {
614
+ if (!_h && !_e && (_f = stream_2.return)) yield _f.call(stream_2);
615
+ }
616
+ finally { if (e_2) throw e_2.error; }
617
+ }
618
+ // When a tool is called, the content is empty
619
+ expect(response).toBe("");
620
+ const spans = memoryExporter.getFinishedSpans();
621
+ expect(spans.length).toBe(1);
622
+ const span = spans[0];
623
+ expect(span.name).toBe("OpenAI Chat Completions");
624
+ expect(span.attributes).toMatchInlineSnapshot(`
625
+ {
626
+ "input.mime_type": "application/json",
627
+ "input.value": "{"messages":[{"role":"user","content":"What's the weather today?"}],"model":"gpt-3.5-turbo","tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","description":"Get the weather for a location.","parameters":{"type":"object","properties":{"location":{"type":"string"}}}}}],"stream":true}",
628
+ "llm.input_messages.0.message.content": "What's the weather today?",
629
+ "llm.input_messages.0.message.role": "user",
630
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","tools":[{"type":"function","function":{"name":"getCurrentLocation","parameters":{"type":"object","properties":{}},"description":"Get the current location of the user."}},{"type":"function","function":{"name":"getWeather","description":"Get the weather for a location.","parameters":{"type":"object","properties":{"location":{"type":"string"}}}}}],"stream":true}",
631
+ "llm.model_name": "gpt-3.5-turbo",
632
+ "llm.output_messages.0.message.content": "",
633
+ "llm.output_messages.0.message.role": "assistant",
634
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.arguments": "{}",
635
+ "llm.output_messages.0.message.tool_calls.0.tool_call.function.name": "getWeather",
636
+ "openinference.span.kind": "llm",
637
+ "output.mime_type": "text/plain",
638
+ "output.value": "",
639
+ }
640
+ `);
641
+ }));
642
+ it("should capture a function call with streaming", () => __awaiter(void 0, void 0, void 0, function* () {
643
+ var _j, e_3, _k, _l;
644
+ jest.spyOn(openai, "post").mockImplementation(
645
+ // @ts-expect-error the response type is not correct - this is just for testing
646
+ () => __awaiter(void 0, void 0, void 0, function* () {
647
+ const iterator = () => (function () {
648
+ return __asyncGenerator(this, arguments, function* () {
649
+ yield yield __await({
650
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
651
+ object: "chat.completion.chunk",
652
+ created: 1705535755,
653
+ model: "gpt-3.5-turbo-0613",
654
+ system_fingerprint: null,
655
+ choices: [
656
+ {
657
+ index: 0,
658
+ delta: {
659
+ role: "assistant",
660
+ content: null,
661
+ function_call: { name: "getWeather", arguments: "" },
662
+ },
663
+ logprobs: null,
664
+ finish_reason: null,
665
+ },
666
+ ],
667
+ });
668
+ yield yield __await({
669
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
670
+ object: "chat.completion.chunk",
671
+ created: 1705535755,
672
+ model: "gpt-3.5-turbo-0613",
673
+ system_fingerprint: null,
674
+ choices: [
675
+ {
676
+ index: 0,
677
+ delta: { function_call: { arguments: "{}" } },
678
+ logprobs: null,
679
+ finish_reason: null,
680
+ },
681
+ ],
682
+ });
683
+ yield yield __await({
684
+ id: "chatcmpl-8iA39kCtuVHIVDr9AnBdJZjgSjNWL",
685
+ object: "chat.completion.chunk",
686
+ created: 1705535755,
687
+ model: "gpt-3.5-turbo-0613",
688
+ system_fingerprint: null,
689
+ choices: [
690
+ {
691
+ index: 0,
692
+ delta: {},
693
+ logprobs: null,
694
+ finish_reason: "function_call",
695
+ },
696
+ ],
697
+ });
698
+ });
699
+ })();
700
+ const controller = new AbortController();
701
+ return new streaming_1.Stream(iterator, controller);
702
+ }));
703
+ const stream = yield openai.chat.completions.create({
704
+ messages: [{ role: "user", content: "What's the weather today?" }],
705
+ model: "gpt-3.5-turbo",
706
+ functions: [
707
+ {
708
+ name: "getWeather",
709
+ description: "Get the weather for a location.",
710
+ parameters: {
711
+ type: "object",
712
+ properties: {
713
+ location: { type: "string" },
714
+ },
715
+ },
716
+ },
717
+ {
718
+ name: "getCurrentLocation",
719
+ description: "Get the current location of the user.",
720
+ parameters: { type: "object", properties: {} },
721
+ },
722
+ ],
723
+ stream: true,
724
+ });
725
+ let response = "";
726
+ try {
727
+ for (var _m = true, stream_3 = __asyncValues(stream), stream_3_1; stream_3_1 = yield stream_3.next(), _j = stream_3_1.done, !_j; _m = true) {
728
+ _l = stream_3_1.value;
729
+ _m = false;
730
+ const chunk = _l;
731
+ if (chunk.choices[0].delta.content)
732
+ response += chunk.choices[0].delta.content;
733
+ }
734
+ }
735
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
736
+ finally {
737
+ try {
738
+ if (!_m && !_j && (_k = stream_3.return)) yield _k.call(stream_3);
739
+ }
740
+ finally { if (e_3) throw e_3.error; }
741
+ }
742
+ // When a tool is called, the content is empty
743
+ expect(response).toBe("");
744
+ const spans = memoryExporter.getFinishedSpans();
745
+ expect(spans.length).toBe(1);
746
+ const span = spans[0];
747
+ expect(span.name).toBe("OpenAI Chat Completions");
748
+ expect(span.attributes).toMatchInlineSnapshot(`
749
+ {
750
+ "input.mime_type": "application/json",
751
+ "input.value": "{"messages":[{"role":"user","content":"What's the weather today?"}],"model":"gpt-3.5-turbo","functions":[{"name":"getWeather","description":"Get the weather for a location.","parameters":{"type":"object","properties":{"location":{"type":"string"}}}},{"name":"getCurrentLocation","description":"Get the current location of the user.","parameters":{"type":"object","properties":{}}}],"stream":true}",
752
+ "llm.input_messages.0.message.content": "What's the weather today?",
753
+ "llm.input_messages.0.message.role": "user",
754
+ "llm.invocation_parameters": "{"model":"gpt-3.5-turbo","functions":[{"name":"getWeather","description":"Get the weather for a location.","parameters":{"type":"object","properties":{"location":{"type":"string"}}}},{"name":"getCurrentLocation","description":"Get the current location of the user.","parameters":{"type":"object","properties":{}}}],"stream":true}",
755
+ "llm.model_name": "gpt-3.5-turbo",
756
+ "llm.output_messages.0.message.content": "",
757
+ "llm.output_messages.0.message.function_call_arguments_json": "{}",
758
+ "llm.output_messages.0.message.function_call_name": "getWeather",
759
+ "llm.output_messages.0.message.role": "assistant",
760
+ "openinference.span.kind": "llm",
761
+ "output.mime_type": "text/plain",
762
+ "output.value": "",
763
+ }
764
+ `);
765
+ }));
160
766
  });
161
767
  //# sourceMappingURL=openai.test.js.map