@langchain/openai 0.5.6 → 0.5.8
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 +326 -27
- package/dist/chat_models.d.ts +61 -3
- package/dist/chat_models.js +327 -28
- package/dist/tools/dalle.cjs +13 -10
- package/dist/tools/dalle.js +13 -10
- package/dist/types.d.ts +29 -0
- package/package.json +3 -3
package/dist/chat_models.cjs
CHANGED
|
@@ -50,6 +50,132 @@ function messageToOpenAIRole(message) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
exports.messageToOpenAIRole = messageToOpenAIRole;
|
|
53
|
+
const completionsApiContentBlockConverter = {
|
|
54
|
+
providerName: "ChatOpenAI",
|
|
55
|
+
fromStandardTextBlock(block) {
|
|
56
|
+
return { type: "text", text: block.text };
|
|
57
|
+
},
|
|
58
|
+
fromStandardImageBlock(block) {
|
|
59
|
+
if (block.source_type === "url") {
|
|
60
|
+
return {
|
|
61
|
+
type: "image_url",
|
|
62
|
+
image_url: {
|
|
63
|
+
url: block.url,
|
|
64
|
+
...(block.metadata?.detail
|
|
65
|
+
? { detail: block.metadata.detail }
|
|
66
|
+
: {}),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (block.source_type === "base64") {
|
|
71
|
+
const url = `data:${block.mime_type ?? ""};base64,${block.data}`;
|
|
72
|
+
return {
|
|
73
|
+
type: "image_url",
|
|
74
|
+
image_url: {
|
|
75
|
+
url,
|
|
76
|
+
...(block.metadata?.detail
|
|
77
|
+
? { detail: block.metadata.detail }
|
|
78
|
+
: {}),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Image content blocks with source_type ${block.source_type} are not supported for ChatOpenAI`);
|
|
83
|
+
},
|
|
84
|
+
fromStandardAudioBlock(block) {
|
|
85
|
+
if (block.source_type === "url") {
|
|
86
|
+
const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
|
|
87
|
+
if (!data) {
|
|
88
|
+
throw new Error(`URL audio blocks with source_type ${block.source_type} must be formatted as a data URL for ChatOpenAI`);
|
|
89
|
+
}
|
|
90
|
+
const rawMimeType = data.mime_type || block.mime_type || "";
|
|
91
|
+
let mimeType;
|
|
92
|
+
try {
|
|
93
|
+
mimeType = (0, messages_1.parseMimeType)(rawMimeType);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new Error(`Audio blocks with source_type ${block.source_type} must have mime type of audio/wav or audio/mp3`);
|
|
97
|
+
}
|
|
98
|
+
if (mimeType.type !== "audio" ||
|
|
99
|
+
(mimeType.subtype !== "wav" && mimeType.subtype !== "mp3")) {
|
|
100
|
+
throw new Error(`Audio blocks with source_type ${block.source_type} must have mime type of audio/wav or audio/mp3`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
type: "input_audio",
|
|
104
|
+
input_audio: {
|
|
105
|
+
format: mimeType.subtype,
|
|
106
|
+
data: data.data,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (block.source_type === "base64") {
|
|
111
|
+
let mimeType;
|
|
112
|
+
try {
|
|
113
|
+
mimeType = (0, messages_1.parseMimeType)(block.mime_type ?? "");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new Error(`Audio blocks with source_type ${block.source_type} must have mime type of audio/wav or audio/mp3`);
|
|
117
|
+
}
|
|
118
|
+
if (mimeType.type !== "audio" ||
|
|
119
|
+
(mimeType.subtype !== "wav" && mimeType.subtype !== "mp3")) {
|
|
120
|
+
throw new Error(`Audio blocks with source_type ${block.source_type} must have mime type of audio/wav or audio/mp3`);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
type: "input_audio",
|
|
124
|
+
input_audio: {
|
|
125
|
+
format: mimeType.subtype,
|
|
126
|
+
data: block.data,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`Audio content blocks with source_type ${block.source_type} are not supported for ChatOpenAI`);
|
|
131
|
+
},
|
|
132
|
+
fromStandardFileBlock(block) {
|
|
133
|
+
if (block.source_type === "url") {
|
|
134
|
+
const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
|
|
135
|
+
if (!data) {
|
|
136
|
+
throw new Error(`URL file blocks with source_type ${block.source_type} must be formatted as a data URL for ChatOpenAI`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
type: "file",
|
|
140
|
+
file: {
|
|
141
|
+
file_data: block.url,
|
|
142
|
+
...(block.metadata?.filename || block.metadata?.name
|
|
143
|
+
? {
|
|
144
|
+
filename: (block.metadata?.filename ||
|
|
145
|
+
block.metadata?.name),
|
|
146
|
+
}
|
|
147
|
+
: {}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (block.source_type === "base64") {
|
|
152
|
+
return {
|
|
153
|
+
type: "file",
|
|
154
|
+
file: {
|
|
155
|
+
file_data: `data:${block.mime_type ?? ""};base64,${block.data}`,
|
|
156
|
+
...(block.metadata?.filename ||
|
|
157
|
+
block.metadata?.name ||
|
|
158
|
+
block.metadata?.title
|
|
159
|
+
? {
|
|
160
|
+
filename: (block.metadata?.filename ||
|
|
161
|
+
block.metadata?.name ||
|
|
162
|
+
block.metadata?.title),
|
|
163
|
+
}
|
|
164
|
+
: {}),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (block.source_type === "id") {
|
|
169
|
+
return {
|
|
170
|
+
type: "file",
|
|
171
|
+
file: {
|
|
172
|
+
file_id: block.id,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
throw new Error(`File content blocks with source_type ${block.source_type} are not supported for ChatOpenAI`);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
53
179
|
// Used in LangSmith, export is important here
|
|
54
180
|
function _convertMessagesToOpenAIParams(messages, model) {
|
|
55
181
|
// TODO: Function messages do not support array content, fix cast
|
|
@@ -58,10 +184,18 @@ function _convertMessagesToOpenAIParams(messages, model) {
|
|
|
58
184
|
if (role === "system" && isReasoningModel(model)) {
|
|
59
185
|
role = "developer";
|
|
60
186
|
}
|
|
187
|
+
const content = typeof message.content === "string"
|
|
188
|
+
? message.content
|
|
189
|
+
: message.content.map((m) => {
|
|
190
|
+
if ((0, messages_1.isDataContentBlock)(m)) {
|
|
191
|
+
return (0, messages_1.convertToProviderContentBlock)(m, completionsApiContentBlockConverter);
|
|
192
|
+
}
|
|
193
|
+
return m;
|
|
194
|
+
});
|
|
61
195
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
196
|
const completionParam = {
|
|
63
197
|
role,
|
|
64
|
-
content
|
|
198
|
+
content,
|
|
65
199
|
};
|
|
66
200
|
if (message.name != null) {
|
|
67
201
|
completionParam.name = message.name;
|
|
@@ -98,8 +232,32 @@ function _convertMessagesToOpenAIParams(messages, model) {
|
|
|
98
232
|
}
|
|
99
233
|
exports._convertMessagesToOpenAIParams = _convertMessagesToOpenAIParams;
|
|
100
234
|
const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
|
|
101
|
-
function
|
|
102
|
-
|
|
235
|
+
function _convertReasoningSummaryToOpenAIResponsesParams(reasoning) {
|
|
236
|
+
// combine summary parts that have the the same index and then remove the indexes
|
|
237
|
+
const summary = (reasoning.summary.length > 1
|
|
238
|
+
? reasoning.summary.reduce((acc, curr) => {
|
|
239
|
+
const last = acc.at(-1);
|
|
240
|
+
if (last.index === curr.index) {
|
|
241
|
+
last.text += curr.text;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
acc.push(curr);
|
|
245
|
+
}
|
|
246
|
+
return acc;
|
|
247
|
+
}, [{ ...reasoning.summary[0] }])
|
|
248
|
+
: reasoning.summary).map((s) => Object.fromEntries(Object.entries(s).filter(([k]) => k !== "index")));
|
|
249
|
+
return {
|
|
250
|
+
...reasoning,
|
|
251
|
+
summary,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function _convertMessagesToOpenAIResponsesParams(messages, model, zdrEnabled) {
|
|
255
|
+
const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
|
|
256
|
+
const lastAIMessageId = lastAIMessage?.response_metadata?.id;
|
|
257
|
+
const newMessages = lastAIMessageId && lastAIMessageId.startsWith("resp_") && !zdrEnabled
|
|
258
|
+
? messages.slice(messages.indexOf(lastAIMessage) + 1)
|
|
259
|
+
: messages;
|
|
260
|
+
return newMessages.flatMap((lcMsg) => {
|
|
103
261
|
let role = messageToOpenAIRole(lcMsg);
|
|
104
262
|
if (role === "system" && isReasoningModel(model))
|
|
105
263
|
role = "developer";
|
|
@@ -149,15 +307,25 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
|
149
307
|
};
|
|
150
308
|
}
|
|
151
309
|
if (role === "assistant") {
|
|
310
|
+
// if we have the original response items, just reuse them
|
|
311
|
+
if (!zdrEnabled &&
|
|
312
|
+
lcMsg.response_metadata.output != null &&
|
|
313
|
+
Array.isArray(lcMsg.response_metadata.output) &&
|
|
314
|
+
lcMsg.response_metadata.output.length > 0 &&
|
|
315
|
+
lcMsg.response_metadata.output.every((item) => "type" in item)) {
|
|
316
|
+
return lcMsg.response_metadata.output;
|
|
317
|
+
}
|
|
318
|
+
// otherwise, try to reconstruct the response from what we have
|
|
152
319
|
const input = [];
|
|
153
320
|
// reasoning items
|
|
154
|
-
if (lcMsg.additional_kwargs.reasoning != null) {
|
|
321
|
+
if (!zdrEnabled && lcMsg.additional_kwargs.reasoning != null) {
|
|
155
322
|
const isReasoningItem = (item) => typeof item === "object" &&
|
|
156
323
|
item != null &&
|
|
157
324
|
"type" in item &&
|
|
158
325
|
item.type === "reasoning";
|
|
159
326
|
if (isReasoningItem(lcMsg.additional_kwargs.reasoning)) {
|
|
160
|
-
|
|
327
|
+
const reasoningItem = _convertReasoningSummaryToOpenAIResponsesParams(lcMsg.additional_kwargs.reasoning);
|
|
328
|
+
input.push(reasoningItem);
|
|
161
329
|
}
|
|
162
330
|
}
|
|
163
331
|
// ai content
|
|
@@ -174,6 +342,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
|
174
342
|
input.push({
|
|
175
343
|
type: "message",
|
|
176
344
|
role: "assistant",
|
|
345
|
+
...(lcMsg.id && !zdrEnabled ? { id: lcMsg.id } : {}),
|
|
177
346
|
content: typeof content === "string"
|
|
178
347
|
? content
|
|
179
348
|
: content.flatMap((item) => {
|
|
@@ -201,7 +370,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
|
201
370
|
name: toolCall.name,
|
|
202
371
|
arguments: JSON.stringify(toolCall.args),
|
|
203
372
|
call_id: toolCall.id,
|
|
204
|
-
id: functionCallIds?.[toolCall.id],
|
|
373
|
+
...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
|
|
205
374
|
})));
|
|
206
375
|
}
|
|
207
376
|
else if (lcMsg.additional_kwargs.tool_calls != null) {
|
|
@@ -209,21 +378,17 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
|
209
378
|
type: "function_call",
|
|
210
379
|
name: toolCall.function.name,
|
|
211
380
|
call_id: toolCall.id,
|
|
212
|
-
id: functionCallIds?.[toolCall.id],
|
|
381
|
+
...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
|
|
213
382
|
arguments: toolCall.function.arguments,
|
|
214
383
|
})));
|
|
215
384
|
}
|
|
216
385
|
const toolOutputs = lcMsg.response_metadata.output?.length
|
|
217
386
|
? lcMsg.response_metadata.output
|
|
218
387
|
: lcMsg.additional_kwargs.tool_outputs;
|
|
388
|
+
let computerCalls = [];
|
|
219
389
|
if (toolOutputs != null) {
|
|
220
390
|
const castToolOutputs = toolOutputs;
|
|
221
|
-
|
|
222
|
-
const computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
|
|
223
|
-
// NOTE: Reasoning outputs must be passed to the model BEFORE computer calls.
|
|
224
|
-
if (reasoningCalls.length > 0 && computerCalls.length > 0) {
|
|
225
|
-
input.push(...reasoningCalls);
|
|
226
|
-
}
|
|
391
|
+
computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
|
|
227
392
|
if (computerCalls.length > 0)
|
|
228
393
|
input.push(...computerCalls);
|
|
229
394
|
}
|
|
@@ -232,6 +397,9 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
|
|
|
232
397
|
const content = typeof lcMsg.content === "string"
|
|
233
398
|
? lcMsg.content
|
|
234
399
|
: lcMsg.content.flatMap((item) => {
|
|
400
|
+
if ((0, messages_1.isDataContentBlock)(item)) {
|
|
401
|
+
return (0, messages_1.convertToProviderContentBlock)(item, completionsApiContentBlockConverter);
|
|
402
|
+
}
|
|
235
403
|
if (item.type === "text") {
|
|
236
404
|
return { type: "input_text", text: item.text };
|
|
237
405
|
}
|
|
@@ -424,6 +592,39 @@ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
|
|
|
424
592
|
else if (chunk.type === "response.refusal.done") {
|
|
425
593
|
additional_kwargs.refusal = chunk.refusal;
|
|
426
594
|
}
|
|
595
|
+
else if (chunk.type === "response.output_item.added" &&
|
|
596
|
+
"item" in chunk &&
|
|
597
|
+
chunk.item.type === "reasoning") {
|
|
598
|
+
const summary = chunk
|
|
599
|
+
.item.summary
|
|
600
|
+
? chunk.item.summary.map((s, index) => ({
|
|
601
|
+
...s,
|
|
602
|
+
index,
|
|
603
|
+
}))
|
|
604
|
+
: undefined;
|
|
605
|
+
additional_kwargs.reasoning = {
|
|
606
|
+
// We only capture ID in the first chunk or else the concatenated result of all chunks will
|
|
607
|
+
// have an ID field that is repeated once per chunk. There is special handling for the `type`
|
|
608
|
+
// field that prevents this, however.
|
|
609
|
+
id: chunk.item.id,
|
|
610
|
+
type: chunk.item.type,
|
|
611
|
+
...(summary ? { summary } : {}),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
else if (chunk.type === "response.reasoning_summary_part.added") {
|
|
615
|
+
additional_kwargs.reasoning = {
|
|
616
|
+
type: "reasoning",
|
|
617
|
+
summary: [{ ...chunk.part, index: chunk.summary_index }],
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
else if (chunk.type === "response.reasoning_summary_text.delta") {
|
|
621
|
+
additional_kwargs.reasoning = {
|
|
622
|
+
type: "reasoning",
|
|
623
|
+
summary: [
|
|
624
|
+
{ text: chunk.delta, type: "summary_text", index: chunk.summary_index },
|
|
625
|
+
],
|
|
626
|
+
};
|
|
627
|
+
}
|
|
427
628
|
else {
|
|
428
629
|
return null;
|
|
429
630
|
}
|
|
@@ -466,9 +667,7 @@ function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
|
|
|
466
667
|
return (0, tools_js_1._convertToOpenAITool)(tool, fields);
|
|
467
668
|
}
|
|
468
669
|
function isReasoningModel(model) {
|
|
469
|
-
return (model
|
|
470
|
-
model?.startsWith("o3") ||
|
|
471
|
-
model?.startsWith("o4"));
|
|
670
|
+
return model && /^o\d/.test(model);
|
|
472
671
|
}
|
|
473
672
|
/**
|
|
474
673
|
* OpenAI chat model integration.
|
|
@@ -1076,6 +1275,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1076
1275
|
"tags",
|
|
1077
1276
|
"metadata",
|
|
1078
1277
|
"disableStreaming",
|
|
1278
|
+
"useResponsesApi",
|
|
1279
|
+
"zdrEnabled",
|
|
1280
|
+
"reasoning",
|
|
1079
1281
|
];
|
|
1080
1282
|
}
|
|
1081
1283
|
constructor(fields) {
|
|
@@ -1253,18 +1455,51 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1253
1455
|
writable: true,
|
|
1254
1456
|
value: void 0
|
|
1255
1457
|
});
|
|
1458
|
+
/**
|
|
1459
|
+
* @deprecated Use {@link reasoning} object instead.
|
|
1460
|
+
*/
|
|
1256
1461
|
Object.defineProperty(this, "reasoningEffort", {
|
|
1257
1462
|
enumerable: true,
|
|
1258
1463
|
configurable: true,
|
|
1259
1464
|
writable: true,
|
|
1260
1465
|
value: void 0
|
|
1261
1466
|
});
|
|
1467
|
+
/**
|
|
1468
|
+
* Options for reasoning models.
|
|
1469
|
+
*/
|
|
1470
|
+
Object.defineProperty(this, "reasoning", {
|
|
1471
|
+
enumerable: true,
|
|
1472
|
+
configurable: true,
|
|
1473
|
+
writable: true,
|
|
1474
|
+
value: void 0
|
|
1475
|
+
});
|
|
1476
|
+
/**
|
|
1477
|
+
* Whether to use the responses API for all requests. If `false` the responses API will be used
|
|
1478
|
+
* only when required in order to fulfill the request.
|
|
1479
|
+
*/
|
|
1262
1480
|
Object.defineProperty(this, "useResponsesApi", {
|
|
1263
1481
|
enumerable: true,
|
|
1264
1482
|
configurable: true,
|
|
1265
1483
|
writable: true,
|
|
1266
1484
|
value: false
|
|
1267
1485
|
});
|
|
1486
|
+
/**
|
|
1487
|
+
* Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable
|
|
1488
|
+
* output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your
|
|
1489
|
+
* OpenAI organization or project. This must be configured directly with OpenAI.
|
|
1490
|
+
*
|
|
1491
|
+
* See:
|
|
1492
|
+
* https://help.openai.com/en/articles/10503543-data-residency-for-the-openai-api
|
|
1493
|
+
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-store
|
|
1494
|
+
*
|
|
1495
|
+
* @default false
|
|
1496
|
+
*/
|
|
1497
|
+
Object.defineProperty(this, "zdrEnabled", {
|
|
1498
|
+
enumerable: true,
|
|
1499
|
+
configurable: true,
|
|
1500
|
+
writable: true,
|
|
1501
|
+
value: void 0
|
|
1502
|
+
});
|
|
1268
1503
|
this.openAIApiKey =
|
|
1269
1504
|
fields?.apiKey ??
|
|
1270
1505
|
fields?.openAIApiKey ??
|
|
@@ -1292,7 +1527,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1292
1527
|
this.__includeRawResponse = fields?.__includeRawResponse;
|
|
1293
1528
|
this.audio = fields?.audio;
|
|
1294
1529
|
this.modalities = fields?.modalities;
|
|
1295
|
-
this.reasoningEffort = fields?.reasoningEffort;
|
|
1530
|
+
this.reasoningEffort = fields?.reasoningEffort ?? fields?.reasoning?.effort;
|
|
1531
|
+
this.reasoning =
|
|
1532
|
+
fields?.reasoning ??
|
|
1533
|
+
(fields?.reasoningEffort
|
|
1534
|
+
? { effort: fields.reasoningEffort }
|
|
1535
|
+
: undefined);
|
|
1296
1536
|
this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
|
|
1297
1537
|
this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
|
|
1298
1538
|
this.disableStreaming = fields?.disableStreaming ?? this.disableStreaming;
|
|
@@ -1315,6 +1555,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1315
1555
|
if (fields?.supportsStrictToolCalling !== undefined) {
|
|
1316
1556
|
this.supportsStrictToolCalling = fields.supportsStrictToolCalling;
|
|
1317
1557
|
}
|
|
1558
|
+
this.zdrEnabled = fields?.zdrEnabled ?? false;
|
|
1318
1559
|
}
|
|
1319
1560
|
getLsParams(options) {
|
|
1320
1561
|
const params = this.invocationParams(options);
|
|
@@ -1353,6 +1594,35 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1353
1594
|
}
|
|
1354
1595
|
return resFormat;
|
|
1355
1596
|
}
|
|
1597
|
+
getReasoningParams(options) {
|
|
1598
|
+
if (!isReasoningModel(this.model)) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
let reasoning;
|
|
1602
|
+
// apply reasoning options in reverse order of priority without else blocks so that highest priority overrides lowest priority
|
|
1603
|
+
if (this.reasoningEffort !== undefined) {
|
|
1604
|
+
reasoning = { effort: this.reasoningEffort };
|
|
1605
|
+
}
|
|
1606
|
+
if (this.reasoning !== undefined) {
|
|
1607
|
+
reasoning = {
|
|
1608
|
+
...reasoning,
|
|
1609
|
+
...this.reasoning,
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
if (options?.reasoning_effort !== undefined) {
|
|
1613
|
+
reasoning = {
|
|
1614
|
+
...reasoning,
|
|
1615
|
+
effort: options.reasoning_effort,
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
if (options?.reasoning !== undefined) {
|
|
1619
|
+
reasoning = {
|
|
1620
|
+
...reasoning,
|
|
1621
|
+
...options.reasoning,
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
return reasoning;
|
|
1625
|
+
}
|
|
1356
1626
|
/**
|
|
1357
1627
|
* Get the parameters used to invoke the model
|
|
1358
1628
|
*/
|
|
@@ -1429,11 +1699,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1429
1699
|
})(),
|
|
1430
1700
|
parallel_tool_calls: options?.parallel_tool_calls,
|
|
1431
1701
|
max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
|
|
1702
|
+
...(this.zdrEnabled ? { store: false } : {}),
|
|
1432
1703
|
...this.modelKwargs,
|
|
1433
1704
|
};
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
params.reasoning =
|
|
1705
|
+
const reasoning = this.getReasoningParams(options);
|
|
1706
|
+
if (reasoning !== undefined) {
|
|
1707
|
+
params.reasoning = reasoning;
|
|
1437
1708
|
}
|
|
1438
1709
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1439
1710
|
return params;
|
|
@@ -1480,9 +1751,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1480
1751
|
if (options?.prediction !== undefined) {
|
|
1481
1752
|
params.prediction = options.prediction;
|
|
1482
1753
|
}
|
|
1483
|
-
const
|
|
1484
|
-
if (
|
|
1485
|
-
params.reasoning_effort =
|
|
1754
|
+
const reasoning = this.getReasoningParams(options);
|
|
1755
|
+
if (reasoning !== undefined && reasoning.effort !== undefined) {
|
|
1756
|
+
params.reasoning_effort = reasoning.effort;
|
|
1486
1757
|
}
|
|
1487
1758
|
if (isReasoningModel(params.model)) {
|
|
1488
1759
|
params.max_completion_tokens =
|
|
@@ -1636,10 +1907,17 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1636
1907
|
}
|
|
1637
1908
|
async *_streamResponseChunks(messages, options, runManager) {
|
|
1638
1909
|
if (this._useResponseApi(options)) {
|
|
1910
|
+
const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
|
|
1911
|
+
const lastAIMessageId = lastAIMessage?.response_metadata?.id;
|
|
1639
1912
|
const streamIterable = await this.responseApiWithRetry({
|
|
1640
1913
|
...this.invocationParams(options, { streaming: true }),
|
|
1641
|
-
input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
|
|
1914
|
+
input: _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled),
|
|
1642
1915
|
stream: true,
|
|
1916
|
+
...(lastAIMessageId &&
|
|
1917
|
+
lastAIMessageId.startsWith("resp_") &&
|
|
1918
|
+
!this.zdrEnabled
|
|
1919
|
+
? { previous_response_id: lastAIMessageId }
|
|
1920
|
+
: {}),
|
|
1643
1921
|
}, options);
|
|
1644
1922
|
for await (const data of streamIterable) {
|
|
1645
1923
|
const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
|
|
@@ -1773,8 +2051,18 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1773
2051
|
},
|
|
1774
2052
|
};
|
|
1775
2053
|
}
|
|
1776
|
-
const
|
|
1777
|
-
const
|
|
2054
|
+
const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
|
|
2055
|
+
const lastAIMessageId = lastAIMessage?.response_metadata?.id;
|
|
2056
|
+
const input = _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled);
|
|
2057
|
+
const data = await this.responseApiWithRetry({
|
|
2058
|
+
input,
|
|
2059
|
+
...invocationParams,
|
|
2060
|
+
...(lastAIMessageId &&
|
|
2061
|
+
lastAIMessageId.startsWith("resp_") &&
|
|
2062
|
+
!this.zdrEnabled
|
|
2063
|
+
? { previous_response_id: lastAIMessageId }
|
|
2064
|
+
: {}),
|
|
2065
|
+
}, { signal: options?.signal, ...options?.options });
|
|
1778
2066
|
return {
|
|
1779
2067
|
generations: [
|
|
1780
2068
|
{
|
|
@@ -1794,12 +2082,23 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
|
|
|
1794
2082
|
},
|
|
1795
2083
|
};
|
|
1796
2084
|
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Determines whether the responses API should be used for the given request.
|
|
2087
|
+
*
|
|
2088
|
+
* @internal
|
|
2089
|
+
*
|
|
2090
|
+
* @param options The parsed call options for the request.
|
|
2091
|
+
* @returns `true` if the responses API should be used, either because it is explicitly enabled,
|
|
2092
|
+
* or because the request requires it.
|
|
2093
|
+
*/
|
|
1797
2094
|
_useResponseApi(options) {
|
|
1798
2095
|
const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
|
|
1799
2096
|
const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
|
|
1800
2097
|
options?.text != null ||
|
|
1801
2098
|
options?.truncation != null ||
|
|
1802
|
-
options?.include != null
|
|
2099
|
+
options?.include != null ||
|
|
2100
|
+
options?.reasoning?.summary != null ||
|
|
2101
|
+
this.reasoning?.summary != null;
|
|
1803
2102
|
return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
|
|
1804
2103
|
}
|
|
1805
2104
|
/** @ignore */
|
package/dist/chat_models.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { BaseChatModel, type BindToolsInput, type LangSmithParams, type BaseChat
|
|
|
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";
|
|
9
|
-
import type
|
|
9
|
+
import { type OpenAICallOptions, type OpenAIChatInput, type OpenAICoreRequestOptions, type ChatOpenAIResponseFormat } from "./types.js";
|
|
10
10
|
import { OpenAIToolChoice } from "./utils/openai.js";
|
|
11
11
|
export type { OpenAICallOptions, OpenAIChatInput };
|
|
12
12
|
interface TokenUsage {
|
|
@@ -117,29 +117,54 @@ export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCa
|
|
|
117
117
|
/**
|
|
118
118
|
* Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
|
|
119
119
|
* Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
|
|
120
|
+
*
|
|
121
|
+
* @deprecated Use {@link reasoning} object instead.
|
|
120
122
|
*/
|
|
121
123
|
reasoning_effort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
|
|
124
|
+
/**
|
|
125
|
+
* Options for reasoning models.
|
|
126
|
+
*
|
|
127
|
+
* Note that some options, like reasoning summaries, are only available when using the responses
|
|
128
|
+
* API. If these options are set, the responses API will be used to fulfill the request.
|
|
129
|
+
*
|
|
130
|
+
* These options will be ignored when not using a reasoning model.
|
|
131
|
+
*/
|
|
132
|
+
reasoning?: OpenAIClient.Reasoning;
|
|
122
133
|
/**
|
|
123
134
|
* Configuration options for a text response from the model. Can be plain text or
|
|
124
135
|
* structured JSON data.
|
|
136
|
+
*
|
|
137
|
+
* If set, the Responses API will be used to fulfill the request.
|
|
125
138
|
*/
|
|
126
139
|
text?: ResponsesCreateParams["text"];
|
|
127
140
|
/**
|
|
128
141
|
* The truncation strategy to use for the model response.
|
|
142
|
+
*
|
|
143
|
+
* If set, the Responses API will be used to fulfill the request.
|
|
129
144
|
*/
|
|
130
145
|
truncation?: ResponsesCreateParams["truncation"];
|
|
131
146
|
/**
|
|
132
147
|
* Specify additional output data to include in the model response.
|
|
148
|
+
*
|
|
149
|
+
* If set, the Responses API will be used to fulfill the request.
|
|
133
150
|
*/
|
|
134
151
|
include?: ResponsesCreateParams["include"];
|
|
135
152
|
/**
|
|
136
|
-
* The unique ID of the previous response to the model. Use this to create
|
|
137
|
-
*
|
|
153
|
+
* The unique ID of the previous response to the model. Use this to create multi-turn
|
|
154
|
+
* conversations. Will be set automatically if {@link ChatOpenAI.zdrEnabled} is `false`, provided
|
|
155
|
+
* that AIMessages included in the request have a `response_metadata.id` property. Ignored unless
|
|
156
|
+
* {@link ChatOpenAI.useResponsesApi} is `true`.
|
|
157
|
+
*
|
|
158
|
+
* If set, the Responses API will be used to fulfill the request.
|
|
138
159
|
*/
|
|
139
160
|
previous_response_id?: ResponsesCreateParams["previous_response_id"];
|
|
140
161
|
}
|
|
141
162
|
export interface ChatOpenAIFields extends Partial<OpenAIChatInput>, BaseChatModelParams {
|
|
142
163
|
configuration?: ClientOptions;
|
|
164
|
+
/**
|
|
165
|
+
* Whether to use the responses API for all requests. If `false` the responses API will be used
|
|
166
|
+
* only when required in order to fulfill the request.
|
|
167
|
+
*/
|
|
143
168
|
useResponsesApi?: boolean;
|
|
144
169
|
}
|
|
145
170
|
/**
|
|
@@ -721,12 +746,36 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
721
746
|
supportsStrictToolCalling?: boolean;
|
|
722
747
|
audio?: OpenAIClient.Chat.ChatCompletionAudioParam;
|
|
723
748
|
modalities?: Array<OpenAIClient.Chat.ChatCompletionModality>;
|
|
749
|
+
/**
|
|
750
|
+
* @deprecated Use {@link reasoning} object instead.
|
|
751
|
+
*/
|
|
724
752
|
reasoningEffort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
|
|
753
|
+
/**
|
|
754
|
+
* Options for reasoning models.
|
|
755
|
+
*/
|
|
756
|
+
reasoning?: OpenAIClient.Reasoning;
|
|
757
|
+
/**
|
|
758
|
+
* Whether to use the responses API for all requests. If `false` the responses API will be used
|
|
759
|
+
* only when required in order to fulfill the request.
|
|
760
|
+
*/
|
|
725
761
|
useResponsesApi: boolean;
|
|
762
|
+
/**
|
|
763
|
+
* Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable
|
|
764
|
+
* output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your
|
|
765
|
+
* OpenAI organization or project. This must be configured directly with OpenAI.
|
|
766
|
+
*
|
|
767
|
+
* See:
|
|
768
|
+
* https://help.openai.com/en/articles/10503543-data-residency-for-the-openai-api
|
|
769
|
+
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-store
|
|
770
|
+
*
|
|
771
|
+
* @default false
|
|
772
|
+
*/
|
|
773
|
+
zdrEnabled?: boolean | undefined;
|
|
726
774
|
constructor(fields?: ChatOpenAIFields);
|
|
727
775
|
getLsParams(options: this["ParsedCallOptions"]): LangSmithParams;
|
|
728
776
|
bindTools(tools: ChatOpenAIToolType[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
|
|
729
777
|
private createResponseFormat;
|
|
778
|
+
private getReasoningParams;
|
|
730
779
|
/**
|
|
731
780
|
* Get the parameters used to invoke the model
|
|
732
781
|
*/
|
|
@@ -749,6 +798,15 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
|
|
|
749
798
|
} & ClientOptions;
|
|
750
799
|
/** @ignore */
|
|
751
800
|
_responseApiGenerate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
|
|
801
|
+
/**
|
|
802
|
+
* Determines whether the responses API should be used for the given request.
|
|
803
|
+
*
|
|
804
|
+
* @internal
|
|
805
|
+
*
|
|
806
|
+
* @param options The parsed call options for the request.
|
|
807
|
+
* @returns `true` if the responses API should be used, either because it is explicitly enabled,
|
|
808
|
+
* or because the request requires it.
|
|
809
|
+
*/
|
|
752
810
|
protected _useResponseApi(options: this["ParsedCallOptions"] | undefined): boolean;
|
|
753
811
|
/** @ignore */
|
|
754
812
|
_generate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
|