@langchain/openai 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat_models.cjs +489 -8
- package/dist/chat_models.d.ts +46 -5
- package/dist/chat_models.js +490 -9
- package/package.json +1 -1
package/dist/chat_models.cjs
CHANGED
|
@@ -97,6 +97,319 @@ function _convertMessagesToOpenAIParams(messages, model) {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
exports._convertMessagesToOpenAIParams = _convertMessagesToOpenAIParams;
|
|
100
|
+
const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
|
|
101
|
+
function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
102
|
+
return messages.flatMap((lcMsg) => {
|
|
103
|
+
let role = messageToOpenAIRole(lcMsg);
|
|
104
|
+
if (role === "system" && isReasoningModel(model))
|
|
105
|
+
role = "developer";
|
|
106
|
+
if (role === "function") {
|
|
107
|
+
throw new Error("Function messages are not supported in Responses API");
|
|
108
|
+
}
|
|
109
|
+
if (role === "tool") {
|
|
110
|
+
const toolMessage = lcMsg;
|
|
111
|
+
// Handle computer call output
|
|
112
|
+
if (toolMessage.additional_kwargs?.type === "computer_call_output") {
|
|
113
|
+
const output = (() => {
|
|
114
|
+
if (typeof toolMessage.content === "string") {
|
|
115
|
+
return {
|
|
116
|
+
type: "computer_screenshot",
|
|
117
|
+
image_url: toolMessage.content,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(toolMessage.content)) {
|
|
121
|
+
return toolMessage.content.find((i) => i.type === "computer_screenshot");
|
|
122
|
+
}
|
|
123
|
+
return toolMessage.content;
|
|
124
|
+
})();
|
|
125
|
+
return {
|
|
126
|
+
type: "computer_call_output",
|
|
127
|
+
output,
|
|
128
|
+
call_id: toolMessage.tool_call_id,
|
|
129
|
+
id: toolMessage.id,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
type: "function_call_output",
|
|
134
|
+
call_id: toolMessage.tool_call_id,
|
|
135
|
+
id: toolMessage.id,
|
|
136
|
+
output: typeof toolMessage.content !== "string"
|
|
137
|
+
? JSON.stringify(toolMessage.content)
|
|
138
|
+
: toolMessage.content,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (role === "assistant") {
|
|
142
|
+
const functionCallIds =
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
144
|
+
lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
|
|
145
|
+
if ((0, messages_1.isAIMessage)(lcMsg) && !!lcMsg.tool_calls?.length) {
|
|
146
|
+
return lcMsg.tool_calls.map((toolCall) => ({
|
|
147
|
+
type: "function_call",
|
|
148
|
+
name: toolCall.name,
|
|
149
|
+
arguments: JSON.stringify(toolCall.args),
|
|
150
|
+
call_id: toolCall.id,
|
|
151
|
+
// @ts-expect-error Might come from a non-Responses API message
|
|
152
|
+
id: functionCallIds?.[toolCall.id],
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
if (lcMsg.additional_kwargs.tool_calls != null) {
|
|
156
|
+
return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
|
|
157
|
+
type: "function_call",
|
|
158
|
+
name: toolCall.function.name,
|
|
159
|
+
call_id: toolCall.id,
|
|
160
|
+
// @ts-expect-error Might come from a non-Responses API message
|
|
161
|
+
id: functionCallIds?.[toolCall.id],
|
|
162
|
+
arguments: toolCall.function.arguments,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
let { content } = lcMsg;
|
|
166
|
+
if (lcMsg.additional_kwargs.refusal != null) {
|
|
167
|
+
if (typeof content === "string") {
|
|
168
|
+
content = [{ type: "output_text", text: content, annotations: [] }];
|
|
169
|
+
}
|
|
170
|
+
content = [
|
|
171
|
+
...content,
|
|
172
|
+
{ type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
type: "message",
|
|
177
|
+
role: "assistant",
|
|
178
|
+
content: typeof content === "string"
|
|
179
|
+
? content
|
|
180
|
+
: content.flatMap((item) => {
|
|
181
|
+
if (item.type === "text") {
|
|
182
|
+
return {
|
|
183
|
+
type: "output_text",
|
|
184
|
+
text: item.text,
|
|
185
|
+
// @ts-expect-error TODO: add types for `annotations`
|
|
186
|
+
annotations: item.annotations ?? [],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (item.type === "output_text" || item.type === "refusal") {
|
|
190
|
+
return item;
|
|
191
|
+
}
|
|
192
|
+
return [];
|
|
193
|
+
}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (role === "user") {
|
|
197
|
+
return {
|
|
198
|
+
type: "message",
|
|
199
|
+
role: "user",
|
|
200
|
+
content: typeof lcMsg.content === "string"
|
|
201
|
+
? lcMsg.content
|
|
202
|
+
: lcMsg.content.flatMap((item) => {
|
|
203
|
+
if (item.type === "text") {
|
|
204
|
+
return { type: "input_text", text: item.text };
|
|
205
|
+
}
|
|
206
|
+
if (item.type === "image_url") {
|
|
207
|
+
const image_url = typeof item.image_url === "string"
|
|
208
|
+
? item.image_url
|
|
209
|
+
: item.image_url.url;
|
|
210
|
+
const detail = typeof item.image_url === "string"
|
|
211
|
+
? "auto"
|
|
212
|
+
: item.image_url.detail;
|
|
213
|
+
return { type: "input_image", image_url, detail };
|
|
214
|
+
}
|
|
215
|
+
if (item.type === "input_text" ||
|
|
216
|
+
item.type === "input_image" ||
|
|
217
|
+
item.type === "input_file") {
|
|
218
|
+
return item;
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return [];
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function _convertOpenAIResponsesMessageToBaseMessage(response) {
|
|
228
|
+
if (response.error) {
|
|
229
|
+
// TODO: add support for `addLangChainErrorFields`
|
|
230
|
+
const error = new Error(response.error.message);
|
|
231
|
+
error.name = response.error.code;
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
const content = [];
|
|
235
|
+
const tool_calls = [];
|
|
236
|
+
const invalid_tool_calls = [];
|
|
237
|
+
const response_metadata = {
|
|
238
|
+
model: response.model,
|
|
239
|
+
created_at: response.created_at,
|
|
240
|
+
id: response.id,
|
|
241
|
+
incomplete_details: response.incomplete_details,
|
|
242
|
+
metadata: response.metadata,
|
|
243
|
+
object: response.object,
|
|
244
|
+
status: response.status,
|
|
245
|
+
user: response.user,
|
|
246
|
+
// for compatibility with chat completion calls.
|
|
247
|
+
model_name: response.model,
|
|
248
|
+
};
|
|
249
|
+
const additional_kwargs = {};
|
|
250
|
+
for (const item of response.output) {
|
|
251
|
+
if (item.type === "message") {
|
|
252
|
+
// TODO: how to handle refusals?
|
|
253
|
+
content.push(...item.content.map((part) => {
|
|
254
|
+
if (part.type === "output_text") {
|
|
255
|
+
if ("parsed" in part && part.parsed != null) {
|
|
256
|
+
additional_kwargs.parsed = part.parsed;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
type: "text",
|
|
260
|
+
text: part.text,
|
|
261
|
+
annotations: part.annotations,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return part;
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
else if (item.type === "function_call") {
|
|
268
|
+
const fnAdapter = {
|
|
269
|
+
function: { name: item.name, arguments: item.arguments },
|
|
270
|
+
id: item.call_id,
|
|
271
|
+
};
|
|
272
|
+
try {
|
|
273
|
+
tool_calls.push((0, openai_tools_1.parseToolCall)(fnAdapter, { returnId: true }));
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
let errMessage;
|
|
277
|
+
if (typeof e === "object" &&
|
|
278
|
+
e != null &&
|
|
279
|
+
"message" in e &&
|
|
280
|
+
typeof e.message === "string") {
|
|
281
|
+
errMessage = e.message;
|
|
282
|
+
}
|
|
283
|
+
invalid_tool_calls.push((0, openai_tools_1.makeInvalidToolCall)(fnAdapter, errMessage));
|
|
284
|
+
}
|
|
285
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] ??= {};
|
|
286
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY][item.call_id] = item.id;
|
|
287
|
+
}
|
|
288
|
+
else if (item.type === "reasoning") {
|
|
289
|
+
additional_kwargs.reasoning = item;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
additional_kwargs.tool_outputs ??= [];
|
|
293
|
+
additional_kwargs.tool_outputs.push(item);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return new messages_1.AIMessage({
|
|
297
|
+
id: response.id,
|
|
298
|
+
content,
|
|
299
|
+
tool_calls,
|
|
300
|
+
invalid_tool_calls,
|
|
301
|
+
usage_metadata: response.usage,
|
|
302
|
+
additional_kwargs,
|
|
303
|
+
response_metadata,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
|
|
307
|
+
const content = [];
|
|
308
|
+
let generationInfo = {};
|
|
309
|
+
let usage_metadata;
|
|
310
|
+
const tool_call_chunks = [];
|
|
311
|
+
const response_metadata = {};
|
|
312
|
+
const additional_kwargs = {};
|
|
313
|
+
let id;
|
|
314
|
+
if (chunk.type === "response.output_text.delta") {
|
|
315
|
+
content.push({
|
|
316
|
+
type: "text",
|
|
317
|
+
text: chunk.delta,
|
|
318
|
+
index: chunk.content_index,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
else if (chunk.type === "response.output_text.annotation.added") {
|
|
322
|
+
content.push({
|
|
323
|
+
type: "text",
|
|
324
|
+
text: "",
|
|
325
|
+
annotations: [chunk.annotation],
|
|
326
|
+
index: chunk.content_index,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
else if (chunk.type === "response.output_item.added" &&
|
|
330
|
+
chunk.item.type === "message") {
|
|
331
|
+
id = chunk.item.id;
|
|
332
|
+
}
|
|
333
|
+
else if (chunk.type === "response.output_item.added" &&
|
|
334
|
+
chunk.item.type === "function_call") {
|
|
335
|
+
tool_call_chunks.push({
|
|
336
|
+
type: "tool_call_chunk",
|
|
337
|
+
name: chunk.item.name,
|
|
338
|
+
args: chunk.item.arguments,
|
|
339
|
+
id: chunk.item.id,
|
|
340
|
+
index: chunk.output_index,
|
|
341
|
+
});
|
|
342
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
|
|
343
|
+
[chunk.item.call_id]: chunk.item.id,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
else if (chunk.type === "response.output_item.done" &&
|
|
347
|
+
(chunk.item.type === "web_search_call" ||
|
|
348
|
+
chunk.item.type === "file_search_call")) {
|
|
349
|
+
additional_kwargs.tool_outputs = [chunk.item];
|
|
350
|
+
}
|
|
351
|
+
else if (chunk.type === "response.created") {
|
|
352
|
+
response_metadata.id = chunk.response.id;
|
|
353
|
+
response_metadata.model_name = chunk.response.model;
|
|
354
|
+
response_metadata.model = chunk.response.model;
|
|
355
|
+
}
|
|
356
|
+
else if (chunk.type === "response.completed") {
|
|
357
|
+
const msg = _convertOpenAIResponsesMessageToBaseMessage(chunk.response);
|
|
358
|
+
usage_metadata = chunk.response.usage;
|
|
359
|
+
if (chunk.response.text?.format?.type === "json_schema") {
|
|
360
|
+
additional_kwargs.parsed ??= JSON.parse(msg.text);
|
|
361
|
+
}
|
|
362
|
+
for (const [key, value] of Object.entries(chunk.response)) {
|
|
363
|
+
if (key !== "id")
|
|
364
|
+
response_metadata[key] = value;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (chunk.type === "response.function_call_arguments.delta") {
|
|
368
|
+
tool_call_chunks.push({
|
|
369
|
+
type: "tool_call_chunk",
|
|
370
|
+
args: chunk.delta,
|
|
371
|
+
index: chunk.output_index,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (chunk.type === "response.web_search_call.completed" ||
|
|
375
|
+
chunk.type === "response.file_search_call.completed") {
|
|
376
|
+
generationInfo = {
|
|
377
|
+
tool_outputs: {
|
|
378
|
+
id: chunk.item_id,
|
|
379
|
+
type: chunk.type.replace("response.", "").replace(".completed", ""),
|
|
380
|
+
status: "completed",
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
else if (chunk.type === "response.refusal.done") {
|
|
385
|
+
additional_kwargs.refusal = chunk.refusal;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return new outputs_1.ChatGenerationChunk({
|
|
391
|
+
// Legacy reasons, `onLLMNewToken` should pulls this out
|
|
392
|
+
text: content.map((part) => part.text).join(""),
|
|
393
|
+
message: new messages_1.AIMessageChunk({
|
|
394
|
+
id,
|
|
395
|
+
content,
|
|
396
|
+
tool_call_chunks,
|
|
397
|
+
usage_metadata,
|
|
398
|
+
additional_kwargs,
|
|
399
|
+
response_metadata,
|
|
400
|
+
}),
|
|
401
|
+
generationInfo,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function isBuiltInTool(tool) {
|
|
405
|
+
return "type" in tool && tool.type !== "function";
|
|
406
|
+
}
|
|
407
|
+
function isBuiltInToolChoice(tool_choice) {
|
|
408
|
+
return (tool_choice != null &&
|
|
409
|
+
typeof tool_choice === "object" &&
|
|
410
|
+
"type" in tool_choice &&
|
|
411
|
+
tool_choice.type !== "function");
|
|
412
|
+
}
|
|
100
413
|
function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
|
|
101
414
|
if ((0, base_1.isOpenAITool)(tool)) {
|
|
102
415
|
if (fields?.strict !== undefined) {
|
|
@@ -904,6 +1217,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
904
1217
|
writable: true,
|
|
905
1218
|
value: void 0
|
|
906
1219
|
});
|
|
1220
|
+
Object.defineProperty(this, "useResponsesApi", {
|
|
1221
|
+
enumerable: true,
|
|
1222
|
+
configurable: true,
|
|
1223
|
+
writable: true,
|
|
1224
|
+
value: false
|
|
1225
|
+
});
|
|
907
1226
|
this.openAIApiKey =
|
|
908
1227
|
fields?.apiKey ??
|
|
909
1228
|
fields?.openAIApiKey ??
|
|
@@ -933,6 +1252,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
933
1252
|
this.modalities = fields?.modalities;
|
|
934
1253
|
this.reasoningEffort = fields?.reasoningEffort;
|
|
935
1254
|
this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
|
|
1255
|
+
this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
|
|
936
1256
|
if (this.model === "o1") {
|
|
937
1257
|
this.disableStreaming = true;
|
|
938
1258
|
}
|
|
@@ -970,7 +1290,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
970
1290
|
strict = this.supportsStrictToolCalling;
|
|
971
1291
|
}
|
|
972
1292
|
return this.bind({
|
|
973
|
-
tools: tools.map((tool) =>
|
|
1293
|
+
tools: tools.map((tool) => isBuiltInTool(tool)
|
|
1294
|
+
? tool
|
|
1295
|
+
: _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
|
|
974
1296
|
...kwargs,
|
|
975
1297
|
});
|
|
976
1298
|
}
|
|
@@ -996,6 +1318,80 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
996
1318
|
else if (this.supportsStrictToolCalling !== undefined) {
|
|
997
1319
|
strict = this.supportsStrictToolCalling;
|
|
998
1320
|
}
|
|
1321
|
+
if (this._useResponseApi(options)) {
|
|
1322
|
+
const params = {
|
|
1323
|
+
model: this.model,
|
|
1324
|
+
temperature: this.temperature,
|
|
1325
|
+
top_p: this.topP,
|
|
1326
|
+
user: this.user,
|
|
1327
|
+
// if include_usage is set or streamUsage then stream must be set to true.
|
|
1328
|
+
stream: this.streaming,
|
|
1329
|
+
previous_response_id: options?.previous_response_id,
|
|
1330
|
+
truncation: options?.truncation,
|
|
1331
|
+
include: options?.include,
|
|
1332
|
+
tools: options?.tools?.length
|
|
1333
|
+
? options.tools
|
|
1334
|
+
.map((tool) => {
|
|
1335
|
+
if (isBuiltInTool(tool)) {
|
|
1336
|
+
return tool;
|
|
1337
|
+
}
|
|
1338
|
+
else if ((0, base_1.isOpenAITool)(tool)) {
|
|
1339
|
+
return {
|
|
1340
|
+
type: "function",
|
|
1341
|
+
name: tool.function.name,
|
|
1342
|
+
parameters: tool.function.parameters,
|
|
1343
|
+
description: tool.function.description,
|
|
1344
|
+
strict,
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
})
|
|
1351
|
+
.filter((tool) => tool !== null)
|
|
1352
|
+
: undefined,
|
|
1353
|
+
tool_choice: isBuiltInToolChoice(options?.tool_choice)
|
|
1354
|
+
? options?.tool_choice
|
|
1355
|
+
: (() => {
|
|
1356
|
+
const formatted = (0, openai_js_1.formatToOpenAIToolChoice)(options?.tool_choice);
|
|
1357
|
+
if (typeof formatted === "object" && "type" in formatted) {
|
|
1358
|
+
return { type: "function", name: formatted.function.name };
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
return undefined;
|
|
1362
|
+
}
|
|
1363
|
+
})(),
|
|
1364
|
+
text: (() => {
|
|
1365
|
+
if (options?.text)
|
|
1366
|
+
return options.text;
|
|
1367
|
+
const format = this.createResponseFormat(options?.response_format);
|
|
1368
|
+
if (format?.type === "json_schema") {
|
|
1369
|
+
if (format.json_schema.schema != null) {
|
|
1370
|
+
return {
|
|
1371
|
+
format: {
|
|
1372
|
+
type: "json_schema",
|
|
1373
|
+
schema: format.json_schema.schema,
|
|
1374
|
+
description: format.json_schema.description,
|
|
1375
|
+
name: format.json_schema.name,
|
|
1376
|
+
strict: format.json_schema.strict,
|
|
1377
|
+
},
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
return undefined;
|
|
1381
|
+
}
|
|
1382
|
+
return { format };
|
|
1383
|
+
})(),
|
|
1384
|
+
parallel_tool_calls: options?.parallel_tool_calls,
|
|
1385
|
+
max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
|
|
1386
|
+
...this.modelKwargs,
|
|
1387
|
+
};
|
|
1388
|
+
const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
|
|
1389
|
+
if (reasoningEffort !== undefined) {
|
|
1390
|
+
params.reasoning = { effort: reasoningEffort };
|
|
1391
|
+
}
|
|
1392
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1393
|
+
return params;
|
|
1394
|
+
}
|
|
999
1395
|
let streamOptionsConfig = {};
|
|
1000
1396
|
if (options?.stream_options !== undefined) {
|
|
1001
1397
|
streamOptionsConfig = { stream_options: options.stream_options };
|
|
@@ -1049,6 +1445,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1049
1445
|
else {
|
|
1050
1446
|
params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
|
|
1051
1447
|
}
|
|
1448
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1052
1449
|
return params;
|
|
1053
1450
|
}
|
|
1054
1451
|
_convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
|
|
@@ -1192,6 +1589,20 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1192
1589
|
};
|
|
1193
1590
|
}
|
|
1194
1591
|
async *_streamResponseChunks(messages, options, runManager) {
|
|
1592
|
+
if (this._useResponseApi(options)) {
|
|
1593
|
+
const streamIterable = await this.responseApiWithRetry({
|
|
1594
|
+
...this.invocationParams(options, { streaming: true }),
|
|
1595
|
+
input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
|
|
1596
|
+
stream: true,
|
|
1597
|
+
}, options);
|
|
1598
|
+
for await (const data of streamIterable) {
|
|
1599
|
+
const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
|
|
1600
|
+
if (chunk == null)
|
|
1601
|
+
continue;
|
|
1602
|
+
yield chunk;
|
|
1603
|
+
}
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1195
1606
|
const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
|
|
1196
1607
|
const params = {
|
|
1197
1608
|
...this.invocationParams(options, {
|
|
@@ -1296,7 +1707,60 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1296
1707
|
return this._identifyingParams();
|
|
1297
1708
|
}
|
|
1298
1709
|
/** @ignore */
|
|
1710
|
+
async _responseApiGenerate(messages, options, runManager) {
|
|
1711
|
+
const invocationParams = this.invocationParams(options);
|
|
1712
|
+
if (invocationParams.stream) {
|
|
1713
|
+
const stream = this._streamResponseChunks(messages, options, runManager);
|
|
1714
|
+
let finalChunk;
|
|
1715
|
+
for await (const chunk of stream) {
|
|
1716
|
+
chunk.message.response_metadata = {
|
|
1717
|
+
...chunk.generationInfo,
|
|
1718
|
+
...chunk.message.response_metadata,
|
|
1719
|
+
};
|
|
1720
|
+
finalChunk = finalChunk?.concat(chunk) ?? chunk;
|
|
1721
|
+
}
|
|
1722
|
+
return {
|
|
1723
|
+
generations: finalChunk ? [finalChunk] : [],
|
|
1724
|
+
llmOutput: {
|
|
1725
|
+
estimatedTokenUsage: finalChunk?.message
|
|
1726
|
+
?.usage_metadata,
|
|
1727
|
+
},
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
|
|
1731
|
+
const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
|
|
1732
|
+
return {
|
|
1733
|
+
generations: [
|
|
1734
|
+
{
|
|
1735
|
+
text: data.output_text,
|
|
1736
|
+
message: _convertOpenAIResponsesMessageToBaseMessage(data),
|
|
1737
|
+
},
|
|
1738
|
+
],
|
|
1739
|
+
llmOutput: {
|
|
1740
|
+
id: data.id,
|
|
1741
|
+
estimatedTokenUsage: data.usage
|
|
1742
|
+
? {
|
|
1743
|
+
promptTokens: data.usage.input_tokens,
|
|
1744
|
+
completionTokens: data.usage.output_tokens,
|
|
1745
|
+
totalTokens: data.usage.total_tokens,
|
|
1746
|
+
}
|
|
1747
|
+
: undefined,
|
|
1748
|
+
},
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
_useResponseApi(options) {
|
|
1752
|
+
const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
|
|
1753
|
+
const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
|
|
1754
|
+
options?.text != null ||
|
|
1755
|
+
options?.truncation != null ||
|
|
1756
|
+
options?.include != null;
|
|
1757
|
+
return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
|
|
1758
|
+
}
|
|
1759
|
+
/** @ignore */
|
|
1299
1760
|
async _generate(messages, options, runManager) {
|
|
1761
|
+
if (this._useResponseApi(options)) {
|
|
1762
|
+
return this._responseApiGenerate(messages, options, runManager);
|
|
1763
|
+
}
|
|
1300
1764
|
const usageMetadata = {};
|
|
1301
1765
|
const params = this.invocationParams(options);
|
|
1302
1766
|
const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
|
|
@@ -1534,6 +1998,22 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1534
1998
|
}
|
|
1535
1999
|
});
|
|
1536
2000
|
}
|
|
2001
|
+
async responseApiWithRetry(request, options) {
|
|
2002
|
+
return this.caller.call(async () => {
|
|
2003
|
+
const requestOptions = this._getClientOptions(options);
|
|
2004
|
+
try {
|
|
2005
|
+
// use parse if dealing with json_schema
|
|
2006
|
+
if (request.text?.format?.type === "json_schema" && !request.stream) {
|
|
2007
|
+
return await this.client.responses.parse(request, requestOptions);
|
|
2008
|
+
}
|
|
2009
|
+
return await this.client.responses.create(request, requestOptions);
|
|
2010
|
+
}
|
|
2011
|
+
catch (e) {
|
|
2012
|
+
const error = (0, openai_js_1.wrapOpenAIClientError)(e);
|
|
2013
|
+
throw error;
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
1537
2017
|
/**
|
|
1538
2018
|
* Call the beta chat completions parse endpoint. This should only be called if
|
|
1539
2019
|
* response_format is set to "json_object".
|
|
@@ -1656,7 +2136,13 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1656
2136
|
},
|
|
1657
2137
|
});
|
|
1658
2138
|
if (isZodSchema(schema)) {
|
|
1659
|
-
|
|
2139
|
+
const altParser = output_parsers_1.StructuredOutputParser.fromZodSchema(schema);
|
|
2140
|
+
outputParser = runnables_1.RunnableLambda.from((aiMessage) => {
|
|
2141
|
+
if ("parsed" in aiMessage.additional_kwargs) {
|
|
2142
|
+
return aiMessage.additional_kwargs.parsed;
|
|
2143
|
+
}
|
|
2144
|
+
return altParser;
|
|
2145
|
+
});
|
|
1660
2146
|
}
|
|
1661
2147
|
else {
|
|
1662
2148
|
outputParser = new output_parsers_1.JsonOutputParser();
|
|
@@ -1744,12 +2230,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1744
2230
|
const parsedWithFallback = parserAssign.withFallbacks({
|
|
1745
2231
|
fallbacks: [parserNone],
|
|
1746
2232
|
});
|
|
1747
|
-
return runnables_1.RunnableSequence.from([
|
|
1748
|
-
{
|
|
1749
|
-
raw: llm,
|
|
1750
|
-
},
|
|
1751
|
-
parsedWithFallback,
|
|
1752
|
-
]);
|
|
2233
|
+
return runnables_1.RunnableSequence.from([{ raw: llm }, parsedWithFallback]);
|
|
1753
2234
|
}
|
|
1754
2235
|
}
|
|
1755
2236
|
exports.ChatOpenAI = ChatOpenAI;
|
package/dist/chat_models.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ClientOptions, OpenAI as OpenAIClient } from "openai";
|
|
|
2
2
|
import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
|
|
3
3
|
import { AIMessageChunk, type BaseMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, SystemMessageChunk, ToolMessageChunk } from "@langchain/core/messages";
|
|
4
4
|
import { ChatGenerationChunk, type ChatResult } from "@langchain/core/outputs";
|
|
5
|
-
import { BaseChatModel, BindToolsInput, LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
|
|
5
|
+
import { BaseChatModel, type BindToolsInput, type LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
|
|
6
6
|
import { type BaseFunctionCallOptions, type BaseLanguageModelInput, type StructuredOutputMethodOptions } from "@langchain/core/language_models/base";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { Runnable } from "@langchain/core/runnables";
|
|
@@ -21,7 +21,21 @@ type OpenAIRoleEnum = "system" | "developer" | "assistant" | "user" | "function"
|
|
|
21
21
|
type OpenAICompletionParam = OpenAIClient.Chat.Completions.ChatCompletionMessageParam;
|
|
22
22
|
export declare function messageToOpenAIRole(message: BaseMessage): OpenAIRoleEnum;
|
|
23
23
|
export declare function _convertMessagesToOpenAIParams(messages: BaseMessage[], model?: string): OpenAICompletionParam[];
|
|
24
|
-
type
|
|
24
|
+
type ExcludeController<T> = T extends {
|
|
25
|
+
controller: unknown;
|
|
26
|
+
} ? never : T;
|
|
27
|
+
type ExcludeNonController<T> = T extends {
|
|
28
|
+
controller: unknown;
|
|
29
|
+
} ? T : never;
|
|
30
|
+
type ResponsesCreate = OpenAIClient.Responses["create"];
|
|
31
|
+
type ResponsesCreateParams = Parameters<OpenAIClient.Responses["create"]>[0];
|
|
32
|
+
type ResponsesTool = Exclude<ResponsesCreateParams["tools"], undefined>[number];
|
|
33
|
+
type ResponsesToolChoice = Exclude<ResponsesCreateParams["tool_choice"], undefined>;
|
|
34
|
+
type ResponsesCreateInvoke = ExcludeController<Awaited<ReturnType<ResponsesCreate>>>;
|
|
35
|
+
type ResponsesCreateStream = ExcludeNonController<Awaited<ReturnType<ResponsesCreate>>>;
|
|
36
|
+
type ResponseInvocationParams = Omit<ResponsesCreateParams, "input">;
|
|
37
|
+
type ChatCompletionInvocationParams = Omit<OpenAIClient.Chat.ChatCompletionCreateParams, "messages">;
|
|
38
|
+
type ChatOpenAIToolType = BindToolsInput | OpenAIClient.ChatCompletionTool | ResponsesTool;
|
|
25
39
|
export interface ChatOpenAIStructuredOutputMethodOptions<IncludeRaw extends boolean> extends StructuredOutputMethodOptions<IncludeRaw> {
|
|
26
40
|
/**
|
|
27
41
|
* strict: If `true` and `method` = "function_calling", model output is
|
|
@@ -41,7 +55,7 @@ export interface ChatOpenAIStructuredOutputMethodOptions<IncludeRaw extends bool
|
|
|
41
55
|
}
|
|
42
56
|
export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCallOptions {
|
|
43
57
|
tools?: ChatOpenAIToolType[];
|
|
44
|
-
tool_choice?: OpenAIToolChoice;
|
|
58
|
+
tool_choice?: OpenAIToolChoice | ResponsesToolChoice;
|
|
45
59
|
promptIndex?: number;
|
|
46
60
|
response_format?: ChatOpenAIResponseFormat;
|
|
47
61
|
seed?: number;
|
|
@@ -105,9 +119,28 @@ export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCa
|
|
|
105
119
|
* Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
|
|
106
120
|
*/
|
|
107
121
|
reasoning_effort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
|
|
122
|
+
/**
|
|
123
|
+
* Configuration options for a text response from the model. Can be plain text or
|
|
124
|
+
* structured JSON data.
|
|
125
|
+
*/
|
|
126
|
+
text?: ResponsesCreateParams["text"];
|
|
127
|
+
/**
|
|
128
|
+
* The truncation strategy to use for the model response.
|
|
129
|
+
*/
|
|
130
|
+
truncation?: ResponsesCreateParams["truncation"];
|
|
131
|
+
/**
|
|
132
|
+
* Specify additional output data to include in the model response.
|
|
133
|
+
*/
|
|
134
|
+
include?: ResponsesCreateParams["include"];
|
|
135
|
+
/**
|
|
136
|
+
* The unique ID of the previous response to the model. Use this to create
|
|
137
|
+
* multi-turn conversations.
|
|
138
|
+
*/
|
|
139
|
+
previous_response_id?: ResponsesCreateParams["previous_response_id"];
|
|
108
140
|
}
|
|
109
141
|
export interface ChatOpenAIFields extends Partial<OpenAIChatInput>, BaseChatModelParams {
|
|
110
142
|
configuration?: ClientOptions;
|
|
143
|
+
useResponsesApi?: boolean;
|
|
111
144
|
}
|
|
112
145
|
/**
|
|
113
146
|
* OpenAI chat model integration.
|
|
@@ -689,6 +722,7 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
689
722
|
audio?: OpenAIClient.Chat.ChatCompletionAudioParam;
|
|
690
723
|
modalities?: Array<OpenAIClient.Chat.ChatCompletionModality>;
|
|
691
724
|
reasoningEffort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
|
|
725
|
+
useResponsesApi: boolean;
|
|
692
726
|
constructor(fields?: ChatOpenAIFields);
|
|
693
727
|
getLsParams(options: this["ParsedCallOptions"]): LangSmithParams;
|
|
694
728
|
bindTools(tools: ChatOpenAIToolType[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
|
|
@@ -696,9 +730,9 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
696
730
|
/**
|
|
697
731
|
* Get the parameters used to invoke the model
|
|
698
732
|
*/
|
|
699
|
-
invocationParams(options?: this["ParsedCallOptions"], extra?: {
|
|
733
|
+
invocationParams<Type extends "responses" | "completion" = "completion">(options?: this["ParsedCallOptions"], extra?: {
|
|
700
734
|
streaming?: boolean;
|
|
701
|
-
}):
|
|
735
|
+
}): Type extends "responses" ? ResponseInvocationParams : ChatCompletionInvocationParams;
|
|
702
736
|
protected _convertOpenAIChatCompletionMessageToBaseMessage(message: OpenAIClient.Chat.Completions.ChatCompletionMessage, rawResponse: OpenAIClient.Chat.Completions.ChatCompletion): BaseMessage;
|
|
703
737
|
protected _convertOpenAIDeltaToBaseMessageChunk(delta: Record<string, any>, rawResponse: OpenAIClient.Chat.Completions.ChatCompletionChunk, defaultRole?: OpenAIRoleEnum): AIMessageChunk | HumanMessageChunk | SystemMessageChunk | FunctionMessageChunk | ToolMessageChunk | ChatMessageChunk;
|
|
704
738
|
/** @ignore */
|
|
@@ -714,6 +748,9 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
714
748
|
model_name: string;
|
|
715
749
|
} & ClientOptions;
|
|
716
750
|
/** @ignore */
|
|
751
|
+
_responseApiGenerate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
|
|
752
|
+
protected _useResponseApi(options: this["ParsedCallOptions"] | undefined): boolean;
|
|
753
|
+
/** @ignore */
|
|
717
754
|
_generate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
|
|
718
755
|
/**
|
|
719
756
|
* Estimate the number of tokens a prompt will use.
|
|
@@ -736,6 +773,10 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
736
773
|
*/
|
|
737
774
|
completionWithRetry(request: OpenAIClient.Chat.ChatCompletionCreateParamsStreaming, options?: OpenAICoreRequestOptions): Promise<AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>>;
|
|
738
775
|
completionWithRetry(request: OpenAIClient.Chat.ChatCompletionCreateParamsNonStreaming, options?: OpenAICoreRequestOptions): Promise<OpenAIClient.Chat.Completions.ChatCompletion>;
|
|
776
|
+
protected responseApiWithRetry(request: ResponsesCreateParams & {
|
|
777
|
+
stream: true;
|
|
778
|
+
}, options?: OpenAICoreRequestOptions): Promise<ResponsesCreateStream>;
|
|
779
|
+
protected responseApiWithRetry(request: ResponsesCreateParams, options?: OpenAICoreRequestOptions): Promise<ResponsesCreateInvoke>;
|
|
739
780
|
/**
|
|
740
781
|
* Call the beta chat completions parse endpoint. This should only be called if
|
|
741
782
|
* response_format is set to "json_object".
|
package/dist/chat_models.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ChatGenerationChunk, } from "@langchain/core/outputs";
|
|
|
4
4
|
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
|
5
5
|
import { BaseChatModel, } from "@langchain/core/language_models/chat_models";
|
|
6
6
|
import { isOpenAITool, } from "@langchain/core/language_models/base";
|
|
7
|
-
import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
|
|
7
|
+
import { RunnableLambda, RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
|
|
8
8
|
import { JsonOutputParser, StructuredOutputParser, } from "@langchain/core/output_parsers";
|
|
9
9
|
import { JsonOutputKeyToolsParser, convertLangChainToolCallToOpenAI, makeInvalidToolCall, parseToolCall, } from "@langchain/core/output_parsers/openai_tools";
|
|
10
10
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
@@ -92,6 +92,319 @@ export function _convertMessagesToOpenAIParams(messages, model) {
|
|
|
92
92
|
return completionParam;
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
+
const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
|
|
96
|
+
function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
97
|
+
return messages.flatMap((lcMsg) => {
|
|
98
|
+
let role = messageToOpenAIRole(lcMsg);
|
|
99
|
+
if (role === "system" && isReasoningModel(model))
|
|
100
|
+
role = "developer";
|
|
101
|
+
if (role === "function") {
|
|
102
|
+
throw new Error("Function messages are not supported in Responses API");
|
|
103
|
+
}
|
|
104
|
+
if (role === "tool") {
|
|
105
|
+
const toolMessage = lcMsg;
|
|
106
|
+
// Handle computer call output
|
|
107
|
+
if (toolMessage.additional_kwargs?.type === "computer_call_output") {
|
|
108
|
+
const output = (() => {
|
|
109
|
+
if (typeof toolMessage.content === "string") {
|
|
110
|
+
return {
|
|
111
|
+
type: "computer_screenshot",
|
|
112
|
+
image_url: toolMessage.content,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(toolMessage.content)) {
|
|
116
|
+
return toolMessage.content.find((i) => i.type === "computer_screenshot");
|
|
117
|
+
}
|
|
118
|
+
return toolMessage.content;
|
|
119
|
+
})();
|
|
120
|
+
return {
|
|
121
|
+
type: "computer_call_output",
|
|
122
|
+
output,
|
|
123
|
+
call_id: toolMessage.tool_call_id,
|
|
124
|
+
id: toolMessage.id,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
type: "function_call_output",
|
|
129
|
+
call_id: toolMessage.tool_call_id,
|
|
130
|
+
id: toolMessage.id,
|
|
131
|
+
output: typeof toolMessage.content !== "string"
|
|
132
|
+
? JSON.stringify(toolMessage.content)
|
|
133
|
+
: toolMessage.content,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (role === "assistant") {
|
|
137
|
+
const functionCallIds =
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
139
|
+
lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
|
|
140
|
+
if (isAIMessage(lcMsg) && !!lcMsg.tool_calls?.length) {
|
|
141
|
+
return lcMsg.tool_calls.map((toolCall) => ({
|
|
142
|
+
type: "function_call",
|
|
143
|
+
name: toolCall.name,
|
|
144
|
+
arguments: JSON.stringify(toolCall.args),
|
|
145
|
+
call_id: toolCall.id,
|
|
146
|
+
// @ts-expect-error Might come from a non-Responses API message
|
|
147
|
+
id: functionCallIds?.[toolCall.id],
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
if (lcMsg.additional_kwargs.tool_calls != null) {
|
|
151
|
+
return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
|
|
152
|
+
type: "function_call",
|
|
153
|
+
name: toolCall.function.name,
|
|
154
|
+
call_id: toolCall.id,
|
|
155
|
+
// @ts-expect-error Might come from a non-Responses API message
|
|
156
|
+
id: functionCallIds?.[toolCall.id],
|
|
157
|
+
arguments: toolCall.function.arguments,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
let { content } = lcMsg;
|
|
161
|
+
if (lcMsg.additional_kwargs.refusal != null) {
|
|
162
|
+
if (typeof content === "string") {
|
|
163
|
+
content = [{ type: "output_text", text: content, annotations: [] }];
|
|
164
|
+
}
|
|
165
|
+
content = [
|
|
166
|
+
...content,
|
|
167
|
+
{ type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
type: "message",
|
|
172
|
+
role: "assistant",
|
|
173
|
+
content: typeof content === "string"
|
|
174
|
+
? content
|
|
175
|
+
: content.flatMap((item) => {
|
|
176
|
+
if (item.type === "text") {
|
|
177
|
+
return {
|
|
178
|
+
type: "output_text",
|
|
179
|
+
text: item.text,
|
|
180
|
+
// @ts-expect-error TODO: add types for `annotations`
|
|
181
|
+
annotations: item.annotations ?? [],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (item.type === "output_text" || item.type === "refusal") {
|
|
185
|
+
return item;
|
|
186
|
+
}
|
|
187
|
+
return [];
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (role === "user") {
|
|
192
|
+
return {
|
|
193
|
+
type: "message",
|
|
194
|
+
role: "user",
|
|
195
|
+
content: typeof lcMsg.content === "string"
|
|
196
|
+
? lcMsg.content
|
|
197
|
+
: lcMsg.content.flatMap((item) => {
|
|
198
|
+
if (item.type === "text") {
|
|
199
|
+
return { type: "input_text", text: item.text };
|
|
200
|
+
}
|
|
201
|
+
if (item.type === "image_url") {
|
|
202
|
+
const image_url = typeof item.image_url === "string"
|
|
203
|
+
? item.image_url
|
|
204
|
+
: item.image_url.url;
|
|
205
|
+
const detail = typeof item.image_url === "string"
|
|
206
|
+
? "auto"
|
|
207
|
+
: item.image_url.detail;
|
|
208
|
+
return { type: "input_image", image_url, detail };
|
|
209
|
+
}
|
|
210
|
+
if (item.type === "input_text" ||
|
|
211
|
+
item.type === "input_image" ||
|
|
212
|
+
item.type === "input_file") {
|
|
213
|
+
return item;
|
|
214
|
+
}
|
|
215
|
+
return [];
|
|
216
|
+
}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return [];
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function _convertOpenAIResponsesMessageToBaseMessage(response) {
|
|
223
|
+
if (response.error) {
|
|
224
|
+
// TODO: add support for `addLangChainErrorFields`
|
|
225
|
+
const error = new Error(response.error.message);
|
|
226
|
+
error.name = response.error.code;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
const content = [];
|
|
230
|
+
const tool_calls = [];
|
|
231
|
+
const invalid_tool_calls = [];
|
|
232
|
+
const response_metadata = {
|
|
233
|
+
model: response.model,
|
|
234
|
+
created_at: response.created_at,
|
|
235
|
+
id: response.id,
|
|
236
|
+
incomplete_details: response.incomplete_details,
|
|
237
|
+
metadata: response.metadata,
|
|
238
|
+
object: response.object,
|
|
239
|
+
status: response.status,
|
|
240
|
+
user: response.user,
|
|
241
|
+
// for compatibility with chat completion calls.
|
|
242
|
+
model_name: response.model,
|
|
243
|
+
};
|
|
244
|
+
const additional_kwargs = {};
|
|
245
|
+
for (const item of response.output) {
|
|
246
|
+
if (item.type === "message") {
|
|
247
|
+
// TODO: how to handle refusals?
|
|
248
|
+
content.push(...item.content.map((part) => {
|
|
249
|
+
if (part.type === "output_text") {
|
|
250
|
+
if ("parsed" in part && part.parsed != null) {
|
|
251
|
+
additional_kwargs.parsed = part.parsed;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
type: "text",
|
|
255
|
+
text: part.text,
|
|
256
|
+
annotations: part.annotations,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return part;
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
else if (item.type === "function_call") {
|
|
263
|
+
const fnAdapter = {
|
|
264
|
+
function: { name: item.name, arguments: item.arguments },
|
|
265
|
+
id: item.call_id,
|
|
266
|
+
};
|
|
267
|
+
try {
|
|
268
|
+
tool_calls.push(parseToolCall(fnAdapter, { returnId: true }));
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
let errMessage;
|
|
272
|
+
if (typeof e === "object" &&
|
|
273
|
+
e != null &&
|
|
274
|
+
"message" in e &&
|
|
275
|
+
typeof e.message === "string") {
|
|
276
|
+
errMessage = e.message;
|
|
277
|
+
}
|
|
278
|
+
invalid_tool_calls.push(makeInvalidToolCall(fnAdapter, errMessage));
|
|
279
|
+
}
|
|
280
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] ??= {};
|
|
281
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY][item.call_id] = item.id;
|
|
282
|
+
}
|
|
283
|
+
else if (item.type === "reasoning") {
|
|
284
|
+
additional_kwargs.reasoning = item;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
additional_kwargs.tool_outputs ??= [];
|
|
288
|
+
additional_kwargs.tool_outputs.push(item);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return new AIMessage({
|
|
292
|
+
id: response.id,
|
|
293
|
+
content,
|
|
294
|
+
tool_calls,
|
|
295
|
+
invalid_tool_calls,
|
|
296
|
+
usage_metadata: response.usage,
|
|
297
|
+
additional_kwargs,
|
|
298
|
+
response_metadata,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
|
|
302
|
+
const content = [];
|
|
303
|
+
let generationInfo = {};
|
|
304
|
+
let usage_metadata;
|
|
305
|
+
const tool_call_chunks = [];
|
|
306
|
+
const response_metadata = {};
|
|
307
|
+
const additional_kwargs = {};
|
|
308
|
+
let id;
|
|
309
|
+
if (chunk.type === "response.output_text.delta") {
|
|
310
|
+
content.push({
|
|
311
|
+
type: "text",
|
|
312
|
+
text: chunk.delta,
|
|
313
|
+
index: chunk.content_index,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
else if (chunk.type === "response.output_text.annotation.added") {
|
|
317
|
+
content.push({
|
|
318
|
+
type: "text",
|
|
319
|
+
text: "",
|
|
320
|
+
annotations: [chunk.annotation],
|
|
321
|
+
index: chunk.content_index,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
else if (chunk.type === "response.output_item.added" &&
|
|
325
|
+
chunk.item.type === "message") {
|
|
326
|
+
id = chunk.item.id;
|
|
327
|
+
}
|
|
328
|
+
else if (chunk.type === "response.output_item.added" &&
|
|
329
|
+
chunk.item.type === "function_call") {
|
|
330
|
+
tool_call_chunks.push({
|
|
331
|
+
type: "tool_call_chunk",
|
|
332
|
+
name: chunk.item.name,
|
|
333
|
+
args: chunk.item.arguments,
|
|
334
|
+
id: chunk.item.id,
|
|
335
|
+
index: chunk.output_index,
|
|
336
|
+
});
|
|
337
|
+
additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
|
|
338
|
+
[chunk.item.call_id]: chunk.item.id,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
else if (chunk.type === "response.output_item.done" &&
|
|
342
|
+
(chunk.item.type === "web_search_call" ||
|
|
343
|
+
chunk.item.type === "file_search_call")) {
|
|
344
|
+
additional_kwargs.tool_outputs = [chunk.item];
|
|
345
|
+
}
|
|
346
|
+
else if (chunk.type === "response.created") {
|
|
347
|
+
response_metadata.id = chunk.response.id;
|
|
348
|
+
response_metadata.model_name = chunk.response.model;
|
|
349
|
+
response_metadata.model = chunk.response.model;
|
|
350
|
+
}
|
|
351
|
+
else if (chunk.type === "response.completed") {
|
|
352
|
+
const msg = _convertOpenAIResponsesMessageToBaseMessage(chunk.response);
|
|
353
|
+
usage_metadata = chunk.response.usage;
|
|
354
|
+
if (chunk.response.text?.format?.type === "json_schema") {
|
|
355
|
+
additional_kwargs.parsed ??= JSON.parse(msg.text);
|
|
356
|
+
}
|
|
357
|
+
for (const [key, value] of Object.entries(chunk.response)) {
|
|
358
|
+
if (key !== "id")
|
|
359
|
+
response_metadata[key] = value;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else if (chunk.type === "response.function_call_arguments.delta") {
|
|
363
|
+
tool_call_chunks.push({
|
|
364
|
+
type: "tool_call_chunk",
|
|
365
|
+
args: chunk.delta,
|
|
366
|
+
index: chunk.output_index,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
else if (chunk.type === "response.web_search_call.completed" ||
|
|
370
|
+
chunk.type === "response.file_search_call.completed") {
|
|
371
|
+
generationInfo = {
|
|
372
|
+
tool_outputs: {
|
|
373
|
+
id: chunk.item_id,
|
|
374
|
+
type: chunk.type.replace("response.", "").replace(".completed", ""),
|
|
375
|
+
status: "completed",
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
else if (chunk.type === "response.refusal.done") {
|
|
380
|
+
additional_kwargs.refusal = chunk.refusal;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
return new ChatGenerationChunk({
|
|
386
|
+
// Legacy reasons, `onLLMNewToken` should pulls this out
|
|
387
|
+
text: content.map((part) => part.text).join(""),
|
|
388
|
+
message: new AIMessageChunk({
|
|
389
|
+
id,
|
|
390
|
+
content,
|
|
391
|
+
tool_call_chunks,
|
|
392
|
+
usage_metadata,
|
|
393
|
+
additional_kwargs,
|
|
394
|
+
response_metadata,
|
|
395
|
+
}),
|
|
396
|
+
generationInfo,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function isBuiltInTool(tool) {
|
|
400
|
+
return "type" in tool && tool.type !== "function";
|
|
401
|
+
}
|
|
402
|
+
function isBuiltInToolChoice(tool_choice) {
|
|
403
|
+
return (tool_choice != null &&
|
|
404
|
+
typeof tool_choice === "object" &&
|
|
405
|
+
"type" in tool_choice &&
|
|
406
|
+
tool_choice.type !== "function");
|
|
407
|
+
}
|
|
95
408
|
function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
|
|
96
409
|
if (isOpenAITool(tool)) {
|
|
97
410
|
if (fields?.strict !== undefined) {
|
|
@@ -899,6 +1212,12 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
899
1212
|
writable: true,
|
|
900
1213
|
value: void 0
|
|
901
1214
|
});
|
|
1215
|
+
Object.defineProperty(this, "useResponsesApi", {
|
|
1216
|
+
enumerable: true,
|
|
1217
|
+
configurable: true,
|
|
1218
|
+
writable: true,
|
|
1219
|
+
value: false
|
|
1220
|
+
});
|
|
902
1221
|
this.openAIApiKey =
|
|
903
1222
|
fields?.apiKey ??
|
|
904
1223
|
fields?.openAIApiKey ??
|
|
@@ -928,6 +1247,7 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
928
1247
|
this.modalities = fields?.modalities;
|
|
929
1248
|
this.reasoningEffort = fields?.reasoningEffort;
|
|
930
1249
|
this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
|
|
1250
|
+
this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
|
|
931
1251
|
if (this.model === "o1") {
|
|
932
1252
|
this.disableStreaming = true;
|
|
933
1253
|
}
|
|
@@ -965,7 +1285,9 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
965
1285
|
strict = this.supportsStrictToolCalling;
|
|
966
1286
|
}
|
|
967
1287
|
return this.bind({
|
|
968
|
-
tools: tools.map((tool) =>
|
|
1288
|
+
tools: tools.map((tool) => isBuiltInTool(tool)
|
|
1289
|
+
? tool
|
|
1290
|
+
: _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
|
|
969
1291
|
...kwargs,
|
|
970
1292
|
});
|
|
971
1293
|
}
|
|
@@ -991,6 +1313,80 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
991
1313
|
else if (this.supportsStrictToolCalling !== undefined) {
|
|
992
1314
|
strict = this.supportsStrictToolCalling;
|
|
993
1315
|
}
|
|
1316
|
+
if (this._useResponseApi(options)) {
|
|
1317
|
+
const params = {
|
|
1318
|
+
model: this.model,
|
|
1319
|
+
temperature: this.temperature,
|
|
1320
|
+
top_p: this.topP,
|
|
1321
|
+
user: this.user,
|
|
1322
|
+
// if include_usage is set or streamUsage then stream must be set to true.
|
|
1323
|
+
stream: this.streaming,
|
|
1324
|
+
previous_response_id: options?.previous_response_id,
|
|
1325
|
+
truncation: options?.truncation,
|
|
1326
|
+
include: options?.include,
|
|
1327
|
+
tools: options?.tools?.length
|
|
1328
|
+
? options.tools
|
|
1329
|
+
.map((tool) => {
|
|
1330
|
+
if (isBuiltInTool(tool)) {
|
|
1331
|
+
return tool;
|
|
1332
|
+
}
|
|
1333
|
+
else if (isOpenAITool(tool)) {
|
|
1334
|
+
return {
|
|
1335
|
+
type: "function",
|
|
1336
|
+
name: tool.function.name,
|
|
1337
|
+
parameters: tool.function.parameters,
|
|
1338
|
+
description: tool.function.description,
|
|
1339
|
+
strict,
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
else {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
})
|
|
1346
|
+
.filter((tool) => tool !== null)
|
|
1347
|
+
: undefined,
|
|
1348
|
+
tool_choice: isBuiltInToolChoice(options?.tool_choice)
|
|
1349
|
+
? options?.tool_choice
|
|
1350
|
+
: (() => {
|
|
1351
|
+
const formatted = formatToOpenAIToolChoice(options?.tool_choice);
|
|
1352
|
+
if (typeof formatted === "object" && "type" in formatted) {
|
|
1353
|
+
return { type: "function", name: formatted.function.name };
|
|
1354
|
+
}
|
|
1355
|
+
else {
|
|
1356
|
+
return undefined;
|
|
1357
|
+
}
|
|
1358
|
+
})(),
|
|
1359
|
+
text: (() => {
|
|
1360
|
+
if (options?.text)
|
|
1361
|
+
return options.text;
|
|
1362
|
+
const format = this.createResponseFormat(options?.response_format);
|
|
1363
|
+
if (format?.type === "json_schema") {
|
|
1364
|
+
if (format.json_schema.schema != null) {
|
|
1365
|
+
return {
|
|
1366
|
+
format: {
|
|
1367
|
+
type: "json_schema",
|
|
1368
|
+
schema: format.json_schema.schema,
|
|
1369
|
+
description: format.json_schema.description,
|
|
1370
|
+
name: format.json_schema.name,
|
|
1371
|
+
strict: format.json_schema.strict,
|
|
1372
|
+
},
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
return undefined;
|
|
1376
|
+
}
|
|
1377
|
+
return { format };
|
|
1378
|
+
})(),
|
|
1379
|
+
parallel_tool_calls: options?.parallel_tool_calls,
|
|
1380
|
+
max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
|
|
1381
|
+
...this.modelKwargs,
|
|
1382
|
+
};
|
|
1383
|
+
const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
|
|
1384
|
+
if (reasoningEffort !== undefined) {
|
|
1385
|
+
params.reasoning = { effort: reasoningEffort };
|
|
1386
|
+
}
|
|
1387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1388
|
+
return params;
|
|
1389
|
+
}
|
|
994
1390
|
let streamOptionsConfig = {};
|
|
995
1391
|
if (options?.stream_options !== undefined) {
|
|
996
1392
|
streamOptionsConfig = { stream_options: options.stream_options };
|
|
@@ -1044,6 +1440,7 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1044
1440
|
else {
|
|
1045
1441
|
params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
|
|
1046
1442
|
}
|
|
1443
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1047
1444
|
return params;
|
|
1048
1445
|
}
|
|
1049
1446
|
_convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
|
|
@@ -1187,6 +1584,20 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1187
1584
|
};
|
|
1188
1585
|
}
|
|
1189
1586
|
async *_streamResponseChunks(messages, options, runManager) {
|
|
1587
|
+
if (this._useResponseApi(options)) {
|
|
1588
|
+
const streamIterable = await this.responseApiWithRetry({
|
|
1589
|
+
...this.invocationParams(options, { streaming: true }),
|
|
1590
|
+
input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
|
|
1591
|
+
stream: true,
|
|
1592
|
+
}, options);
|
|
1593
|
+
for await (const data of streamIterable) {
|
|
1594
|
+
const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
|
|
1595
|
+
if (chunk == null)
|
|
1596
|
+
continue;
|
|
1597
|
+
yield chunk;
|
|
1598
|
+
}
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1190
1601
|
const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
|
|
1191
1602
|
const params = {
|
|
1192
1603
|
...this.invocationParams(options, {
|
|
@@ -1291,7 +1702,60 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1291
1702
|
return this._identifyingParams();
|
|
1292
1703
|
}
|
|
1293
1704
|
/** @ignore */
|
|
1705
|
+
async _responseApiGenerate(messages, options, runManager) {
|
|
1706
|
+
const invocationParams = this.invocationParams(options);
|
|
1707
|
+
if (invocationParams.stream) {
|
|
1708
|
+
const stream = this._streamResponseChunks(messages, options, runManager);
|
|
1709
|
+
let finalChunk;
|
|
1710
|
+
for await (const chunk of stream) {
|
|
1711
|
+
chunk.message.response_metadata = {
|
|
1712
|
+
...chunk.generationInfo,
|
|
1713
|
+
...chunk.message.response_metadata,
|
|
1714
|
+
};
|
|
1715
|
+
finalChunk = finalChunk?.concat(chunk) ?? chunk;
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
generations: finalChunk ? [finalChunk] : [],
|
|
1719
|
+
llmOutput: {
|
|
1720
|
+
estimatedTokenUsage: finalChunk?.message
|
|
1721
|
+
?.usage_metadata,
|
|
1722
|
+
},
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
|
|
1726
|
+
const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
|
|
1727
|
+
return {
|
|
1728
|
+
generations: [
|
|
1729
|
+
{
|
|
1730
|
+
text: data.output_text,
|
|
1731
|
+
message: _convertOpenAIResponsesMessageToBaseMessage(data),
|
|
1732
|
+
},
|
|
1733
|
+
],
|
|
1734
|
+
llmOutput: {
|
|
1735
|
+
id: data.id,
|
|
1736
|
+
estimatedTokenUsage: data.usage
|
|
1737
|
+
? {
|
|
1738
|
+
promptTokens: data.usage.input_tokens,
|
|
1739
|
+
completionTokens: data.usage.output_tokens,
|
|
1740
|
+
totalTokens: data.usage.total_tokens,
|
|
1741
|
+
}
|
|
1742
|
+
: undefined,
|
|
1743
|
+
},
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
_useResponseApi(options) {
|
|
1747
|
+
const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
|
|
1748
|
+
const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
|
|
1749
|
+
options?.text != null ||
|
|
1750
|
+
options?.truncation != null ||
|
|
1751
|
+
options?.include != null;
|
|
1752
|
+
return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
|
|
1753
|
+
}
|
|
1754
|
+
/** @ignore */
|
|
1294
1755
|
async _generate(messages, options, runManager) {
|
|
1756
|
+
if (this._useResponseApi(options)) {
|
|
1757
|
+
return this._responseApiGenerate(messages, options, runManager);
|
|
1758
|
+
}
|
|
1295
1759
|
const usageMetadata = {};
|
|
1296
1760
|
const params = this.invocationParams(options);
|
|
1297
1761
|
const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
|
|
@@ -1529,6 +1993,22 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1529
1993
|
}
|
|
1530
1994
|
});
|
|
1531
1995
|
}
|
|
1996
|
+
async responseApiWithRetry(request, options) {
|
|
1997
|
+
return this.caller.call(async () => {
|
|
1998
|
+
const requestOptions = this._getClientOptions(options);
|
|
1999
|
+
try {
|
|
2000
|
+
// use parse if dealing with json_schema
|
|
2001
|
+
if (request.text?.format?.type === "json_schema" && !request.stream) {
|
|
2002
|
+
return await this.client.responses.parse(request, requestOptions);
|
|
2003
|
+
}
|
|
2004
|
+
return await this.client.responses.create(request, requestOptions);
|
|
2005
|
+
}
|
|
2006
|
+
catch (e) {
|
|
2007
|
+
const error = wrapOpenAIClientError(e);
|
|
2008
|
+
throw error;
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
1532
2012
|
/**
|
|
1533
2013
|
* Call the beta chat completions parse endpoint. This should only be called if
|
|
1534
2014
|
* response_format is set to "json_object".
|
|
@@ -1651,7 +2131,13 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1651
2131
|
},
|
|
1652
2132
|
});
|
|
1653
2133
|
if (isZodSchema(schema)) {
|
|
1654
|
-
|
|
2134
|
+
const altParser = StructuredOutputParser.fromZodSchema(schema);
|
|
2135
|
+
outputParser = RunnableLambda.from((aiMessage) => {
|
|
2136
|
+
if ("parsed" in aiMessage.additional_kwargs) {
|
|
2137
|
+
return aiMessage.additional_kwargs.parsed;
|
|
2138
|
+
}
|
|
2139
|
+
return altParser;
|
|
2140
|
+
});
|
|
1655
2141
|
}
|
|
1656
2142
|
else {
|
|
1657
2143
|
outputParser = new JsonOutputParser();
|
|
@@ -1739,12 +2225,7 @@ export class ChatOpenAI extends BaseChatModel {
|
|
|
1739
2225
|
const parsedWithFallback = parserAssign.withFallbacks({
|
|
1740
2226
|
fallbacks: [parserNone],
|
|
1741
2227
|
});
|
|
1742
|
-
return RunnableSequence.from([
|
|
1743
|
-
{
|
|
1744
|
-
raw: llm,
|
|
1745
|
-
},
|
|
1746
|
-
parsedWithFallback,
|
|
1747
|
-
]);
|
|
2228
|
+
return RunnableSequence.from([{ raw: llm }, parsedWithFallback]);
|
|
1748
2229
|
}
|
|
1749
2230
|
}
|
|
1750
2231
|
function isZodSchema(
|