@langchain/openai 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,17 +19,8 @@ const openai_js_1 = require("./utils/openai.cjs");
19
19
  const openai_format_fndef_js_1 = require("./utils/openai-format-fndef.cjs");
20
20
  const tools_js_1 = require("./utils/tools.cjs");
21
21
  const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
22
- function isBuiltInTool(tool) {
23
- return "type" in tool && tool.type !== "function";
24
- }
25
- function isBuiltInToolChoice(tool_choice) {
26
- return (tool_choice != null &&
27
- typeof tool_choice === "object" &&
28
- "type" in tool_choice &&
29
- tool_choice.type !== "function");
30
- }
31
22
  function isReasoningModel(model) {
32
- return model && /^o\d/.test(model);
23
+ return model && (/^o\d/.test(model) || model.startsWith("gpt-5"));
33
24
  }
34
25
  function isStructuredOutputMethodParams(x
35
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -327,6 +318,8 @@ class BaseChatOpenAI extends chat_models_1.BaseChatModel {
327
318
  "disableStreaming",
328
319
  "zdrEnabled",
329
320
  "reasoning",
321
+ "verbosity",
322
+ "promptCacheKey",
330
323
  ];
331
324
  }
332
325
  getLsParams(options) {
@@ -545,6 +538,17 @@ class BaseChatOpenAI extends chat_models_1.BaseChatModel {
545
538
  writable: true,
546
539
  value: void 0
547
540
  });
541
+ /**
542
+ * Used by OpenAI to cache responses for similar requests to optimize your cache
543
+ * hit rates.
544
+ * [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
545
+ */
546
+ Object.defineProperty(this, "promptCacheKey", {
547
+ enumerable: true,
548
+ configurable: true,
549
+ writable: true,
550
+ value: void 0
551
+ });
548
552
  Object.defineProperty(this, "defaultOptions", {
549
553
  enumerable: true,
550
554
  configurable: true,
@@ -584,6 +588,7 @@ class BaseChatOpenAI extends chat_models_1.BaseChatModel {
584
588
  this.reasoning = fields?.reasoning;
585
589
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
586
590
  this.disableStreaming = fields?.disableStreaming ?? this.disableStreaming;
591
+ this.promptCacheKey = fields?.promptCacheKey ?? this.promptCacheKey;
587
592
  this.streaming = fields?.streaming ?? false;
588
593
  if (this.disableStreaming)
589
594
  this.streaming = false;
@@ -677,6 +682,9 @@ class BaseChatOpenAI extends chat_models_1.BaseChatModel {
677
682
  }
678
683
  // TODO: move to completions class
679
684
  _convertChatOpenAIToolToCompletionsTool(tool, fields) {
685
+ if ((0, tools_js_1.isCustomTool)(tool)) {
686
+ return (0, tools_js_1.convertResponsesCustomTool)(tool.metadata.customTool);
687
+ }
680
688
  if ((0, base_1.isOpenAITool)(tool)) {
681
689
  if (fields?.strict !== undefined) {
682
690
  return {
@@ -700,7 +708,7 @@ class BaseChatOpenAI extends chat_models_1.BaseChatModel {
700
708
  strict = this.supportsStrictToolCalling;
701
709
  }
702
710
  return this.withConfig({
703
- tools: tools.map((tool) => isBuiltInTool(tool)
711
+ tools: tools.map((tool) => (0, tools_js_1.isBuiltInTool)(tool)
704
712
  ? tool
705
713
  : this._convertChatOpenAIToolToCompletionsTool(tool, { strict })),
706
714
  ...kwargs,
@@ -1036,16 +1044,29 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1036
1044
  strict,
1037
1045
  })
1038
1046
  : undefined,
1039
- tool_choice: isBuiltInToolChoice(options?.tool_choice)
1047
+ tool_choice: (0, tools_js_1.isBuiltInToolChoice)(options?.tool_choice)
1040
1048
  ? options?.tool_choice
1041
1049
  : (() => {
1042
1050
  const formatted = (0, openai_js_1.formatToOpenAIToolChoice)(options?.tool_choice);
1043
1051
  if (typeof formatted === "object" && "type" in formatted) {
1044
- return { type: "function", name: formatted.function.name };
1045
- }
1046
- else {
1047
- return undefined;
1052
+ if (formatted.type === "function") {
1053
+ return { type: "function", name: formatted.function.name };
1054
+ }
1055
+ else if (formatted.type === "allowed_tools") {
1056
+ return {
1057
+ type: "allowed_tools",
1058
+ mode: formatted.allowed_tools.mode,
1059
+ tools: formatted.allowed_tools.tools,
1060
+ };
1061
+ }
1062
+ else if (formatted.type === "custom") {
1063
+ return {
1064
+ type: "custom",
1065
+ name: formatted.custom.name,
1066
+ };
1067
+ }
1048
1068
  }
1069
+ return undefined;
1049
1070
  })(),
1050
1071
  text: (() => {
1051
1072
  if (options?.text)
@@ -1069,6 +1090,7 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1069
1090
  })(),
1070
1091
  parallel_tool_calls: options?.parallel_tool_calls,
1071
1092
  max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1093
+ prompt_cache_key: options?.promptCacheKey ?? this.promptCacheKey,
1072
1094
  ...(this.zdrEnabled ? { store: false } : {}),
1073
1095
  ...this.modelKwargs,
1074
1096
  };
@@ -1231,6 +1253,15 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1231
1253
  else if (item.type === "reasoning") {
1232
1254
  additional_kwargs.reasoning = item;
1233
1255
  }
1256
+ else if (item.type === "custom_tool_call") {
1257
+ const parsed = (0, tools_js_1.parseCustomToolCall)(item);
1258
+ if (parsed) {
1259
+ tool_calls.push(parsed);
1260
+ }
1261
+ else {
1262
+ invalid_tool_calls.push((0, openai_tools_1.makeInvalidToolCall)(item, "Malformed custom tool call"));
1263
+ }
1264
+ }
1234
1265
  else {
1235
1266
  additional_kwargs.tool_outputs ??= [];
1236
1267
  additional_kwargs.tool_outputs.push(item);
@@ -1262,7 +1293,7 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1262
1293
  index: chunk.content_index,
1263
1294
  });
1264
1295
  }
1265
- else if (chunk.type === "response.output_text_annotation.added") {
1296
+ else if (chunk.type === "response.output_text.annotation.added") {
1266
1297
  content.push({
1267
1298
  type: "text",
1268
1299
  text: "",
@@ -1297,6 +1328,7 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1297
1328
  "mcp_list_tools",
1298
1329
  "mcp_approval_request",
1299
1330
  "image_generation_call",
1331
+ "custom_tool_call",
1300
1332
  ].includes(chunk.item.type)) {
1301
1333
  additional_kwargs.tool_outputs = [chunk.item];
1302
1334
  }
@@ -1438,6 +1470,14 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1438
1470
  call_id: toolMessage.tool_call_id,
1439
1471
  };
1440
1472
  }
1473
+ // Handle custom tool output
1474
+ if (toolMessage.metadata?.customTool) {
1475
+ return {
1476
+ type: "custom_tool_call_output",
1477
+ call_id: toolMessage.tool_call_id,
1478
+ output: toolMessage.content,
1479
+ };
1480
+ }
1441
1481
  return {
1442
1482
  type: "function_call_output",
1443
1483
  call_id: toolMessage.tool_call_id,
@@ -1476,41 +1516,54 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1476
1516
  { type: "refusal", refusal: additional_kwargs.refusal },
1477
1517
  ];
1478
1518
  }
1479
- input.push({
1480
- type: "message",
1481
- role: "assistant",
1482
- ...(lcMsg.id && !this.zdrEnabled && lcMsg.id.startsWith("msg_")
1483
- ? { id: lcMsg.id }
1484
- : {}),
1485
- content: typeof content === "string"
1486
- ? content
1487
- : content.flatMap((item) => {
1488
- if (item.type === "text") {
1489
- return {
1490
- type: "output_text",
1491
- text: item.text,
1492
- // @ts-expect-error TODO: add types for `annotations`
1493
- annotations: item.annotations ?? [],
1494
- };
1495
- }
1496
- if (item.type === "output_text" ||
1497
- item.type === "refusal") {
1498
- return item;
1499
- }
1500
- return [];
1501
- }),
1502
- });
1519
+ if (typeof content === "string" || content.length > 0) {
1520
+ input.push({
1521
+ type: "message",
1522
+ role: "assistant",
1523
+ ...(lcMsg.id && !this.zdrEnabled && lcMsg.id.startsWith("msg_")
1524
+ ? { id: lcMsg.id }
1525
+ : {}),
1526
+ content: typeof content === "string"
1527
+ ? content
1528
+ : content.flatMap((item) => {
1529
+ if (item.type === "text") {
1530
+ return {
1531
+ type: "output_text",
1532
+ text: item.text,
1533
+ // @ts-expect-error TODO: add types for `annotations`
1534
+ annotations: item.annotations ?? [],
1535
+ };
1536
+ }
1537
+ if (item.type === "output_text" ||
1538
+ item.type === "refusal") {
1539
+ return item;
1540
+ }
1541
+ return [];
1542
+ }),
1543
+ });
1544
+ }
1503
1545
  const functionCallIds = additional_kwargs?.[_FUNCTION_CALL_IDS_MAP_KEY];
1504
1546
  if ((0, messages_1.isAIMessage)(lcMsg) && !!lcMsg.tool_calls?.length) {
1505
- input.push(...lcMsg.tool_calls.map((toolCall) => ({
1506
- type: "function_call",
1507
- name: toolCall.name,
1508
- arguments: JSON.stringify(toolCall.args),
1509
- call_id: toolCall.id,
1510
- ...(this.zdrEnabled
1511
- ? { id: functionCallIds?.[toolCall.id] }
1512
- : {}),
1513
- })));
1547
+ input.push(...lcMsg.tool_calls.map((toolCall) => {
1548
+ if ((0, tools_js_1.isCustomToolCall)(toolCall)) {
1549
+ return {
1550
+ type: "custom_tool_call",
1551
+ id: toolCall.call_id,
1552
+ call_id: toolCall.id ?? "",
1553
+ input: toolCall.args.input,
1554
+ name: toolCall.name,
1555
+ };
1556
+ }
1557
+ return {
1558
+ type: "function_call",
1559
+ name: toolCall.name,
1560
+ arguments: JSON.stringify(toolCall.args),
1561
+ call_id: toolCall.id,
1562
+ ...(this.zdrEnabled
1563
+ ? { id: functionCallIds?.[toolCall.id] }
1564
+ : {}),
1565
+ };
1566
+ }));
1514
1567
  }
1515
1568
  else if (additional_kwargs?.tool_calls) {
1516
1569
  input.push(...additional_kwargs.tool_calls.map((toolCall) => ({
@@ -1613,7 +1666,7 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1613
1666
  _reduceChatOpenAITools(tools, fields) {
1614
1667
  const reducedTools = [];
1615
1668
  for (const tool of tools) {
1616
- if (isBuiltInTool(tool)) {
1669
+ if ((0, tools_js_1.isBuiltInTool)(tool)) {
1617
1670
  if (tool.type === "image_generation" && fields?.stream) {
1618
1671
  // OpenAI sends a 400 error if partial_images is not set and we want to stream.
1619
1672
  // We also set it to 1 since we don't support partial images yet.
@@ -1630,6 +1683,9 @@ class ChatOpenAIResponses extends BaseChatOpenAI {
1630
1683
  strict: fields?.strict ?? null,
1631
1684
  });
1632
1685
  }
1686
+ else if ((0, tools_js_1.isOpenAICustomTool)(tool)) {
1687
+ reducedTools.push((0, tools_js_1.convertCompletionsCustomTool)(tool));
1688
+ }
1633
1689
  }
1634
1690
  return reducedTools;
1635
1691
  }
@@ -1687,6 +1743,7 @@ class ChatOpenAICompletions extends BaseChatOpenAI {
1687
1743
  ? { modalities: this.modalities || options?.modalities }
1688
1744
  : {}),
1689
1745
  ...this.modelKwargs,
1746
+ prompt_cache_key: options?.promptCacheKey ?? this.promptCacheKey,
1690
1747
  };
1691
1748
  if (options?.prediction !== undefined) {
1692
1749
  params.prediction = options.prediction;
@@ -1934,6 +1991,7 @@ class ChatOpenAICompletions extends BaseChatOpenAI {
1934
1991
  return await this.client.chat.completions.parse(request, clientOptions);
1935
1992
  }
1936
1993
  else {
1994
+ console.log("request", request);
1937
1995
  return await this.client.chat.completions.create(request, clientOptions);
1938
1996
  }
1939
1997
  }
@@ -2656,14 +2714,18 @@ class ChatOpenAI extends BaseChatOpenAI {
2656
2714
  this.completions = fields?.completions ?? new ChatOpenAICompletions(fields);
2657
2715
  }
2658
2716
  _useResponsesApi(options) {
2659
- const usesBuiltInTools = options?.tools?.some(isBuiltInTool);
2717
+ const usesBuiltInTools = options?.tools?.some(tools_js_1.isBuiltInTool);
2660
2718
  const hasResponsesOnlyKwargs = options?.previous_response_id != null ||
2661
2719
  options?.text != null ||
2662
2720
  options?.truncation != null ||
2663
2721
  options?.include != null ||
2664
2722
  options?.reasoning?.summary != null ||
2665
2723
  this.reasoning?.summary != null;
2666
- return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
2724
+ const hasCustomTools = options?.tools?.some(tools_js_1.isOpenAICustomTool);
2725
+ return (this.useResponsesApi ||
2726
+ usesBuiltInTools ||
2727
+ hasResponsesOnlyKwargs ||
2728
+ hasCustomTools);
2667
2729
  }
2668
2730
  getLsParams(options) {
2669
2731
  const optionsWithDefaults = this._combineCallOptions(options);
@@ -2,16 +2,13 @@ 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 ChatGeneration, type ChatResult } from "@langchain/core/outputs";
5
- import { BaseChatModel, type BindToolsInput, type LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
5
+ import { BaseChatModel, 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 { Runnable } from "@langchain/core/runnables";
8
- import type { ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema } from "openai/resources/shared";
9
8
  import { InteropZodType } from "@langchain/core/utils/types";
10
- import { type OpenAICallOptions, type OpenAIChatInput, type OpenAICoreRequestOptions, type ChatOpenAIResponseFormat, ChatOpenAIReasoningSummary } from "./types.js";
9
+ import { type OpenAICallOptions, type OpenAIChatInput, type OpenAICoreRequestOptions, type ChatOpenAIResponseFormat, ChatOpenAIReasoningSummary, ResponseFormatConfiguration } from "./types.js";
11
10
  import { OpenAIToolChoice } from "./utils/openai.js";
12
- type ResponsesTool = NonNullable<OpenAIClient.Responses.ResponseCreateParams["tools"]>[number];
13
- type ResponsesToolChoice = NonNullable<OpenAIClient.Responses.ResponseCreateParams["tool_choice"]>;
14
- type ChatOpenAIToolType = BindToolsInput | OpenAIClient.Chat.ChatCompletionTool | ResponsesTool;
11
+ import { ChatOpenAIToolType, ResponsesTool, ResponsesToolChoice } from "./utils/tools.js";
15
12
  interface OpenAILLMOutput {
16
13
  tokenUsage: {
17
14
  completionTokens?: number;
@@ -112,6 +109,12 @@ export interface BaseChatOpenAICallOptions extends OpenAICallOptions, BaseFuncti
112
109
  * Specifies the service tier for prioritization and latency optimization.
113
110
  */
114
111
  service_tier?: OpenAIClient.Chat.ChatCompletionCreateParams["service_tier"];
112
+ /**
113
+ * Used by OpenAI to cache responses for similar requests to optimize your cache
114
+ * hit rates. Replaces the `user` field.
115
+ * [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
116
+ */
117
+ promptCacheKey?: string;
115
118
  }
116
119
  export interface BaseChatOpenAIFields extends Partial<OpenAIChatInput>, BaseChatModelParams {
117
120
  /**
@@ -170,6 +173,12 @@ export declare abstract class BaseChatOpenAI<CallOptions extends BaseChatOpenAIC
170
173
  * Specifies the service tier for prioritization and latency optimization.
171
174
  */
172
175
  service_tier?: OpenAIClient.Chat.ChatCompletionCreateParams["service_tier"];
176
+ /**
177
+ * Used by OpenAI to cache responses for similar requests to optimize your cache
178
+ * hit rates.
179
+ * [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
180
+ */
181
+ promptCacheKey: string;
173
182
  protected defaultOptions: CallOptions;
174
183
  _llmType(): string;
175
184
  static lc_name(): string;
@@ -201,7 +210,7 @@ export declare abstract class BaseChatOpenAI<CallOptions extends BaseChatOpenAIC
201
210
  * Returns an openai compatible response format from a set of options
202
211
  * @internal
203
212
  */
204
- protected _getResponseFormat(resFormat?: CallOptions["response_format"]): ResponseFormatText | ResponseFormatJSONObject | ResponseFormatJSONSchema | undefined;
213
+ protected _getResponseFormat(resFormat?: CallOptions["response_format"]): ResponseFormatConfiguration | undefined;
205
214
  protected _combineCallOptions(additionalOptions?: this["ParsedCallOptions"]): this["ParsedCallOptions"];
206
215
  /** @internal */
207
216
  _getClientOptions(options: OpenAICoreRequestOptions | undefined): OpenAICoreRequestOptions;
@@ -285,7 +294,7 @@ export declare class ChatOpenAIResponses<CallOptions extends ChatOpenAIResponses
285
294
  /** @internal */
286
295
  protected _convertResponsesDeltaToBaseMessageChunk(chunk: OpenAIClient.Responses.ResponseStreamEvent): ChatGenerationChunk | null;
287
296
  /** @internal */
288
- protected _convertMessagesToResponsesParams(messages: BaseMessage[]): (OpenAIClient.Responses.ResponseReasoningItem | OpenAIClient.Responses.EasyInputMessage | OpenAIClient.Responses.ResponseInputItem.Message | OpenAIClient.Responses.ResponseOutputMessage | OpenAIClient.Responses.ResponseFileSearchToolCall | OpenAIClient.Responses.ResponseComputerToolCall | OpenAIClient.Responses.ResponseInputItem.ComputerCallOutput | OpenAIClient.Responses.ResponseFunctionWebSearch | OpenAIClient.Responses.ResponseFunctionToolCall | OpenAIClient.Responses.ResponseInputItem.FunctionCallOutput | OpenAIClient.Responses.ResponseInputItem.ImageGenerationCall | OpenAIClient.Responses.ResponseCodeInterpreterToolCall | OpenAIClient.Responses.ResponseInputItem.LocalShellCall | OpenAIClient.Responses.ResponseInputItem.LocalShellCallOutput | OpenAIClient.Responses.ResponseInputItem.McpListTools | OpenAIClient.Responses.ResponseInputItem.McpApprovalRequest | OpenAIClient.Responses.ResponseInputItem.McpApprovalResponse | OpenAIClient.Responses.ResponseInputItem.McpCall | OpenAIClient.Responses.ResponseInputItem.ItemReference)[];
297
+ protected _convertMessagesToResponsesParams(messages: BaseMessage[]): (OpenAIClient.Responses.ResponseReasoningItem | OpenAIClient.Responses.EasyInputMessage | OpenAIClient.Responses.ResponseInputItem.Message | OpenAIClient.Responses.ResponseOutputMessage | OpenAIClient.Responses.ResponseFileSearchToolCall | OpenAIClient.Responses.ResponseComputerToolCall | OpenAIClient.Responses.ResponseInputItem.ComputerCallOutput | OpenAIClient.Responses.ResponseFunctionWebSearch | OpenAIClient.Responses.ResponseFunctionToolCall | OpenAIClient.Responses.ResponseInputItem.FunctionCallOutput | OpenAIClient.Responses.ResponseInputItem.ImageGenerationCall | OpenAIClient.Responses.ResponseCodeInterpreterToolCall | OpenAIClient.Responses.ResponseInputItem.LocalShellCall | OpenAIClient.Responses.ResponseInputItem.LocalShellCallOutput | OpenAIClient.Responses.ResponseInputItem.McpListTools | OpenAIClient.Responses.ResponseInputItem.McpApprovalRequest | OpenAIClient.Responses.ResponseInputItem.McpApprovalResponse | OpenAIClient.Responses.ResponseInputItem.McpCall | OpenAIClient.Responses.ResponseCustomToolCallOutput | OpenAIClient.Responses.ResponseCustomToolCall | OpenAIClient.Responses.ResponseInputItem.ItemReference)[];
289
298
  /** @internal */
290
299
  protected _convertReasoningSummary(reasoning: ChatOpenAIReasoningSummary): OpenAIClient.Responses.ResponseReasoningItem;
291
300
  /** @internal */
@@ -882,7 +891,7 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
882
891
  protected completions: ChatOpenAICompletions;
883
892
  get lc_serializable_keys(): string[];
884
893
  constructor(fields?: ChatOpenAIFields | undefined);
885
- protected _useResponsesApi(options: this["ParsedCallOptions"] | undefined): boolean;
894
+ protected _useResponsesApi(options: this["ParsedCallOptions"] | undefined): boolean | undefined;
886
895
  getLsParams(options: this["ParsedCallOptions"]): LangSmithParams;
887
896
  invocationParams(options?: this["ParsedCallOptions"]): ChatResponsesInvocationParams | ChatCompletionsInvocationParams;
888
897
  /** @ignore */
@@ -3,7 +3,7 @@ import { AIMessage, AIMessageChunk, ChatMessage, ChatMessageChunk, FunctionMessa
3
3
  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
- import { isOpenAITool, } from "@langchain/core/language_models/base";
6
+ import { isOpenAITool as isOpenAIFunctionTool, } from "@langchain/core/language_models/base";
7
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";
@@ -12,19 +12,10 @@ import { toJsonSchema } from "@langchain/core/utils/json_schema";
12
12
  import { getEndpoint } from "./utils/azure.js";
13
13
  import { formatToOpenAIToolChoice, interopZodResponseFormat, wrapOpenAIClientError, } from "./utils/openai.js";
14
14
  import { formatFunctionDefinitions, } from "./utils/openai-format-fndef.js";
15
- import { _convertToOpenAITool } from "./utils/tools.js";
15
+ import { _convertToOpenAITool, convertCompletionsCustomTool, convertResponsesCustomTool, isBuiltInTool, isBuiltInToolChoice, isCustomTool, isCustomToolCall, isOpenAICustomTool, parseCustomToolCall, } from "./utils/tools.js";
16
16
  const _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__";
17
- function isBuiltInTool(tool) {
18
- return "type" in tool && tool.type !== "function";
19
- }
20
- function isBuiltInToolChoice(tool_choice) {
21
- return (tool_choice != null &&
22
- typeof tool_choice === "object" &&
23
- "type" in tool_choice &&
24
- tool_choice.type !== "function");
25
- }
26
17
  function isReasoningModel(model) {
27
- return model && /^o\d/.test(model);
18
+ return model && (/^o\d/.test(model) || model.startsWith("gpt-5"));
28
19
  }
29
20
  function isStructuredOutputMethodParams(x
30
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -322,6 +313,8 @@ export class BaseChatOpenAI extends BaseChatModel {
322
313
  "disableStreaming",
323
314
  "zdrEnabled",
324
315
  "reasoning",
316
+ "verbosity",
317
+ "promptCacheKey",
325
318
  ];
326
319
  }
327
320
  getLsParams(options) {
@@ -540,6 +533,17 @@ export class BaseChatOpenAI extends BaseChatModel {
540
533
  writable: true,
541
534
  value: void 0
542
535
  });
536
+ /**
537
+ * Used by OpenAI to cache responses for similar requests to optimize your cache
538
+ * hit rates.
539
+ * [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
540
+ */
541
+ Object.defineProperty(this, "promptCacheKey", {
542
+ enumerable: true,
543
+ configurable: true,
544
+ writable: true,
545
+ value: void 0
546
+ });
543
547
  Object.defineProperty(this, "defaultOptions", {
544
548
  enumerable: true,
545
549
  configurable: true,
@@ -579,6 +583,7 @@ export class BaseChatOpenAI extends BaseChatModel {
579
583
  this.reasoning = fields?.reasoning;
580
584
  this.maxTokens = fields?.maxCompletionTokens ?? fields?.maxTokens;
581
585
  this.disableStreaming = fields?.disableStreaming ?? this.disableStreaming;
586
+ this.promptCacheKey = fields?.promptCacheKey ?? this.promptCacheKey;
582
587
  this.streaming = fields?.streaming ?? false;
583
588
  if (this.disableStreaming)
584
589
  this.streaming = false;
@@ -672,7 +677,10 @@ export class BaseChatOpenAI extends BaseChatModel {
672
677
  }
673
678
  // TODO: move to completions class
674
679
  _convertChatOpenAIToolToCompletionsTool(tool, fields) {
675
- if (isOpenAITool(tool)) {
680
+ if (isCustomTool(tool)) {
681
+ return convertResponsesCustomTool(tool.metadata.customTool);
682
+ }
683
+ if (isOpenAIFunctionTool(tool)) {
676
684
  if (fields?.strict !== undefined) {
677
685
  return {
678
686
  ...tool,
@@ -1035,11 +1043,24 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1035
1043
  : (() => {
1036
1044
  const formatted = formatToOpenAIToolChoice(options?.tool_choice);
1037
1045
  if (typeof formatted === "object" && "type" in formatted) {
1038
- return { type: "function", name: formatted.function.name };
1039
- }
1040
- else {
1041
- return undefined;
1046
+ if (formatted.type === "function") {
1047
+ return { type: "function", name: formatted.function.name };
1048
+ }
1049
+ else if (formatted.type === "allowed_tools") {
1050
+ return {
1051
+ type: "allowed_tools",
1052
+ mode: formatted.allowed_tools.mode,
1053
+ tools: formatted.allowed_tools.tools,
1054
+ };
1055
+ }
1056
+ else if (formatted.type === "custom") {
1057
+ return {
1058
+ type: "custom",
1059
+ name: formatted.custom.name,
1060
+ };
1061
+ }
1042
1062
  }
1063
+ return undefined;
1043
1064
  })(),
1044
1065
  text: (() => {
1045
1066
  if (options?.text)
@@ -1063,6 +1084,7 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1063
1084
  })(),
1064
1085
  parallel_tool_calls: options?.parallel_tool_calls,
1065
1086
  max_output_tokens: this.maxTokens === -1 ? undefined : this.maxTokens,
1087
+ prompt_cache_key: options?.promptCacheKey ?? this.promptCacheKey,
1066
1088
  ...(this.zdrEnabled ? { store: false } : {}),
1067
1089
  ...this.modelKwargs,
1068
1090
  };
@@ -1225,6 +1247,15 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1225
1247
  else if (item.type === "reasoning") {
1226
1248
  additional_kwargs.reasoning = item;
1227
1249
  }
1250
+ else if (item.type === "custom_tool_call") {
1251
+ const parsed = parseCustomToolCall(item);
1252
+ if (parsed) {
1253
+ tool_calls.push(parsed);
1254
+ }
1255
+ else {
1256
+ invalid_tool_calls.push(makeInvalidToolCall(item, "Malformed custom tool call"));
1257
+ }
1258
+ }
1228
1259
  else {
1229
1260
  additional_kwargs.tool_outputs ??= [];
1230
1261
  additional_kwargs.tool_outputs.push(item);
@@ -1256,7 +1287,7 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1256
1287
  index: chunk.content_index,
1257
1288
  });
1258
1289
  }
1259
- else if (chunk.type === "response.output_text_annotation.added") {
1290
+ else if (chunk.type === "response.output_text.annotation.added") {
1260
1291
  content.push({
1261
1292
  type: "text",
1262
1293
  text: "",
@@ -1291,6 +1322,7 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1291
1322
  "mcp_list_tools",
1292
1323
  "mcp_approval_request",
1293
1324
  "image_generation_call",
1325
+ "custom_tool_call",
1294
1326
  ].includes(chunk.item.type)) {
1295
1327
  additional_kwargs.tool_outputs = [chunk.item];
1296
1328
  }
@@ -1432,6 +1464,14 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1432
1464
  call_id: toolMessage.tool_call_id,
1433
1465
  };
1434
1466
  }
1467
+ // Handle custom tool output
1468
+ if (toolMessage.metadata?.customTool) {
1469
+ return {
1470
+ type: "custom_tool_call_output",
1471
+ call_id: toolMessage.tool_call_id,
1472
+ output: toolMessage.content,
1473
+ };
1474
+ }
1435
1475
  return {
1436
1476
  type: "function_call_output",
1437
1477
  call_id: toolMessage.tool_call_id,
@@ -1470,41 +1510,54 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1470
1510
  { type: "refusal", refusal: additional_kwargs.refusal },
1471
1511
  ];
1472
1512
  }
1473
- input.push({
1474
- type: "message",
1475
- role: "assistant",
1476
- ...(lcMsg.id && !this.zdrEnabled && lcMsg.id.startsWith("msg_")
1477
- ? { id: lcMsg.id }
1478
- : {}),
1479
- content: typeof content === "string"
1480
- ? content
1481
- : content.flatMap((item) => {
1482
- if (item.type === "text") {
1483
- return {
1484
- type: "output_text",
1485
- text: item.text,
1486
- // @ts-expect-error TODO: add types for `annotations`
1487
- annotations: item.annotations ?? [],
1488
- };
1489
- }
1490
- if (item.type === "output_text" ||
1491
- item.type === "refusal") {
1492
- return item;
1493
- }
1494
- return [];
1495
- }),
1496
- });
1513
+ if (typeof content === "string" || content.length > 0) {
1514
+ input.push({
1515
+ type: "message",
1516
+ role: "assistant",
1517
+ ...(lcMsg.id && !this.zdrEnabled && lcMsg.id.startsWith("msg_")
1518
+ ? { id: lcMsg.id }
1519
+ : {}),
1520
+ content: typeof content === "string"
1521
+ ? content
1522
+ : content.flatMap((item) => {
1523
+ if (item.type === "text") {
1524
+ return {
1525
+ type: "output_text",
1526
+ text: item.text,
1527
+ // @ts-expect-error TODO: add types for `annotations`
1528
+ annotations: item.annotations ?? [],
1529
+ };
1530
+ }
1531
+ if (item.type === "output_text" ||
1532
+ item.type === "refusal") {
1533
+ return item;
1534
+ }
1535
+ return [];
1536
+ }),
1537
+ });
1538
+ }
1497
1539
  const functionCallIds = additional_kwargs?.[_FUNCTION_CALL_IDS_MAP_KEY];
1498
1540
  if (isAIMessage(lcMsg) && !!lcMsg.tool_calls?.length) {
1499
- input.push(...lcMsg.tool_calls.map((toolCall) => ({
1500
- type: "function_call",
1501
- name: toolCall.name,
1502
- arguments: JSON.stringify(toolCall.args),
1503
- call_id: toolCall.id,
1504
- ...(this.zdrEnabled
1505
- ? { id: functionCallIds?.[toolCall.id] }
1506
- : {}),
1507
- })));
1541
+ input.push(...lcMsg.tool_calls.map((toolCall) => {
1542
+ if (isCustomToolCall(toolCall)) {
1543
+ return {
1544
+ type: "custom_tool_call",
1545
+ id: toolCall.call_id,
1546
+ call_id: toolCall.id ?? "",
1547
+ input: toolCall.args.input,
1548
+ name: toolCall.name,
1549
+ };
1550
+ }
1551
+ return {
1552
+ type: "function_call",
1553
+ name: toolCall.name,
1554
+ arguments: JSON.stringify(toolCall.args),
1555
+ call_id: toolCall.id,
1556
+ ...(this.zdrEnabled
1557
+ ? { id: functionCallIds?.[toolCall.id] }
1558
+ : {}),
1559
+ };
1560
+ }));
1508
1561
  }
1509
1562
  else if (additional_kwargs?.tool_calls) {
1510
1563
  input.push(...additional_kwargs.tool_calls.map((toolCall) => ({
@@ -1615,7 +1668,7 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1615
1668
  }
1616
1669
  reducedTools.push(tool);
1617
1670
  }
1618
- else if (isOpenAITool(tool)) {
1671
+ else if (isOpenAIFunctionTool(tool)) {
1619
1672
  reducedTools.push({
1620
1673
  type: "function",
1621
1674
  name: tool.function.name,
@@ -1624,6 +1677,9 @@ export class ChatOpenAIResponses extends BaseChatOpenAI {
1624
1677
  strict: fields?.strict ?? null,
1625
1678
  });
1626
1679
  }
1680
+ else if (isOpenAICustomTool(tool)) {
1681
+ reducedTools.push(convertCompletionsCustomTool(tool));
1682
+ }
1627
1683
  }
1628
1684
  return reducedTools;
1629
1685
  }
@@ -1680,6 +1736,7 @@ export class ChatOpenAICompletions extends BaseChatOpenAI {
1680
1736
  ? { modalities: this.modalities || options?.modalities }
1681
1737
  : {}),
1682
1738
  ...this.modelKwargs,
1739
+ prompt_cache_key: options?.promptCacheKey ?? this.promptCacheKey,
1683
1740
  };
1684
1741
  if (options?.prediction !== undefined) {
1685
1742
  params.prediction = options.prediction;
@@ -1927,6 +1984,7 @@ export class ChatOpenAICompletions extends BaseChatOpenAI {
1927
1984
  return await this.client.chat.completions.parse(request, clientOptions);
1928
1985
  }
1929
1986
  else {
1987
+ console.log("request", request);
1930
1988
  return await this.client.chat.completions.create(request, clientOptions);
1931
1989
  }
1932
1990
  }
@@ -2655,7 +2713,11 @@ export class ChatOpenAI extends BaseChatOpenAI {
2655
2713
  options?.include != null ||
2656
2714
  options?.reasoning?.summary != null ||
2657
2715
  this.reasoning?.summary != null;
2658
- return this.useResponsesApi || usesBuiltInTools || hasResponsesOnlyKwargs;
2716
+ const hasCustomTools = options?.tools?.some(isOpenAICustomTool);
2717
+ return (this.useResponsesApi ||
2718
+ usesBuiltInTools ||
2719
+ hasResponsesOnlyKwargs ||
2720
+ hasCustomTools);
2659
2721
  }
2660
2722
  getLsParams(options) {
2661
2723
  const optionsWithDefaults = this._combineCallOptions(options);
package/dist/index.cjs CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.convertPromptToOpenAI = exports.toFile = exports.OpenAIClient = void 0;
17
+ exports.customTool = exports.convertPromptToOpenAI = exports.toFile = exports.OpenAIClient = void 0;
18
18
  var openai_1 = require("openai");
19
19
  Object.defineProperty(exports, "OpenAIClient", { enumerable: true, get: function () { return openai_1.OpenAI; } });
20
20
  Object.defineProperty(exports, "toFile", { enumerable: true, get: function () { return openai_1.toFile; } });
@@ -30,3 +30,5 @@ __exportStar(require("./utils/azure.cjs"), exports);
30
30
  __exportStar(require("./tools/index.cjs"), exports);
31
31
  var prompts_js_1 = require("./utils/prompts.cjs");
32
32
  Object.defineProperty(exports, "convertPromptToOpenAI", { enumerable: true, get: function () { return prompts_js_1.convertPromptToOpenAI; } });
33
+ var custom_js_1 = require("./tools/custom.cjs");
34
+ Object.defineProperty(exports, "customTool", { enumerable: true, get: function () { return custom_js_1.customTool; } });
package/dist/index.d.ts CHANGED
@@ -10,3 +10,4 @@ export * from "./utils/openai.js";
10
10
  export * from "./utils/azure.js";
11
11
  export * from "./tools/index.js";
12
12
  export { convertPromptToOpenAI } from "./utils/prompts.js";
13
+ export { customTool } from "./tools/custom.js";
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export * from "./utils/openai.js";
10
10
  export * from "./utils/azure.js";
11
11
  export * from "./tools/index.js";
12
12
  export { convertPromptToOpenAI } from "./utils/prompts.js";
13
+ export { customTool } from "./tools/custom.js";
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.customTool = customTool;
4
+ const runnables_1 = require("@langchain/core/runnables");
5
+ const singletons_1 = require("@langchain/core/singletons");
6
+ const tools_1 = require("@langchain/core/tools");
7
+ function customTool(func, fields) {
8
+ return new tools_1.DynamicTool({
9
+ ...fields,
10
+ description: "",
11
+ metadata: {
12
+ customTool: fields,
13
+ },
14
+ func: async (input, runManager, config) => new Promise((resolve, reject) => {
15
+ const childConfig = (0, runnables_1.patchConfig)(config, {
16
+ callbacks: runManager?.getChild(),
17
+ });
18
+ void singletons_1.AsyncLocalStorageProviderSingleton.runWithConfig((0, runnables_1.pickRunnableConfigKeys)(childConfig), async () => {
19
+ try {
20
+ resolve(func(input, childConfig));
21
+ }
22
+ catch (e) {
23
+ reject(e);
24
+ }
25
+ });
26
+ }),
27
+ });
28
+ }
@@ -0,0 +1,5 @@
1
+ import { RunnableFunc } from "@langchain/core/runnables";
2
+ import { DynamicTool, ToolRunnableConfig } from "@langchain/core/tools";
3
+ import OpenAI from "openai";
4
+ export type CustomToolFields = Omit<OpenAI.Responses.CustomTool, "type">;
5
+ export declare function customTool(func: RunnableFunc<string, string, ToolRunnableConfig>, fields: CustomToolFields): DynamicTool<string>;
@@ -0,0 +1,25 @@
1
+ import { patchConfig, pickRunnableConfigKeys, } from "@langchain/core/runnables";
2
+ import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons";
3
+ import { DynamicTool } from "@langchain/core/tools";
4
+ export function customTool(func, fields) {
5
+ return new DynamicTool({
6
+ ...fields,
7
+ description: "",
8
+ metadata: {
9
+ customTool: fields,
10
+ },
11
+ func: async (input, runManager, config) => new Promise((resolve, reject) => {
12
+ const childConfig = patchConfig(config, {
13
+ callbacks: runManager?.getChild(),
14
+ });
15
+ void AsyncLocalStorageProviderSingleton.runWithConfig(pickRunnableConfigKeys(childConfig), async () => {
16
+ try {
17
+ resolve(func(input, childConfig));
18
+ }
19
+ catch (e) {
20
+ reject(e);
21
+ }
22
+ });
23
+ }),
24
+ });
25
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenAI as OpenAIClient } from "openai";
2
- import type { ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema } from "openai/resources/shared";
2
+ import type { ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema, ResponseFormatTextPython, ResponseFormatTextGrammar } from "openai/resources/shared";
3
3
  import { TiktokenModel } from "js-tiktoken/lite";
4
4
  import type { BaseLanguageModelCallOptions } from "@langchain/core/language_models/base";
5
5
  import { InteropZodObject } from "@langchain/core/utils/types";
@@ -76,6 +76,8 @@ export declare interface OpenAIBaseInput {
76
76
  * `OPENAI_API_KEY` environment variable.
77
77
  */
78
78
  apiKey?: string;
79
+ /** The verbosity of the model's response. */
80
+ verbosity: "low" | "medium" | "high";
79
81
  }
80
82
  export type OpenAICoreRequestOptions = OpenAIClient.RequestOptions;
81
83
  export interface OpenAICallOptions extends BaseLanguageModelCallOptions {
@@ -154,6 +156,12 @@ export interface OpenAIChatInput extends OpenAIBaseInput {
154
156
  * Specifies the service tier for prioritization and latency optimization.
155
157
  */
156
158
  service_tier?: OpenAIClient.Responses.ResponseCreateParams["service_tier"];
159
+ /**
160
+ * Used by OpenAI to cache responses for similar requests to optimize your cache
161
+ * hit rates. Replaces the `user` field.
162
+ * [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
163
+ */
164
+ promptCacheKey?: string;
157
165
  }
158
166
  export interface AzureOpenAIInput {
159
167
  /**
@@ -241,4 +249,5 @@ export type ChatOpenAIReasoningSummary = Omit<OpenAIClient.Responses.ResponseRea
241
249
  index?: number;
242
250
  }>;
243
251
  };
244
- export type ChatOpenAIResponseFormat = ResponseFormatText | ResponseFormatJSONObject | ChatOpenAIResponseFormatJSONSchema;
252
+ export type ChatOpenAIResponseFormat = ResponseFormatText | ResponseFormatJSONObject | ResponseFormatTextGrammar | ResponseFormatTextPython | ChatOpenAIResponseFormatJSONSchema;
253
+ export type ResponseFormatConfiguration = ResponseFormatText | ResponseFormatJSONObject | ResponseFormatJSONSchema;
@@ -1,6 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports._convertToOpenAITool = _convertToOpenAITool;
4
+ exports.isBuiltInTool = isBuiltInTool;
5
+ exports.isBuiltInToolChoice = isBuiltInToolChoice;
6
+ exports.isCustomTool = isCustomTool;
7
+ exports.isOpenAICustomTool = isOpenAICustomTool;
8
+ exports.parseCustomToolCall = parseCustomToolCall;
9
+ exports.isCustomToolCall = isCustomToolCall;
10
+ exports.convertCompletionsCustomTool = convertCompletionsCustomTool;
11
+ exports.convertResponsesCustomTool = convertResponsesCustomTool;
4
12
  const function_calling_1 = require("@langchain/core/utils/function_calling");
5
13
  const openai_js_1 = require("./openai.cjs");
6
14
  /**
@@ -24,8 +32,113 @@ tool, fields) {
24
32
  else {
25
33
  toolDef = tool;
26
34
  }
27
- if (fields?.strict !== undefined) {
35
+ if (toolDef.type === "function" && fields?.strict !== undefined) {
28
36
  toolDef.function.strict = fields.strict;
29
37
  }
30
38
  return toolDef;
31
39
  }
40
+ function isBuiltInTool(tool) {
41
+ return "type" in tool && tool.type !== "function" && tool.type !== "custom";
42
+ }
43
+ function isBuiltInToolChoice(tool_choice) {
44
+ return (tool_choice != null &&
45
+ typeof tool_choice === "object" &&
46
+ "type" in tool_choice &&
47
+ tool_choice.type !== "function");
48
+ }
49
+ function isCustomTool(tool) {
50
+ return (typeof tool === "object" &&
51
+ tool !== null &&
52
+ "metadata" in tool &&
53
+ typeof tool.metadata === "object" &&
54
+ tool.metadata !== null &&
55
+ "customTool" in tool.metadata &&
56
+ typeof tool.metadata.customTool === "object" &&
57
+ tool.metadata.customTool !== null);
58
+ }
59
+ function isOpenAICustomTool(tool) {
60
+ return ("type" in tool &&
61
+ tool.type === "custom" &&
62
+ "custom" in tool &&
63
+ typeof tool.custom === "object" &&
64
+ tool.custom !== null);
65
+ }
66
+ function parseCustomToolCall(
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ rawToolCall) {
69
+ if (rawToolCall.type !== "custom_tool_call") {
70
+ return undefined;
71
+ }
72
+ return {
73
+ ...rawToolCall,
74
+ type: "tool_call",
75
+ call_id: rawToolCall.id,
76
+ id: rawToolCall.call_id,
77
+ name: rawToolCall.name,
78
+ isCustomTool: true,
79
+ args: {
80
+ input: rawToolCall.input,
81
+ },
82
+ };
83
+ }
84
+ function isCustomToolCall(toolCall) {
85
+ return (toolCall.type === "tool_call" &&
86
+ "isCustomTool" in toolCall &&
87
+ toolCall.isCustomTool === true);
88
+ }
89
+ function convertCompletionsCustomTool(tool) {
90
+ const getFormat = () => {
91
+ if (!tool.custom.format) {
92
+ return undefined;
93
+ }
94
+ if (tool.custom.format.type === "grammar") {
95
+ return {
96
+ type: "grammar",
97
+ definition: tool.custom.format.grammar.definition,
98
+ syntax: tool.custom.format.grammar.syntax,
99
+ };
100
+ }
101
+ if (tool.custom.format.type === "text") {
102
+ return {
103
+ type: "text",
104
+ };
105
+ }
106
+ return undefined;
107
+ };
108
+ return {
109
+ type: "custom",
110
+ name: tool.custom.name,
111
+ description: tool.custom.description,
112
+ format: getFormat(),
113
+ };
114
+ }
115
+ function convertResponsesCustomTool(tool) {
116
+ const getFormat = () => {
117
+ if (!tool.format) {
118
+ return undefined;
119
+ }
120
+ if (tool.format.type === "grammar") {
121
+ return {
122
+ type: "grammar",
123
+ grammar: {
124
+ definition: tool.format.definition,
125
+ syntax: tool.format.syntax,
126
+ },
127
+ };
128
+ }
129
+ if (tool.format.type === "text") {
130
+ return {
131
+ type: "text",
132
+ };
133
+ }
134
+ return undefined;
135
+ };
136
+ return {
137
+ type: "custom",
138
+ custom: {
139
+ name: tool.name,
140
+ description: tool.description,
141
+ format: getFormat(),
142
+ },
143
+ };
144
+ }
@@ -1,5 +1,11 @@
1
1
  import { OpenAI as OpenAIClient } from "openai";
2
+ import { ToolCall } from "@langchain/core/messages/tool";
2
3
  import { BindToolsInput } from "@langchain/core/language_models/chat_models";
4
+ import { DynamicTool } from "@langchain/core/tools";
5
+ import { OpenAIToolChoice } from "./openai.js";
6
+ export type ResponsesTool = NonNullable<OpenAIClient.Responses.ResponseCreateParams["tools"]>[number];
7
+ export type ResponsesToolChoice = NonNullable<OpenAIClient.Responses.ResponseCreateParams["tool_choice"]>;
8
+ export type ChatOpenAIToolType = BindToolsInput | OpenAIClient.Chat.ChatCompletionTool | ResponsesTool;
3
9
  /**
4
10
  * Formats a tool in either OpenAI format, or LangChain structured tool format
5
11
  * into an OpenAI tool format. If the tool is already in OpenAI format, return without
@@ -18,3 +24,21 @@ export declare function _convertToOpenAITool(tool: BindToolsInput, fields?: {
18
24
  */
19
25
  strict?: boolean;
20
26
  }): OpenAIClient.ChatCompletionTool;
27
+ export declare function isBuiltInTool(tool: ChatOpenAIToolType): tool is ResponsesTool;
28
+ export declare function isBuiltInToolChoice(tool_choice: OpenAIToolChoice | ResponsesToolChoice | undefined): tool_choice is ResponsesToolChoice;
29
+ export type CustomToolCall = ToolCall & {
30
+ call_id: string;
31
+ isCustomTool: true;
32
+ };
33
+ type LangchainCustomTool = DynamicTool<string> & {
34
+ metadata: {
35
+ customTool: OpenAIClient.Responses.CustomTool;
36
+ };
37
+ };
38
+ export declare function isCustomTool(tool: unknown): tool is LangchainCustomTool;
39
+ export declare function isOpenAICustomTool(tool: ChatOpenAIToolType): tool is OpenAIClient.Chat.ChatCompletionCustomTool;
40
+ export declare function parseCustomToolCall(rawToolCall: Record<string, any>): CustomToolCall | undefined;
41
+ export declare function isCustomToolCall(toolCall: ToolCall): toolCall is CustomToolCall;
42
+ export declare function convertCompletionsCustomTool(tool: OpenAIClient.Chat.ChatCompletionCustomTool): OpenAIClient.Responses.CustomTool;
43
+ export declare function convertResponsesCustomTool(tool: OpenAIClient.Responses.CustomTool): OpenAIClient.Chat.ChatCompletionCustomTool;
44
+ export {};
@@ -21,8 +21,113 @@ tool, fields) {
21
21
  else {
22
22
  toolDef = tool;
23
23
  }
24
- if (fields?.strict !== undefined) {
24
+ if (toolDef.type === "function" && fields?.strict !== undefined) {
25
25
  toolDef.function.strict = fields.strict;
26
26
  }
27
27
  return toolDef;
28
28
  }
29
+ export function isBuiltInTool(tool) {
30
+ return "type" in tool && tool.type !== "function" && tool.type !== "custom";
31
+ }
32
+ export function isBuiltInToolChoice(tool_choice) {
33
+ return (tool_choice != null &&
34
+ typeof tool_choice === "object" &&
35
+ "type" in tool_choice &&
36
+ tool_choice.type !== "function");
37
+ }
38
+ export function isCustomTool(tool) {
39
+ return (typeof tool === "object" &&
40
+ tool !== null &&
41
+ "metadata" in tool &&
42
+ typeof tool.metadata === "object" &&
43
+ tool.metadata !== null &&
44
+ "customTool" in tool.metadata &&
45
+ typeof tool.metadata.customTool === "object" &&
46
+ tool.metadata.customTool !== null);
47
+ }
48
+ export function isOpenAICustomTool(tool) {
49
+ return ("type" in tool &&
50
+ tool.type === "custom" &&
51
+ "custom" in tool &&
52
+ typeof tool.custom === "object" &&
53
+ tool.custom !== null);
54
+ }
55
+ export function parseCustomToolCall(
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ rawToolCall) {
58
+ if (rawToolCall.type !== "custom_tool_call") {
59
+ return undefined;
60
+ }
61
+ return {
62
+ ...rawToolCall,
63
+ type: "tool_call",
64
+ call_id: rawToolCall.id,
65
+ id: rawToolCall.call_id,
66
+ name: rawToolCall.name,
67
+ isCustomTool: true,
68
+ args: {
69
+ input: rawToolCall.input,
70
+ },
71
+ };
72
+ }
73
+ export function isCustomToolCall(toolCall) {
74
+ return (toolCall.type === "tool_call" &&
75
+ "isCustomTool" in toolCall &&
76
+ toolCall.isCustomTool === true);
77
+ }
78
+ export function convertCompletionsCustomTool(tool) {
79
+ const getFormat = () => {
80
+ if (!tool.custom.format) {
81
+ return undefined;
82
+ }
83
+ if (tool.custom.format.type === "grammar") {
84
+ return {
85
+ type: "grammar",
86
+ definition: tool.custom.format.grammar.definition,
87
+ syntax: tool.custom.format.grammar.syntax,
88
+ };
89
+ }
90
+ if (tool.custom.format.type === "text") {
91
+ return {
92
+ type: "text",
93
+ };
94
+ }
95
+ return undefined;
96
+ };
97
+ return {
98
+ type: "custom",
99
+ name: tool.custom.name,
100
+ description: tool.custom.description,
101
+ format: getFormat(),
102
+ };
103
+ }
104
+ export function convertResponsesCustomTool(tool) {
105
+ const getFormat = () => {
106
+ if (!tool.format) {
107
+ return undefined;
108
+ }
109
+ if (tool.format.type === "grammar") {
110
+ return {
111
+ type: "grammar",
112
+ grammar: {
113
+ definition: tool.format.definition,
114
+ syntax: tool.format.syntax,
115
+ },
116
+ };
117
+ }
118
+ if (tool.format.type === "text") {
119
+ return {
120
+ type: "text",
121
+ };
122
+ }
123
+ return undefined;
124
+ };
125
+ return {
126
+ type: "custom",
127
+ custom: {
128
+ name: tool.name,
129
+ description: tool.description,
130
+ format: getFormat(),
131
+ },
132
+ };
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,11 +36,11 @@
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
38
  "js-tiktoken": "^1.0.12",
39
- "openai": "^5.3.0",
39
+ "openai": "^5.12.1",
40
40
  "zod": "^3.25.32"
41
41
  },
42
42
  "peerDependencies": {
43
- "@langchain/core": ">=0.3.58 <0.4.0"
43
+ "@langchain/core": ">=0.3.68 <0.4.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@azure/identity": "^4.2.1",