openai 4.14.2 → 4.15.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +114 -1
  3. package/core.d.ts +1 -0
  4. package/core.d.ts.map +1 -1
  5. package/core.js +5 -1
  6. package/core.js.map +1 -1
  7. package/core.mjs +5 -1
  8. package/core.mjs.map +1 -1
  9. package/index.d.mts +2 -0
  10. package/index.d.ts +2 -0
  11. package/index.d.ts.map +1 -1
  12. package/index.js +2 -0
  13. package/index.js.map +1 -1
  14. package/index.mjs +2 -0
  15. package/index.mjs.map +1 -1
  16. package/lib/AbstractChatCompletionRunner.d.ts +111 -0
  17. package/lib/AbstractChatCompletionRunner.d.ts.map +1 -0
  18. package/lib/AbstractChatCompletionRunner.js +386 -0
  19. package/lib/AbstractChatCompletionRunner.js.map +1 -0
  20. package/lib/AbstractChatCompletionRunner.mjs +382 -0
  21. package/lib/AbstractChatCompletionRunner.mjs.map +1 -0
  22. package/lib/ChatCompletionRunFunctions.test.d.ts +2 -0
  23. package/lib/ChatCompletionRunFunctions.test.d.ts.map +1 -0
  24. package/lib/ChatCompletionRunFunctions.test.js +1850 -0
  25. package/lib/ChatCompletionRunFunctions.test.js.map +1 -0
  26. package/lib/ChatCompletionRunFunctions.test.mjs +1845 -0
  27. package/lib/ChatCompletionRunFunctions.test.mjs.map +1 -0
  28. package/lib/ChatCompletionRunner.d.ts +17 -0
  29. package/lib/ChatCompletionRunner.d.ts.map +1 -0
  30. package/lib/ChatCompletionRunner.js +19 -0
  31. package/lib/ChatCompletionRunner.js.map +1 -0
  32. package/lib/ChatCompletionRunner.mjs +15 -0
  33. package/lib/ChatCompletionRunner.mjs.map +1 -0
  34. package/lib/ChatCompletionStream.d.ts +111 -0
  35. package/lib/ChatCompletionStream.d.ts.map +1 -0
  36. package/lib/ChatCompletionStream.js +210 -0
  37. package/lib/ChatCompletionStream.js.map +1 -0
  38. package/lib/ChatCompletionStream.mjs +206 -0
  39. package/lib/ChatCompletionStream.mjs.map +1 -0
  40. package/lib/ChatCompletionStreamingRunner.d.ts +20 -0
  41. package/lib/ChatCompletionStreamingRunner.d.ts.map +1 -0
  42. package/lib/ChatCompletionStreamingRunner.js +18 -0
  43. package/lib/ChatCompletionStreamingRunner.js.map +1 -0
  44. package/lib/ChatCompletionStreamingRunner.mjs +14 -0
  45. package/lib/ChatCompletionStreamingRunner.mjs.map +1 -0
  46. package/lib/RunnableFunction.d.ts +70 -0
  47. package/lib/RunnableFunction.d.ts.map +1 -0
  48. package/lib/RunnableFunction.js +22 -0
  49. package/lib/RunnableFunction.js.map +1 -0
  50. package/lib/RunnableFunction.mjs +17 -0
  51. package/lib/RunnableFunction.mjs.map +1 -0
  52. package/lib/jsonschema.d.ts +106 -0
  53. package/lib/jsonschema.d.ts.map +1 -0
  54. package/lib/jsonschema.js +11 -0
  55. package/lib/jsonschema.js.map +1 -0
  56. package/lib/jsonschema.mjs +10 -0
  57. package/lib/jsonschema.mjs.map +1 -0
  58. package/package.json +1 -1
  59. package/resources/beta/beta.d.ts +9 -0
  60. package/resources/beta/beta.d.ts.map +1 -0
  61. package/resources/beta/beta.js +40 -0
  62. package/resources/beta/beta.js.map +1 -0
  63. package/resources/beta/beta.mjs +13 -0
  64. package/resources/beta/beta.mjs.map +1 -0
  65. package/resources/beta/chat/chat.d.ts +9 -0
  66. package/resources/beta/chat/chat.d.ts.map +1 -0
  67. package/resources/beta/chat/chat.js +40 -0
  68. package/resources/beta/chat/chat.js.map +1 -0
  69. package/resources/beta/chat/chat.mjs +13 -0
  70. package/resources/beta/chat/chat.mjs.map +1 -0
  71. package/resources/beta/chat/completions.d.ts +28 -0
  72. package/resources/beta/chat/completions.d.ts.map +1 -0
  73. package/resources/beta/chat/completions.js +32 -0
  74. package/resources/beta/chat/completions.js.map +1 -0
  75. package/resources/beta/chat/completions.mjs +24 -0
  76. package/resources/beta/chat/completions.mjs.map +1 -0
  77. package/resources/beta/chat/index.d.ts +3 -0
  78. package/resources/beta/chat/index.d.ts.map +1 -0
  79. package/resources/beta/chat/index.js +9 -0
  80. package/resources/beta/chat/index.js.map +1 -0
  81. package/resources/beta/chat/index.mjs +4 -0
  82. package/resources/beta/chat/index.mjs.map +1 -0
  83. package/resources/beta/index.d.ts +3 -0
  84. package/resources/beta/index.d.ts.map +1 -0
  85. package/resources/beta/index.js +9 -0
  86. package/resources/beta/index.js.map +1 -0
  87. package/resources/beta/index.mjs +4 -0
  88. package/resources/beta/index.mjs.map +1 -0
  89. package/resources/index.d.ts +1 -0
  90. package/resources/index.d.ts.map +1 -1
  91. package/resources/index.js +3 -1
  92. package/resources/index.js.map +1 -1
  93. package/resources/index.mjs +1 -0
  94. package/resources/index.mjs.map +1 -1
  95. package/src/core.ts +11 -2
  96. package/src/index.ts +3 -0
  97. package/src/lib/AbstractChatCompletionRunner.ts +488 -0
  98. package/src/lib/ChatCompletionRunFunctions.test.ts +1987 -0
  99. package/src/lib/ChatCompletionRunner.ts +42 -0
  100. package/src/lib/ChatCompletionStream.ts +327 -0
  101. package/src/lib/ChatCompletionStreamingRunner.ts +43 -0
  102. package/src/lib/RunnableFunction.ts +96 -0
  103. package/src/lib/jsonschema.ts +148 -0
  104. package/src/resources/beta/beta.ts +12 -0
  105. package/src/resources/beta/chat/chat.ts +12 -0
  106. package/src/resources/beta/chat/completions.ts +70 -0
  107. package/src/resources/beta/chat/index.ts +4 -0
  108. package/src/resources/beta/index.ts +4 -0
  109. package/src/resources/index.ts +1 -0
  110. package/src/streaming.ts +13 -4
  111. package/src/version.ts +1 -1
  112. package/streaming.d.ts +13 -0
  113. package/streaming.d.ts.map +1 -1
  114. package/streaming.js +13 -4
  115. package/streaming.js.map +1 -1
  116. package/streaming.mjs +13 -4
  117. package/streaming.mjs.map +1 -1
  118. package/version.d.ts +1 -1
  119. package/version.js +1 -1
  120. package/version.mjs +1 -1
@@ -0,0 +1,1850 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const openai_1 = __importDefault(require("openai"));
7
+ const stream_1 = require("stream");
8
+ const completions_1 = require("openai/resources/beta/chat/completions");
9
+ const node_fetch_1 = require("node-fetch");
10
+ /**
11
+ * Creates a mock `fetch` function and a `handleRequest` function for intercepting `fetch` calls.
12
+ *
13
+ * You call `handleRequest` with a callback function that handles the next `fetch` call.
14
+ * It returns a Promise that:
15
+ * - waits for the next call to `fetch`
16
+ * - calls the callback with the `fetch` arguments
17
+ * - resolves `fetch` with the callback output
18
+ */
19
+ function mockFetch() {
20
+ const fetchQueue = [];
21
+ const handlerQueue = [];
22
+ const enqueueHandler = () => {
23
+ handlerQueue.push(new Promise((resolve) => {
24
+ fetchQueue.push((handle) => {
25
+ enqueueHandler();
26
+ resolve(handle);
27
+ });
28
+ }));
29
+ };
30
+ enqueueHandler();
31
+ async function fetch(req, init) {
32
+ const handler = await handlerQueue.shift();
33
+ if (!handler)
34
+ throw new Error('expected handler to be defined');
35
+ const signal = init?.signal;
36
+ if (!signal)
37
+ return await handler(req, init);
38
+ return await Promise.race([
39
+ handler(req, init),
40
+ new Promise((resolve, reject) => {
41
+ if (signal.aborted) {
42
+ // @ts-ignore does exist in Node
43
+ reject(new DOMException('The user aborted a request.', 'AbortError'));
44
+ return;
45
+ }
46
+ signal.addEventListener('abort', (e) => {
47
+ // @ts-ignore does exist in Node
48
+ reject(new DOMException('The user aborted a request.', 'AbortError'));
49
+ });
50
+ }),
51
+ ]);
52
+ }
53
+ function handleRequest(handle) {
54
+ return new Promise((resolve) => {
55
+ fetchQueue.shift()?.(async (req, init) => {
56
+ try {
57
+ return await handle(req, init);
58
+ }
59
+ finally {
60
+ resolve();
61
+ }
62
+ });
63
+ });
64
+ }
65
+ return { fetch, handleRequest };
66
+ }
67
+ // mockChatCompletionFetch is like mockFetch, but with better a more convenient handleRequest to mock
68
+ // chat completion request/responses.
69
+ function mockChatCompletionFetch() {
70
+ const { fetch, handleRequest: handleRawRequest } = mockFetch();
71
+ function handleRequest(handler) {
72
+ return handleRawRequest(async (req, init) => {
73
+ const rawBody = init?.body;
74
+ if (typeof rawBody !== 'string')
75
+ throw new Error(`expected init.body to be a string`);
76
+ const body = JSON.parse(rawBody);
77
+ return new node_fetch_1.Response(JSON.stringify(await handler(body)), {
78
+ headers: { 'Content-Type': 'application/json' },
79
+ });
80
+ });
81
+ }
82
+ return { fetch, handleRequest };
83
+ }
84
+ // mockStreamingChatCompletionFetch is like mockFetch, but with better a more convenient handleRequest to mock
85
+ // streaming chat completion request/responses.
86
+ function mockStreamingChatCompletionFetch() {
87
+ const { fetch, handleRequest: handleRawRequest } = mockFetch();
88
+ function handleRequest(handler) {
89
+ return handleRawRequest(async (req, init) => {
90
+ const rawBody = init?.body;
91
+ if (typeof rawBody !== 'string')
92
+ throw new Error(`expected init.body to be a string`);
93
+ const body = JSON.parse(rawBody);
94
+ const stream = new stream_1.PassThrough();
95
+ (async () => {
96
+ for await (const chunk of handler(body)) {
97
+ stream.write(`data: ${JSON.stringify(chunk)}\n\n`);
98
+ }
99
+ stream.end(`data: [DONE]\n\n`);
100
+ })();
101
+ return new node_fetch_1.Response(stream, {
102
+ headers: {
103
+ 'Content-Type': 'text/event-stream',
104
+ 'Transfer-Encoding': 'chunked',
105
+ },
106
+ });
107
+ });
108
+ }
109
+ return { fetch, handleRequest };
110
+ }
111
+ // contentChoiceDeltas returns an async iterator which mocks a delta stream of a by splitting the
112
+ // argument into chunks separated by whitespace.
113
+ function* contentChoiceDeltas(content, { index = 0, role = 'assistant', } = {}) {
114
+ const deltas = content.split(/\s+/g);
115
+ for (let i = 0; i < deltas.length; i++) {
116
+ yield {
117
+ index,
118
+ finish_reason: i === deltas.length - 1 ? 'stop' : null,
119
+ delta: {
120
+ role,
121
+ content: deltas[i] ? `${deltas[i]}${i === deltas.length - 1 ? '' : ' '}` : null,
122
+ },
123
+ };
124
+ }
125
+ }
126
+ // functionCallDeltas returns an async iterator which mocks a delta stream of a functionCall by splitting
127
+ // the argument into chunks separated by whitespace.
128
+ function* functionCallDeltas(args, { index = 0, name, role = 'assistant', }) {
129
+ const deltas = args.split(/\s+/g);
130
+ for (let i = 0; i < deltas.length; i++) {
131
+ yield {
132
+ index,
133
+ finish_reason: i === deltas.length - 1 ? 'function_call' : null,
134
+ delta: {
135
+ role,
136
+ function_call: {
137
+ arguments: `${deltas[i] || ''}${i === deltas.length - 1 ? '' : ' '}`,
138
+ ...(i === deltas.length - 1 ? { name } : null),
139
+ },
140
+ },
141
+ };
142
+ }
143
+ }
144
+ class RunnerListener {
145
+ constructor(runner) {
146
+ this.runner = runner;
147
+ this.contents = [];
148
+ this.messages = [];
149
+ this.chatCompletions = [];
150
+ this.functionCalls = [];
151
+ this.functionCallResults = [];
152
+ this.finalContent = null;
153
+ this.gotConnect = false;
154
+ this.gotAbort = false;
155
+ this.gotEnd = false;
156
+ this.onceMessageCallCount = 0;
157
+ runner
158
+ .on('connect', () => (this.gotConnect = true))
159
+ .on('content', (content) => this.contents.push(content))
160
+ .on('message', (message) => this.messages.push(message))
161
+ .on('chatCompletion', (completion) => this.chatCompletions.push(completion))
162
+ .on('functionCall', (functionCall) => this.functionCalls.push(functionCall))
163
+ .on('functionCallResult', (result) => this.functionCallResults.push(result))
164
+ .on('finalContent', (content) => (this.finalContent = content))
165
+ .on('finalMessage', (message) => (this.finalMessage = message))
166
+ .on('finalChatCompletion', (completion) => (this.finalChatCompletion = completion))
167
+ .on('finalFunctionCall', (functionCall) => (this.finalFunctionCall = functionCall))
168
+ .on('finalFunctionCallResult', (result) => (this.finalFunctionCallResult = result))
169
+ .on('totalUsage', (usage) => (this.totalUsage = usage))
170
+ .on('error', (error) => (this.error = error))
171
+ .on('abort', () => (this.gotAbort = true))
172
+ .on('end', () => (this.gotEnd = true))
173
+ .once('message', () => this.onceMessageCallCount++);
174
+ }
175
+ async sanityCheck({ error } = {}) {
176
+ expect(this.onceMessageCallCount).toBeLessThanOrEqual(1);
177
+ expect(this.gotAbort).toEqual(this.runner.aborted);
178
+ if (this.runner.aborted)
179
+ expect(this.runner.errored).toBe(true);
180
+ if (error) {
181
+ expect(this.error?.message).toEqual(error);
182
+ expect(this.runner.errored).toBe(true);
183
+ await expect(this.runner.finalChatCompletion()).rejects.toThrow(error);
184
+ await expect(this.runner.finalMessage()).rejects.toThrow(error);
185
+ await expect(this.runner.finalContent()).rejects.toThrow(error);
186
+ await expect(this.runner.finalFunctionCall()).rejects.toThrow(error);
187
+ await expect(this.runner.finalFunctionCallResult()).rejects.toThrow(error);
188
+ await expect(this.runner.totalUsage()).rejects.toThrow(error);
189
+ await expect(this.runner.done()).rejects.toThrow(error);
190
+ }
191
+ else {
192
+ expect(this.error).toBeUndefined();
193
+ expect(this.runner.errored).toBe(false);
194
+ }
195
+ if (!this.gotConnect) {
196
+ expect(this.contents).toEqual([]);
197
+ expect(this.messages).toEqual([]);
198
+ expect(this.chatCompletions).toEqual([]);
199
+ expect(this.functionCalls).toEqual([]);
200
+ expect(this.functionCallResults).toEqual([]);
201
+ expect(this.finalContent).toBeUndefined();
202
+ expect(this.finalMessage).toBeUndefined();
203
+ expect(this.finalChatCompletion).toBeUndefined();
204
+ expect(this.finalFunctionCall).toBeUndefined();
205
+ expect(this.finalFunctionCallResult).toBeUndefined();
206
+ expect(this.totalUsage).toBeUndefined();
207
+ expect(this.gotEnd).toBe(true);
208
+ return;
209
+ }
210
+ if (error)
211
+ return;
212
+ const expectedContents = this.messages
213
+ .filter((m) => m.role === 'assistant')
214
+ .map((m) => m.content)
215
+ .filter(Boolean);
216
+ expect(this.contents).toEqual(expectedContents);
217
+ expect(this.finalMessage).toEqual(this.messages[this.messages.length - 1]);
218
+ expect(await this.runner.finalMessage()).toEqual(this.finalMessage);
219
+ expect(this.finalContent).toEqual(expectedContents[expectedContents.length - 1] ?? null);
220
+ expect(await this.runner.finalContent()).toEqual(this.finalContent);
221
+ expect(this.finalChatCompletion).toEqual(this.chatCompletions[this.chatCompletions.length - 1]);
222
+ expect(await this.runner.finalChatCompletion()).toEqual(this.finalChatCompletion);
223
+ expect(this.finalFunctionCall).toEqual(this.functionCalls[this.functionCalls.length - 1]);
224
+ expect(await this.runner.finalFunctionCall()).toEqual(this.finalFunctionCall);
225
+ expect(this.finalFunctionCallResult).toEqual(this.functionCallResults[this.functionCallResults.length - 1]);
226
+ expect(await this.runner.finalFunctionCallResult()).toEqual(this.finalFunctionCallResult);
227
+ expect(this.chatCompletions).toEqual(this.runner.allChatCompletions());
228
+ expect(this.messages).toEqual(this.runner.messages.slice(-this.messages.length));
229
+ if (this.chatCompletions.some((c) => c.usage)) {
230
+ const totalUsage = {
231
+ completion_tokens: 0,
232
+ prompt_tokens: 0,
233
+ total_tokens: 0,
234
+ };
235
+ for (const { usage } of this.chatCompletions) {
236
+ if (usage) {
237
+ totalUsage.completion_tokens += usage.completion_tokens;
238
+ totalUsage.prompt_tokens += usage.prompt_tokens;
239
+ totalUsage.total_tokens += usage.total_tokens;
240
+ }
241
+ }
242
+ expect(this.totalUsage).toEqual(totalUsage);
243
+ expect(await this.runner.totalUsage()).toEqual(totalUsage);
244
+ }
245
+ expect(this.gotEnd).toBe(true);
246
+ }
247
+ }
248
+ class StreamingRunnerListener {
249
+ constructor(runner) {
250
+ this.runner = runner;
251
+ this.eventChunks = [];
252
+ this.eventContents = [];
253
+ this.eventMessages = [];
254
+ this.eventChatCompletions = [];
255
+ this.eventFunctionCalls = [];
256
+ this.eventFunctionCallResults = [];
257
+ this.finalContent = null;
258
+ this.gotConnect = false;
259
+ this.gotEnd = false;
260
+ runner
261
+ .on('connect', () => (this.gotConnect = true))
262
+ .on('chunk', (chunk) => this.eventChunks.push(chunk))
263
+ .on('content', (delta, snapshot) => this.eventContents.push([delta, snapshot]))
264
+ .on('message', (message) => this.eventMessages.push(message))
265
+ .on('chatCompletion', (completion) => this.eventChatCompletions.push(completion))
266
+ .on('functionCall', (functionCall) => this.eventFunctionCalls.push(functionCall))
267
+ .on('functionCallResult', (result) => this.eventFunctionCallResults.push(result))
268
+ .on('finalContent', (content) => (this.finalContent = content))
269
+ .on('finalMessage', (message) => (this.finalMessage = message))
270
+ .on('finalChatCompletion', (completion) => (this.finalChatCompletion = completion))
271
+ .on('finalFunctionCall', (functionCall) => (this.finalFunctionCall = functionCall))
272
+ .on('finalFunctionCallResult', (result) => (this.finalFunctionCallResult = result))
273
+ .on('error', (error) => (this.error = error))
274
+ .on('end', () => (this.gotEnd = true));
275
+ }
276
+ async sanityCheck({ error } = {}) {
277
+ if (error) {
278
+ expect(this.error?.message).toEqual(error);
279
+ expect(this.runner.errored).toBe(true);
280
+ await expect(this.runner.finalChatCompletion()).rejects.toThrow(error);
281
+ await expect(this.runner.finalMessage()).rejects.toThrow(error);
282
+ await expect(this.runner.finalContent()).rejects.toThrow(error);
283
+ await expect(this.runner.finalFunctionCall()).rejects.toThrow(error);
284
+ await expect(this.runner.finalFunctionCallResult()).rejects.toThrow(error);
285
+ await expect(this.runner.done()).rejects.toThrow(error);
286
+ }
287
+ else {
288
+ expect(this.error).toBeUndefined();
289
+ expect(this.runner.errored).toBe(false);
290
+ }
291
+ if (!this.gotConnect) {
292
+ expect(this.eventContents).toEqual([]);
293
+ expect(this.eventMessages).toEqual([]);
294
+ expect(this.eventChatCompletions).toEqual([]);
295
+ expect(this.eventFunctionCalls).toEqual([]);
296
+ expect(this.eventFunctionCallResults).toEqual([]);
297
+ expect(this.finalContent).toBeUndefined();
298
+ expect(this.finalMessage).toBeUndefined();
299
+ expect(this.finalChatCompletion).toBeUndefined();
300
+ expect(this.finalFunctionCall).toBeUndefined();
301
+ expect(this.finalFunctionCallResult).toBeUndefined();
302
+ expect(this.gotEnd).toBe(true);
303
+ return;
304
+ }
305
+ if (error)
306
+ return;
307
+ if (this.eventContents.length)
308
+ expect(this.eventChunks.length).toBeGreaterThan(0);
309
+ expect(this.finalMessage).toEqual(this.eventMessages[this.eventMessages.length - 1]);
310
+ expect(await this.runner.finalMessage()).toEqual(this.finalMessage);
311
+ expect(this.finalContent).toEqual(this.eventContents[this.eventContents.length - 1]?.[1] ?? null);
312
+ expect(await this.runner.finalContent()).toEqual(this.finalContent);
313
+ expect(this.finalChatCompletion).toEqual(this.eventChatCompletions[this.eventChatCompletions.length - 1]);
314
+ expect(await this.runner.finalChatCompletion()).toEqual(this.finalChatCompletion);
315
+ expect(this.finalFunctionCall).toEqual(this.eventFunctionCalls[this.eventFunctionCalls.length - 1]);
316
+ expect(await this.runner.finalFunctionCall()).toEqual(this.finalFunctionCall);
317
+ expect(this.finalFunctionCallResult).toEqual(this.eventFunctionCallResults[this.eventFunctionCallResults.length - 1]);
318
+ expect(await this.runner.finalFunctionCallResult()).toEqual(this.finalFunctionCallResult);
319
+ expect(this.eventChatCompletions).toEqual(this.runner.allChatCompletions());
320
+ expect(this.eventMessages).toEqual(this.runner.messages.slice(-this.eventMessages.length));
321
+ if (error) {
322
+ expect(this.error?.message).toEqual(error);
323
+ expect(this.runner.errored).toBe(true);
324
+ }
325
+ else {
326
+ expect(this.error).toBeUndefined();
327
+ expect(this.runner.errored).toBe(false);
328
+ }
329
+ expect(this.gotEnd).toBe(true);
330
+ }
331
+ }
332
+ function _typeTests() {
333
+ const openai = new openai_1.default();
334
+ openai.beta.chat.completions.runFunctions({
335
+ messages: [
336
+ { role: 'user', content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}' },
337
+ ],
338
+ model: 'gpt-3.5-turbo',
339
+ functions: [
340
+ {
341
+ name: 'numProperties',
342
+ function: (obj) => String(Object.keys(obj).length),
343
+ parameters: { type: 'object' },
344
+ parse: (str) => {
345
+ const result = JSON.parse(str);
346
+ if (!(result instanceof Object) || Array.isArray(result)) {
347
+ throw new Error('must be an object');
348
+ }
349
+ return result;
350
+ },
351
+ description: 'gets the number of properties on an object',
352
+ },
353
+ {
354
+ function: (str) => String(str.length),
355
+ parameters: { type: 'string' },
356
+ description: 'gets the length of a string',
357
+ },
358
+ // @ts-expect-error function must accept string if parse is omitted
359
+ {
360
+ function: (obj) => String(Object.keys(obj).length),
361
+ parameters: { type: 'object' },
362
+ description: 'gets the number of properties on an object',
363
+ },
364
+ ],
365
+ });
366
+ openai.beta.chat.completions.runFunctions({
367
+ messages: [
368
+ { role: 'user', content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}' },
369
+ ],
370
+ model: 'gpt-3.5-turbo',
371
+ functions: [
372
+ new completions_1.ParsingFunction({
373
+ name: 'numProperties',
374
+ // @ts-expect-error parse and function don't match
375
+ parse: (str) => str,
376
+ function: (obj) => String(Object.keys(obj).length),
377
+ parameters: { type: 'object' },
378
+ description: 'gets the number of properties on an object',
379
+ }),
380
+ ],
381
+ });
382
+ openai.beta.chat.completions.runFunctions({
383
+ messages: [
384
+ { role: 'user', content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}' },
385
+ ],
386
+ model: 'gpt-3.5-turbo',
387
+ functions: [
388
+ new completions_1.ParsingFunction({
389
+ name: 'numProperties',
390
+ parse: (str) => {
391
+ const result = JSON.parse(str);
392
+ if (!(result instanceof Object) || Array.isArray(result)) {
393
+ throw new Error('must be an object');
394
+ }
395
+ return result;
396
+ },
397
+ function: (obj) => String(Object.keys(obj).length),
398
+ parameters: { type: 'object' },
399
+ description: 'gets the number of properties on an object',
400
+ }),
401
+ new completions_1.ParsingFunction({
402
+ name: 'keys',
403
+ parse: (str) => {
404
+ const result = JSON.parse(str);
405
+ if (!(result instanceof Object)) {
406
+ throw new Error('must be an Object');
407
+ }
408
+ return result;
409
+ },
410
+ function: (obj) => Object.keys(obj).join(', '),
411
+ parameters: { type: 'object' },
412
+ description: 'gets the number of properties on an object',
413
+ }),
414
+ new completions_1.ParsingFunction({
415
+ name: 'len2',
416
+ // @ts-expect-error parse and function don't match
417
+ parse: (str) => str,
418
+ function: (obj) => String(Object.keys(obj).length),
419
+ parameters: { type: 'object' },
420
+ description: 'gets the number of properties on an object',
421
+ }),
422
+ ],
423
+ });
424
+ openai.beta.chat.completions.runFunctions({
425
+ messages: [
426
+ { role: 'user', content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}' },
427
+ ],
428
+ model: 'gpt-3.5-turbo',
429
+ // @ts-ignore error occurs here in TS 4
430
+ functions: [
431
+ {
432
+ name: 'numProperties',
433
+ parse: (str) => {
434
+ const result = JSON.parse(str);
435
+ if (!(result instanceof Object) || Array.isArray(result)) {
436
+ throw new Error('must be an object');
437
+ }
438
+ return result;
439
+ },
440
+ function: (obj) => String(Object.keys(obj).length),
441
+ parameters: { type: 'object' },
442
+ description: 'gets the number of properties on an object',
443
+ },
444
+ {
445
+ name: 'keys',
446
+ parse: (str) => {
447
+ const result = JSON.parse(str);
448
+ if (!(result instanceof Object)) {
449
+ throw new Error('must be an Object');
450
+ }
451
+ return result;
452
+ },
453
+ function: (obj) => Object.keys(obj).join(', '),
454
+ parameters: { type: 'object' },
455
+ description: 'gets the number of properties on an object',
456
+ },
457
+ {
458
+ name: 'len2',
459
+ parse: (str) => str,
460
+ // @ts-ignore error occurs here in TS 5
461
+ // function input doesn't match parse output
462
+ function: (obj) => String(Object.keys(obj).length),
463
+ parameters: { type: 'object' },
464
+ description: 'gets the number of properties on an object',
465
+ },
466
+ ],
467
+ });
468
+ }
469
+ describe('resource completions', () => {
470
+ describe('runFunctions with stream: false', () => {
471
+ test('successful flow', async () => {
472
+ const { fetch, handleRequest } = mockChatCompletionFetch();
473
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
474
+ const runner = openai.beta.chat.completions.runFunctions({
475
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
476
+ model: 'gpt-3.5-turbo',
477
+ functions: [
478
+ {
479
+ function: function getWeather() {
480
+ return `it's raining`;
481
+ },
482
+ parameters: {},
483
+ description: 'gets the weather',
484
+ },
485
+ ],
486
+ });
487
+ const listener = new RunnerListener(runner);
488
+ await Promise.all([
489
+ handleRequest(async (request) => {
490
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
491
+ return {
492
+ id: '1',
493
+ choices: [
494
+ {
495
+ index: 0,
496
+ finish_reason: 'function_call',
497
+ message: {
498
+ role: 'assistant',
499
+ content: null,
500
+ function_call: {
501
+ arguments: '',
502
+ name: 'getWeather',
503
+ },
504
+ },
505
+ },
506
+ ],
507
+ created: Math.floor(Date.now() / 1000),
508
+ model: 'gpt-3.5-turbo',
509
+ object: 'chat.completion',
510
+ };
511
+ }),
512
+ handleRequest(async (request) => {
513
+ expect(request.messages).toEqual([
514
+ { role: 'user', content: 'tell me what the weather is like' },
515
+ {
516
+ role: 'assistant',
517
+ content: null,
518
+ function_call: {
519
+ arguments: '',
520
+ name: 'getWeather',
521
+ },
522
+ },
523
+ {
524
+ role: 'function',
525
+ content: `it's raining`,
526
+ name: 'getWeather',
527
+ },
528
+ ]);
529
+ return {
530
+ id: '2',
531
+ choices: [
532
+ {
533
+ index: 0,
534
+ finish_reason: 'stop',
535
+ message: {
536
+ role: 'assistant',
537
+ content: `it's raining`,
538
+ },
539
+ },
540
+ ],
541
+ created: Math.floor(Date.now() / 1000),
542
+ model: 'gpt-3.5-turbo',
543
+ object: 'chat.completion',
544
+ };
545
+ }),
546
+ runner.done(),
547
+ ]);
548
+ expect(listener.messages).toEqual([
549
+ { role: 'user', content: 'tell me what the weather is like' },
550
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
551
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
552
+ { role: 'assistant', content: "it's raining" },
553
+ ]);
554
+ expect(listener.functionCallResults).toEqual([`it's raining`]);
555
+ await listener.sanityCheck();
556
+ });
557
+ test('flow with abort', async () => {
558
+ const { fetch, handleRequest } = mockChatCompletionFetch();
559
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
560
+ const controller = new AbortController();
561
+ const runner = openai.beta.chat.completions.runFunctions({
562
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
563
+ model: 'gpt-3.5-turbo',
564
+ functions: [
565
+ {
566
+ function: function getWeather() {
567
+ return `it's raining`;
568
+ },
569
+ parameters: {},
570
+ description: 'gets the weather',
571
+ },
572
+ ],
573
+ }, { signal: controller.signal });
574
+ const listener = new RunnerListener(runner);
575
+ await handleRequest(async (request) => {
576
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
577
+ return {
578
+ id: '1',
579
+ choices: [
580
+ {
581
+ index: 0,
582
+ finish_reason: 'function_call',
583
+ message: {
584
+ role: 'assistant',
585
+ content: null,
586
+ function_call: {
587
+ arguments: '',
588
+ name: 'getWeather',
589
+ },
590
+ },
591
+ },
592
+ ],
593
+ created: Math.floor(Date.now() / 1000),
594
+ model: 'gpt-3.5-turbo',
595
+ object: 'chat.completion',
596
+ };
597
+ });
598
+ controller.abort();
599
+ await runner.done().catch(() => { });
600
+ expect(listener.messages).toEqual([
601
+ { role: 'user', content: 'tell me what the weather is like' },
602
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
603
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
604
+ ]);
605
+ expect(listener.functionCallResults).toEqual([`it's raining`]);
606
+ await listener.sanityCheck({ error: 'Request was aborted.' });
607
+ expect(runner.aborted).toBe(true);
608
+ });
609
+ test('successful flow with parse', async () => {
610
+ const { fetch, handleRequest } = mockChatCompletionFetch();
611
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
612
+ const runner = openai.beta.chat.completions.runFunctions({
613
+ messages: [
614
+ {
615
+ role: 'user',
616
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
617
+ },
618
+ ],
619
+ model: 'gpt-3.5-turbo',
620
+ functions: [
621
+ new completions_1.ParsingFunction({
622
+ name: 'numProperties',
623
+ function: (obj) => String(Object.keys(obj).length),
624
+ parameters: { type: 'object' },
625
+ parse: (str) => {
626
+ const result = JSON.parse(str);
627
+ if (!(result instanceof Object) || Array.isArray(result)) {
628
+ throw new Error('must be an object');
629
+ }
630
+ return result;
631
+ },
632
+ description: 'gets the number of properties on an object',
633
+ }),
634
+ ],
635
+ });
636
+ const listener = new RunnerListener(runner);
637
+ await Promise.all([
638
+ handleRequest(async (request) => {
639
+ expect(request.messages).toEqual([
640
+ {
641
+ role: 'user',
642
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
643
+ },
644
+ ]);
645
+ return {
646
+ id: '1',
647
+ choices: [
648
+ {
649
+ index: 0,
650
+ finish_reason: 'function_call',
651
+ message: {
652
+ role: 'assistant',
653
+ content: null,
654
+ function_call: {
655
+ arguments: '{"a": 1, "b": 2, "c": 3}',
656
+ name: 'numProperties',
657
+ },
658
+ },
659
+ },
660
+ ],
661
+ created: Math.floor(Date.now() / 1000),
662
+ model: 'gpt-3.5-turbo',
663
+ object: 'chat.completion',
664
+ usage: {
665
+ completion_tokens: 5,
666
+ prompt_tokens: 20,
667
+ total_tokens: 25,
668
+ },
669
+ };
670
+ }),
671
+ handleRequest(async (request) => {
672
+ expect(request.messages).toEqual([
673
+ {
674
+ role: 'user',
675
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
676
+ },
677
+ {
678
+ role: 'assistant',
679
+ content: null,
680
+ function_call: {
681
+ arguments: '{"a": 1, "b": 2, "c": 3}',
682
+ name: 'numProperties',
683
+ },
684
+ },
685
+ {
686
+ role: 'function',
687
+ content: '3',
688
+ name: 'numProperties',
689
+ },
690
+ ]);
691
+ return {
692
+ id: '2',
693
+ choices: [
694
+ {
695
+ index: 0,
696
+ finish_reason: 'stop',
697
+ message: {
698
+ role: 'assistant',
699
+ content: `there are 3 properties in {"a": 1, "b": 2, "c": 3}`,
700
+ },
701
+ },
702
+ ],
703
+ created: Math.floor(Date.now() / 1000),
704
+ model: 'gpt-3.5-turbo',
705
+ object: 'chat.completion',
706
+ usage: {
707
+ completion_tokens: 10,
708
+ prompt_tokens: 25,
709
+ total_tokens: 35,
710
+ },
711
+ };
712
+ }),
713
+ runner.done(),
714
+ ]);
715
+ expect(listener.messages).toEqual([
716
+ {
717
+ role: 'user',
718
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
719
+ },
720
+ {
721
+ role: 'assistant',
722
+ content: null,
723
+ function_call: { name: 'numProperties', arguments: '{"a": 1, "b": 2, "c": 3}' },
724
+ },
725
+ { role: 'function', content: '3', name: 'numProperties' },
726
+ { role: 'assistant', content: 'there are 3 properties in {"a": 1, "b": 2, "c": 3}' },
727
+ ]);
728
+ expect(listener.functionCallResults).toEqual(['3']);
729
+ await listener.sanityCheck();
730
+ });
731
+ test('flow with parse error', async () => {
732
+ const { fetch, handleRequest } = mockChatCompletionFetch();
733
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
734
+ const runner = openai.beta.chat.completions.runFunctions({
735
+ messages: [
736
+ {
737
+ role: 'user',
738
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
739
+ },
740
+ ],
741
+ model: 'gpt-3.5-turbo',
742
+ functions: [
743
+ new completions_1.ParsingFunction({
744
+ name: 'numProperties',
745
+ function: (obj) => String(Object.keys(obj).length),
746
+ parameters: { type: 'object' },
747
+ parse: (str) => {
748
+ const result = JSON.parse(str);
749
+ if (!(result instanceof Object) || Array.isArray(result)) {
750
+ throw new Error('must be an object');
751
+ }
752
+ return result;
753
+ },
754
+ description: 'gets the number of properties on an object',
755
+ }),
756
+ ],
757
+ });
758
+ const listener = new RunnerListener(runner);
759
+ await Promise.all([
760
+ handleRequest(async (request) => {
761
+ expect(request.messages).toEqual([
762
+ {
763
+ role: 'user',
764
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
765
+ },
766
+ ]);
767
+ return {
768
+ id: '1',
769
+ choices: [
770
+ {
771
+ index: 0,
772
+ finish_reason: 'function_call',
773
+ message: {
774
+ role: 'assistant',
775
+ content: null,
776
+ function_call: {
777
+ arguments: '[{"a": 1, "b": 2, "c": 3}]',
778
+ name: 'numProperties',
779
+ },
780
+ },
781
+ },
782
+ ],
783
+ created: Math.floor(Date.now() / 1000),
784
+ model: 'gpt-3.5-turbo',
785
+ object: 'chat.completion',
786
+ };
787
+ }),
788
+ handleRequest(async (request) => {
789
+ expect(request.messages).toEqual([
790
+ {
791
+ role: 'user',
792
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
793
+ },
794
+ {
795
+ role: 'assistant',
796
+ content: null,
797
+ function_call: {
798
+ arguments: '[{"a": 1, "b": 2, "c": 3}]',
799
+ name: 'numProperties',
800
+ },
801
+ },
802
+ {
803
+ role: 'function',
804
+ content: `must be an object`,
805
+ name: 'numProperties',
806
+ },
807
+ ]);
808
+ return {
809
+ id: '2',
810
+ choices: [
811
+ {
812
+ index: 0,
813
+ finish_reason: 'function_call',
814
+ message: {
815
+ role: 'assistant',
816
+ content: null,
817
+ function_call: {
818
+ arguments: '{"a": 1, "b": 2, "c": 3}',
819
+ name: 'numProperties',
820
+ },
821
+ },
822
+ },
823
+ ],
824
+ created: Math.floor(Date.now() / 1000),
825
+ model: 'gpt-3.5-turbo',
826
+ object: 'chat.completion',
827
+ };
828
+ }),
829
+ handleRequest(async (request) => {
830
+ expect(request.messages).toEqual([
831
+ {
832
+ role: 'user',
833
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
834
+ },
835
+ {
836
+ role: 'assistant',
837
+ content: null,
838
+ function_call: {
839
+ arguments: '[{"a": 1, "b": 2, "c": 3}]',
840
+ name: 'numProperties',
841
+ },
842
+ },
843
+ {
844
+ role: 'function',
845
+ content: `must be an object`,
846
+ name: 'numProperties',
847
+ },
848
+ {
849
+ role: 'assistant',
850
+ content: null,
851
+ function_call: {
852
+ arguments: '{"a": 1, "b": 2, "c": 3}',
853
+ name: 'numProperties',
854
+ },
855
+ },
856
+ {
857
+ role: 'function',
858
+ content: '3',
859
+ name: 'numProperties',
860
+ },
861
+ ]);
862
+ return {
863
+ id: '3',
864
+ choices: [
865
+ {
866
+ index: 0,
867
+ finish_reason: 'stop',
868
+ message: {
869
+ role: 'assistant',
870
+ content: `there are 3 properties in {"a": 1, "b": 2, "c": 3}`,
871
+ },
872
+ },
873
+ ],
874
+ created: Math.floor(Date.now() / 1000),
875
+ model: 'gpt-3.5-turbo',
876
+ object: 'chat.completion',
877
+ };
878
+ }),
879
+ runner.done(),
880
+ ]);
881
+ expect(listener.messages).toEqual([
882
+ {
883
+ role: 'user',
884
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
885
+ },
886
+ {
887
+ role: 'assistant',
888
+ content: null,
889
+ function_call: { name: 'numProperties', arguments: '[{"a": 1, "b": 2, "c": 3}]' },
890
+ },
891
+ { role: 'function', content: `must be an object`, name: 'numProperties' },
892
+ {
893
+ role: 'assistant',
894
+ content: null,
895
+ function_call: { name: 'numProperties', arguments: '{"a": 1, "b": 2, "c": 3}' },
896
+ },
897
+ { role: 'function', content: '3', name: 'numProperties' },
898
+ { role: 'assistant', content: 'there are 3 properties in {"a": 1, "b": 2, "c": 3}' },
899
+ ]);
900
+ expect(listener.functionCallResults).toEqual([`must be an object`, '3']);
901
+ await listener.sanityCheck();
902
+ });
903
+ test('single function call', async () => {
904
+ const { fetch, handleRequest } = mockChatCompletionFetch();
905
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
906
+ const runner = openai.beta.chat.completions.runFunctions({
907
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
908
+ model: 'gpt-3.5-turbo',
909
+ function_call: {
910
+ name: 'getWeather',
911
+ },
912
+ functions: [
913
+ {
914
+ function: function getWeather() {
915
+ return `it's raining`;
916
+ },
917
+ parameters: {},
918
+ description: 'gets the weather',
919
+ },
920
+ ],
921
+ });
922
+ const listener = new RunnerListener(runner);
923
+ await Promise.all([
924
+ handleRequest(async (request) => {
925
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
926
+ return {
927
+ id: '1',
928
+ choices: [
929
+ {
930
+ index: 0,
931
+ finish_reason: 'function_call',
932
+ message: {
933
+ role: 'assistant',
934
+ content: null,
935
+ function_call: {
936
+ arguments: '',
937
+ name: 'getWeather',
938
+ },
939
+ },
940
+ },
941
+ ],
942
+ created: Math.floor(Date.now() / 1000),
943
+ model: 'gpt-3.5-turbo',
944
+ object: 'chat.completion',
945
+ };
946
+ }),
947
+ runner.done(),
948
+ ]);
949
+ expect(listener.messages).toEqual([
950
+ { role: 'user', content: 'tell me what the weather is like' },
951
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
952
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
953
+ ]);
954
+ expect(listener.functionCallResults).toEqual([`it's raining`]);
955
+ await listener.sanityCheck();
956
+ });
957
+ test('wrong function name', async () => {
958
+ const { fetch, handleRequest } = mockChatCompletionFetch();
959
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
960
+ const runner = openai.beta.chat.completions.runFunctions({
961
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
962
+ model: 'gpt-3.5-turbo',
963
+ functions: [
964
+ {
965
+ function: function getWeather() {
966
+ return `it's raining`;
967
+ },
968
+ parameters: {},
969
+ description: 'gets the weather',
970
+ },
971
+ ],
972
+ });
973
+ const listener = new RunnerListener(runner);
974
+ await Promise.all([
975
+ handleRequest(async (request) => {
976
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
977
+ return {
978
+ id: '1',
979
+ choices: [
980
+ {
981
+ index: 0,
982
+ finish_reason: 'function_call',
983
+ message: {
984
+ role: 'assistant',
985
+ content: null,
986
+ function_call: {
987
+ arguments: '',
988
+ name: 'get_weather',
989
+ },
990
+ },
991
+ },
992
+ ],
993
+ created: Math.floor(Date.now() / 1000),
994
+ model: 'gpt-3.5-turbo',
995
+ object: 'chat.completion',
996
+ };
997
+ }),
998
+ handleRequest(async (request) => {
999
+ expect(request.messages).toEqual([
1000
+ { role: 'user', content: 'tell me what the weather is like' },
1001
+ {
1002
+ role: 'assistant',
1003
+ content: null,
1004
+ function_call: {
1005
+ arguments: '',
1006
+ name: 'get_weather',
1007
+ },
1008
+ },
1009
+ {
1010
+ role: 'function',
1011
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1012
+ name: 'get_weather',
1013
+ },
1014
+ ]);
1015
+ return {
1016
+ id: '2',
1017
+ choices: [
1018
+ {
1019
+ index: 0,
1020
+ finish_reason: 'function_call',
1021
+ message: {
1022
+ role: 'assistant',
1023
+ content: null,
1024
+ function_call: {
1025
+ arguments: '',
1026
+ name: 'getWeather',
1027
+ },
1028
+ },
1029
+ },
1030
+ ],
1031
+ created: Math.floor(Date.now() / 1000),
1032
+ model: 'gpt-3.5-turbo',
1033
+ object: 'chat.completion',
1034
+ };
1035
+ }),
1036
+ handleRequest(async (request) => {
1037
+ expect(request.messages).toEqual([
1038
+ { role: 'user', content: 'tell me what the weather is like' },
1039
+ {
1040
+ role: 'assistant',
1041
+ content: null,
1042
+ function_call: {
1043
+ arguments: '',
1044
+ name: 'get_weather',
1045
+ },
1046
+ },
1047
+ {
1048
+ role: 'function',
1049
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1050
+ name: 'get_weather',
1051
+ },
1052
+ {
1053
+ role: 'assistant',
1054
+ content: null,
1055
+ function_call: {
1056
+ arguments: '',
1057
+ name: 'getWeather',
1058
+ },
1059
+ },
1060
+ {
1061
+ role: 'function',
1062
+ content: `it's raining`,
1063
+ name: 'getWeather',
1064
+ },
1065
+ ]);
1066
+ return {
1067
+ id: '3',
1068
+ choices: [
1069
+ {
1070
+ index: 0,
1071
+ finish_reason: 'stop',
1072
+ message: {
1073
+ role: 'assistant',
1074
+ content: `it's raining`,
1075
+ },
1076
+ },
1077
+ ],
1078
+ created: Math.floor(Date.now() / 1000),
1079
+ model: 'gpt-3.5-turbo',
1080
+ object: 'chat.completion',
1081
+ };
1082
+ }),
1083
+ runner.done(),
1084
+ ]);
1085
+ expect(listener.messages).toEqual([
1086
+ { role: 'user', content: 'tell me what the weather is like' },
1087
+ { role: 'assistant', content: null, function_call: { name: 'get_weather', arguments: '' } },
1088
+ {
1089
+ role: 'function',
1090
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1091
+ name: 'get_weather',
1092
+ },
1093
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
1094
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
1095
+ { role: 'assistant', content: "it's raining" },
1096
+ ]);
1097
+ expect(listener.functionCallResults).toEqual([
1098
+ `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1099
+ `it's raining`,
1100
+ ]);
1101
+ await listener.sanityCheck();
1102
+ });
1103
+ test('wrong function name with single function call', async () => {
1104
+ const { fetch, handleRequest } = mockChatCompletionFetch();
1105
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1106
+ const runner = openai.beta.chat.completions.runFunctions({
1107
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1108
+ model: 'gpt-3.5-turbo',
1109
+ function_call: {
1110
+ name: 'getWeather',
1111
+ },
1112
+ functions: [
1113
+ {
1114
+ function: function getWeather() {
1115
+ return `it's raining`;
1116
+ },
1117
+ parameters: {},
1118
+ description: 'gets the weather',
1119
+ },
1120
+ ],
1121
+ });
1122
+ const listener = new RunnerListener(runner);
1123
+ await Promise.all([
1124
+ handleRequest(async (request) => {
1125
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1126
+ return {
1127
+ id: '1',
1128
+ choices: [
1129
+ {
1130
+ index: 0,
1131
+ finish_reason: 'function_call',
1132
+ message: {
1133
+ role: 'assistant',
1134
+ content: null,
1135
+ function_call: {
1136
+ arguments: '',
1137
+ name: 'get_weather',
1138
+ },
1139
+ },
1140
+ },
1141
+ ],
1142
+ created: Math.floor(Date.now() / 1000),
1143
+ model: 'gpt-3.5-turbo',
1144
+ object: 'chat.completion',
1145
+ };
1146
+ }),
1147
+ runner.done(),
1148
+ ]);
1149
+ expect(listener.messages).toEqual([
1150
+ { role: 'user', content: 'tell me what the weather is like' },
1151
+ { role: 'assistant', content: null, function_call: { name: 'get_weather', arguments: '' } },
1152
+ {
1153
+ role: 'function',
1154
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1155
+ name: 'get_weather',
1156
+ },
1157
+ ]);
1158
+ expect(listener.functionCallResults).toEqual([
1159
+ `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1160
+ ]);
1161
+ await listener.sanityCheck();
1162
+ });
1163
+ });
1164
+ describe('runFunctions with stream: true', () => {
1165
+ test('successful flow', async () => {
1166
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1167
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1168
+ const runner = openai.beta.chat.completions.runFunctions({
1169
+ stream: true,
1170
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1171
+ model: 'gpt-3.5-turbo',
1172
+ functions: [
1173
+ {
1174
+ function: function getWeather() {
1175
+ return `it's raining`;
1176
+ },
1177
+ parameters: {},
1178
+ description: 'gets the weather',
1179
+ },
1180
+ ],
1181
+ });
1182
+ const listener = new StreamingRunnerListener(runner);
1183
+ await Promise.all([
1184
+ handleRequest(async function* (request) {
1185
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1186
+ yield {
1187
+ id: '1',
1188
+ choices: [
1189
+ {
1190
+ index: 0,
1191
+ finish_reason: 'function_call',
1192
+ delta: {
1193
+ role: 'assistant',
1194
+ content: null,
1195
+ function_call: {
1196
+ arguments: '',
1197
+ name: 'getWeather',
1198
+ },
1199
+ },
1200
+ },
1201
+ ],
1202
+ created: Math.floor(Date.now() / 1000),
1203
+ model: 'gpt-3.5-turbo',
1204
+ object: 'chat.completion.chunk',
1205
+ };
1206
+ }),
1207
+ handleRequest(async function* (request) {
1208
+ expect(request.messages).toEqual([
1209
+ { role: 'user', content: 'tell me what the weather is like' },
1210
+ {
1211
+ role: 'assistant',
1212
+ content: null,
1213
+ function_call: {
1214
+ arguments: '',
1215
+ name: 'getWeather',
1216
+ },
1217
+ },
1218
+ {
1219
+ role: 'function',
1220
+ content: `it's raining`,
1221
+ name: 'getWeather',
1222
+ },
1223
+ ]);
1224
+ for (const choice of contentChoiceDeltas(`it's raining`)) {
1225
+ yield {
1226
+ id: '2',
1227
+ choices: [choice],
1228
+ created: Math.floor(Date.now() / 1000),
1229
+ model: 'gpt-3.5-turbo',
1230
+ object: 'chat.completion',
1231
+ };
1232
+ }
1233
+ }),
1234
+ runner.done(),
1235
+ ]);
1236
+ expect(listener.eventMessages).toEqual([
1237
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
1238
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
1239
+ { role: 'assistant', content: "it's raining" },
1240
+ ]);
1241
+ expect(listener.eventFunctionCallResults).toEqual([`it's raining`]);
1242
+ await listener.sanityCheck();
1243
+ });
1244
+ test('flow with abort', async () => {
1245
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1246
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1247
+ const controller = new AbortController();
1248
+ const runner = openai.beta.chat.completions.runFunctions({
1249
+ stream: true,
1250
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1251
+ model: 'gpt-3.5-turbo',
1252
+ functions: [
1253
+ {
1254
+ function: function getWeather() {
1255
+ return `it's raining`;
1256
+ },
1257
+ parameters: {},
1258
+ description: 'gets the weather',
1259
+ },
1260
+ ],
1261
+ }, { signal: controller.signal });
1262
+ runner.on('functionCallResult', () => controller.abort());
1263
+ const listener = new StreamingRunnerListener(runner);
1264
+ await handleRequest(async function* (request) {
1265
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1266
+ yield {
1267
+ id: '1',
1268
+ choices: [
1269
+ {
1270
+ index: 0,
1271
+ finish_reason: 'function_call',
1272
+ delta: {
1273
+ role: 'assistant',
1274
+ content: null,
1275
+ function_call: {
1276
+ arguments: '',
1277
+ name: 'getWeather',
1278
+ },
1279
+ },
1280
+ },
1281
+ ],
1282
+ created: Math.floor(Date.now() / 1000),
1283
+ model: 'gpt-3.5-turbo',
1284
+ object: 'chat.completion',
1285
+ };
1286
+ });
1287
+ await runner.done().catch(() => { });
1288
+ expect(listener.eventMessages).toEqual([
1289
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
1290
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
1291
+ ]);
1292
+ expect(listener.eventFunctionCallResults).toEqual([`it's raining`]);
1293
+ await listener.sanityCheck({ error: 'Request was aborted.' });
1294
+ expect(runner.aborted).toBe(true);
1295
+ });
1296
+ test('successful flow with parse', async () => {
1297
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1298
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1299
+ const runner = openai.beta.chat.completions.runFunctions({
1300
+ stream: true,
1301
+ messages: [
1302
+ {
1303
+ role: 'user',
1304
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1305
+ },
1306
+ ],
1307
+ model: 'gpt-3.5-turbo',
1308
+ functions: [
1309
+ new completions_1.ParsingFunction({
1310
+ name: 'numProperties',
1311
+ function: (obj) => String(Object.keys(obj).length),
1312
+ parameters: { type: 'object' },
1313
+ parse: (str) => {
1314
+ const result = JSON.parse(str);
1315
+ if (!(result instanceof Object) || Array.isArray(result)) {
1316
+ throw new Error('must be an object');
1317
+ }
1318
+ return result;
1319
+ },
1320
+ description: 'gets the number of properties on an object',
1321
+ }),
1322
+ ],
1323
+ });
1324
+ const listener = new StreamingRunnerListener(runner);
1325
+ await Promise.all([
1326
+ handleRequest(async function* (request) {
1327
+ expect(request.messages).toEqual([
1328
+ {
1329
+ role: 'user',
1330
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1331
+ },
1332
+ ]);
1333
+ yield {
1334
+ id: '1',
1335
+ choices: [
1336
+ {
1337
+ index: 0,
1338
+ finish_reason: 'function_call',
1339
+ delta: {
1340
+ role: 'assistant',
1341
+ content: null,
1342
+ function_call: {
1343
+ arguments: '{"a": 1, "b": 2, "c": 3}',
1344
+ name: 'numProperties',
1345
+ },
1346
+ },
1347
+ },
1348
+ ],
1349
+ created: Math.floor(Date.now() / 1000),
1350
+ model: 'gpt-3.5-turbo',
1351
+ object: 'chat.completion',
1352
+ };
1353
+ }),
1354
+ handleRequest(async function* (request) {
1355
+ expect(request.messages).toEqual([
1356
+ {
1357
+ role: 'user',
1358
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1359
+ },
1360
+ {
1361
+ role: 'assistant',
1362
+ content: null,
1363
+ function_call: {
1364
+ arguments: '{"a": 1, "b": 2, "c": 3}',
1365
+ name: 'numProperties',
1366
+ },
1367
+ },
1368
+ {
1369
+ role: 'function',
1370
+ content: '3',
1371
+ name: 'numProperties',
1372
+ },
1373
+ ]);
1374
+ for (const choice of contentChoiceDeltas(`there are 3 properties in {"a": 1, "b": 2, "c": 3}`)) {
1375
+ yield {
1376
+ id: '2',
1377
+ choices: [choice],
1378
+ created: Math.floor(Date.now() / 1000),
1379
+ model: 'gpt-3.5-turbo',
1380
+ object: 'chat.completion',
1381
+ };
1382
+ }
1383
+ }),
1384
+ runner.done(),
1385
+ ]);
1386
+ expect(listener.eventMessages).toEqual([
1387
+ {
1388
+ role: 'assistant',
1389
+ content: null,
1390
+ function_call: { name: 'numProperties', arguments: '{"a": 1, "b": 2, "c": 3}' },
1391
+ },
1392
+ { role: 'function', content: '3', name: 'numProperties' },
1393
+ { role: 'assistant', content: 'there are 3 properties in {"a": 1, "b": 2, "c": 3}' },
1394
+ ]);
1395
+ expect(listener.eventFunctionCallResults).toEqual(['3']);
1396
+ await listener.sanityCheck();
1397
+ });
1398
+ test('flow with parse error', async () => {
1399
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1400
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1401
+ const runner = openai.beta.chat.completions.runFunctions({
1402
+ stream: true,
1403
+ messages: [
1404
+ {
1405
+ role: 'user',
1406
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1407
+ },
1408
+ ],
1409
+ model: 'gpt-3.5-turbo',
1410
+ functions: [
1411
+ new completions_1.ParsingFunction({
1412
+ name: 'numProperties',
1413
+ function: (obj) => String(Object.keys(obj).length),
1414
+ parameters: { type: 'object' },
1415
+ parse: (str) => {
1416
+ const result = JSON.parse(str);
1417
+ if (!(result instanceof Object) || Array.isArray(result)) {
1418
+ throw new Error('must be an object');
1419
+ }
1420
+ return result;
1421
+ },
1422
+ description: 'gets the number of properties on an object',
1423
+ }),
1424
+ ],
1425
+ });
1426
+ const listener = new StreamingRunnerListener(runner);
1427
+ await Promise.all([
1428
+ handleRequest(async function* (request) {
1429
+ expect(request.messages).toEqual([
1430
+ {
1431
+ role: 'user',
1432
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1433
+ },
1434
+ ]);
1435
+ for (const choice of functionCallDeltas('[{"a": 1, "b": 2, "c": 3}]', { name: 'numProperties' })) {
1436
+ yield {
1437
+ id: '1',
1438
+ choices: [choice],
1439
+ created: Math.floor(Date.now() / 1000),
1440
+ model: 'gpt-3.5-turbo',
1441
+ object: 'chat.completion',
1442
+ };
1443
+ }
1444
+ }),
1445
+ handleRequest(async function* (request) {
1446
+ expect(request.messages).toEqual([
1447
+ {
1448
+ role: 'user',
1449
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1450
+ },
1451
+ {
1452
+ role: 'assistant',
1453
+ content: null,
1454
+ function_call: {
1455
+ arguments: '[{"a": 1, "b": 2, "c": 3}]',
1456
+ name: 'numProperties',
1457
+ },
1458
+ },
1459
+ {
1460
+ role: 'function',
1461
+ content: `must be an object`,
1462
+ name: 'numProperties',
1463
+ },
1464
+ ]);
1465
+ for (const choice of functionCallDeltas('{"a": 1, "b": 2, "c": 3}', { name: 'numProperties' })) {
1466
+ yield {
1467
+ id: '2',
1468
+ choices: [choice],
1469
+ created: Math.floor(Date.now() / 1000),
1470
+ model: 'gpt-3.5-turbo',
1471
+ object: 'chat.completion',
1472
+ };
1473
+ }
1474
+ }),
1475
+ handleRequest(async function* (request) {
1476
+ expect(request.messages).toEqual([
1477
+ {
1478
+ role: 'user',
1479
+ content: 'can you tell me how many properties are in {"a": 1, "b": 2, "c": 3}',
1480
+ },
1481
+ {
1482
+ role: 'assistant',
1483
+ content: null,
1484
+ function_call: {
1485
+ arguments: '[{"a": 1, "b": 2, "c": 3}]',
1486
+ name: 'numProperties',
1487
+ },
1488
+ },
1489
+ {
1490
+ role: 'function',
1491
+ content: `must be an object`,
1492
+ name: 'numProperties',
1493
+ },
1494
+ {
1495
+ role: 'assistant',
1496
+ content: null,
1497
+ function_call: {
1498
+ arguments: '{"a": 1, "b": 2, "c": 3}',
1499
+ name: 'numProperties',
1500
+ },
1501
+ },
1502
+ {
1503
+ role: 'function',
1504
+ content: '3',
1505
+ name: 'numProperties',
1506
+ },
1507
+ ]);
1508
+ for (const choice of contentChoiceDeltas(`there are 3 properties in {"a": 1, "b": 2, "c": 3}`)) {
1509
+ yield {
1510
+ id: '3',
1511
+ choices: [choice],
1512
+ created: Math.floor(Date.now() / 1000),
1513
+ model: 'gpt-3.5-turbo',
1514
+ object: 'chat.completion',
1515
+ };
1516
+ }
1517
+ }),
1518
+ runner.done(),
1519
+ ]);
1520
+ expect(listener.eventMessages).toEqual([
1521
+ {
1522
+ role: 'assistant',
1523
+ content: null,
1524
+ function_call: { name: 'numProperties', arguments: '[{"a": 1, "b": 2, "c": 3}]' },
1525
+ },
1526
+ { role: 'function', content: `must be an object`, name: 'numProperties' },
1527
+ {
1528
+ role: 'assistant',
1529
+ content: null,
1530
+ function_call: { name: 'numProperties', arguments: '{"a": 1, "b": 2, "c": 3}' },
1531
+ },
1532
+ { role: 'function', content: '3', name: 'numProperties' },
1533
+ { role: 'assistant', content: 'there are 3 properties in {"a": 1, "b": 2, "c": 3}' },
1534
+ ]);
1535
+ expect(listener.eventFunctionCallResults).toEqual([`must be an object`, '3']);
1536
+ await listener.sanityCheck();
1537
+ });
1538
+ test('single function call', async () => {
1539
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1540
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1541
+ const runner = openai.beta.chat.completions.runFunctions({
1542
+ stream: true,
1543
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1544
+ model: 'gpt-3.5-turbo',
1545
+ function_call: {
1546
+ name: 'getWeather',
1547
+ },
1548
+ functions: [
1549
+ {
1550
+ function: function getWeather() {
1551
+ return `it's raining`;
1552
+ },
1553
+ parameters: {},
1554
+ description: 'gets the weather',
1555
+ },
1556
+ ],
1557
+ });
1558
+ const listener = new StreamingRunnerListener(runner);
1559
+ await Promise.all([
1560
+ handleRequest(async function* (request) {
1561
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1562
+ yield {
1563
+ id: '1',
1564
+ choices: [
1565
+ {
1566
+ index: 0,
1567
+ finish_reason: 'function_call',
1568
+ delta: {
1569
+ role: 'assistant',
1570
+ content: null,
1571
+ function_call: {
1572
+ arguments: '',
1573
+ name: 'getWeather',
1574
+ },
1575
+ },
1576
+ },
1577
+ ],
1578
+ created: Math.floor(Date.now() / 1000),
1579
+ model: 'gpt-3.5-turbo',
1580
+ object: 'chat.completion',
1581
+ };
1582
+ }),
1583
+ runner.done(),
1584
+ ]);
1585
+ expect(listener.eventMessages).toEqual([
1586
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
1587
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
1588
+ ]);
1589
+ expect(listener.eventFunctionCallResults).toEqual([`it's raining`]);
1590
+ await listener.sanityCheck();
1591
+ });
1592
+ test('wrong function name', async () => {
1593
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1594
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1595
+ const runner = openai.beta.chat.completions.runFunctions({
1596
+ stream: true,
1597
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1598
+ model: 'gpt-3.5-turbo',
1599
+ functions: [
1600
+ {
1601
+ function: function getWeather() {
1602
+ return `it's raining`;
1603
+ },
1604
+ parameters: {},
1605
+ description: 'gets the weather',
1606
+ },
1607
+ ],
1608
+ });
1609
+ const listener = new StreamingRunnerListener(runner);
1610
+ await Promise.all([
1611
+ handleRequest(async function* (request) {
1612
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1613
+ yield {
1614
+ id: '1',
1615
+ choices: [
1616
+ {
1617
+ index: 0,
1618
+ finish_reason: 'function_call',
1619
+ delta: {
1620
+ role: 'assistant',
1621
+ content: null,
1622
+ function_call: {
1623
+ arguments: '',
1624
+ name: 'get_weather',
1625
+ },
1626
+ },
1627
+ },
1628
+ ],
1629
+ created: Math.floor(Date.now() / 1000),
1630
+ model: 'gpt-3.5-turbo',
1631
+ object: 'chat.completion',
1632
+ };
1633
+ }),
1634
+ handleRequest(async function* (request) {
1635
+ expect(request.messages).toEqual([
1636
+ { role: 'user', content: 'tell me what the weather is like' },
1637
+ {
1638
+ role: 'assistant',
1639
+ content: null,
1640
+ function_call: {
1641
+ arguments: '',
1642
+ name: 'get_weather',
1643
+ },
1644
+ },
1645
+ {
1646
+ role: 'function',
1647
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1648
+ name: 'get_weather',
1649
+ },
1650
+ ]);
1651
+ yield {
1652
+ id: '2',
1653
+ choices: [
1654
+ {
1655
+ index: 0,
1656
+ finish_reason: 'function_call',
1657
+ delta: {
1658
+ role: 'assistant',
1659
+ content: null,
1660
+ function_call: {
1661
+ arguments: '',
1662
+ name: 'getWeather',
1663
+ },
1664
+ },
1665
+ },
1666
+ ],
1667
+ created: Math.floor(Date.now() / 1000),
1668
+ model: 'gpt-3.5-turbo',
1669
+ object: 'chat.completion',
1670
+ };
1671
+ }),
1672
+ handleRequest(async function* (request) {
1673
+ expect(request.messages).toEqual([
1674
+ { role: 'user', content: 'tell me what the weather is like' },
1675
+ {
1676
+ role: 'assistant',
1677
+ content: null,
1678
+ function_call: {
1679
+ arguments: '',
1680
+ name: 'get_weather',
1681
+ },
1682
+ },
1683
+ {
1684
+ role: 'function',
1685
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1686
+ name: 'get_weather',
1687
+ },
1688
+ {
1689
+ role: 'assistant',
1690
+ content: null,
1691
+ function_call: {
1692
+ arguments: '',
1693
+ name: 'getWeather',
1694
+ },
1695
+ },
1696
+ {
1697
+ role: 'function',
1698
+ content: `it's raining`,
1699
+ name: 'getWeather',
1700
+ },
1701
+ ]);
1702
+ for (const choice of contentChoiceDeltas(`it's raining`)) {
1703
+ yield {
1704
+ id: '3',
1705
+ choices: [choice],
1706
+ created: Math.floor(Date.now() / 1000),
1707
+ model: 'gpt-3.5-turbo',
1708
+ object: 'chat.completion',
1709
+ };
1710
+ }
1711
+ }),
1712
+ runner.done(),
1713
+ ]);
1714
+ expect(listener.eventMessages).toEqual([
1715
+ { role: 'assistant', content: null, function_call: { name: 'get_weather', arguments: '' } },
1716
+ {
1717
+ role: 'function',
1718
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1719
+ name: 'get_weather',
1720
+ },
1721
+ { role: 'assistant', content: null, function_call: { name: 'getWeather', arguments: '' } },
1722
+ { role: 'function', content: `it's raining`, name: 'getWeather' },
1723
+ { role: 'assistant', content: "it's raining" },
1724
+ ]);
1725
+ expect(listener.eventFunctionCallResults).toEqual([
1726
+ `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1727
+ `it's raining`,
1728
+ ]);
1729
+ await listener.sanityCheck();
1730
+ });
1731
+ test('wrong function name with single function call', async () => {
1732
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1733
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1734
+ const runner = openai.beta.chat.completions.runFunctions({
1735
+ stream: true,
1736
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1737
+ model: 'gpt-3.5-turbo',
1738
+ function_call: {
1739
+ name: 'getWeather',
1740
+ },
1741
+ functions: [
1742
+ {
1743
+ function: function getWeather() {
1744
+ return `it's raining`;
1745
+ },
1746
+ parameters: {},
1747
+ description: 'gets the weather',
1748
+ },
1749
+ ],
1750
+ });
1751
+ const listener = new StreamingRunnerListener(runner);
1752
+ await Promise.all([
1753
+ handleRequest(async function* (request) {
1754
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1755
+ yield {
1756
+ id: '1',
1757
+ choices: [
1758
+ {
1759
+ index: 0,
1760
+ finish_reason: 'function_call',
1761
+ delta: {
1762
+ role: 'assistant',
1763
+ content: null,
1764
+ function_call: {
1765
+ arguments: '',
1766
+ name: 'get_weather',
1767
+ },
1768
+ },
1769
+ },
1770
+ ],
1771
+ created: Math.floor(Date.now() / 1000),
1772
+ model: 'gpt-3.5-turbo',
1773
+ object: 'chat.completion',
1774
+ };
1775
+ }),
1776
+ runner.done(),
1777
+ ]);
1778
+ expect(listener.eventMessages).toEqual([
1779
+ { role: 'assistant', content: null, function_call: { name: 'get_weather', arguments: '' } },
1780
+ {
1781
+ role: 'function',
1782
+ content: `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1783
+ name: 'get_weather',
1784
+ },
1785
+ ]);
1786
+ expect(listener.eventFunctionCallResults).toEqual([
1787
+ `Invalid function_call: "get_weather". Available options are: "getWeather". Please try again`,
1788
+ ]);
1789
+ await listener.sanityCheck();
1790
+ });
1791
+ });
1792
+ describe('stream', () => {
1793
+ test('successful flow', async () => {
1794
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1795
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1796
+ const runner = openai.beta.chat.completions.stream({
1797
+ stream: true,
1798
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1799
+ model: 'gpt-3.5-turbo',
1800
+ });
1801
+ const listener = new StreamingRunnerListener(runner);
1802
+ await Promise.all([
1803
+ handleRequest(async function* (request) {
1804
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1805
+ for (const choice of contentChoiceDeltas(`The weather is great today!`)) {
1806
+ yield {
1807
+ id: '1',
1808
+ choices: [choice],
1809
+ created: Math.floor(Date.now() / 1000),
1810
+ model: 'gpt-3.5-turbo',
1811
+ object: 'chat.completion',
1812
+ };
1813
+ }
1814
+ }),
1815
+ runner.done(),
1816
+ ]);
1817
+ expect(listener.finalMessage).toEqual({ role: 'assistant', content: 'The weather is great today!' });
1818
+ await listener.sanityCheck();
1819
+ });
1820
+ test('toReadableStream and fromReadableStream', async () => {
1821
+ const { fetch, handleRequest } = mockStreamingChatCompletionFetch();
1822
+ const openai = new openai_1.default({ apiKey: 'something1234', baseURL: 'http://127.0.0.1:4010', fetch });
1823
+ const runner = openai.beta.chat.completions.stream({
1824
+ stream: true,
1825
+ messages: [{ role: 'user', content: 'tell me what the weather is like' }],
1826
+ model: 'gpt-3.5-turbo',
1827
+ });
1828
+ const proxied = completions_1.ChatCompletionStreamingRunner.fromReadableStream(runner.toReadableStream());
1829
+ const listener = new StreamingRunnerListener(proxied);
1830
+ await Promise.all([
1831
+ handleRequest(async function* (request) {
1832
+ expect(request.messages).toEqual([{ role: 'user', content: 'tell me what the weather is like' }]);
1833
+ for (const choice of contentChoiceDeltas(`The weather is great today!`)) {
1834
+ yield {
1835
+ id: '1',
1836
+ choices: [choice],
1837
+ created: Math.floor(Date.now() / 1000),
1838
+ model: 'gpt-3.5-turbo',
1839
+ object: 'chat.completion',
1840
+ };
1841
+ }
1842
+ }),
1843
+ proxied.done(),
1844
+ ]);
1845
+ expect(listener.finalMessage).toEqual({ role: 'assistant', content: 'The weather is great today!' });
1846
+ await listener.sanityCheck();
1847
+ });
1848
+ });
1849
+ });
1850
+ //# sourceMappingURL=ChatCompletionRunFunctions.test.js.map