@langchain/openai 0.4.3 → 0.4.5-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -554,11 +554,14 @@ class AzureChatOpenAI extends chat_models_js_1.ChatOpenAI {
554
554
  if (!params.baseURL) {
555
555
  delete params.baseURL;
556
556
  }
557
+ let env = (0, env_1.getEnv)();
558
+ if (env === "node" || env === "deno") {
559
+ env = `(${env}/${process.version}; ${process.platform}; ${process.arch})`;
560
+ }
561
+ const specifiedUserAgent = params.defaultHeaders?.["User-Agent"];
557
562
  params.defaultHeaders = {
558
563
  ...params.defaultHeaders,
559
- "User-Agent": params.defaultHeaders?.["User-Agent"]
560
- ? `${params.defaultHeaders["User-Agent"]}: langchainjs-azure-openai-v2`
561
- : `langchainjs-azure-openai-v2`,
564
+ "User-Agent": `langchainjs-azure-openai/2.0.0 (${env})${specifiedUserAgent ? ` ${specifiedUserAgent}` : ""}`,
562
565
  };
563
566
  this.client = new openai_1.AzureOpenAI({
564
567
  apiVersion: this.azureOpenAIApiVersion,
@@ -1,5 +1,5 @@
1
1
  import { AzureOpenAI as AzureOpenAIClient } from "openai";
2
- import { getEnvironmentVariable } from "@langchain/core/utils/env";
2
+ import { getEnv, getEnvironmentVariable } from "@langchain/core/utils/env";
3
3
  import { ChatOpenAI, } from "../chat_models.js";
4
4
  import { getEndpoint } from "../utils/azure.js";
5
5
  /**
@@ -551,11 +551,14 @@ export class AzureChatOpenAI extends ChatOpenAI {
551
551
  if (!params.baseURL) {
552
552
  delete params.baseURL;
553
553
  }
554
+ let env = getEnv();
555
+ if (env === "node" || env === "deno") {
556
+ env = `(${env}/${process.version}; ${process.platform}; ${process.arch})`;
557
+ }
558
+ const specifiedUserAgent = params.defaultHeaders?.["User-Agent"];
554
559
  params.defaultHeaders = {
555
560
  ...params.defaultHeaders,
556
- "User-Agent": params.defaultHeaders?.["User-Agent"]
557
- ? `${params.defaultHeaders["User-Agent"]}: langchainjs-azure-openai-v2`
558
- : `langchainjs-azure-openai-v2`,
561
+ "User-Agent": `langchainjs-azure-openai/2.0.0 (${env})${specifiedUserAgent ? ` ${specifiedUserAgent}` : ""}`,
559
562
  };
560
563
  this.client = new AzureOpenAIClient({
561
564
  apiVersion: this.azureOpenAIApiVersion,
@@ -55,7 +55,7 @@ function _convertMessagesToOpenAIParams(messages, model) {
55
55
  // TODO: Function messages do not support array content, fix cast
56
56
  return messages.flatMap((message) => {
57
57
  let role = messageToOpenAIRole(message);
58
- if (role === "system" && model?.startsWith("o1")) {
58
+ if (role === "system" && isReasoningModel(model)) {
59
59
  role = "developer";
60
60
  }
61
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -97,6 +97,283 @@ 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
+ return {
112
+ type: "function_call_output",
113
+ call_id: toolMessage.tool_call_id,
114
+ id: toolMessage.id,
115
+ output: typeof toolMessage.content !== "string"
116
+ ? JSON.stringify(toolMessage.content)
117
+ : toolMessage.content,
118
+ };
119
+ }
120
+ if (role === "assistant") {
121
+ const functionCallIds =
122
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
123
+ lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
124
+ if ((0, messages_1.isAIMessage)(lcMsg) && !!lcMsg.tool_calls?.length) {
125
+ return lcMsg.tool_calls.map((toolCall) => ({
126
+ type: "function_call",
127
+ name: toolCall.name,
128
+ arguments: JSON.stringify(toolCall.args),
129
+ call_id: toolCall.id,
130
+ id: functionCallIds[toolCall.id],
131
+ }));
132
+ }
133
+ if (lcMsg.additional_kwargs.tool_calls != null) {
134
+ return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
135
+ type: "function_call",
136
+ name: toolCall.function.name,
137
+ call_id: toolCall.id,
138
+ id: functionCallIds[toolCall.id],
139
+ arguments: toolCall.function.arguments,
140
+ }));
141
+ }
142
+ let { content } = lcMsg;
143
+ if (lcMsg.additional_kwargs.refusal != null) {
144
+ if (typeof content === "string") {
145
+ content = [{ type: "output_text", text: content, annotations: [] }];
146
+ }
147
+ content = [
148
+ ...content,
149
+ { type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
150
+ ];
151
+ }
152
+ return {
153
+ type: "message",
154
+ role: "assistant",
155
+ content: typeof content === "string"
156
+ ? content
157
+ : content.flatMap((item) => {
158
+ if (item.type === "text") {
159
+ return {
160
+ type: "output_text",
161
+ text: item.text,
162
+ // @ts-expect-error TODO: add types for `annotations`
163
+ annotations: item.annotations ?? [],
164
+ };
165
+ }
166
+ if (item.type === "output_text" || item.type === "refusal") {
167
+ return item;
168
+ }
169
+ return [];
170
+ }),
171
+ };
172
+ }
173
+ if (role === "user") {
174
+ return {
175
+ type: "message",
176
+ role: "user",
177
+ content: typeof lcMsg.content === "string"
178
+ ? lcMsg.content
179
+ : lcMsg.content.flatMap((item) => {
180
+ if (item.type === "text") {
181
+ return { type: "input_text", text: item.text };
182
+ }
183
+ if (item.type === "image_url") {
184
+ const image_url = typeof item.image_url === "string"
185
+ ? item.image_url
186
+ : item.image_url.url;
187
+ const detail = typeof item.image_url === "string"
188
+ ? "auto"
189
+ : item.image_url.detail;
190
+ return { type: "input_image", image_url, detail };
191
+ }
192
+ if (item.type === "input_text" ||
193
+ item.type === "input_image" ||
194
+ item.type === "input_file") {
195
+ return item;
196
+ }
197
+ return [];
198
+ }),
199
+ };
200
+ }
201
+ return [];
202
+ });
203
+ }
204
+ function _convertOpenAIResponsesMessageToBaseMessage(response) {
205
+ if (response.error) {
206
+ // TODO: add support for `addLangChainErrorFields`
207
+ const error = new Error(response.error.message);
208
+ error.name = response.error.code;
209
+ throw error;
210
+ }
211
+ const content = [];
212
+ const tool_calls = [];
213
+ const invalid_tool_calls = [];
214
+ const response_metadata = {
215
+ model: response.model,
216
+ created_at: response.created_at,
217
+ id: response.id,
218
+ incomplete_details: response.incomplete_details,
219
+ metadata: response.metadata,
220
+ object: response.object,
221
+ status: response.status,
222
+ user: response.user,
223
+ // for compatibility with chat completion calls.
224
+ model_name: response.model,
225
+ };
226
+ const additional_kwargs = {};
227
+ for (const item of response.output) {
228
+ if (item.type === "message") {
229
+ // TODO: how to handle refusals?
230
+ content.push(...item.content.map((part) => {
231
+ if (part.type === "output_text") {
232
+ return {
233
+ type: "text",
234
+ text: part.text,
235
+ annotations: part.annotations,
236
+ };
237
+ }
238
+ return part;
239
+ }));
240
+ }
241
+ else if (item.type === "function_call") {
242
+ const fnAdapter = {
243
+ function: { name: item.name, arguments: item.arguments },
244
+ id: item.call_id,
245
+ };
246
+ try {
247
+ tool_calls.push((0, openai_tools_1.parseToolCall)(fnAdapter, { returnId: true }));
248
+ }
249
+ catch (e) {
250
+ let errMessage;
251
+ if (typeof e === "object" &&
252
+ e != null &&
253
+ "message" in e &&
254
+ typeof e.message === "string") {
255
+ errMessage = e.message;
256
+ }
257
+ invalid_tool_calls.push((0, openai_tools_1.makeInvalidToolCall)(fnAdapter, errMessage));
258
+ }
259
+ }
260
+ else if (item.type === "reasoning") {
261
+ additional_kwargs.reasoning = item;
262
+ }
263
+ else {
264
+ additional_kwargs.tool_outputs ??= [];
265
+ additional_kwargs.tool_outputs.push(item);
266
+ }
267
+ }
268
+ return new messages_1.AIMessage({
269
+ id: response.id,
270
+ content,
271
+ tool_calls,
272
+ invalid_tool_calls,
273
+ usage_metadata: response.usage,
274
+ additional_kwargs,
275
+ response_metadata,
276
+ });
277
+ }
278
+ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
279
+ const content = [];
280
+ let generationInfo = {};
281
+ let usage_metadata;
282
+ const tool_call_chunks = [];
283
+ const response_metadata = {};
284
+ const additional_kwargs = {};
285
+ let id;
286
+ if (chunk.type === "response.output_text.delta") {
287
+ content.push({
288
+ type: "text",
289
+ text: chunk.delta,
290
+ index: chunk.content_index,
291
+ });
292
+ }
293
+ else if (chunk.type === "response.output_text.annotation.added") {
294
+ content.push({
295
+ type: "text",
296
+ text: "",
297
+ annotations: [chunk.annotation],
298
+ index: chunk.content_index,
299
+ });
300
+ }
301
+ else if (chunk.type === "response.output_item.added" &&
302
+ chunk.item.type === "message") {
303
+ id = chunk.item.id;
304
+ }
305
+ else if (chunk.type === "response.output_item.added" &&
306
+ chunk.item.type === "function_call") {
307
+ tool_call_chunks.push({
308
+ type: "tool_call_chunk",
309
+ name: chunk.item.name,
310
+ args: chunk.item.arguments,
311
+ id: chunk.item.id,
312
+ index: chunk.output_index,
313
+ });
314
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
315
+ [chunk.item.call_id]: chunk.item.id,
316
+ };
317
+ }
318
+ else if (chunk.type === "response.output_item.done" &&
319
+ (chunk.item.type === "web_search_call" ||
320
+ chunk.item.type === "file_search_call")) {
321
+ additional_kwargs.tool_outputs = [chunk.item];
322
+ }
323
+ else if (chunk.type === "response.created") {
324
+ response_metadata.id = chunk.response.id;
325
+ response_metadata.model_name = chunk.response.model;
326
+ response_metadata.model = chunk.response.model;
327
+ }
328
+ else if (chunk.type === "response.completed") {
329
+ usage_metadata = chunk.response.usage;
330
+ }
331
+ else if (chunk.type === "response.function_call_arguments.delta") {
332
+ tool_call_chunks.push({
333
+ type: "tool_call_chunk",
334
+ args: chunk.delta,
335
+ index: chunk.output_index,
336
+ });
337
+ }
338
+ else if (chunk.type === "response.web_search_call.completed" ||
339
+ chunk.type === "response.file_search_call.completed") {
340
+ generationInfo = {
341
+ tool_outputs: {
342
+ id: chunk.item_id,
343
+ type: chunk.type.replace("response.", "").replace(".completed", ""),
344
+ status: "completed",
345
+ },
346
+ };
347
+ }
348
+ else if (chunk.type === "response.refusal.done") {
349
+ additional_kwargs.refusal = chunk.refusal;
350
+ }
351
+ else {
352
+ return null;
353
+ }
354
+ return new outputs_1.ChatGenerationChunk({
355
+ // Legacy reasons, `onLLMNewToken` should pulls this out
356
+ text: content.map((part) => part.text).join(""),
357
+ message: new messages_1.AIMessageChunk({
358
+ id,
359
+ content,
360
+ tool_call_chunks,
361
+ usage_metadata,
362
+ additional_kwargs,
363
+ response_metadata,
364
+ }),
365
+ generationInfo,
366
+ });
367
+ }
368
+ function isBuiltInTool(tool) {
369
+ return "type" in tool && tool.type !== "function";
370
+ }
371
+ function isBuiltInToolChoice(tool_choice) {
372
+ return (tool_choice != null &&
373
+ typeof tool_choice === "object" &&
374
+ "type" in tool_choice &&
375
+ tool_choice.type !== "function");
376
+ }
100
377
  function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
101
378
  if ((0, base_1.isOpenAITool)(tool)) {
102
379
  if (fields?.strict !== undefined) {
@@ -112,6 +389,9 @@ function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
112
389
  }
113
390
  return (0, tools_js_1._convertToOpenAITool)(tool, fields);
114
391
  }
392
+ function isReasoningModel(model) {
393
+ return model?.startsWith("o1") || model?.startsWith("o3");
394
+ }
115
395
  /**
116
396
  * OpenAI chat model integration.
117
397
  *
@@ -901,6 +1181,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
901
1181
  writable: true,
902
1182
  value: void 0
903
1183
  });
1184
+ Object.defineProperty(this, "useResponsesApi", {
1185
+ enumerable: true,
1186
+ configurable: true,
1187
+ writable: true,
1188
+ value: false
1189
+ });
904
1190
  this.openAIApiKey =
905
1191
  fields?.apiKey ??
906
1192
  fields?.openAIApiKey ??
@@ -918,7 +1204,6 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
918
1204
  this.topP = fields?.topP ?? this.topP;
919
1205
  this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty;
920
1206
  this.presencePenalty = fields?.presencePenalty ?? this.presencePenalty;
921
- this.maxTokens = fields?.maxTokens;
922
1207
  this.logprobs = fields?.logprobs;
923
1208
  this.topLogprobs = fields?.topLogprobs;
924
1209
  this.n = fields?.n ?? this.n;
@@ -930,6 +1215,8 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
930
1215
  this.audio = fields?.audio;
931
1216
  this.modalities = fields?.modalities;
932
1217
  this.reasoningEffort = fields?.reasoningEffort;
1218
+ this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1219
+ this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
933
1220
  if (this.model === "o1") {
934
1221
  this.disableStreaming = true;
935
1222
  }
@@ -967,7 +1254,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
967
1254
  strict = this.supportsStrictToolCalling;
968
1255
  }
969
1256
  return this.bind({
970
- tools: tools.map((tool) => _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
1257
+ tools: tools.map((tool) => isBuiltInTool(tool)
1258
+ ? tool
1259
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
971
1260
  ...kwargs,
972
1261
  });
973
1262
  }
@@ -993,6 +1282,80 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
993
1282
  else if (this.supportsStrictToolCalling !== undefined) {
994
1283
  strict = this.supportsStrictToolCalling;
995
1284
  }
1285
+ if (this._useResponseApi(options)) {
1286
+ const params = {
1287
+ model: this.model,
1288
+ temperature: this.temperature,
1289
+ top_p: this.topP,
1290
+ user: this.user,
1291
+ // if include_usage is set or streamUsage then stream must be set to true.
1292
+ stream: this.streaming,
1293
+ previous_response_id: options?.previous_response_id,
1294
+ truncation: options?.truncation,
1295
+ include: options?.include,
1296
+ tools: options?.tools?.length
1297
+ ? options.tools
1298
+ .map((tool) => {
1299
+ if (isBuiltInTool(tool)) {
1300
+ return tool;
1301
+ }
1302
+ else if ((0, base_1.isOpenAITool)(tool)) {
1303
+ return {
1304
+ type: "function",
1305
+ name: tool.function.name,
1306
+ parameters: tool.function.parameters,
1307
+ description: tool.function.description,
1308
+ strict,
1309
+ };
1310
+ }
1311
+ else {
1312
+ return null;
1313
+ }
1314
+ })
1315
+ .filter((tool) => tool !== null)
1316
+ : undefined,
1317
+ tool_choice: isBuiltInToolChoice(options?.tool_choice)
1318
+ ? options?.tool_choice
1319
+ : (() => {
1320
+ const formatted = (0, openai_js_1.formatToOpenAIToolChoice)(options?.tool_choice);
1321
+ if (typeof formatted === "object" && "type" in formatted) {
1322
+ return { type: "function", name: formatted.function.name };
1323
+ }
1324
+ else {
1325
+ return undefined;
1326
+ }
1327
+ })(),
1328
+ text: (() => {
1329
+ if (options?.text)
1330
+ return options.text;
1331
+ const format = this.createResponseFormat(options?.response_format);
1332
+ if (format?.type === "json_schema") {
1333
+ if (format.json_schema.schema != null) {
1334
+ return {
1335
+ format: {
1336
+ type: "json_schema",
1337
+ schema: format.json_schema.schema,
1338
+ description: format.json_schema.description,
1339
+ name: format.json_schema.name,
1340
+ strict: format.json_schema.strict,
1341
+ },
1342
+ };
1343
+ }
1344
+ return undefined;
1345
+ }
1346
+ return { format };
1347
+ })(),
1348
+ parallel_tool_calls: options?.parallel_tool_calls,
1349
+ max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1350
+ ...this.modelKwargs,
1351
+ };
1352
+ const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1353
+ if (reasoningEffort !== undefined) {
1354
+ params.reasoning = { effort: reasoningEffort };
1355
+ }
1356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1357
+ return params;
1358
+ }
996
1359
  let streamOptionsConfig = {};
997
1360
  if (options?.stream_options !== undefined) {
998
1361
  streamOptionsConfig = { stream_options: options.stream_options };
@@ -1006,7 +1369,6 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1006
1369
  top_p: this.topP,
1007
1370
  frequency_penalty: this.frequencyPenalty,
1008
1371
  presence_penalty: this.presencePenalty,
1009
- max_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1010
1372
  logprobs: this.logprobs,
1011
1373
  top_logprobs: this.topLogprobs,
1012
1374
  n: this.n,
@@ -1040,6 +1402,14 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1040
1402
  if (reasoningEffort !== undefined) {
1041
1403
  params.reasoning_effort = reasoningEffort;
1042
1404
  }
1405
+ if (isReasoningModel(params.model)) {
1406
+ params.max_completion_tokens =
1407
+ this.maxTokens === -1 ? undefined : this.maxTokens;
1408
+ }
1409
+ else {
1410
+ params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
1411
+ }
1412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1043
1413
  return params;
1044
1414
  }
1045
1415
  _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
@@ -1183,6 +1553,20 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1183
1553
  };
1184
1554
  }
1185
1555
  async *_streamResponseChunks(messages, options, runManager) {
1556
+ if (this._useResponseApi(options)) {
1557
+ const streamIterable = await this.responseApiWithRetry({
1558
+ ...this.invocationParams(options, { streaming: true }),
1559
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1560
+ stream: true,
1561
+ }, options);
1562
+ for await (const data of streamIterable) {
1563
+ const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
1564
+ if (chunk == null)
1565
+ continue;
1566
+ yield chunk;
1567
+ }
1568
+ return;
1569
+ }
1186
1570
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
1187
1571
  const params = {
1188
1572
  ...this.invocationParams(options, {
@@ -1287,7 +1671,60 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1287
1671
  return this._identifyingParams();
1288
1672
  }
1289
1673
  /** @ignore */
1674
+ async _responseApiGenerate(messages, options, runManager) {
1675
+ const invocationParams = this.invocationParams(options);
1676
+ if (invocationParams.stream) {
1677
+ const stream = this._streamResponseChunks(messages, options, runManager);
1678
+ let finalChunk;
1679
+ for await (const chunk of stream) {
1680
+ chunk.message.response_metadata = {
1681
+ ...chunk.generationInfo,
1682
+ ...chunk.message.response_metadata,
1683
+ };
1684
+ finalChunk = finalChunk?.concat(chunk) ?? chunk;
1685
+ }
1686
+ return {
1687
+ generations: finalChunk ? [finalChunk] : [],
1688
+ llmOutput: {
1689
+ estimatedTokenUsage: finalChunk?.message
1690
+ ?.usage_metadata,
1691
+ },
1692
+ };
1693
+ }
1694
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1695
+ const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
1696
+ return {
1697
+ generations: [
1698
+ {
1699
+ text: data.output_text,
1700
+ message: _convertOpenAIResponsesMessageToBaseMessage(data),
1701
+ },
1702
+ ],
1703
+ llmOutput: {
1704
+ id: data.id,
1705
+ estimatedTokenUsage: data.usage
1706
+ ? {
1707
+ promptTokens: data.usage.input_tokens,
1708
+ completionTokens: data.usage.output_tokens,
1709
+ totalTokens: data.usage.total_tokens,
1710
+ }
1711
+ : undefined,
1712
+ },
1713
+ };
1714
+ }
1715
+ _useResponseApi(options) {
1716
+ const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1717
+ const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1718
+ options?.text != null ||
1719
+ options?.truncation != null ||
1720
+ options?.include != null;
1721
+ return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1722
+ }
1723
+ /** @ignore */
1290
1724
  async _generate(messages, options, runManager) {
1725
+ if (this._useResponseApi(options)) {
1726
+ return this._responseApiGenerate(messages, options, runManager);
1727
+ }
1291
1728
  const usageMetadata = {};
1292
1729
  const params = this.invocationParams(options);
1293
1730
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
@@ -1525,6 +1962,19 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1525
1962
  }
1526
1963
  });
1527
1964
  }
1965
+ async responseApiWithRetry(request, options) {
1966
+ return this.caller.call(async () => {
1967
+ const requestOptions = this._getClientOptions(options);
1968
+ try {
1969
+ const res = await this.client.responses.create(request, requestOptions);
1970
+ return res;
1971
+ }
1972
+ catch (e) {
1973
+ const error = (0, openai_js_1.wrapOpenAIClientError)(e);
1974
+ throw error;
1975
+ }
1976
+ });
1977
+ }
1528
1978
  /**
1529
1979
  * Call the beta chat completions parse endpoint. This should only be called if
1530
1980
  * response_format is set to "json_object".
@@ -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 ChatOpenAIToolType = BindToolsInput | OpenAIClient.ChatCompletionTool;
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
- }): Omit<OpenAIClient.Chat.ChatCompletionCreateParams, "messages">;
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".
@@ -51,7 +51,7 @@ export function _convertMessagesToOpenAIParams(messages, model) {
51
51
  // TODO: Function messages do not support array content, fix cast
52
52
  return messages.flatMap((message) => {
53
53
  let role = messageToOpenAIRole(message);
54
- if (role === "system" && model?.startsWith("o1")) {
54
+ if (role === "system" && isReasoningModel(model)) {
55
55
  role = "developer";
56
56
  }
57
57
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -92,6 +92,283 @@ 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
+ return {
107
+ type: "function_call_output",
108
+ call_id: toolMessage.tool_call_id,
109
+ id: toolMessage.id,
110
+ output: typeof toolMessage.content !== "string"
111
+ ? JSON.stringify(toolMessage.content)
112
+ : toolMessage.content,
113
+ };
114
+ }
115
+ if (role === "assistant") {
116
+ const functionCallIds =
117
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
118
+ lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
119
+ if (isAIMessage(lcMsg) && !!lcMsg.tool_calls?.length) {
120
+ return lcMsg.tool_calls.map((toolCall) => ({
121
+ type: "function_call",
122
+ name: toolCall.name,
123
+ arguments: JSON.stringify(toolCall.args),
124
+ call_id: toolCall.id,
125
+ id: functionCallIds[toolCall.id],
126
+ }));
127
+ }
128
+ if (lcMsg.additional_kwargs.tool_calls != null) {
129
+ return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
130
+ type: "function_call",
131
+ name: toolCall.function.name,
132
+ call_id: toolCall.id,
133
+ id: functionCallIds[toolCall.id],
134
+ arguments: toolCall.function.arguments,
135
+ }));
136
+ }
137
+ let { content } = lcMsg;
138
+ if (lcMsg.additional_kwargs.refusal != null) {
139
+ if (typeof content === "string") {
140
+ content = [{ type: "output_text", text: content, annotations: [] }];
141
+ }
142
+ content = [
143
+ ...content,
144
+ { type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
145
+ ];
146
+ }
147
+ return {
148
+ type: "message",
149
+ role: "assistant",
150
+ content: typeof content === "string"
151
+ ? content
152
+ : content.flatMap((item) => {
153
+ if (item.type === "text") {
154
+ return {
155
+ type: "output_text",
156
+ text: item.text,
157
+ // @ts-expect-error TODO: add types for `annotations`
158
+ annotations: item.annotations ?? [],
159
+ };
160
+ }
161
+ if (item.type === "output_text" || item.type === "refusal") {
162
+ return item;
163
+ }
164
+ return [];
165
+ }),
166
+ };
167
+ }
168
+ if (role === "user") {
169
+ return {
170
+ type: "message",
171
+ role: "user",
172
+ content: typeof lcMsg.content === "string"
173
+ ? lcMsg.content
174
+ : lcMsg.content.flatMap((item) => {
175
+ if (item.type === "text") {
176
+ return { type: "input_text", text: item.text };
177
+ }
178
+ if (item.type === "image_url") {
179
+ const image_url = typeof item.image_url === "string"
180
+ ? item.image_url
181
+ : item.image_url.url;
182
+ const detail = typeof item.image_url === "string"
183
+ ? "auto"
184
+ : item.image_url.detail;
185
+ return { type: "input_image", image_url, detail };
186
+ }
187
+ if (item.type === "input_text" ||
188
+ item.type === "input_image" ||
189
+ item.type === "input_file") {
190
+ return item;
191
+ }
192
+ return [];
193
+ }),
194
+ };
195
+ }
196
+ return [];
197
+ });
198
+ }
199
+ function _convertOpenAIResponsesMessageToBaseMessage(response) {
200
+ if (response.error) {
201
+ // TODO: add support for `addLangChainErrorFields`
202
+ const error = new Error(response.error.message);
203
+ error.name = response.error.code;
204
+ throw error;
205
+ }
206
+ const content = [];
207
+ const tool_calls = [];
208
+ const invalid_tool_calls = [];
209
+ const response_metadata = {
210
+ model: response.model,
211
+ created_at: response.created_at,
212
+ id: response.id,
213
+ incomplete_details: response.incomplete_details,
214
+ metadata: response.metadata,
215
+ object: response.object,
216
+ status: response.status,
217
+ user: response.user,
218
+ // for compatibility with chat completion calls.
219
+ model_name: response.model,
220
+ };
221
+ const additional_kwargs = {};
222
+ for (const item of response.output) {
223
+ if (item.type === "message") {
224
+ // TODO: how to handle refusals?
225
+ content.push(...item.content.map((part) => {
226
+ if (part.type === "output_text") {
227
+ return {
228
+ type: "text",
229
+ text: part.text,
230
+ annotations: part.annotations,
231
+ };
232
+ }
233
+ return part;
234
+ }));
235
+ }
236
+ else if (item.type === "function_call") {
237
+ const fnAdapter = {
238
+ function: { name: item.name, arguments: item.arguments },
239
+ id: item.call_id,
240
+ };
241
+ try {
242
+ tool_calls.push(parseToolCall(fnAdapter, { returnId: true }));
243
+ }
244
+ catch (e) {
245
+ let errMessage;
246
+ if (typeof e === "object" &&
247
+ e != null &&
248
+ "message" in e &&
249
+ typeof e.message === "string") {
250
+ errMessage = e.message;
251
+ }
252
+ invalid_tool_calls.push(makeInvalidToolCall(fnAdapter, errMessage));
253
+ }
254
+ }
255
+ else if (item.type === "reasoning") {
256
+ additional_kwargs.reasoning = item;
257
+ }
258
+ else {
259
+ additional_kwargs.tool_outputs ??= [];
260
+ additional_kwargs.tool_outputs.push(item);
261
+ }
262
+ }
263
+ return new AIMessage({
264
+ id: response.id,
265
+ content,
266
+ tool_calls,
267
+ invalid_tool_calls,
268
+ usage_metadata: response.usage,
269
+ additional_kwargs,
270
+ response_metadata,
271
+ });
272
+ }
273
+ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
274
+ const content = [];
275
+ let generationInfo = {};
276
+ let usage_metadata;
277
+ const tool_call_chunks = [];
278
+ const response_metadata = {};
279
+ const additional_kwargs = {};
280
+ let id;
281
+ if (chunk.type === "response.output_text.delta") {
282
+ content.push({
283
+ type: "text",
284
+ text: chunk.delta,
285
+ index: chunk.content_index,
286
+ });
287
+ }
288
+ else if (chunk.type === "response.output_text.annotation.added") {
289
+ content.push({
290
+ type: "text",
291
+ text: "",
292
+ annotations: [chunk.annotation],
293
+ index: chunk.content_index,
294
+ });
295
+ }
296
+ else if (chunk.type === "response.output_item.added" &&
297
+ chunk.item.type === "message") {
298
+ id = chunk.item.id;
299
+ }
300
+ else if (chunk.type === "response.output_item.added" &&
301
+ chunk.item.type === "function_call") {
302
+ tool_call_chunks.push({
303
+ type: "tool_call_chunk",
304
+ name: chunk.item.name,
305
+ args: chunk.item.arguments,
306
+ id: chunk.item.id,
307
+ index: chunk.output_index,
308
+ });
309
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
310
+ [chunk.item.call_id]: chunk.item.id,
311
+ };
312
+ }
313
+ else if (chunk.type === "response.output_item.done" &&
314
+ (chunk.item.type === "web_search_call" ||
315
+ chunk.item.type === "file_search_call")) {
316
+ additional_kwargs.tool_outputs = [chunk.item];
317
+ }
318
+ else if (chunk.type === "response.created") {
319
+ response_metadata.id = chunk.response.id;
320
+ response_metadata.model_name = chunk.response.model;
321
+ response_metadata.model = chunk.response.model;
322
+ }
323
+ else if (chunk.type === "response.completed") {
324
+ usage_metadata = chunk.response.usage;
325
+ }
326
+ else if (chunk.type === "response.function_call_arguments.delta") {
327
+ tool_call_chunks.push({
328
+ type: "tool_call_chunk",
329
+ args: chunk.delta,
330
+ index: chunk.output_index,
331
+ });
332
+ }
333
+ else if (chunk.type === "response.web_search_call.completed" ||
334
+ chunk.type === "response.file_search_call.completed") {
335
+ generationInfo = {
336
+ tool_outputs: {
337
+ id: chunk.item_id,
338
+ type: chunk.type.replace("response.", "").replace(".completed", ""),
339
+ status: "completed",
340
+ },
341
+ };
342
+ }
343
+ else if (chunk.type === "response.refusal.done") {
344
+ additional_kwargs.refusal = chunk.refusal;
345
+ }
346
+ else {
347
+ return null;
348
+ }
349
+ return new ChatGenerationChunk({
350
+ // Legacy reasons, `onLLMNewToken` should pulls this out
351
+ text: content.map((part) => part.text).join(""),
352
+ message: new AIMessageChunk({
353
+ id,
354
+ content,
355
+ tool_call_chunks,
356
+ usage_metadata,
357
+ additional_kwargs,
358
+ response_metadata,
359
+ }),
360
+ generationInfo,
361
+ });
362
+ }
363
+ function isBuiltInTool(tool) {
364
+ return "type" in tool && tool.type !== "function";
365
+ }
366
+ function isBuiltInToolChoice(tool_choice) {
367
+ return (tool_choice != null &&
368
+ typeof tool_choice === "object" &&
369
+ "type" in tool_choice &&
370
+ tool_choice.type !== "function");
371
+ }
95
372
  function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
96
373
  if (isOpenAITool(tool)) {
97
374
  if (fields?.strict !== undefined) {
@@ -107,6 +384,9 @@ function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
107
384
  }
108
385
  return _convertToOpenAITool(tool, fields);
109
386
  }
387
+ function isReasoningModel(model) {
388
+ return model?.startsWith("o1") || model?.startsWith("o3");
389
+ }
110
390
  /**
111
391
  * OpenAI chat model integration.
112
392
  *
@@ -896,6 +1176,12 @@ export class ChatOpenAI extends BaseChatModel {
896
1176
  writable: true,
897
1177
  value: void 0
898
1178
  });
1179
+ Object.defineProperty(this, "useResponsesApi", {
1180
+ enumerable: true,
1181
+ configurable: true,
1182
+ writable: true,
1183
+ value: false
1184
+ });
899
1185
  this.openAIApiKey =
900
1186
  fields?.apiKey ??
901
1187
  fields?.openAIApiKey ??
@@ -913,7 +1199,6 @@ export class ChatOpenAI extends BaseChatModel {
913
1199
  this.topP = fields?.topP ?? this.topP;
914
1200
  this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty;
915
1201
  this.presencePenalty = fields?.presencePenalty ?? this.presencePenalty;
916
- this.maxTokens = fields?.maxTokens;
917
1202
  this.logprobs = fields?.logprobs;
918
1203
  this.topLogprobs = fields?.topLogprobs;
919
1204
  this.n = fields?.n ?? this.n;
@@ -925,6 +1210,8 @@ export class ChatOpenAI extends BaseChatModel {
925
1210
  this.audio = fields?.audio;
926
1211
  this.modalities = fields?.modalities;
927
1212
  this.reasoningEffort = fields?.reasoningEffort;
1213
+ this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1214
+ this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
928
1215
  if (this.model === "o1") {
929
1216
  this.disableStreaming = true;
930
1217
  }
@@ -962,7 +1249,9 @@ export class ChatOpenAI extends BaseChatModel {
962
1249
  strict = this.supportsStrictToolCalling;
963
1250
  }
964
1251
  return this.bind({
965
- tools: tools.map((tool) => _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
1252
+ tools: tools.map((tool) => isBuiltInTool(tool)
1253
+ ? tool
1254
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
966
1255
  ...kwargs,
967
1256
  });
968
1257
  }
@@ -988,6 +1277,80 @@ export class ChatOpenAI extends BaseChatModel {
988
1277
  else if (this.supportsStrictToolCalling !== undefined) {
989
1278
  strict = this.supportsStrictToolCalling;
990
1279
  }
1280
+ if (this._useResponseApi(options)) {
1281
+ const params = {
1282
+ model: this.model,
1283
+ temperature: this.temperature,
1284
+ top_p: this.topP,
1285
+ user: this.user,
1286
+ // if include_usage is set or streamUsage then stream must be set to true.
1287
+ stream: this.streaming,
1288
+ previous_response_id: options?.previous_response_id,
1289
+ truncation: options?.truncation,
1290
+ include: options?.include,
1291
+ tools: options?.tools?.length
1292
+ ? options.tools
1293
+ .map((tool) => {
1294
+ if (isBuiltInTool(tool)) {
1295
+ return tool;
1296
+ }
1297
+ else if (isOpenAITool(tool)) {
1298
+ return {
1299
+ type: "function",
1300
+ name: tool.function.name,
1301
+ parameters: tool.function.parameters,
1302
+ description: tool.function.description,
1303
+ strict,
1304
+ };
1305
+ }
1306
+ else {
1307
+ return null;
1308
+ }
1309
+ })
1310
+ .filter((tool) => tool !== null)
1311
+ : undefined,
1312
+ tool_choice: isBuiltInToolChoice(options?.tool_choice)
1313
+ ? options?.tool_choice
1314
+ : (() => {
1315
+ const formatted = formatToOpenAIToolChoice(options?.tool_choice);
1316
+ if (typeof formatted === "object" && "type" in formatted) {
1317
+ return { type: "function", name: formatted.function.name };
1318
+ }
1319
+ else {
1320
+ return undefined;
1321
+ }
1322
+ })(),
1323
+ text: (() => {
1324
+ if (options?.text)
1325
+ return options.text;
1326
+ const format = this.createResponseFormat(options?.response_format);
1327
+ if (format?.type === "json_schema") {
1328
+ if (format.json_schema.schema != null) {
1329
+ return {
1330
+ format: {
1331
+ type: "json_schema",
1332
+ schema: format.json_schema.schema,
1333
+ description: format.json_schema.description,
1334
+ name: format.json_schema.name,
1335
+ strict: format.json_schema.strict,
1336
+ },
1337
+ };
1338
+ }
1339
+ return undefined;
1340
+ }
1341
+ return { format };
1342
+ })(),
1343
+ parallel_tool_calls: options?.parallel_tool_calls,
1344
+ max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1345
+ ...this.modelKwargs,
1346
+ };
1347
+ const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1348
+ if (reasoningEffort !== undefined) {
1349
+ params.reasoning = { effort: reasoningEffort };
1350
+ }
1351
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1352
+ return params;
1353
+ }
991
1354
  let streamOptionsConfig = {};
992
1355
  if (options?.stream_options !== undefined) {
993
1356
  streamOptionsConfig = { stream_options: options.stream_options };
@@ -1001,7 +1364,6 @@ export class ChatOpenAI extends BaseChatModel {
1001
1364
  top_p: this.topP,
1002
1365
  frequency_penalty: this.frequencyPenalty,
1003
1366
  presence_penalty: this.presencePenalty,
1004
- max_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1005
1367
  logprobs: this.logprobs,
1006
1368
  top_logprobs: this.topLogprobs,
1007
1369
  n: this.n,
@@ -1035,6 +1397,14 @@ export class ChatOpenAI extends BaseChatModel {
1035
1397
  if (reasoningEffort !== undefined) {
1036
1398
  params.reasoning_effort = reasoningEffort;
1037
1399
  }
1400
+ if (isReasoningModel(params.model)) {
1401
+ params.max_completion_tokens =
1402
+ this.maxTokens === -1 ? undefined : this.maxTokens;
1403
+ }
1404
+ else {
1405
+ params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
1406
+ }
1407
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1038
1408
  return params;
1039
1409
  }
1040
1410
  _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
@@ -1178,6 +1548,20 @@ export class ChatOpenAI extends BaseChatModel {
1178
1548
  };
1179
1549
  }
1180
1550
  async *_streamResponseChunks(messages, options, runManager) {
1551
+ if (this._useResponseApi(options)) {
1552
+ const streamIterable = await this.responseApiWithRetry({
1553
+ ...this.invocationParams(options, { streaming: true }),
1554
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1555
+ stream: true,
1556
+ }, options);
1557
+ for await (const data of streamIterable) {
1558
+ const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
1559
+ if (chunk == null)
1560
+ continue;
1561
+ yield chunk;
1562
+ }
1563
+ return;
1564
+ }
1181
1565
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
1182
1566
  const params = {
1183
1567
  ...this.invocationParams(options, {
@@ -1282,7 +1666,60 @@ export class ChatOpenAI extends BaseChatModel {
1282
1666
  return this._identifyingParams();
1283
1667
  }
1284
1668
  /** @ignore */
1669
+ async _responseApiGenerate(messages, options, runManager) {
1670
+ const invocationParams = this.invocationParams(options);
1671
+ if (invocationParams.stream) {
1672
+ const stream = this._streamResponseChunks(messages, options, runManager);
1673
+ let finalChunk;
1674
+ for await (const chunk of stream) {
1675
+ chunk.message.response_metadata = {
1676
+ ...chunk.generationInfo,
1677
+ ...chunk.message.response_metadata,
1678
+ };
1679
+ finalChunk = finalChunk?.concat(chunk) ?? chunk;
1680
+ }
1681
+ return {
1682
+ generations: finalChunk ? [finalChunk] : [],
1683
+ llmOutput: {
1684
+ estimatedTokenUsage: finalChunk?.message
1685
+ ?.usage_metadata,
1686
+ },
1687
+ };
1688
+ }
1689
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1690
+ const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
1691
+ return {
1692
+ generations: [
1693
+ {
1694
+ text: data.output_text,
1695
+ message: _convertOpenAIResponsesMessageToBaseMessage(data),
1696
+ },
1697
+ ],
1698
+ llmOutput: {
1699
+ id: data.id,
1700
+ estimatedTokenUsage: data.usage
1701
+ ? {
1702
+ promptTokens: data.usage.input_tokens,
1703
+ completionTokens: data.usage.output_tokens,
1704
+ totalTokens: data.usage.total_tokens,
1705
+ }
1706
+ : undefined,
1707
+ },
1708
+ };
1709
+ }
1710
+ _useResponseApi(options) {
1711
+ const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1712
+ const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1713
+ options?.text != null ||
1714
+ options?.truncation != null ||
1715
+ options?.include != null;
1716
+ return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1717
+ }
1718
+ /** @ignore */
1285
1719
  async _generate(messages, options, runManager) {
1720
+ if (this._useResponseApi(options)) {
1721
+ return this._responseApiGenerate(messages, options, runManager);
1722
+ }
1286
1723
  const usageMetadata = {};
1287
1724
  const params = this.invocationParams(options);
1288
1725
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
@@ -1520,6 +1957,19 @@ export class ChatOpenAI extends BaseChatModel {
1520
1957
  }
1521
1958
  });
1522
1959
  }
1960
+ async responseApiWithRetry(request, options) {
1961
+ return this.caller.call(async () => {
1962
+ const requestOptions = this._getClientOptions(options);
1963
+ try {
1964
+ const res = await this.client.responses.create(request, requestOptions);
1965
+ return res;
1966
+ }
1967
+ catch (e) {
1968
+ const error = wrapOpenAIClientError(e);
1969
+ throw error;
1970
+ }
1971
+ });
1972
+ }
1523
1973
  /**
1524
1974
  * Call the beta chat completions parse endpoint. This should only be called if
1525
1975
  * response_format is set to "json_object".
package/dist/types.d.ts CHANGED
@@ -12,6 +12,12 @@ export declare interface OpenAIBaseInput {
12
12
  * tokens as possible given the prompt and the model's maximum context size.
13
13
  */
14
14
  maxTokens?: number;
15
+ /**
16
+ * Maximum number of tokens to generate in the completion. -1 returns as many
17
+ * tokens as possible given the prompt and the model's maximum context size.
18
+ * Alias for `maxTokens` for reasoning models.
19
+ */
20
+ maxCompletionTokens?: number;
15
21
  /** Total probability mass of tokens to consider at each step */
16
22
  topP: number;
17
23
  /** Penalizes repeated tokens according to frequency */
@@ -140,7 +146,7 @@ export interface OpenAIChatInput extends OpenAIBaseInput {
140
146
  * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
141
147
  * Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
142
148
  */
143
- reasoningEffort?: OpenAIClient.ChatCompletionReasoningEffort;
149
+ reasoningEffort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
144
150
  }
145
151
  export declare interface AzureOpenAIInput {
146
152
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.4.3",
3
+ "version": "0.4.5-rc.0",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,7 +36,7 @@
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
38
  "js-tiktoken": "^1.0.12",
39
- "openai": "^4.77.0",
39
+ "openai": "^4.87.3",
40
40
  "zod": "^3.22.4",
41
41
  "zod-to-json-schema": "^3.22.3"
42
42
  },