@celljs/ai-openai 3.4.4

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/lib/common/api/api-protocol.d.ts +47 -0
  4. package/lib/common/api/api-protocol.d.ts.map +1 -0
  5. package/lib/common/api/api-protocol.js +6 -0
  6. package/lib/common/api/api-protocol.js.map +1 -0
  7. package/lib/common/api/chat-request.d.ts +198 -0
  8. package/lib/common/api/chat-request.d.ts.map +1 -0
  9. package/lib/common/api/chat-request.js +145 -0
  10. package/lib/common/api/chat-request.js.map +1 -0
  11. package/lib/common/api/chat-response.d.ts +193 -0
  12. package/lib/common/api/chat-response.d.ts.map +1 -0
  13. package/lib/common/api/chat-response.js +124 -0
  14. package/lib/common/api/chat-response.js.map +1 -0
  15. package/lib/common/api/embeddings-request.d.ts +59 -0
  16. package/lib/common/api/embeddings-request.d.ts.map +1 -0
  17. package/lib/common/api/embeddings-request.js +49 -0
  18. package/lib/common/api/embeddings-request.js.map +1 -0
  19. package/lib/common/api/embeddings-response.d.ts +27 -0
  20. package/lib/common/api/embeddings-response.d.ts.map +1 -0
  21. package/lib/common/api/embeddings-response.js +30 -0
  22. package/lib/common/api/embeddings-response.js.map +1 -0
  23. package/lib/common/api/index.d.ts +10 -0
  24. package/lib/common/api/index.d.ts.map +1 -0
  25. package/lib/common/api/index.js +13 -0
  26. package/lib/common/api/index.js.map +1 -0
  27. package/lib/common/api/message.d.ts +216 -0
  28. package/lib/common/api/message.d.ts.map +1 -0
  29. package/lib/common/api/message.js +179 -0
  30. package/lib/common/api/message.js.map +1 -0
  31. package/lib/common/api/openai-api.d.ts +18 -0
  32. package/lib/common/api/openai-api.d.ts.map +1 -0
  33. package/lib/common/api/openai-api.js +99 -0
  34. package/lib/common/api/openai-api.js.map +1 -0
  35. package/lib/common/api/openai-api.spec.d.ts +2 -0
  36. package/lib/common/api/openai-api.spec.d.ts.map +1 -0
  37. package/lib/common/api/openai-api.spec.js +162 -0
  38. package/lib/common/api/openai-api.spec.js.map +1 -0
  39. package/lib/common/api/openai-model.d.ts +117 -0
  40. package/lib/common/api/openai-model.d.ts.map +1 -0
  41. package/lib/common/api/openai-model.js +121 -0
  42. package/lib/common/api/openai-model.js.map +1 -0
  43. package/lib/common/api/response-format.d.ts +60 -0
  44. package/lib/common/api/response-format.d.ts.map +1 -0
  45. package/lib/common/api/response-format.js +79 -0
  46. package/lib/common/api/response-format.js.map +1 -0
  47. package/lib/common/api/usage.d.ts +79 -0
  48. package/lib/common/api/usage.d.ts.map +1 -0
  49. package/lib/common/api/usage.js +95 -0
  50. package/lib/common/api/usage.js.map +1 -0
  51. package/lib/common/index.d.ts +4 -0
  52. package/lib/common/index.d.ts.map +1 -0
  53. package/lib/common/index.js +7 -0
  54. package/lib/common/index.js.map +1 -0
  55. package/lib/common/model/index.d.ts +5 -0
  56. package/lib/common/model/index.d.ts.map +1 -0
  57. package/lib/common/model/index.js +8 -0
  58. package/lib/common/model/index.js.map +1 -0
  59. package/lib/common/model/openai-chat-model.d.ts +37 -0
  60. package/lib/common/model/openai-chat-model.d.ts.map +1 -0
  61. package/lib/common/model/openai-chat-model.js +357 -0
  62. package/lib/common/model/openai-chat-model.js.map +1 -0
  63. package/lib/common/model/openai-chat-model.spec.d.ts +2 -0
  64. package/lib/common/model/openai-chat-model.spec.d.ts.map +1 -0
  65. package/lib/common/model/openai-chat-model.spec.js +82 -0
  66. package/lib/common/model/openai-chat-model.spec.js.map +1 -0
  67. package/lib/common/model/openai-chat-options.d.ts +159 -0
  68. package/lib/common/model/openai-chat-options.d.ts.map +1 -0
  69. package/lib/common/model/openai-chat-options.js +231 -0
  70. package/lib/common/model/openai-chat-options.js.map +1 -0
  71. package/lib/common/model/openai-embedding-model.d.ts +15 -0
  72. package/lib/common/model/openai-embedding-model.d.ts.map +1 -0
  73. package/lib/common/model/openai-embedding-model.js +64 -0
  74. package/lib/common/model/openai-embedding-model.js.map +1 -0
  75. package/lib/common/model/openai-embedding-model.spec.d.ts +2 -0
  76. package/lib/common/model/openai-embedding-model.spec.d.ts.map +1 -0
  77. package/lib/common/model/openai-embedding-model.spec.js +100 -0
  78. package/lib/common/model/openai-embedding-model.spec.js.map +1 -0
  79. package/lib/common/model/openai-embedding-options.d.ts +35 -0
  80. package/lib/common/model/openai-embedding-options.d.ts.map +1 -0
  81. package/lib/common/model/openai-embedding-options.js +56 -0
  82. package/lib/common/model/openai-embedding-options.js.map +1 -0
  83. package/lib/common/module.d.ts +4 -0
  84. package/lib/common/module.d.ts.map +1 -0
  85. package/lib/common/module.js +6 -0
  86. package/lib/common/module.js.map +1 -0
  87. package/lib/common/support/index.d.ts +3 -0
  88. package/lib/common/support/index.d.ts.map +1 -0
  89. package/lib/common/support/index.js +6 -0
  90. package/lib/common/support/index.js.map +1 -0
  91. package/lib/common/support/openai-api-response-headers.d.ts +34 -0
  92. package/lib/common/support/openai-api-response-headers.d.ts.map +1 -0
  93. package/lib/common/support/openai-api-response-headers.js +45 -0
  94. package/lib/common/support/openai-api-response-headers.js.map +1 -0
  95. package/lib/common/support/openai-response-header-extractor.d.ts +12 -0
  96. package/lib/common/support/openai-response-header-extractor.d.ts.map +1 -0
  97. package/lib/common/support/openai-response-header-extractor.js +131 -0
  98. package/lib/common/support/openai-response-header-extractor.js.map +1 -0
  99. package/lib/common/test/mock-openai-api.d.ts +13 -0
  100. package/lib/common/test/mock-openai-api.d.ts.map +1 -0
  101. package/lib/common/test/mock-openai-api.js +57 -0
  102. package/lib/common/test/mock-openai-api.js.map +1 -0
  103. package/lib/common/test/test-container.d.ts +3 -0
  104. package/lib/common/test/test-container.d.ts.map +1 -0
  105. package/lib/common/test/test-container.js +16 -0
  106. package/lib/common/test/test-container.js.map +1 -0
  107. package/lib/package.spec.d.ts +1 -0
  108. package/lib/package.spec.d.ts.map +1 -0
  109. package/lib/package.spec.js +4 -0
  110. package/lib/package.spec.js.map +1 -0
  111. package/package.json +46 -0
  112. package/src/common/api/api-protocol.ts +51 -0
  113. package/src/common/api/chat-request.ts +258 -0
  114. package/src/common/api/chat-response.ts +218 -0
  115. package/src/common/api/embeddings-request.ts +92 -0
  116. package/src/common/api/embeddings-response.ts +44 -0
  117. package/src/common/api/index.ts +9 -0
  118. package/src/common/api/message.ts +273 -0
  119. package/src/common/api/openai-api.spec.ts +175 -0
  120. package/src/common/api/openai-api.ts +111 -0
  121. package/src/common/api/openai-model.ts +137 -0
  122. package/src/common/api/response-format.ts +109 -0
  123. package/src/common/api/usage.ts +135 -0
  124. package/src/common/index.ts +3 -0
  125. package/src/common/model/index.ts +4 -0
  126. package/src/common/model/openai-chat-model.spec.ts +85 -0
  127. package/src/common/model/openai-chat-model.ts +399 -0
  128. package/src/common/model/openai-chat-options.ts +326 -0
  129. package/src/common/model/openai-embedding-model.spec.ts +112 -0
  130. package/src/common/model/openai-embedding-model.ts +83 -0
  131. package/src/common/model/openai-embedding-options.ts +72 -0
  132. package/src/common/module.ts +4 -0
  133. package/src/common/support/index.ts +2 -0
  134. package/src/common/support/openai-api-response-headers.ts +46 -0
  135. package/src/common/support/openai-response-header-extractor.ts +141 -0
  136. package/src/common/test/mock-openai-api.ts +70 -0
  137. package/src/common/test/test-container.ts +13 -0
  138. package/src/package.spec.ts +4 -0
@@ -0,0 +1,399 @@
1
+ import {
2
+ AssistantMessage,
3
+ ChatGenerationMetadata,
4
+ ChatModel,
5
+ ChatResponse,
6
+ ChatResponseMetadata,
7
+ FunctionCallbackRegister,
8
+ FunctionCallingOptions,
9
+ Generation,
10
+ Media,
11
+ MessageType,
12
+ Prompt,
13
+ RateLimit,
14
+ SystemMessage,
15
+ ToolResponseMessage,
16
+ UsageUtil,
17
+ Usage,
18
+ UserMessage,
19
+ ToolHandler,
20
+ PromptTemplate
21
+ } from '@celljs/ai-core';
22
+ import { Observable, of } from 'rxjs';
23
+ import { map, bufferCount, mergeMap, concatAll } from 'rxjs/operators';
24
+ import {
25
+ ChatCompletionRequest,
26
+ ChatCompletion,
27
+ ChatCompletionMessage,
28
+ Role,
29
+ ToolCall as OpenAIToolCall,
30
+ OpenAIAPI,
31
+ Choice,
32
+ MediaContent,
33
+ Format,
34
+ AudioOutput,
35
+ Function,
36
+ FunctionTool,
37
+ OutputModality,
38
+ ChatCompletionChunk,
39
+ ChatCompletionFinishReason
40
+ } from '../api';
41
+ import { Autowired, ByteUtil, Component, IllegalArgumentError, Logger, MimeTypeUtils, MimeType } from '@celljs/core';
42
+ import { OpenAIChatOptions } from './openai-chat-options';
43
+ import { OpenAIResponseHeaderExtractor } from '../support';
44
+
45
+ /**
46
+ * {@link ChatModel} implementation for {@literal OpenAI}
47
+ * backed by {@link OpenAIAPI}.
48
+ */
49
+ @Component(ChatModel)
50
+ export class OpenAIChatModel implements ChatModel {
51
+
52
+ /**
53
+ * The default options used for the chat completion requests.
54
+ */
55
+ @Autowired(OpenAIChatOptions)
56
+ protected readonly defaultOptions: OpenAIChatOptions;
57
+
58
+ /**
59
+ * Low-level access to the OpenAI API.
60
+ */
61
+ @Autowired(OpenAIAPI)
62
+ protected readonly chatApi: OpenAIAPI;
63
+
64
+ @Autowired(Logger)
65
+ protected readonly logger: Logger;
66
+
67
+ @Autowired(FunctionCallbackRegister)
68
+ protected readonly functionCallbackRegister: FunctionCallbackRegister;
69
+
70
+ @Autowired(ToolHandler)
71
+ protected readonly toolHandler: ToolHandler;
72
+
73
+ @Autowired(PromptTemplate)
74
+ protected readonly promptTemplate: PromptTemplate;
75
+
76
+ protected fromAudioData(audioData: any): string {
77
+ if (audioData instanceof Uint8Array) {
78
+ return ByteUtil.encodeBase64(audioData);
79
+ } else {
80
+ throw new IllegalArgumentError(`Unsupported audio data type: ${audioData.constructor.name}`);
81
+ }
82
+ }
83
+
84
+ protected fromMediaData(mimeType: MimeType, mediaContentData: any): string {
85
+ if (mediaContentData instanceof Uint8Array) {
86
+ return `data:${mimeType.toString()};base64,${ByteUtil.encodeBase64(mediaContentData)}`;
87
+ } else if (typeof mediaContentData === 'string') {
88
+ return mediaContentData;
89
+ } else {
90
+ throw new IllegalArgumentError(`Unsupported media data type: ${mediaContentData.constructor.name}`);
91
+ }
92
+ }
93
+
94
+ protected mapToMediaContent(media: Media): MediaContent {
95
+ const mimeType = media.mimeType.toString();
96
+ if (mimeType === 'audio/mp3') {
97
+ return MediaContent.createInputAudio(this.fromAudioData(media.data), Format.MP3);
98
+ }
99
+ if (mimeType === 'audio/wav') {
100
+ return MediaContent.createInputAudio(this.fromAudioData(media.data), Format.WAV);
101
+ } else {
102
+ return MediaContent.createImageUrl(this.fromMediaData(media.mimeType, media.data));
103
+ }
104
+ }
105
+
106
+ protected getFunctionTools(functionNames: Set<string>): FunctionTool[] {
107
+ return this.functionCallbackRegister.resolve(Array.from(functionNames)).map(functionCallback => {
108
+ const func = Function.create(functionCallback.description, functionCallback.name, JSON.parse(functionCallback.inputTypeSchema));
109
+ return FunctionTool.create(func);
110
+ });
111
+ }
112
+
113
+ protected createRequest(prompt: Prompt, stream: boolean): ChatCompletionRequest {
114
+ const chatCompletionMessages = prompt.instructions.map(message => {
115
+ if (message instanceof UserMessage || message instanceof SystemMessage) {
116
+ let content: any = message.content;
117
+ if (message instanceof UserMessage) {
118
+ if (message.media?.length > 0) {
119
+ const contentList = [MediaContent.createText(message.content), ...message.media.map(media => this.mapToMediaContent(media))];
120
+ content = contentList;
121
+ }
122
+ }
123
+ const role = message.messageType === MessageType.USER ? Role.USER : Role.SYSTEM;
124
+ return ChatCompletionMessage.create(content, role);
125
+ } else if (message instanceof AssistantMessage) {
126
+ const assistantMessage = message;
127
+ let toolCalls: OpenAIToolCall[] = [];
128
+ if (assistantMessage.toolCalls.length > 0) {
129
+ toolCalls = assistantMessage.toolCalls.map(toolCall => ({
130
+ id: toolCall.id,
131
+ type: 'function',
132
+ function: {
133
+ name: toolCall.name,
134
+ arguments: toolCall.arguments
135
+ }
136
+ }));
137
+ }
138
+ let audioOutput: AudioOutput | undefined;
139
+ if (assistantMessage.media.length > 0) {
140
+ if (assistantMessage.media.length !== 1) {
141
+ throw new IllegalArgumentError('Only one media content is supported for assistant messages');
142
+ }
143
+ const media = assistantMessage.media[0];
144
+ audioOutput = AudioOutput.create(media.id);
145
+ }
146
+ const chatCompletionMessage = ChatCompletionMessage.create(assistantMessage.content, Role.ASSISTANT);
147
+ chatCompletionMessage.toolCalls = toolCalls;
148
+ chatCompletionMessage.audioOutput = audioOutput;
149
+ return chatCompletionMessage;
150
+ } else if (message instanceof ToolResponseMessage) {
151
+ message.responses.forEach(response => {
152
+ if (!response.id) {
153
+ throw new IllegalArgumentError('ToolResponseMessage must have an id');
154
+ }
155
+ });
156
+ return message.responses.map(tr => {
157
+ const chatCompletionMessage = ChatCompletionMessage.create(tr.responseData, Role.TOOL);
158
+ chatCompletionMessage.name = tr.name;
159
+ chatCompletionMessage.toolCallId = tr.id;
160
+ return chatCompletionMessage;
161
+ });
162
+ } else {
163
+ throw new IllegalArgumentError(`Unsupported message type: ${message.messageType}`);
164
+ }
165
+ }).flat();
166
+
167
+ let request = new ChatCompletionRequest();
168
+ request.messages = chatCompletionMessages;
169
+ request.stream = stream;
170
+
171
+ // Runtime options
172
+ let runtimeOptions: OpenAIChatOptions = OpenAIChatOptions.builder().build();
173
+ if (prompt.options) {
174
+ runtimeOptions = Object.assign(runtimeOptions, prompt.options);
175
+ }
176
+
177
+ const enabledToolsToUse = FunctionCallingOptions.getEnabledFunctionsToCall(runtimeOptions);
178
+
179
+ if (this.defaultOptions.functions.size > 0) {
180
+ for (const functionName of this.defaultOptions.functions) {
181
+ enabledToolsToUse.add(functionName);
182
+ }
183
+ }
184
+
185
+ runtimeOptions = Object.assign(this.defaultOptions, runtimeOptions);
186
+ request = Object.assign(request, runtimeOptions);
187
+
188
+ if (enabledToolsToUse.size > 0) {
189
+ request.tools = this.getFunctionTools(enabledToolsToUse);
190
+ }
191
+
192
+ // Remove `streamOptions` from the request if it is not a streaming request
193
+ if (request.streamOptions && !stream) {
194
+ request.streamOptions = undefined;
195
+ }
196
+ return request;
197
+ }
198
+
199
+ protected buildGeneration(choice: Choice, metadata: Record<string, any>, request: ChatCompletionRequest): Generation {
200
+ const toolCalls = choice.message.toolCalls?.map(toolCall => ({
201
+ id: toolCall.id ?? '',
202
+ type: 'function',
203
+ name: toolCall.function.name,
204
+ arguments: JSON.stringify(toolCall.function.arguments)
205
+ })) ?? [];
206
+
207
+ const finishReason = choice.finishReason ?? '';
208
+ const generationMetadata = ChatGenerationMetadata.from(finishReason);
209
+
210
+ const media: Media[] = [];
211
+ let textContent = choice.message.content;
212
+ const audioOutput = choice.message.audioOutput;
213
+ if (audioOutput) {
214
+ const mimeType = `audio/${request.audioParameters?.format?.toLowerCase()}`;
215
+ const audioData = ByteUtil.decodeBase64(audioOutput.data);
216
+ media.push({
217
+ mimeType: MimeTypeUtils.parseMimeType(mimeType),
218
+ data: audioData,
219
+ id: audioOutput.id
220
+ });
221
+ if (!textContent) {
222
+ textContent = audioOutput.transcript;
223
+ }
224
+ generationMetadata.metadata.audioId = audioOutput.id;
225
+ generationMetadata.metadata.audioExpiresAt = audioOutput.expiresAt;
226
+ }
227
+
228
+ const assistantMessage = new AssistantMessage(textContent, media, metadata, toolCalls);
229
+ return {
230
+ output: assistantMessage,
231
+ metadata: generationMetadata
232
+ };
233
+ }
234
+
235
+ protected buildChatResponseMetadataByResult(result: ChatCompletion, usage: Usage, rateLimit: RateLimit = RateLimit.createEmpty()): ChatResponseMetadata {
236
+ return ChatResponseMetadata.builder()
237
+ .id(result.id ?? '')
238
+ .usage(usage)
239
+ .model(result.model ?? '')
240
+ .keyValue('created', result.created ?? 0)
241
+ .keyValue('system-fingerprint', result.systemFingerprint ?? '')
242
+ .rateLimit(rateLimit)
243
+ .build();
244
+ }
245
+
246
+ protected buildChatResponseMetadata(chatResponseMetadata: ChatResponseMetadata, usage: Usage): ChatResponseMetadata {
247
+ return ChatResponseMetadata.builder()
248
+ .id(chatResponseMetadata.id ?? '')
249
+ .usage(usage)
250
+ .model(chatResponseMetadata.model ?? '')
251
+ .rateLimit(chatResponseMetadata.rateLimit)
252
+ .build();
253
+ }
254
+
255
+ call(prompt: Prompt): Promise<ChatResponse> {
256
+ return this.internalCall(prompt);
257
+ }
258
+
259
+ protected async internalCall(prompt: Prompt, previousChatResponse?: ChatResponse): Promise<ChatResponse> {
260
+ const request = this.createRequest(prompt, false);
261
+ const completionEntity = await this.chatApi.chat(request);
262
+ const chatCompletion = completionEntity.body;
263
+ if (!chatCompletion) {
264
+ this.logger.warn(`No chat completion returned for prompt: ${prompt}`);
265
+ return ChatResponse.from([]);
266
+ }
267
+ const choices = chatCompletion.choices;
268
+ if (!choices || choices.length === 0) {
269
+ this.logger.warn(`No choices returned for prompt: ${prompt}`);
270
+ return ChatResponse.from([]);
271
+ }
272
+ const generations = choices.map(choice => {
273
+ const metadata = {
274
+ id: chatCompletion.id ?? '',
275
+ role: choice.message.role ?? '',
276
+ index: choice.index,
277
+ finishReason: choice.finishReason ?? '',
278
+ refusal: choice.message.refusal ?? ''
279
+ };
280
+ return this.buildGeneration(choice, metadata, request);
281
+ });
282
+ const rateLimit = OpenAIResponseHeaderExtractor.extractAIResponseHeaders(completionEntity);
283
+ const usage = chatCompletion.usage ?? {};
284
+ const chatResponse = ChatResponse.from(generations, this.buildChatResponseMetadataByResult(chatCompletion, usage, rateLimit));
285
+ if (FunctionCallingOptions.isProxyToolCalls(prompt.options, this.defaultOptions)) {
286
+ if (ChatResponse.isToolCall(chatResponse, new Set([ChatCompletionFinishReason.TOOL_CALLS, ChatCompletionFinishReason.STOP]))) {
287
+ const toolCallConversation = await this.toolHandler.handle(prompt, chatResponse);
288
+ return this.internalCall(await this.promptTemplate.create(toolCallConversation, { chatOptions: prompt.options }), chatResponse);
289
+ }
290
+ }
291
+ return chatResponse;
292
+ }
293
+
294
+ protected chunkToChatCompletion(chunk: ChatCompletionChunk): ChatCompletion {
295
+ const choices = chunk.choices.map(chunkChoice => {
296
+ const choice = new Choice();
297
+ choice.finishReason = chunkChoice.finishReason;
298
+ choice.index = chunkChoice.index;
299
+ choice.message = chunkChoice.delta;
300
+ choice.logprobs = chunkChoice.logprobs;
301
+ return choice;
302
+ });
303
+ const chatCompletion = new ChatCompletion();
304
+ chatCompletion.id = chunk.id;
305
+ chatCompletion.choices = choices;
306
+ chatCompletion.created = chunk.created;
307
+ chatCompletion.model = chunk.model;
308
+ chatCompletion.serviceTier = chunk.serviceTier;
309
+ chatCompletion.systemFingerprint = chunk.systemFingerprint;
310
+ chatCompletion.usage = chunk.usage;
311
+ return chatCompletion;
312
+ }
313
+
314
+ protected async internalStream(prompt: Prompt, previousChatResponse?: ChatResponse): Promise<Observable<ChatResponse>> {
315
+ const request = this.createRequest(prompt, true);
316
+ if (request.outputModalities?.includes(OutputModality.AUDIO)) {
317
+ this.logger.warn('Audio output is not supported for streaming requests. Removing audio output.');
318
+ throw new IllegalArgumentError('Audio output is not supported for streaming requests.');
319
+ }
320
+
321
+ if (request.audioParameters) {
322
+ this.logger.warn('Audio parameters are not supported for streaming requests. Removing audio parameters.');
323
+ throw new IllegalArgumentError('Audio parameters are not supported for streaming requests.');
324
+ }
325
+
326
+ const response = await this.chatApi.streamingChat(request);
327
+
328
+ // For chunked responses, only the first chunk contains the choice role.
329
+ // Subsequent chunks contain the choices.
330
+ const roleMap = new Map<string, string>();
331
+
332
+ const chatResponse = response.pipe(
333
+ map(responseEntity => this.chunkToChatCompletion(responseEntity.body)),
334
+ map(chatCompletion => {
335
+ try {
336
+ const generations = chatCompletion.choices.map(choice => {
337
+ const id = chatCompletion.id ?? '';
338
+ if (choice.message.role) {
339
+ roleMap.set(id, choice.message.role);
340
+ }
341
+ const metadata = {
342
+ id,
343
+ role: roleMap.get(id) ?? '',
344
+ index: choice.index,
345
+ finishReason: choice.finishReason ?? '',
346
+ refusal: choice.message.refusal ?? ''
347
+ };
348
+ return this.buildGeneration(choice, metadata, request);
349
+ });
350
+ const usage = chatCompletion.usage;
351
+ const currentChatResponseUsage = usage ? Usage.from(usage.promptTokens, usage.completionTokens, usage.totalTokens) : Usage.createEmpty();
352
+ const accumulatedUsage = UsageUtil.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
353
+ return ChatResponse.from(generations, this.buildChatResponseMetadataByResult(chatCompletion, accumulatedUsage));
354
+ } catch (e) {
355
+ this.logger.error(e);
356
+ return ChatResponse.from([]);
357
+ }
358
+ }),
359
+ // When in stream mode and enabled to include the usage, the OpenAI
360
+ // Chat completion response would have the usage set only in its
361
+ // final response. Hence, the following overlapping buffer is
362
+ // created to store both the current and the subsequent response
363
+ // responses.
364
+ bufferCount(2, 1),
365
+ map(buffer => {
366
+ const firstResponse = buffer[0];
367
+ if (request.streamOptions?.includeUsage && buffer.length === 2) {
368
+ const secondResponse = buffer[1];
369
+ if (secondResponse?.metadata) {
370
+ // If the second response is not empty, then the first
371
+ // response is not the final response.
372
+ const usage = secondResponse.metadata.usage;
373
+ if (!UsageUtil.isEmpty(usage)) {
374
+ // Store the usage from the final response to the
375
+ // penultimate response for accumulation.
376
+ return ChatResponse.from(firstResponse.results, this.buildChatResponseMetadata(firstResponse.metadata, usage));
377
+ }
378
+ }
379
+ }
380
+ return firstResponse;
381
+ }),
382
+ mergeMap(async chatResponse2 => {
383
+ if (FunctionCallingOptions.isProxyToolCalls(prompt.options, this.defaultOptions)) {
384
+ if (ChatResponse.isToolCall(chatResponse2, new Set([ChatCompletionFinishReason.TOOL_CALLS, ChatCompletionFinishReason.STOP]))) {
385
+ const toolCallConversation = await this.toolHandler.handle(prompt, chatResponse2);
386
+ return this.internalStream(await this.promptTemplate.create(toolCallConversation, { chatOptions: prompt.options }), chatResponse2);
387
+ }
388
+ }
389
+ return of(chatResponse2);
390
+ }),
391
+ concatAll()
392
+ );
393
+ return chatResponse;
394
+ }
395
+
396
+ async stream(prompt: Prompt): Promise<Observable<ChatResponse>> {
397
+ return this.internalStream(prompt);
398
+ }
399
+ }