@langchain/openai 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -232,8 +232,32 @@ function _convertMessagesToOpenAIParams(messages, model) {
232
232
  }
233
233
  exports._convertMessagesToOpenAIParams = _convertMessagesToOpenAIParams;
234
234
  const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
235
- function _convertMessagesToOpenAIResponsesParams(messages, model) {
236
- return messages.flatMap((lcMsg) => {
235
+ function _convertReasoningSummaryToOpenAIResponsesParams(reasoning) {
236
+ // combine summary parts that have the the same index and then remove the indexes
237
+ const summary = (reasoning.summary.length > 1
238
+ ? reasoning.summary.reduce((acc, curr) => {
239
+ const last = acc.at(-1);
240
+ if (last.index === curr.index) {
241
+ last.text += curr.text;
242
+ }
243
+ else {
244
+ acc.push(curr);
245
+ }
246
+ return acc;
247
+ }, [{ ...reasoning.summary[0] }])
248
+ : reasoning.summary).map((s) => Object.fromEntries(Object.entries(s).filter(([k]) => k !== "index")));
249
+ return {
250
+ ...reasoning,
251
+ summary,
252
+ };
253
+ }
254
+ function _convertMessagesToOpenAIResponsesParams(messages, model, zdrEnabled) {
255
+ const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
256
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
257
+ const newMessages = lastAIMessageId && lastAIMessageId.startsWith("resp_") && !zdrEnabled
258
+ ? messages.slice(messages.indexOf(lastAIMessage) + 1)
259
+ : messages;
260
+ return newMessages.flatMap((lcMsg) => {
237
261
  let role = messageToOpenAIRole(lcMsg);
238
262
  if (role === "system" && isReasoningModel(model))
239
263
  role = "developer";
@@ -283,15 +307,25 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
283
307
  };
284
308
  }
285
309
  if (role === "assistant") {
310
+ // if we have the original response items, just reuse them
311
+ if (!zdrEnabled &&
312
+ lcMsg.response_metadata.output != null &&
313
+ Array.isArray(lcMsg.response_metadata.output) &&
314
+ lcMsg.response_metadata.output.length > 0 &&
315
+ lcMsg.response_metadata.output.every((item) => "type" in item)) {
316
+ return lcMsg.response_metadata.output;
317
+ }
318
+ // otherwise, try to reconstruct the response from what we have
286
319
  const input = [];
287
320
  // reasoning items
288
- if (lcMsg.additional_kwargs.reasoning != null) {
321
+ if (!zdrEnabled && lcMsg.additional_kwargs.reasoning != null) {
289
322
  const isReasoningItem = (item) => typeof item === "object" &&
290
323
  item != null &&
291
324
  "type" in item &&
292
325
  item.type === "reasoning";
293
326
  if (isReasoningItem(lcMsg.additional_kwargs.reasoning)) {
294
- input.push(lcMsg.additional_kwargs.reasoning);
327
+ const reasoningItem = _convertReasoningSummaryToOpenAIResponsesParams(lcMsg.additional_kwargs.reasoning);
328
+ input.push(reasoningItem);
295
329
  }
296
330
  }
297
331
  // ai content
@@ -308,6 +342,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
308
342
  input.push({
309
343
  type: "message",
310
344
  role: "assistant",
345
+ ...(lcMsg.id && !zdrEnabled ? { id: lcMsg.id } : {}),
311
346
  content: typeof content === "string"
312
347
  ? content
313
348
  : content.flatMap((item) => {
@@ -335,7 +370,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
335
370
  name: toolCall.name,
336
371
  arguments: JSON.stringify(toolCall.args),
337
372
  call_id: toolCall.id,
338
- id: functionCallIds?.[toolCall.id],
373
+ ...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
339
374
  })));
340
375
  }
341
376
  else if (lcMsg.additional_kwargs.tool_calls != null) {
@@ -343,21 +378,17 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
343
378
  type: "function_call",
344
379
  name: toolCall.function.name,
345
380
  call_id: toolCall.id,
346
- id: functionCallIds?.[toolCall.id],
381
+ ...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
347
382
  arguments: toolCall.function.arguments,
348
383
  })));
349
384
  }
350
385
  const toolOutputs = lcMsg.response_metadata.output?.length
351
386
  ? lcMsg.response_metadata.output
352
387
  : lcMsg.additional_kwargs.tool_outputs;
388
+ let computerCalls = [];
353
389
  if (toolOutputs != null) {
354
390
  const castToolOutputs = toolOutputs;
355
- const reasoningCalls = castToolOutputs?.filter((item) => item.type === "reasoning");
356
- const computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
357
- // NOTE: Reasoning outputs must be passed to the model BEFORE computer calls.
358
- if (reasoningCalls.length > 0 && computerCalls.length > 0) {
359
- input.push(...reasoningCalls);
360
- }
391
+ computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
361
392
  if (computerCalls.length > 0)
362
393
  input.push(...computerCalls);
363
394
  }
@@ -561,6 +592,39 @@ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
561
592
  else if (chunk.type === "response.refusal.done") {
562
593
  additional_kwargs.refusal = chunk.refusal;
563
594
  }
595
+ else if (chunk.type === "response.output_item.added" &&
596
+ "item" in chunk &&
597
+ chunk.item.type === "reasoning") {
598
+ const summary = chunk
599
+ .item.summary
600
+ ? chunk.item.summary.map((s, index) => ({
601
+ ...s,
602
+ index,
603
+ }))
604
+ : undefined;
605
+ additional_kwargs.reasoning = {
606
+ // We only capture ID in the first chunk or else the concatenated result of all chunks will
607
+ // have an ID field that is repeated once per chunk. There is special handling for the `type`
608
+ // field that prevents this, however.
609
+ id: chunk.item.id,
610
+ type: chunk.item.type,
611
+ ...(summary ? { summary } : {}),
612
+ };
613
+ }
614
+ else if (chunk.type === "response.reasoning_summary_part.added") {
615
+ additional_kwargs.reasoning = {
616
+ type: "reasoning",
617
+ summary: [{ ...chunk.part, index: chunk.summary_index }],
618
+ };
619
+ }
620
+ else if (chunk.type === "response.reasoning_summary_text.delta") {
621
+ additional_kwargs.reasoning = {
622
+ type: "reasoning",
623
+ summary: [
624
+ { text: chunk.delta, type: "summary_text", index: chunk.summary_index },
625
+ ],
626
+ };
627
+ }
564
628
  else {
565
629
  return null;
566
630
  }
@@ -603,9 +667,7 @@ function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
603
667
  return (0, tools_js_1._convertToOpenAITool)(tool, fields);
604
668
  }
605
669
  function isReasoningModel(model) {
606
- return (model?.startsWith("o1") ||
607
- model?.startsWith("o3") ||
608
- model?.startsWith("o4"));
670
+ return model && /^o\d/.test(model);
609
671
  }
610
672
  /**
611
673
  * OpenAI chat model integration.
@@ -1213,6 +1275,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1213
1275
  "tags",
1214
1276
  "metadata",
1215
1277
  "disableStreaming",
1278
+ "useResponsesApi",
1279
+ "zdrEnabled",
1280
+ "reasoning",
1216
1281
  ];
1217
1282
  }
1218
1283
  constructor(fields) {
@@ -1390,18 +1455,51 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1390
1455
  writable: true,
1391
1456
  value: void 0
1392
1457
  });
1458
+ /**
1459
+ * @deprecated Use {@link reasoning} object instead.
1460
+ */
1393
1461
  Object.defineProperty(this, "reasoningEffort", {
1394
1462
  enumerable: true,
1395
1463
  configurable: true,
1396
1464
  writable: true,
1397
1465
  value: void 0
1398
1466
  });
1467
+ /**
1468
+ * Options for reasoning models.
1469
+ */
1470
+ Object.defineProperty(this, "reasoning", {
1471
+ enumerable: true,
1472
+ configurable: true,
1473
+ writable: true,
1474
+ value: void 0
1475
+ });
1476
+ /**
1477
+ * Whether to use the responses API for all requests. If `false` the responses API will be used
1478
+ * only when required in order to fulfill the request.
1479
+ */
1399
1480
  Object.defineProperty(this, "useResponsesApi", {
1400
1481
  enumerable: true,
1401
1482
  configurable: true,
1402
1483
  writable: true,
1403
1484
  value: false
1404
1485
  });
1486
+ /**
1487
+ * Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable
1488
+ * output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your
1489
+ * OpenAI organization or project. This must be configured directly with OpenAI.
1490
+ *
1491
+ * See:
1492
+ * https://help.openai.com/en/articles/10503543-data-residency-for-the-openai-api
1493
+ * https://platform.openai.com/docs/api-reference/responses/create#responses-create-store
1494
+ *
1495
+ * @default false
1496
+ */
1497
+ Object.defineProperty(this, "zdrEnabled", {
1498
+ enumerable: true,
1499
+ configurable: true,
1500
+ writable: true,
1501
+ value: void 0
1502
+ });
1405
1503
  this.openAIApiKey =
1406
1504
  fields?.apiKey ??
1407
1505
  fields?.openAIApiKey ??
@@ -1429,7 +1527,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1429
1527
  this.__includeRawResponse = fields?.__includeRawResponse;
1430
1528
  this.audio = fields?.audio;
1431
1529
  this.modalities = fields?.modalities;
1432
- this.reasoningEffort = fields?.reasoningEffort;
1530
+ this.reasoningEffort = fields?.reasoningEffort ?? fields?.reasoning?.effort;
1531
+ this.reasoning =
1532
+ fields?.reasoning ??
1533
+ (fields?.reasoningEffort
1534
+ ? { effort: fields.reasoningEffort }
1535
+ : undefined);
1433
1536
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1434
1537
  this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
1435
1538
  this.disableStreaming = fields?.disableStreaming ?? this.disableStreaming;
@@ -1452,6 +1555,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1452
1555
  if (fields?.supportsStrictToolCalling !== undefined) {
1453
1556
  this.supportsStrictToolCalling = fields.supportsStrictToolCalling;
1454
1557
  }
1558
+ this.zdrEnabled = fields?.zdrEnabled ?? false;
1455
1559
  }
1456
1560
  getLsParams(options) {
1457
1561
  const params = this.invocationParams(options);
@@ -1490,6 +1594,35 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1490
1594
  }
1491
1595
  return resFormat;
1492
1596
  }
1597
+ getReasoningParams(options) {
1598
+ if (!isReasoningModel(this.model)) {
1599
+ return;
1600
+ }
1601
+ let reasoning;
1602
+ // apply reasoning options in reverse order of priority without else blocks so that highest priority overrides lowest priority
1603
+ if (this.reasoningEffort !== undefined) {
1604
+ reasoning = { effort: this.reasoningEffort };
1605
+ }
1606
+ if (this.reasoning !== undefined) {
1607
+ reasoning = {
1608
+ ...reasoning,
1609
+ ...this.reasoning,
1610
+ };
1611
+ }
1612
+ if (options?.reasoning_effort !== undefined) {
1613
+ reasoning = {
1614
+ ...reasoning,
1615
+ effort: options.reasoning_effort,
1616
+ };
1617
+ }
1618
+ if (options?.reasoning !== undefined) {
1619
+ reasoning = {
1620
+ ...reasoning,
1621
+ ...options.reasoning,
1622
+ };
1623
+ }
1624
+ return reasoning;
1625
+ }
1493
1626
  /**
1494
1627
  * Get the parameters used to invoke the model
1495
1628
  */
@@ -1566,11 +1699,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1566
1699
  })(),
1567
1700
  parallel_tool_calls: options?.parallel_tool_calls,
1568
1701
  max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1702
+ ...(this.zdrEnabled ? { store: false } : {}),
1569
1703
  ...this.modelKwargs,
1570
1704
  };
1571
- const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1572
- if (reasoningEffort !== undefined) {
1573
- params.reasoning = { effort: reasoningEffort };
1705
+ const reasoning = this.getReasoningParams(options);
1706
+ if (reasoning !== undefined) {
1707
+ params.reasoning = reasoning;
1574
1708
  }
1575
1709
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1576
1710
  return params;
@@ -1617,9 +1751,9 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1617
1751
  if (options?.prediction !== undefined) {
1618
1752
  params.prediction = options.prediction;
1619
1753
  }
1620
- const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1621
- if (reasoningEffort !== undefined) {
1622
- params.reasoning_effort = reasoningEffort;
1754
+ const reasoning = this.getReasoningParams(options);
1755
+ if (reasoning !== undefined && reasoning.effort !== undefined) {
1756
+ params.reasoning_effort = reasoning.effort;
1623
1757
  }
1624
1758
  if (isReasoningModel(params.model)) {
1625
1759
  params.max_completion_tokens =
@@ -1773,10 +1907,17 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1773
1907
  }
1774
1908
  async *_streamResponseChunks(messages, options, runManager) {
1775
1909
  if (this._useResponseApi(options)) {
1910
+ const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
1911
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
1776
1912
  const streamIterable = await this.responseApiWithRetry({
1777
1913
  ...this.invocationParams(options, { streaming: true }),
1778
- input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1914
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled),
1779
1915
  stream: true,
1916
+ ...(lastAIMessageId &&
1917
+ lastAIMessageId.startsWith("resp_") &&
1918
+ !this.zdrEnabled
1919
+ ? { previous_response_id: lastAIMessageId }
1920
+ : {}),
1780
1921
  }, options);
1781
1922
  for await (const data of streamIterable) {
1782
1923
  const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
@@ -1910,8 +2051,18 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1910
2051
  },
1911
2052
  };
1912
2053
  }
1913
- const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1914
- const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
2054
+ const lastAIMessage = messages.filter((m) => (0, messages_1.isAIMessage)(m)).pop();
2055
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
2056
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled);
2057
+ const data = await this.responseApiWithRetry({
2058
+ input,
2059
+ ...invocationParams,
2060
+ ...(lastAIMessageId &&
2061
+ lastAIMessageId.startsWith("resp_") &&
2062
+ !this.zdrEnabled
2063
+ ? { previous_response_id: lastAIMessageId }
2064
+ : {}),
2065
+ }, { signal: options?.signal, ...options?.options });
1915
2066
  return {
1916
2067
  generations: [
1917
2068
  {
@@ -1931,12 +2082,23 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1931
2082
  },
1932
2083
  };
1933
2084
  }
2085
+ /**
2086
+ * Determines whether the responses API should be used for the given request.
2087
+ *
2088
+ * @internal
2089
+ *
2090
+ * @param options The parsed call options for the request.
2091
+ * @returns `true` if the responses API should be used, either because it is explicitly enabled,
2092
+ * or because the request requires it.
2093
+ */
1934
2094
  _useResponseApi(options) {
1935
2095
  const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1936
2096
  const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1937
2097
  options?.text != null ||
1938
2098
  options?.truncation != null ||
1939
- options?.include != null;
2099
+ options?.include != null ||
2100
+ options?.reasoning?.summary != null ||
2101
+ this.reasoning?.summary != null;
1940
2102
  return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1941
2103
  }
1942
2104
  /** @ignore */
@@ -6,7 +6,7 @@ import { BaseChatModel, type BindToolsInput, type LangSmithParams, type BaseChat
6
6
  import { type BaseFunctionCallOptions, type BaseLanguageModelInput, type StructuredOutputMethodOptions } from "@langchain/core/language_models/base";
7
7
  import { z } from "zod";
8
8
  import { Runnable } from "@langchain/core/runnables";
9
- import type { OpenAICallOptions, OpenAIChatInput, OpenAICoreRequestOptions, ChatOpenAIResponseFormat } from "./types.js";
9
+ import { type OpenAICallOptions, type OpenAIChatInput, type OpenAICoreRequestOptions, type ChatOpenAIResponseFormat } from "./types.js";
10
10
  import { OpenAIToolChoice } from "./utils/openai.js";
11
11
  export type { OpenAICallOptions, OpenAIChatInput };
12
12
  interface TokenUsage {
@@ -117,29 +117,54 @@ export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCa
117
117
  /**
118
118
  * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
119
119
  * Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
120
+ *
121
+ * @deprecated Use {@link reasoning} object instead.
120
122
  */
121
123
  reasoning_effort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
124
+ /**
125
+ * Options for reasoning models.
126
+ *
127
+ * Note that some options, like reasoning summaries, are only available when using the responses
128
+ * API. If these options are set, the responses API will be used to fulfill the request.
129
+ *
130
+ * These options will be ignored when not using a reasoning model.
131
+ */
132
+ reasoning?: OpenAIClient.Reasoning;
122
133
  /**
123
134
  * Configuration options for a text response from the model. Can be plain text or
124
135
  * structured JSON data.
136
+ *
137
+ * If set, the Responses API will be used to fulfill the request.
125
138
  */
126
139
  text?: ResponsesCreateParams["text"];
127
140
  /**
128
141
  * The truncation strategy to use for the model response.
142
+ *
143
+ * If set, the Responses API will be used to fulfill the request.
129
144
  */
130
145
  truncation?: ResponsesCreateParams["truncation"];
131
146
  /**
132
147
  * Specify additional output data to include in the model response.
148
+ *
149
+ * If set, the Responses API will be used to fulfill the request.
133
150
  */
134
151
  include?: ResponsesCreateParams["include"];
135
152
  /**
136
- * The unique ID of the previous response to the model. Use this to create
137
- * multi-turn conversations.
153
+ * The unique ID of the previous response to the model. Use this to create multi-turn
154
+ * conversations. Will be set automatically if {@link ChatOpenAI.zdrEnabled} is `false`, provided
155
+ * that AIMessages included in the request have a `response_metadata.id` property. Ignored unless
156
+ * {@link ChatOpenAI.useResponsesApi} is `true`.
157
+ *
158
+ * If set, the Responses API will be used to fulfill the request.
138
159
  */
139
160
  previous_response_id?: ResponsesCreateParams["previous_response_id"];
140
161
  }
141
162
  export interface ChatOpenAIFields extends Partial<OpenAIChatInput>, BaseChatModelParams {
142
163
  configuration?: ClientOptions;
164
+ /**
165
+ * Whether to use the responses API for all requests. If `false` the responses API will be used
166
+ * only when required in order to fulfill the request.
167
+ */
143
168
  useResponsesApi?: boolean;
144
169
  }
145
170
  /**
@@ -721,12 +746,36 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
721
746
  supportsStrictToolCalling?: boolean;
722
747
  audio?: OpenAIClient.Chat.ChatCompletionAudioParam;
723
748
  modalities?: Array<OpenAIClient.Chat.ChatCompletionModality>;
749
+ /**
750
+ * @deprecated Use {@link reasoning} object instead.
751
+ */
724
752
  reasoningEffort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
753
+ /**
754
+ * Options for reasoning models.
755
+ */
756
+ reasoning?: OpenAIClient.Reasoning;
757
+ /**
758
+ * Whether to use the responses API for all requests. If `false` the responses API will be used
759
+ * only when required in order to fulfill the request.
760
+ */
725
761
  useResponsesApi: boolean;
762
+ /**
763
+ * Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable
764
+ * output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your
765
+ * OpenAI organization or project. This must be configured directly with OpenAI.
766
+ *
767
+ * See:
768
+ * https://help.openai.com/en/articles/10503543-data-residency-for-the-openai-api
769
+ * https://platform.openai.com/docs/api-reference/responses/create#responses-create-store
770
+ *
771
+ * @default false
772
+ */
773
+ zdrEnabled?: boolean | undefined;
726
774
  constructor(fields?: ChatOpenAIFields);
727
775
  getLsParams(options: this["ParsedCallOptions"]): LangSmithParams;
728
776
  bindTools(tools: ChatOpenAIToolType[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
729
777
  private createResponseFormat;
778
+ private getReasoningParams;
730
779
  /**
731
780
  * Get the parameters used to invoke the model
732
781
  */
@@ -749,6 +798,15 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
749
798
  } & ClientOptions;
750
799
  /** @ignore */
751
800
  _responseApiGenerate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
801
+ /**
802
+ * Determines whether the responses API should be used for the given request.
803
+ *
804
+ * @internal
805
+ *
806
+ * @param options The parsed call options for the request.
807
+ * @returns `true` if the responses API should be used, either because it is explicitly enabled,
808
+ * or because the request requires it.
809
+ */
752
810
  protected _useResponseApi(options: this["ParsedCallOptions"] | undefined): boolean;
753
811
  /** @ignore */
754
812
  _generate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
@@ -227,8 +227,32 @@ export function _convertMessagesToOpenAIParams(messages, model) {
227
227
  });
228
228
  }
229
229
  const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
230
- function _convertMessagesToOpenAIResponsesParams(messages, model) {
231
- return messages.flatMap((lcMsg) => {
230
+ function _convertReasoningSummaryToOpenAIResponsesParams(reasoning) {
231
+ // combine summary parts that have the the same index and then remove the indexes
232
+ const summary = (reasoning.summary.length > 1
233
+ ? reasoning.summary.reduce((acc, curr) => {
234
+ const last = acc.at(-1);
235
+ if (last.index === curr.index) {
236
+ last.text += curr.text;
237
+ }
238
+ else {
239
+ acc.push(curr);
240
+ }
241
+ return acc;
242
+ }, [{ ...reasoning.summary[0] }])
243
+ : reasoning.summary).map((s) => Object.fromEntries(Object.entries(s).filter(([k]) => k !== "index")));
244
+ return {
245
+ ...reasoning,
246
+ summary,
247
+ };
248
+ }
249
+ function _convertMessagesToOpenAIResponsesParams(messages, model, zdrEnabled) {
250
+ const lastAIMessage = messages.filter((m) => isAIMessage(m)).pop();
251
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
252
+ const newMessages = lastAIMessageId && lastAIMessageId.startsWith("resp_") && !zdrEnabled
253
+ ? messages.slice(messages.indexOf(lastAIMessage) + 1)
254
+ : messages;
255
+ return newMessages.flatMap((lcMsg) => {
232
256
  let role = messageToOpenAIRole(lcMsg);
233
257
  if (role === "system" && isReasoningModel(model))
234
258
  role = "developer";
@@ -278,15 +302,25 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
278
302
  };
279
303
  }
280
304
  if (role === "assistant") {
305
+ // if we have the original response items, just reuse them
306
+ if (!zdrEnabled &&
307
+ lcMsg.response_metadata.output != null &&
308
+ Array.isArray(lcMsg.response_metadata.output) &&
309
+ lcMsg.response_metadata.output.length > 0 &&
310
+ lcMsg.response_metadata.output.every((item) => "type" in item)) {
311
+ return lcMsg.response_metadata.output;
312
+ }
313
+ // otherwise, try to reconstruct the response from what we have
281
314
  const input = [];
282
315
  // reasoning items
283
- if (lcMsg.additional_kwargs.reasoning != null) {
316
+ if (!zdrEnabled && lcMsg.additional_kwargs.reasoning != null) {
284
317
  const isReasoningItem = (item) => typeof item === "object" &&
285
318
  item != null &&
286
319
  "type" in item &&
287
320
  item.type === "reasoning";
288
321
  if (isReasoningItem(lcMsg.additional_kwargs.reasoning)) {
289
- input.push(lcMsg.additional_kwargs.reasoning);
322
+ const reasoningItem = _convertReasoningSummaryToOpenAIResponsesParams(lcMsg.additional_kwargs.reasoning);
323
+ input.push(reasoningItem);
290
324
  }
291
325
  }
292
326
  // ai content
@@ -303,6 +337,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
303
337
  input.push({
304
338
  type: "message",
305
339
  role: "assistant",
340
+ ...(lcMsg.id && !zdrEnabled ? { id: lcMsg.id } : {}),
306
341
  content: typeof content === "string"
307
342
  ? content
308
343
  : content.flatMap((item) => {
@@ -330,7 +365,7 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
330
365
  name: toolCall.name,
331
366
  arguments: JSON.stringify(toolCall.args),
332
367
  call_id: toolCall.id,
333
- id: functionCallIds?.[toolCall.id],
368
+ ...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
334
369
  })));
335
370
  }
336
371
  else if (lcMsg.additional_kwargs.tool_calls != null) {
@@ -338,21 +373,17 @@ function _convertMessagesToOpenAIResponsesParams(messages, model) {
338
373
  type: "function_call",
339
374
  name: toolCall.function.name,
340
375
  call_id: toolCall.id,
341
- id: functionCallIds?.[toolCall.id],
376
+ ...(zdrEnabled ? { id: functionCallIds?.[toolCall.id] } : {}),
342
377
  arguments: toolCall.function.arguments,
343
378
  })));
344
379
  }
345
380
  const toolOutputs = lcMsg.response_metadata.output?.length
346
381
  ? lcMsg.response_metadata.output
347
382
  : lcMsg.additional_kwargs.tool_outputs;
383
+ let computerCalls = [];
348
384
  if (toolOutputs != null) {
349
385
  const castToolOutputs = toolOutputs;
350
- const reasoningCalls = castToolOutputs?.filter((item) => item.type === "reasoning");
351
- const computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
352
- // NOTE: Reasoning outputs must be passed to the model BEFORE computer calls.
353
- if (reasoningCalls.length > 0 && computerCalls.length > 0) {
354
- input.push(...reasoningCalls);
355
- }
386
+ computerCalls = castToolOutputs?.filter((item) => item.type === "computer_call");
356
387
  if (computerCalls.length > 0)
357
388
  input.push(...computerCalls);
358
389
  }
@@ -556,6 +587,39 @@ function _convertOpenAIResponsesDeltaToBaseMessageChunk(chunk) {
556
587
  else if (chunk.type === "response.refusal.done") {
557
588
  additional_kwargs.refusal = chunk.refusal;
558
589
  }
590
+ else if (chunk.type === "response.output_item.added" &&
591
+ "item" in chunk &&
592
+ chunk.item.type === "reasoning") {
593
+ const summary = chunk
594
+ .item.summary
595
+ ? chunk.item.summary.map((s, index) => ({
596
+ ...s,
597
+ index,
598
+ }))
599
+ : undefined;
600
+ additional_kwargs.reasoning = {
601
+ // We only capture ID in the first chunk or else the concatenated result of all chunks will
602
+ // have an ID field that is repeated once per chunk. There is special handling for the `type`
603
+ // field that prevents this, however.
604
+ id: chunk.item.id,
605
+ type: chunk.item.type,
606
+ ...(summary ? { summary } : {}),
607
+ };
608
+ }
609
+ else if (chunk.type === "response.reasoning_summary_part.added") {
610
+ additional_kwargs.reasoning = {
611
+ type: "reasoning",
612
+ summary: [{ ...chunk.part, index: chunk.summary_index }],
613
+ };
614
+ }
615
+ else if (chunk.type === "response.reasoning_summary_text.delta") {
616
+ additional_kwargs.reasoning = {
617
+ type: "reasoning",
618
+ summary: [
619
+ { text: chunk.delta, type: "summary_text", index: chunk.summary_index },
620
+ ],
621
+ };
622
+ }
559
623
  else {
560
624
  return null;
561
625
  }
@@ -598,9 +662,7 @@ function _convertChatOpenAIToolTypeToOpenAITool(tool, fields) {
598
662
  return _convertToOpenAITool(tool, fields);
599
663
  }
600
664
  function isReasoningModel(model) {
601
- return (model?.startsWith("o1") ||
602
- model?.startsWith("o3") ||
603
- model?.startsWith("o4"));
665
+ return model && /^o\d/.test(model);
604
666
  }
605
667
  /**
606
668
  * OpenAI chat model integration.
@@ -1208,6 +1270,9 @@ export class ChatOpenAI extends BaseChatModel {
1208
1270
  "tags",
1209
1271
  "metadata",
1210
1272
  "disableStreaming",
1273
+ "useResponsesApi",
1274
+ "zdrEnabled",
1275
+ "reasoning",
1211
1276
  ];
1212
1277
  }
1213
1278
  constructor(fields) {
@@ -1385,18 +1450,51 @@ export class ChatOpenAI extends BaseChatModel {
1385
1450
  writable: true,
1386
1451
  value: void 0
1387
1452
  });
1453
+ /**
1454
+ * @deprecated Use {@link reasoning} object instead.
1455
+ */
1388
1456
  Object.defineProperty(this, "reasoningEffort", {
1389
1457
  enumerable: true,
1390
1458
  configurable: true,
1391
1459
  writable: true,
1392
1460
  value: void 0
1393
1461
  });
1462
+ /**
1463
+ * Options for reasoning models.
1464
+ */
1465
+ Object.defineProperty(this, "reasoning", {
1466
+ enumerable: true,
1467
+ configurable: true,
1468
+ writable: true,
1469
+ value: void 0
1470
+ });
1471
+ /**
1472
+ * Whether to use the responses API for all requests. If `false` the responses API will be used
1473
+ * only when required in order to fulfill the request.
1474
+ */
1394
1475
  Object.defineProperty(this, "useResponsesApi", {
1395
1476
  enumerable: true,
1396
1477
  configurable: true,
1397
1478
  writable: true,
1398
1479
  value: false
1399
1480
  });
1481
+ /**
1482
+ * Must be set to `true` in tenancies with Zero Data Retention. Setting to `true` will disable
1483
+ * output storage in the Responses API, but this DOES NOT enable Zero Data Retention in your
1484
+ * OpenAI organization or project. This must be configured directly with OpenAI.
1485
+ *
1486
+ * See:
1487
+ * https://help.openai.com/en/articles/10503543-data-residency-for-the-openai-api
1488
+ * https://platform.openai.com/docs/api-reference/responses/create#responses-create-store
1489
+ *
1490
+ * @default false
1491
+ */
1492
+ Object.defineProperty(this, "zdrEnabled", {
1493
+ enumerable: true,
1494
+ configurable: true,
1495
+ writable: true,
1496
+ value: void 0
1497
+ });
1400
1498
  this.openAIApiKey =
1401
1499
  fields?.apiKey ??
1402
1500
  fields?.openAIApiKey ??
@@ -1424,7 +1522,12 @@ export class ChatOpenAI extends BaseChatModel {
1424
1522
  this.__includeRawResponse = fields?.__includeRawResponse;
1425
1523
  this.audio = fields?.audio;
1426
1524
  this.modalities = fields?.modalities;
1427
- this.reasoningEffort = fields?.reasoningEffort;
1525
+ this.reasoningEffort = fields?.reasoningEffort ?? fields?.reasoning?.effort;
1526
+ this.reasoning =
1527
+ fields?.reasoning ??
1528
+ (fields?.reasoningEffort
1529
+ ? { effort: fields.reasoningEffort }
1530
+ : undefined);
1428
1531
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
1429
1532
  this.useResponsesApi = fields?.useResponsesApi ?? this.useResponsesApi;
1430
1533
  this.disableStreaming = fields?.disableStreaming ?? this.disableStreaming;
@@ -1447,6 +1550,7 @@ export class ChatOpenAI extends BaseChatModel {
1447
1550
  if (fields?.supportsStrictToolCalling !== undefined) {
1448
1551
  this.supportsStrictToolCalling = fields.supportsStrictToolCalling;
1449
1552
  }
1553
+ this.zdrEnabled = fields?.zdrEnabled ?? false;
1450
1554
  }
1451
1555
  getLsParams(options) {
1452
1556
  const params = this.invocationParams(options);
@@ -1485,6 +1589,35 @@ export class ChatOpenAI extends BaseChatModel {
1485
1589
  }
1486
1590
  return resFormat;
1487
1591
  }
1592
+ getReasoningParams(options) {
1593
+ if (!isReasoningModel(this.model)) {
1594
+ return;
1595
+ }
1596
+ let reasoning;
1597
+ // apply reasoning options in reverse order of priority without else blocks so that highest priority overrides lowest priority
1598
+ if (this.reasoningEffort !== undefined) {
1599
+ reasoning = { effort: this.reasoningEffort };
1600
+ }
1601
+ if (this.reasoning !== undefined) {
1602
+ reasoning = {
1603
+ ...reasoning,
1604
+ ...this.reasoning,
1605
+ };
1606
+ }
1607
+ if (options?.reasoning_effort !== undefined) {
1608
+ reasoning = {
1609
+ ...reasoning,
1610
+ effort: options.reasoning_effort,
1611
+ };
1612
+ }
1613
+ if (options?.reasoning !== undefined) {
1614
+ reasoning = {
1615
+ ...reasoning,
1616
+ ...options.reasoning,
1617
+ };
1618
+ }
1619
+ return reasoning;
1620
+ }
1488
1621
  /**
1489
1622
  * Get the parameters used to invoke the model
1490
1623
  */
@@ -1561,11 +1694,12 @@ export class ChatOpenAI extends BaseChatModel {
1561
1694
  })(),
1562
1695
  parallel_tool_calls: options?.parallel_tool_calls,
1563
1696
  max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1697
+ ...(this.zdrEnabled ? { store: false } : {}),
1564
1698
  ...this.modelKwargs,
1565
1699
  };
1566
- const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1567
- if (reasoningEffort !== undefined) {
1568
- params.reasoning = { effort: reasoningEffort };
1700
+ const reasoning = this.getReasoningParams(options);
1701
+ if (reasoning !== undefined) {
1702
+ params.reasoning = reasoning;
1569
1703
  }
1570
1704
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1571
1705
  return params;
@@ -1612,9 +1746,9 @@ export class ChatOpenAI extends BaseChatModel {
1612
1746
  if (options?.prediction !== undefined) {
1613
1747
  params.prediction = options.prediction;
1614
1748
  }
1615
- const reasoningEffort = options?.reasoning_effort ?? this.reasoningEffort;
1616
- if (reasoningEffort !== undefined) {
1617
- params.reasoning_effort = reasoningEffort;
1749
+ const reasoning = this.getReasoningParams(options);
1750
+ if (reasoning !== undefined && reasoning.effort !== undefined) {
1751
+ params.reasoning_effort = reasoning.effort;
1618
1752
  }
1619
1753
  if (isReasoningModel(params.model)) {
1620
1754
  params.max_completion_tokens =
@@ -1768,10 +1902,17 @@ export class ChatOpenAI extends BaseChatModel {
1768
1902
  }
1769
1903
  async *_streamResponseChunks(messages, options, runManager) {
1770
1904
  if (this._useResponseApi(options)) {
1905
+ const lastAIMessage = messages.filter((m) => isAIMessage(m)).pop();
1906
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
1771
1907
  const streamIterable = await this.responseApiWithRetry({
1772
1908
  ...this.invocationParams(options, { streaming: true }),
1773
- input: _convertMessagesToOpenAIResponsesParams(messages, this.model),
1909
+ input: _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled),
1774
1910
  stream: true,
1911
+ ...(lastAIMessageId &&
1912
+ lastAIMessageId.startsWith("resp_") &&
1913
+ !this.zdrEnabled
1914
+ ? { previous_response_id: lastAIMessageId }
1915
+ : {}),
1775
1916
  }, options);
1776
1917
  for await (const data of streamIterable) {
1777
1918
  const chunk = _convertOpenAIResponsesDeltaToBaseMessageChunk(data);
@@ -1905,8 +2046,18 @@ export class ChatOpenAI extends BaseChatModel {
1905
2046
  },
1906
2047
  };
1907
2048
  }
1908
- const input = _convertMessagesToOpenAIResponsesParams(messages, this.model);
1909
- const data = await this.responseApiWithRetry({ input, ...invocationParams }, { signal: options?.signal, ...options?.options });
2049
+ const lastAIMessage = messages.filter((m) => isAIMessage(m)).pop();
2050
+ const lastAIMessageId = lastAIMessage?.response_metadata?.id;
2051
+ const input = _convertMessagesToOpenAIResponsesParams(messages, this.model, this.zdrEnabled);
2052
+ const data = await this.responseApiWithRetry({
2053
+ input,
2054
+ ...invocationParams,
2055
+ ...(lastAIMessageId &&
2056
+ lastAIMessageId.startsWith("resp_") &&
2057
+ !this.zdrEnabled
2058
+ ? { previous_response_id: lastAIMessageId }
2059
+ : {}),
2060
+ }, { signal: options?.signal, ...options?.options });
1910
2061
  return {
1911
2062
  generations: [
1912
2063
  {
@@ -1926,12 +2077,23 @@ export class ChatOpenAI extends BaseChatModel {
1926
2077
  },
1927
2078
  };
1928
2079
  }
2080
+ /**
2081
+ * Determines whether the responses API should be used for the given request.
2082
+ *
2083
+ * @internal
2084
+ *
2085
+ * @param options The parsed call options for the request.
2086
+ * @returns `true` if the responses API should be used, either because it is explicitly enabled,
2087
+ * or because the request requires it.
2088
+ */
1929
2089
  _useResponseApi(options) {
1930
2090
  const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
1931
2091
  const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
1932
2092
  options?.text != null ||
1933
2093
  options?.truncation != null ||
1934
- options?.include != null;
2094
+ options?.include != null ||
2095
+ options?.reasoning?.summary != null ||
2096
+ this.reasoning?.summary != null;
1935
2097
  return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
1936
2098
  }
1937
2099
  /** @ignore */
@@ -115,7 +115,7 @@ class DallEAPIWrapper extends tools_1.Tool {
115
115
  if (this.dallEResponseFormat === "url") {
116
116
  return response.flatMap((res) => {
117
117
  const imageUrlContent = res.data
118
- .flatMap((item) => {
118
+ ?.flatMap((item) => {
119
119
  if (!item.url)
120
120
  return [];
121
121
  return {
@@ -126,14 +126,14 @@ class DallEAPIWrapper extends tools_1.Tool {
126
126
  .filter((item) => item !== undefined &&
127
127
  item.type === "image_url" &&
128
128
  typeof item.image_url === "string" &&
129
- item.image_url !== undefined);
129
+ item.image_url !== undefined) ?? [];
130
130
  return imageUrlContent;
131
131
  });
132
132
  }
133
133
  else {
134
134
  return response.flatMap((res) => {
135
135
  const b64Content = res.data
136
- .flatMap((item) => {
136
+ ?.flatMap((item) => {
137
137
  if (!item.b64_json)
138
138
  return [];
139
139
  return {
@@ -148,7 +148,7 @@ class DallEAPIWrapper extends tools_1.Tool {
148
148
  typeof item.image_url === "object" &&
149
149
  "url" in item.image_url &&
150
150
  typeof item.image_url.url === "string" &&
151
- item.image_url.url !== undefined);
151
+ item.image_url.url !== undefined) ?? [];
152
152
  return b64Content;
153
153
  });
154
154
  }
@@ -172,14 +172,17 @@ class DallEAPIWrapper extends tools_1.Tool {
172
172
  const response = await this.client.images.generate(generateImageFields);
173
173
  let data = "";
174
174
  if (this.dallEResponseFormat === "url") {
175
- [data] = response.data
176
- .map((item) => item.url)
177
- .filter((url) => url !== "undefined");
175
+ [data] =
176
+ response.data
177
+ ?.map((item) => item.url)
178
+ .filter((url) => url !== "undefined") ?? [];
178
179
  }
179
180
  else {
180
- [data] = response.data
181
- .map((item) => item.b64_json)
182
- .filter((b64_json) => b64_json !== "undefined");
181
+ [data] =
182
+ response.data
183
+ ?.map((item) => item.b64_json)
184
+ .filter((b64_json) => b64_json !== "undefined") ??
185
+ [];
183
186
  }
184
187
  return data;
185
188
  }
@@ -112,7 +112,7 @@ export class DallEAPIWrapper extends Tool {
112
112
  if (this.dallEResponseFormat === "url") {
113
113
  return response.flatMap((res) => {
114
114
  const imageUrlContent = res.data
115
- .flatMap((item) => {
115
+ ?.flatMap((item) => {
116
116
  if (!item.url)
117
117
  return [];
118
118
  return {
@@ -123,14 +123,14 @@ export class DallEAPIWrapper extends Tool {
123
123
  .filter((item) => item !== undefined &&
124
124
  item.type === "image_url" &&
125
125
  typeof item.image_url === "string" &&
126
- item.image_url !== undefined);
126
+ item.image_url !== undefined) ?? [];
127
127
  return imageUrlContent;
128
128
  });
129
129
  }
130
130
  else {
131
131
  return response.flatMap((res) => {
132
132
  const b64Content = res.data
133
- .flatMap((item) => {
133
+ ?.flatMap((item) => {
134
134
  if (!item.b64_json)
135
135
  return [];
136
136
  return {
@@ -145,7 +145,7 @@ export class DallEAPIWrapper extends Tool {
145
145
  typeof item.image_url === "object" &&
146
146
  "url" in item.image_url &&
147
147
  typeof item.image_url.url === "string" &&
148
- item.image_url.url !== undefined);
148
+ item.image_url.url !== undefined) ?? [];
149
149
  return b64Content;
150
150
  });
151
151
  }
@@ -169,14 +169,17 @@ export class DallEAPIWrapper extends Tool {
169
169
  const response = await this.client.images.generate(generateImageFields);
170
170
  let data = "";
171
171
  if (this.dallEResponseFormat === "url") {
172
- [data] = response.data
173
- .map((item) => item.url)
174
- .filter((url) => url !== "undefined");
172
+ [data] =
173
+ response.data
174
+ ?.map((item) => item.url)
175
+ .filter((url) => url !== "undefined") ?? [];
175
176
  }
176
177
  else {
177
- [data] = response.data
178
- .map((item) => item.b64_json)
179
- .filter((b64_json) => b64_json !== "undefined");
178
+ [data] =
179
+ response.data
180
+ ?.map((item) => item.b64_json)
181
+ .filter((b64_json) => b64_json !== "undefined") ??
182
+ [];
180
183
  }
181
184
  return data;
182
185
  }
package/dist/types.d.ts CHANGED
@@ -135,8 +135,24 @@ export interface OpenAIChatInput extends OpenAIBaseInput {
135
135
  /**
136
136
  * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high.
137
137
  * Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.
138
+ *
139
+ * @deprecated Use the {@link reasoning} object instead.
138
140
  */
139
141
  reasoningEffort?: OpenAIClient.Chat.ChatCompletionReasoningEffort;
142
+ /**
143
+ * Options for reasoning models.
144
+ *
145
+ * Note that some options, like reasoning summaries, are only available when using the responses
146
+ * API. This option is ignored when not using a reasoning model.
147
+ */
148
+ reasoning?: OpenAIClient.Reasoning;
149
+ /**
150
+ * Should be set to `true` in tenancies with Zero Data Retention
151
+ * @see https://platform.openai.com/docs/guides/your-data
152
+ *
153
+ * @default false
154
+ */
155
+ zdrEnabled?: boolean;
140
156
  }
141
157
  export declare interface AzureOpenAIInput {
142
158
  /**
@@ -205,4 +221,17 @@ type ChatOpenAIResponseFormatJSONSchema = Omit<ResponseFormatJSONSchema, "json_s
205
221
  schema: Record<string, any> | z.ZodObject<any, any, any, any>;
206
222
  };
207
223
  };
224
+ /**
225
+ * The summary of a model's reasoning step.
226
+ */
227
+ export type ChatOpenAIReasoningSummary = Omit<OpenAIClient.Responses.ResponseReasoningItem, "summary"> & {
228
+ /**
229
+ * The summary of the reasoning step. The index field will be populated if the response was
230
+ * streamed. This allows LangChain to recompose the reasoning summary output correctly when the
231
+ * AIMessage is used as an input for future generation requests.
232
+ */
233
+ summary: Array<OpenAIClient.Responses.ResponseReasoningItem.Summary & {
234
+ index?: number;
235
+ }>;
236
+ };
208
237
  export type ChatOpenAIResponseFormat = ResponseFormatText | ResponseFormatJSONObject | ChatOpenAIResponseFormatJSONSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
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.93.0",
39
+ "openai": "^4.96.0",
40
40
  "zod": "^3.22.4",
41
41
  "zod-to-json-schema": "^3.22.3"
42
42
  },