@langchain/openai 0.4.5 → 0.4.7

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.
@@ -97,6 +97,318 @@ 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
+ };
130
+ }
131
+ return {
132
+ type: "function_call_output",
133
+ call_id: toolMessage.tool_call_id,
134
+ id: toolMessage.id,
135
+ output: typeof toolMessage.content !== "string"
136
+ ? JSON.stringify(toolMessage.content)
137
+ : toolMessage.content,
138
+ };
139
+ }
140
+ if (role === "assistant") {
141
+ const functionCallIds =
142
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
143
+ lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
144
+ if ((0, messages_1.isAIMessage)(lcMsg) && !!lcMsg.tool_calls?.length) {
145
+ return lcMsg.tool_calls.map((toolCall) => ({
146
+ type: "function_call",
147
+ name: toolCall.name,
148
+ arguments: JSON.stringify(toolCall.args),
149
+ call_id: toolCall.id,
150
+ // @ts-expect-error Might come from a non-Responses API message
151
+ id: functionCallIds?.[toolCall.id],
152
+ }));
153
+ }
154
+ if (lcMsg.additional_kwargs.tool_calls != null) {
155
+ return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
156
+ type: "function_call",
157
+ name: toolCall.function.name,
158
+ call_id: toolCall.id,
159
+ // @ts-expect-error Might come from a non-Responses API message
160
+ id: functionCallIds?.[toolCall.id],
161
+ arguments: toolCall.function.arguments,
162
+ }));
163
+ }
164
+ let { content } = lcMsg;
165
+ if (lcMsg.additional_kwargs.refusal != null) {
166
+ if (typeof content === "string") {
167
+ content = [{ type: "output_text", text: content, annotations: [] }];
168
+ }
169
+ content = [
170
+ ...content,
171
+ { type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
172
+ ];
173
+ }
174
+ return {
175
+ type: "message",
176
+ role: "assistant",
177
+ content: typeof content === "string"
178
+ ? content
179
+ : content.flatMap((item) => {
180
+ if (item.type === "text") {
181
+ return {
182
+ type: "output_text",
183
+ text: item.text,
184
+ // @ts-expect-error TODO: add types for `annotations`
185
+ annotations: item.annotations ?? [],
186
+ };
187
+ }
188
+ if (item.type === "output_text" || item.type === "refusal") {
189
+ return item;
190
+ }
191
+ return [];
192
+ }),
193
+ };
194
+ }
195
+ if (role === "user") {
196
+ return {
197
+ type: "message",
198
+ role: "user",
199
+ content: typeof lcMsg.content === "string"
200
+ ? lcMsg.content
201
+ : lcMsg.content.flatMap((item) => {
202
+ if (item.type === "text") {
203
+ return { type: "input_text", text: item.text };
204
+ }
205
+ if (item.type === "image_url") {
206
+ const image_url = typeof item.image_url === "string"
207
+ ? item.image_url
208
+ : item.image_url.url;
209
+ const detail = typeof item.image_url === "string"
210
+ ? "auto"
211
+ : item.image_url.detail;
212
+ return { type: "input_image", image_url, detail };
213
+ }
214
+ if (item.type === "input_text" ||
215
+ item.type === "input_image" ||
216
+ item.type === "input_file") {
217
+ return item;
218
+ }
219
+ return [];
220
+ }),
221
+ };
222
+ }
223
+ return [];
224
+ });
225
+ }
226
+ function _convertOpenAIResponsesMessageToBaseMessage(response) {
227
+ if (response.error) {
228
+ // TODO: add support for `addLangChainErrorFields`
229
+ const error = new Error(response.error.message);
230
+ error.name = response.error.code;
231
+ throw error;
232
+ }
233
+ const content = [];
234
+ const tool_calls = [];
235
+ const invalid_tool_calls = [];
236
+ const response_metadata = {
237
+ model: response.model,
238
+ created_at: response.created_at,
239
+ id: response.id,
240
+ incomplete_details: response.incomplete_details,
241
+ metadata: response.metadata,
242
+ object: response.object,
243
+ status: response.status,
244
+ user: response.user,
245
+ // for compatibility with chat completion calls.
246
+ model_name: response.model,
247
+ };
248
+ const additional_kwargs = {};
249
+ for (const item of response.output) {
250
+ if (item.type === "message") {
251
+ // TODO: how to handle refusals?
252
+ content.push(...item.content.map((part) => {
253
+ if (part.type === "output_text") {
254
+ if ("parsed" in part && part.parsed != null) {
255
+ additional_kwargs.parsed = part.parsed;
256
+ }
257
+ return {
258
+ type: "text",
259
+ text: part.text,
260
+ annotations: part.annotations,
261
+ };
262
+ }
263
+ return part;
264
+ }));
265
+ }
266
+ else if (item.type === "function_call") {
267
+ const fnAdapter = {
268
+ function: { name: item.name, arguments: item.arguments },
269
+ id: item.call_id,
270
+ };
271
+ try {
272
+ tool_calls.push((0, openai_tools_1.parseToolCall)(fnAdapter, { returnId: true }));
273
+ }
274
+ catch (e) {
275
+ let errMessage;
276
+ if (typeof e === "object" &&
277
+ e != null &&
278
+ "message" in e &&
279
+ typeof e.message === "string") {
280
+ errMessage = e.message;
281
+ }
282
+ invalid_tool_calls.push((0, openai_tools_1.makeInvalidToolCall)(fnAdapter, errMessage));
283
+ }
284
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] ??= {};
285
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY][item.call_id] = item.id;
286
+ }
287
+ else if (item.type === "reasoning") {
288
+ additional_kwargs.reasoning = item;
289
+ }
290
+ else {
291
+ additional_kwargs.tool_outputs ??= [];
292
+ additional_kwargs.tool_outputs.push(item);
293
+ }
294
+ }
295
+ return new messages_1.AIMessage({
296
+ id: response.id,
297
+ content,
298
+ tool_calls,
299
+ invalid_tool_calls,
300
+ usage_metadata: response.usage,
301
+ additional_kwargs,
302
+ response_metadata,
303
+ });
304
+ }
305
+ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
306
+ const content = [];
307
+ let generationInfo = {};
308
+ let usage_metadata;
309
+ const tool_call_chunks = [];
310
+ const response_metadata = {};
311
+ const additional_kwargs = {};
312
+ let id;
313
+ if (chunk.type === "response.output_text.delta") {
314
+ content.push({
315
+ type: "text",
316
+ text: chunk.delta,
317
+ index: chunk.content_index,
318
+ });
319
+ }
320
+ else if (chunk.type === "response.output_text.annotation.added") {
321
+ content.push({
322
+ type: "text",
323
+ text: "",
324
+ annotations: [chunk.annotation],
325
+ index: chunk.content_index,
326
+ });
327
+ }
328
+ else if (chunk.type === "response.output_item.added" &&
329
+ chunk.item.type === "message") {
330
+ id = chunk.item.id;
331
+ }
332
+ else if (chunk.type === "response.output_item.added" &&
333
+ chunk.item.type === "function_call") {
334
+ tool_call_chunks.push({
335
+ type: "tool_call_chunk",
336
+ name: chunk.item.name,
337
+ args: chunk.item.arguments,
338
+ id: chunk.item.id,
339
+ index: chunk.output_index,
340
+ });
341
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
342
+ [chunk.item.call_id]: chunk.item.id,
343
+ };
344
+ }
345
+ else if (chunk.type === "response.output_item.done" &&
346
+ (chunk.item.type === "web_search_call" ||
347
+ chunk.item.type === "file_search_call")) {
348
+ additional_kwargs.tool_outputs = [chunk.item];
349
+ }
350
+ else if (chunk.type === "response.created") {
351
+ response_metadata.id = chunk.response.id;
352
+ response_metadata.model_name = chunk.response.model;
353
+ response_metadata.model = chunk.response.model;
354
+ }
355
+ else if (chunk.type === "response.completed") {
356
+ const msg = _convertOpenAIResponsesMessageToBaseMessage(chunk.response);
357
+ usage_metadata = chunk.response.usage;
358
+ if (chunk.response.text?.format?.type === "json_schema") {
359
+ additional_kwargs.parsed ??= JSON.parse(msg.text);
360
+ }
361
+ for (const [key, value] of Object.entries(chunk.response)) {
362
+ if (key !== "id")
363
+ response_metadata[key] = value;
364
+ }
365
+ }
366
+ else if (chunk.type === "response.function_call_arguments.delta") {
367
+ tool_call_chunks.push({
368
+ type: "tool_call_chunk",
369
+ args: chunk.delta,
370
+ index: chunk.output_index,
371
+ });
372
+ }
373
+ else if (chunk.type === "response.web_search_call.completed" ||
374
+ chunk.type === "response.file_search_call.completed") {
375
+ generationInfo = {
376
+ tool_outputs: {
377
+ id: chunk.item_id,
378
+ type: chunk.type.replace("response.", "").replace(".completed", ""),
379
+ status: "completed",
380
+ },
381
+ };
382
+ }
383
+ else if (chunk.type === "response.refusal.done") {
384
+ additional_kwargs.refusal = chunk.refusal;
385
+ }
386
+ else {
387
+ return null;
388
+ }
389
+ return new outputs_1.ChatGenerationChunk({
390
+ // Legacy reasons, `onLLMNewToken` should pulls this out
391
+ text: content.map((part) => part.text).join(""),
392
+ message: new messages_1.AIMessageChunk({
393
+ id,
394
+ content,
395
+ tool_call_chunks,
396
+ usage_metadata,
397
+ additional_kwargs,
398
+ response_metadata,
399
+ }),
400
+ generationInfo,
401
+ });
402
+ }
403
+ function isBuiltInTool(tool) {
404
+ return "type" in tool && tool.type !== "function";
405
+ }
406
+ function isBuiltInToolChoice(tool_choice) {
407
+ return (tool_choice != null &&
408
+ typeof tool_choice === "object" &&
409
+ "type" in tool_choice &&
410
+ tool_choice.type !== "function");
411
+ }
100
412
  function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
101
413
  if ((0, base_1.isOpenAITool)(tool)) {
102
414
  if (fields?.strict !== undefined) {
@@ -904,6 +1216,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
904
1216
  writable: true,
905
1217
  value: void 0
906
1218
  });
1219
+ Object.defineProperty(this, "useResponsesApi", {
1220
+ enumerable: true,
1221
+ configurable: true,
1222
+ writable: true,
1223
+ value: false
1224
+ });
907
1225
  this.openAIApiKey =
908
1226
  fields?.apiKey ??
909
1227
  fields?.openAIApiKey ??
@@ -933,6 +1251,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
933
1251
  this.modalities = fields?.modalities;
934
1252
  this.reasoningEffort = fields?.reasoningEffort;
935
1253
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1254
+ this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
936
1255
  if (this.model === "o1") {
937
1256
  this.disableStreaming = true;
938
1257
  }
@@ -970,7 +1289,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
970
1289
  strict = this.supportsStrictToolCalling;
971
1290
  }
972
1291
  return this.bind({
973
- tools: tools.map((tool) => _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
1292
+ tools: tools.map((tool) => isBuiltInTool(tool)
1293
+ ? tool
1294
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
974
1295
  ...kwargs,
975
1296
  });
976
1297
  }
@@ -996,6 +1317,80 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
996
1317
  else if (this.supportsStrictToolCalling !== undefined) {
997
1318
  strict = this.supportsStrictToolCalling;
998
1319
  }
1320
+ if (this._useResponseApi(options)) {
1321
+ const params = {
1322
+ model: this.model,
1323
+ temperature: this.temperature,
1324
+ top_p: this.topP,
1325
+ user: this.user,
1326
+ // if include_usage is set or streamUsage then stream must be set to true.
1327
+ stream: this.streaming,
1328
+ previous_response_id: options?.previous_response_id,
1329
+ truncation: options?.truncation,
1330
+ include: options?.include,
1331
+ tools: options?.tools?.length
1332
+ ? options.tools
1333
+ .map((tool) => {
1334
+ if (isBuiltInTool(tool)) {
1335
+ return tool;
1336
+ }
1337
+ else if ((0, base_1.isOpenAITool)(tool)) {
1338
+ return {
1339
+ type: "function",
1340
+ name: tool.function.name,
1341
+ parameters: tool.function.parameters,
1342
+ description: tool.function.description,
1343
+ strict,
1344
+ };
1345
+ }
1346
+ else {
1347
+ return null;
1348
+ }
1349
+ })
1350
+ .filter((tool) => tool !== null)
1351
+ : undefined,
1352
+ tool_choice: isBuiltInToolChoice(options?.tool_choice)
1353
+ ? options?.tool_choice
1354
+ : (() => {
1355
+ const formatted = (0, openai_js_1.formatToOpenAIToolChoice)(options?.tool_choice);
1356
+ if (typeof formatted === "object" && "type" in formatted) {
1357
+ return { type: "function", name: formatted.function.name };
1358
+ }
1359
+ else {
1360
+ return undefined;
1361
+ }
1362
+ })(),
1363
+ text: (() => {
1364
+ if (options?.text)
1365
+ return options.text;
1366
+ const format = this.createResponseFormat(options?.response_format);
1367
+ if (format?.type === "json_schema") {
1368
+ if (format.json_schema.schema != null) {
1369
+ return {
1370
+ format: {
1371
+ type: "json_schema",
1372
+ schema: format.json_schema.schema,
1373
+ description: format.json_schema.description,
1374
+ name: format.json_schema.name,
1375
+ strict: format.json_schema.strict,
1376
+ },
1377
+ };
1378
+ }
1379
+ return undefined;
1380
+ }
1381
+ return { format };
1382
+ })(),
1383
+ parallel_tool_calls: options?.parallel_tool_calls,
1384
+ max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1385
+ ...this.modelKwargs,
1386
+ };
1387
+ const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1388
+ if (reasoningEffort !== undefined) {
1389
+ params.reasoning = { effort: reasoningEffort };
1390
+ }
1391
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1392
+ return params;
1393
+ }
999
1394
  let streamOptionsConfig = {};
1000
1395
  if (options?.stream_options !== undefined) {
1001
1396
  streamOptionsConfig = { stream_options: options.stream_options };
@@ -1049,6 +1444,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1049
1444
  else {
1050
1445
  params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
1051
1446
  }
1447
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1052
1448
  return params;
1053
1449
  }
1054
1450
  _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
@@ -1192,6 +1588,20 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1192
1588
  };
1193
1589
  }
1194
1590
  async *_streamResponseChunks(messages, options, runManager) {
1591
+ if (this._useResponseApi(options)) {
1592
+ const streamIterable = await this.responseApiWithRetry({
1593
+ ...this.invocationParams(options, { streaming: true }),
1594
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1595
+ stream: true,
1596
+ }, options);
1597
+ for await (const data of streamIterable) {
1598
+ const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
1599
+ if (chunk == null)
1600
+ continue;
1601
+ yield chunk;
1602
+ }
1603
+ return;
1604
+ }
1195
1605
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
1196
1606
  const params = {
1197
1607
  ...this.invocationParams(options, {
@@ -1296,7 +1706,60 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1296
1706
  return this._identifyingParams();
1297
1707
  }
1298
1708
  /** @ignore */
1709
+ async _responseApiGenerate(messages, options, runManager) {
1710
+ const invocationParams = this.invocationParams(options);
1711
+ if (invocationParams.stream) {
1712
+ const stream = this._streamResponseChunks(messages, options, runManager);
1713
+ let finalChunk;
1714
+ for await (const chunk of stream) {
1715
+ chunk.message.response_metadata = {
1716
+ ...chunk.generationInfo,
1717
+ ...chunk.message.response_metadata,
1718
+ };
1719
+ finalChunk = finalChunk?.concat(chunk) ?? chunk;
1720
+ }
1721
+ return {
1722
+ generations: finalChunk ? [finalChunk] : [],
1723
+ llmOutput: {
1724
+ estimatedTokenUsage: finalChunk?.message
1725
+ ?.usage_metadata,
1726
+ },
1727
+ };
1728
+ }
1729
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1730
+ const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
1731
+ return {
1732
+ generations: [
1733
+ {
1734
+ text: data.output_text,
1735
+ message: _convertOpenAIResponsesMessageToBaseMessage(data),
1736
+ },
1737
+ ],
1738
+ llmOutput: {
1739
+ id: data.id,
1740
+ estimatedTokenUsage: data.usage
1741
+ ? {
1742
+ promptTokens: data.usage.input_tokens,
1743
+ completionTokens: data.usage.output_tokens,
1744
+ totalTokens: data.usage.total_tokens,
1745
+ }
1746
+ : undefined,
1747
+ },
1748
+ };
1749
+ }
1750
+ _useResponseApi(options) {
1751
+ const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1752
+ const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1753
+ options?.text != null ||
1754
+ options?.truncation != null ||
1755
+ options?.include != null;
1756
+ return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1757
+ }
1758
+ /** @ignore */
1299
1759
  async _generate(messages, options, runManager) {
1760
+ if (this._useResponseApi(options)) {
1761
+ return this._responseApiGenerate(messages, options, runManager);
1762
+ }
1300
1763
  const usageMetadata = {};
1301
1764
  const params = this.invocationParams(options);
1302
1765
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
@@ -1534,6 +1997,22 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1534
1997
  }
1535
1998
  });
1536
1999
  }
2000
+ async responseApiWithRetry(request, options) {
2001
+ return this.caller.call(async () => {
2002
+ const requestOptions = this._getClientOptions(options);
2003
+ try {
2004
+ // use parse if dealing with json_schema
2005
+ if (request.text?.format?.type === "json_schema" && !request.stream) {
2006
+ return await this.client.responses.parse(request, requestOptions);
2007
+ }
2008
+ return await this.client.responses.create(request, requestOptions);
2009
+ }
2010
+ catch (e) {
2011
+ const error = (0, openai_js_1.wrapOpenAIClientError)(e);
2012
+ throw error;
2013
+ }
2014
+ });
2015
+ }
1537
2016
  /**
1538
2017
  * Call the beta chat completions parse endpoint. This should only be called if
1539
2018
  * response_format is set to "json_object".
@@ -1656,7 +2135,13 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1656
2135
  },
1657
2136
  });
1658
2137
  if (isZodSchema(schema)) {
1659
- outputParser = output_parsers_1.StructuredOutputParser.fromZodSchema(schema);
2138
+ const altParser = output_parsers_1.StructuredOutputParser.fromZodSchema(schema);
2139
+ outputParser = runnables_1.RunnableLambda.from((aiMessage) => {
2140
+ if ("parsed" in aiMessage.additional_kwargs) {
2141
+ return aiMessage.additional_kwargs.parsed;
2142
+ }
2143
+ return altParser;
2144
+ });
1660
2145
  }
1661
2146
  else {
1662
2147
  outputParser = new output_parsers_1.JsonOutputParser();
@@ -1744,12 +2229,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1744
2229
  const parsedWithFallback = parserAssign.withFallbacks({
1745
2230
  fallbacks: [parserNone],
1746
2231
  });
1747
- return runnables_1.RunnableSequence.from([
1748
- {
1749
- raw: llm,
1750
- },
1751
- parsedWithFallback,
1752
- ]);
2232
+ return runnables_1.RunnableSequence.from([{ raw: llm }, parsedWithFallback]);
1753
2233
  }
1754
2234
  }
1755
2235
  exports.ChatOpenAI = ChatOpenAI;
@@ -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".
@@ -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,318 @@ 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
+ };
125
+ }
126
+ return {
127
+ type: "function_call_output",
128
+ call_id: toolMessage.tool_call_id,
129
+ id: toolMessage.id,
130
+ output: typeof toolMessage.content !== "string"
131
+ ? JSON.stringify(toolMessage.content)
132
+ : toolMessage.content,
133
+ };
134
+ }
135
+ if (role === "assistant") {
136
+ const functionCallIds =
137
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
138
+ lcMsg.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY];
139
+ if (isAIMessage(lcMsg) && !!lcMsg.tool_calls?.length) {
140
+ return lcMsg.tool_calls.map((toolCall) => ({
141
+ type: "function_call",
142
+ name: toolCall.name,
143
+ arguments: JSON.stringify(toolCall.args),
144
+ call_id: toolCall.id,
145
+ // @ts-expect-error Might come from a non-Responses API message
146
+ id: functionCallIds?.[toolCall.id],
147
+ }));
148
+ }
149
+ if (lcMsg.additional_kwargs.tool_calls != null) {
150
+ return lcMsg.additional_kwargs.tool_calls.map((toolCall) => ({
151
+ type: "function_call",
152
+ name: toolCall.function.name,
153
+ call_id: toolCall.id,
154
+ // @ts-expect-error Might come from a non-Responses API message
155
+ id: functionCallIds?.[toolCall.id],
156
+ arguments: toolCall.function.arguments,
157
+ }));
158
+ }
159
+ let { content } = lcMsg;
160
+ if (lcMsg.additional_kwargs.refusal != null) {
161
+ if (typeof content === "string") {
162
+ content = [{ type: "output_text", text: content, annotations: [] }];
163
+ }
164
+ content = [
165
+ ...content,
166
+ { type: "refusal", refusal: lcMsg.additional_kwargs.refusal },
167
+ ];
168
+ }
169
+ return {
170
+ type: "message",
171
+ role: "assistant",
172
+ content: typeof content === "string"
173
+ ? content
174
+ : content.flatMap((item) => {
175
+ if (item.type === "text") {
176
+ return {
177
+ type: "output_text",
178
+ text: item.text,
179
+ // @ts-expect-error TODO: add types for `annotations`
180
+ annotations: item.annotations ?? [],
181
+ };
182
+ }
183
+ if (item.type === "output_text" || item.type === "refusal") {
184
+ return item;
185
+ }
186
+ return [];
187
+ }),
188
+ };
189
+ }
190
+ if (role === "user") {
191
+ return {
192
+ type: "message",
193
+ role: "user",
194
+ content: typeof lcMsg.content === "string"
195
+ ? lcMsg.content
196
+ : lcMsg.content.flatMap((item) => {
197
+ if (item.type === "text") {
198
+ return { type: "input_text", text: item.text };
199
+ }
200
+ if (item.type === "image_url") {
201
+ const image_url = typeof item.image_url === "string"
202
+ ? item.image_url
203
+ : item.image_url.url;
204
+ const detail = typeof item.image_url === "string"
205
+ ? "auto"
206
+ : item.image_url.detail;
207
+ return { type: "input_image", image_url, detail };
208
+ }
209
+ if (item.type === "input_text" ||
210
+ item.type === "input_image" ||
211
+ item.type === "input_file") {
212
+ return item;
213
+ }
214
+ return [];
215
+ }),
216
+ };
217
+ }
218
+ return [];
219
+ });
220
+ }
221
+ function _convertOpenAIResponsesMessageToBaseMessage(response) {
222
+ if (response.error) {
223
+ // TODO: add support for `addLangChainErrorFields`
224
+ const error = new Error(response.error.message);
225
+ error.name = response.error.code;
226
+ throw error;
227
+ }
228
+ const content = [];
229
+ const tool_calls = [];
230
+ const invalid_tool_calls = [];
231
+ const response_metadata = {
232
+ model: response.model,
233
+ created_at: response.created_at,
234
+ id: response.id,
235
+ incomplete_details: response.incomplete_details,
236
+ metadata: response.metadata,
237
+ object: response.object,
238
+ status: response.status,
239
+ user: response.user,
240
+ // for compatibility with chat completion calls.
241
+ model_name: response.model,
242
+ };
243
+ const additional_kwargs = {};
244
+ for (const item of response.output) {
245
+ if (item.type === "message") {
246
+ // TODO: how to handle refusals?
247
+ content.push(...item.content.map((part) => {
248
+ if (part.type === "output_text") {
249
+ if ("parsed" in part && part.parsed != null) {
250
+ additional_kwargs.parsed = part.parsed;
251
+ }
252
+ return {
253
+ type: "text",
254
+ text: part.text,
255
+ annotations: part.annotations,
256
+ };
257
+ }
258
+ return part;
259
+ }));
260
+ }
261
+ else if (item.type === "function_call") {
262
+ const fnAdapter = {
263
+ function: { name: item.name, arguments: item.arguments },
264
+ id: item.call_id,
265
+ };
266
+ try {
267
+ tool_calls.push(parseToolCall(fnAdapter, { returnId: true }));
268
+ }
269
+ catch (e) {
270
+ let errMessage;
271
+ if (typeof e === "object" &&
272
+ e != null &&
273
+ "message" in e &&
274
+ typeof e.message === "string") {
275
+ errMessage = e.message;
276
+ }
277
+ invalid_tool_calls.push(makeInvalidToolCall(fnAdapter, errMessage));
278
+ }
279
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] ??= {};
280
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY][item.call_id] = item.id;
281
+ }
282
+ else if (item.type === "reasoning") {
283
+ additional_kwargs.reasoning = item;
284
+ }
285
+ else {
286
+ additional_kwargs.tool_outputs ??= [];
287
+ additional_kwargs.tool_outputs.push(item);
288
+ }
289
+ }
290
+ return new AIMessage({
291
+ id: response.id,
292
+ content,
293
+ tool_calls,
294
+ invalid_tool_calls,
295
+ usage_metadata: response.usage,
296
+ additional_kwargs,
297
+ response_metadata,
298
+ });
299
+ }
300
+ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
301
+ const content = [];
302
+ let generationInfo = {};
303
+ let usage_metadata;
304
+ const tool_call_chunks = [];
305
+ const response_metadata = {};
306
+ const additional_kwargs = {};
307
+ let id;
308
+ if (chunk.type === "response.output_text.delta") {
309
+ content.push({
310
+ type: "text",
311
+ text: chunk.delta,
312
+ index: chunk.content_index,
313
+ });
314
+ }
315
+ else if (chunk.type === "response.output_text.annotation.added") {
316
+ content.push({
317
+ type: "text",
318
+ text: "",
319
+ annotations: [chunk.annotation],
320
+ index: chunk.content_index,
321
+ });
322
+ }
323
+ else if (chunk.type === "response.output_item.added" &&
324
+ chunk.item.type === "message") {
325
+ id = chunk.item.id;
326
+ }
327
+ else if (chunk.type === "response.output_item.added" &&
328
+ chunk.item.type === "function_call") {
329
+ tool_call_chunks.push({
330
+ type: "tool_call_chunk",
331
+ name: chunk.item.name,
332
+ args: chunk.item.arguments,
333
+ id: chunk.item.id,
334
+ index: chunk.output_index,
335
+ });
336
+ additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {
337
+ [chunk.item.call_id]: chunk.item.id,
338
+ };
339
+ }
340
+ else if (chunk.type === "response.output_item.done" &&
341
+ (chunk.item.type === "web_search_call" ||
342
+ chunk.item.type === "file_search_call")) {
343
+ additional_kwargs.tool_outputs = [chunk.item];
344
+ }
345
+ else if (chunk.type === "response.created") {
346
+ response_metadata.id = chunk.response.id;
347
+ response_metadata.model_name = chunk.response.model;
348
+ response_metadata.model = chunk.response.model;
349
+ }
350
+ else if (chunk.type === "response.completed") {
351
+ const msg = _convertOpenAIResponsesMessageToBaseMessage(chunk.response);
352
+ usage_metadata = chunk.response.usage;
353
+ if (chunk.response.text?.format?.type === "json_schema") {
354
+ additional_kwargs.parsed ??= JSON.parse(msg.text);
355
+ }
356
+ for (const [key, value] of Object.entries(chunk.response)) {
357
+ if (key !== "id")
358
+ response_metadata[key] = value;
359
+ }
360
+ }
361
+ else if (chunk.type === "response.function_call_arguments.delta") {
362
+ tool_call_chunks.push({
363
+ type: "tool_call_chunk",
364
+ args: chunk.delta,
365
+ index: chunk.output_index,
366
+ });
367
+ }
368
+ else if (chunk.type === "response.web_search_call.completed" ||
369
+ chunk.type === "response.file_search_call.completed") {
370
+ generationInfo = {
371
+ tool_outputs: {
372
+ id: chunk.item_id,
373
+ type: chunk.type.replace("response.", "").replace(".completed", ""),
374
+ status: "completed",
375
+ },
376
+ };
377
+ }
378
+ else if (chunk.type === "response.refusal.done") {
379
+ additional_kwargs.refusal = chunk.refusal;
380
+ }
381
+ else {
382
+ return null;
383
+ }
384
+ return new ChatGenerationChunk({
385
+ // Legacy reasons, `onLLMNewToken` should pulls this out
386
+ text: content.map((part) => part.text).join(""),
387
+ message: new AIMessageChunk({
388
+ id,
389
+ content,
390
+ tool_call_chunks,
391
+ usage_metadata,
392
+ additional_kwargs,
393
+ response_metadata,
394
+ }),
395
+ generationInfo,
396
+ });
397
+ }
398
+ function isBuiltInTool(tool) {
399
+ return "type" in tool && tool.type !== "function";
400
+ }
401
+ function isBuiltInToolChoice(tool_choice) {
402
+ return (tool_choice != null &&
403
+ typeof tool_choice === "object" &&
404
+ "type" in tool_choice &&
405
+ tool_choice.type !== "function");
406
+ }
95
407
  function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
96
408
  if (isOpenAITool(tool)) {
97
409
  if (fields?.strict !== undefined) {
@@ -899,6 +1211,12 @@ export class ChatOpenAI extends BaseChatModel {
899
1211
  writable: true,
900
1212
  value: void 0
901
1213
  });
1214
+ Object.defineProperty(this, "useResponsesApi", {
1215
+ enumerable: true,
1216
+ configurable: true,
1217
+ writable: true,
1218
+ value: false
1219
+ });
902
1220
  this.openAIApiKey =
903
1221
  fields?.apiKey ??
904
1222
  fields?.openAIApiKey ??
@@ -928,6 +1246,7 @@ export class ChatOpenAI extends BaseChatModel {
928
1246
  this.modalities = fields?.modalities;
929
1247
  this.reasoningEffort = fields?.reasoningEffort;
930
1248
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1249
+ this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
931
1250
  if (this.model === "o1") {
932
1251
  this.disableStreaming = true;
933
1252
  }
@@ -965,7 +1284,9 @@ export class ChatOpenAI extends BaseChatModel {
965
1284
  strict = this.supportsStrictToolCalling;
966
1285
  }
967
1286
  return this.bind({
968
- tools: tools.map((tool) => _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
1287
+ tools: tools.map((tool) => isBuiltInTool(tool)
1288
+ ? tool
1289
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })),
969
1290
  ...kwargs,
970
1291
  });
971
1292
  }
@@ -991,6 +1312,80 @@ export class ChatOpenAI extends BaseChatModel {
991
1312
  else if (this.supportsStrictToolCalling !== undefined) {
992
1313
  strict = this.supportsStrictToolCalling;
993
1314
  }
1315
+ if (this._useResponseApi(options)) {
1316
+ const params = {
1317
+ model: this.model,
1318
+ temperature: this.temperature,
1319
+ top_p: this.topP,
1320
+ user: this.user,
1321
+ // if include_usage is set or streamUsage then stream must be set to true.
1322
+ stream: this.streaming,
1323
+ previous_response_id: options?.previous_response_id,
1324
+ truncation: options?.truncation,
1325
+ include: options?.include,
1326
+ tools: options?.tools?.length
1327
+ ? options.tools
1328
+ .map((tool) => {
1329
+ if (isBuiltInTool(tool)) {
1330
+ return tool;
1331
+ }
1332
+ else if (isOpenAITool(tool)) {
1333
+ return {
1334
+ type: "function",
1335
+ name: tool.function.name,
1336
+ parameters: tool.function.parameters,
1337
+ description: tool.function.description,
1338
+ strict,
1339
+ };
1340
+ }
1341
+ else {
1342
+ return null;
1343
+ }
1344
+ })
1345
+ .filter((tool) => tool !== null)
1346
+ : undefined,
1347
+ tool_choice: isBuiltInToolChoice(options?.tool_choice)
1348
+ ? options?.tool_choice
1349
+ : (() => {
1350
+ const formatted = formatToOpenAIToolChoice(options?.tool_choice);
1351
+ if (typeof formatted === "object" && "type" in formatted) {
1352
+ return { type: "function", name: formatted.function.name };
1353
+ }
1354
+ else {
1355
+ return undefined;
1356
+ }
1357
+ })(),
1358
+ text: (() => {
1359
+ if (options?.text)
1360
+ return options.text;
1361
+ const format = this.createResponseFormat(options?.response_format);
1362
+ if (format?.type === "json_schema") {
1363
+ if (format.json_schema.schema != null) {
1364
+ return {
1365
+ format: {
1366
+ type: "json_schema",
1367
+ schema: format.json_schema.schema,
1368
+ description: format.json_schema.description,
1369
+ name: format.json_schema.name,
1370
+ strict: format.json_schema.strict,
1371
+ },
1372
+ };
1373
+ }
1374
+ return undefined;
1375
+ }
1376
+ return { format };
1377
+ })(),
1378
+ parallel_tool_calls: options?.parallel_tool_calls,
1379
+ max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1380
+ ...this.modelKwargs,
1381
+ };
1382
+ const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1383
+ if (reasoningEffort !== undefined) {
1384
+ params.reasoning = { effort: reasoningEffort };
1385
+ }
1386
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1387
+ return params;
1388
+ }
994
1389
  let streamOptionsConfig = {};
995
1390
  if (options?.stream_options !== undefined) {
996
1391
  streamOptionsConfig = { stream_options: options.stream_options };
@@ -1044,6 +1439,7 @@ export class ChatOpenAI extends BaseChatModel {
1044
1439
  else {
1045
1440
  params.max_tokens = this.maxTokens === -1 ? undefined : this.maxTokens;
1046
1441
  }
1442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1047
1443
  return params;
1048
1444
  }
1049
1445
  _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
@@ -1187,6 +1583,20 @@ export class ChatOpenAI extends BaseChatModel {
1187
1583
  };
1188
1584
  }
1189
1585
  async *_streamResponseChunks(messages, options, runManager) {
1586
+ if (this._useResponseApi(options)) {
1587
+ const streamIterable = await this.responseApiWithRetry({
1588
+ ...this.invocationParams(options, { streaming: true }),
1589
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1590
+ stream: true,
1591
+ }, options);
1592
+ for await (const data of streamIterable) {
1593
+ const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
1594
+ if (chunk == null)
1595
+ continue;
1596
+ yield chunk;
1597
+ }
1598
+ return;
1599
+ }
1190
1600
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
1191
1601
  const params = {
1192
1602
  ...this.invocationParams(options, {
@@ -1291,7 +1701,60 @@ export class ChatOpenAI extends BaseChatModel {
1291
1701
  return this._identifyingParams();
1292
1702
  }
1293
1703
  /** @ignore */
1704
+ async _responseApiGenerate(messages, options, runManager) {
1705
+ const invocationParams = this.invocationParams(options);
1706
+ if (invocationParams.stream) {
1707
+ const stream = this._streamResponseChunks(messages, options, runManager);
1708
+ let finalChunk;
1709
+ for await (const chunk of stream) {
1710
+ chunk.message.response_metadata = {
1711
+ ...chunk.generationInfo,
1712
+ ...chunk.message.response_metadata,
1713
+ };
1714
+ finalChunk = finalChunk?.concat(chunk) ?? chunk;
1715
+ }
1716
+ return {
1717
+ generations: finalChunk ? [finalChunk] : [],
1718
+ llmOutput: {
1719
+ estimatedTokenUsage: finalChunk?.message
1720
+ ?.usage_metadata,
1721
+ },
1722
+ };
1723
+ }
1724
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1725
+ const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
1726
+ return {
1727
+ generations: [
1728
+ {
1729
+ text: data.output_text,
1730
+ message: _convertOpenAIResponsesMessageToBaseMessage(data),
1731
+ },
1732
+ ],
1733
+ llmOutput: {
1734
+ id: data.id,
1735
+ estimatedTokenUsage: data.usage
1736
+ ? {
1737
+ promptTokens: data.usage.input_tokens,
1738
+ completionTokens: data.usage.output_tokens,
1739
+ totalTokens: data.usage.total_tokens,
1740
+ }
1741
+ : undefined,
1742
+ },
1743
+ };
1744
+ }
1745
+ _useResponseApi(options) {
1746
+ const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1747
+ const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1748
+ options?.text != null ||
1749
+ options?.truncation != null ||
1750
+ options?.include != null;
1751
+ return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1752
+ }
1753
+ /** @ignore */
1294
1754
  async _generate(messages, options, runManager) {
1755
+ if (this._useResponseApi(options)) {
1756
+ return this._responseApiGenerate(messages, options, runManager);
1757
+ }
1295
1758
  const usageMetadata = {};
1296
1759
  const params = this.invocationParams(options);
1297
1760
  const messagesMapped = _convertMessagesToOpenAIParams(messages, this.model);
@@ -1529,6 +1992,22 @@ export class ChatOpenAI extends BaseChatModel {
1529
1992
  }
1530
1993
  });
1531
1994
  }
1995
+ async responseApiWithRetry(request, options) {
1996
+ return this.caller.call(async () => {
1997
+ const requestOptions = this._getClientOptions(options);
1998
+ try {
1999
+ // use parse if dealing with json_schema
2000
+ if (request.text?.format?.type === "json_schema" && !request.stream) {
2001
+ return await this.client.responses.parse(request, requestOptions);
2002
+ }
2003
+ return await this.client.responses.create(request, requestOptions);
2004
+ }
2005
+ catch (e) {
2006
+ const error = wrapOpenAIClientError(e);
2007
+ throw error;
2008
+ }
2009
+ });
2010
+ }
1532
2011
  /**
1533
2012
  * Call the beta chat completions parse endpoint. This should only be called if
1534
2013
  * response_format is set to "json_object".
@@ -1651,7 +2130,13 @@ export class ChatOpenAI extends BaseChatModel {
1651
2130
  },
1652
2131
  });
1653
2132
  if (isZodSchema(schema)) {
1654
- outputParser = StructuredOutputParser.fromZodSchema(schema);
2133
+ const altParser = StructuredOutputParser.fromZodSchema(schema);
2134
+ outputParser = RunnableLambda.from((aiMessage) => {
2135
+ if ("parsed" in aiMessage.additional_kwargs) {
2136
+ return aiMessage.additional_kwargs.parsed;
2137
+ }
2138
+ return altParser;
2139
+ });
1655
2140
  }
1656
2141
  else {
1657
2142
  outputParser = new JsonOutputParser();
@@ -1739,12 +2224,7 @@ export class ChatOpenAI extends BaseChatModel {
1739
2224
  const parsedWithFallback = parserAssign.withFallbacks({
1740
2225
  fallbacks: [parserNone],
1741
2226
  });
1742
- return RunnableSequence.from([
1743
- {
1744
- raw: llm,
1745
- },
1746
- parsedWithFallback,
1747
- ]);
2227
+ return RunnableSequence.from([{ raw: llm }, parsedWithFallback]);
1748
2228
  }
1749
2229
  }
1750
2230
  function isZodSchema(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {