@langchain/openai 0.2.3 → 0.2.5

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.
@@ -568,7 +568,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
568
568
  tools: isStructuredToolArray(options?.tools)
569
569
  ? options?.tools.map(function_calling_1.convertToOpenAITool)
570
570
  : options?.tools,
571
- tool_choice: options?.tool_choice,
571
+ tool_choice: (0, openai_js_1.formatToOpenAIToolChoice)(options?.tool_choice),
572
572
  response_format: options?.response_format,
573
573
  seed: options?.seed,
574
574
  ...streamOptionsConfig,
@@ -8,6 +8,7 @@ import type { BaseFunctionCallOptions, BaseLanguageModelInput, StructuredOutputM
8
8
  import { z } from "zod";
9
9
  import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
10
10
  import type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput, OpenAICoreRequestOptions, LegacyOpenAIInput } from "./types.js";
11
+ import { OpenAIToolChoice } from "./utils/openai.js";
11
12
  export type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput };
12
13
  interface TokenUsage {
13
14
  completionTokens?: number;
@@ -21,7 +22,7 @@ type OpenAIRoleEnum = "system" | "assistant" | "user" | "function" | "tool";
21
22
  export declare function messageToOpenAIRole(message: BaseMessage): OpenAIRoleEnum;
22
23
  export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCallOptions {
23
24
  tools?: StructuredToolInterface[] | OpenAIClient.ChatCompletionTool[];
24
- tool_choice?: OpenAIClient.ChatCompletionToolChoiceOption;
25
+ tool_choice?: OpenAIToolChoice;
25
26
  promptIndex?: number;
26
27
  response_format?: {
27
28
  type: "json_object";
@@ -9,7 +9,7 @@ import { JsonOutputParser, StructuredOutputParser, } from "@langchain/core/outpu
9
9
  import { JsonOutputKeyToolsParser, convertLangChainToolCallToOpenAI, makeInvalidToolCall, parseToolCall, } from "@langchain/core/output_parsers/openai_tools";
10
10
  import { zodToJsonSchema } from "zod-to-json-schema";
11
11
  import { getEndpoint } from "./utils/azure.js";
12
- import { wrapOpenAIClientError } from "./utils/openai.js";
12
+ import { formatToOpenAIToolChoice, wrapOpenAIClientError, } from "./utils/openai.js";
13
13
  import { formatFunctionDefinitions, } from "./utils/openai-format-fndef.js";
14
14
  function extractGenericMessageCustomRole(message) {
15
15
  if (message.role !== "system" &&
@@ -564,7 +564,7 @@ export class ChatOpenAI extends BaseChatModel {
564
564
  tools: isStructuredToolArray(options?.tools)
565
565
  ? options?.tools.map(convertToOpenAITool)
566
566
  : options?.tools,
567
- tool_choice: options?.tool_choice,
567
+ tool_choice: formatToOpenAIToolChoice(options?.tool_choice),
568
568
  response_format: options?.response_format,
569
569
  seed: options?.seed,
570
570
  ...streamOptionsConfig,
package/dist/legacy.cjs CHANGED
@@ -253,7 +253,7 @@ class OpenAIChat extends llms_1.LLM {
253
253
  this.organization =
254
254
  fields?.configuration?.organization ??
255
255
  (0, env_1.getEnvironmentVariable)("OPENAI_ORGANIZATION");
256
- this.modelName = fields?.modelName ?? this.modelName;
256
+ this.modelName = fields?.model ?? fields?.modelName ?? this.modelName;
257
257
  this.prefixMessages = fields?.prefixMessages ?? this.prefixMessages;
258
258
  this.modelKwargs = fields?.modelKwargs ?? {};
259
259
  this.timeout = fields?.timeout;
package/dist/legacy.js CHANGED
@@ -250,7 +250,7 @@ export class OpenAIChat extends LLM {
250
250
  this.organization =
251
251
  fields?.configuration?.organization ??
252
252
  getEnvironmentVariable("OPENAI_ORGANIZATION");
253
- this.modelName = fields?.modelName ?? this.modelName;
253
+ this.modelName = fields?.model ?? fields?.modelName ?? this.modelName;
254
254
  this.prefixMessages = fields?.prefixMessages ?? this.prefixMessages;
255
255
  this.modelKwargs = fields?.modelKwargs ?? {};
256
256
  this.timeout = fields?.timeout;
package/dist/llms.cjs CHANGED
@@ -75,6 +75,15 @@ class OpenAI extends llms_1.BaseLLM {
75
75
  let model = fields?.model ?? fields?.modelName;
76
76
  if ((model?.startsWith("gpt-3.5-turbo") || model?.startsWith("gpt-4")) &&
77
77
  !model?.includes("-instruct")) {
78
+ console.warn([
79
+ `Your chosen OpenAI model, "${model}", is a chat model and not a text-in/text-out LLM.`,
80
+ `Passing it into the "OpenAI" class is deprecated and only permitted for backwards-compatibility. You may experience odd behavior.`,
81
+ `Please use the "ChatOpenAI" class instead.`,
82
+ "",
83
+ `See this page for more information:`,
84
+ "|",
85
+ `└> https://js.langchain.com/v0.2/docs/integrations/chat/openai`,
86
+ ].join("\n"));
78
87
  // eslint-disable-next-line no-constructor-return
79
88
  return new legacy_js_1.OpenAIChat(fields, configuration);
80
89
  }
package/dist/llms.js CHANGED
@@ -72,6 +72,15 @@ export class OpenAI extends BaseLLM {
72
72
  let model = fields?.model ?? fields?.modelName;
73
73
  if ((model?.startsWith("gpt-3.5-turbo") || model?.startsWith("gpt-4")) &&
74
74
  !model?.includes("-instruct")) {
75
+ console.warn([
76
+ `Your chosen OpenAI model, "${model}", is a chat model and not a text-in/text-out LLM.`,
77
+ `Passing it into the "OpenAI" class is deprecated and only permitted for backwards-compatibility. You may experience odd behavior.`,
78
+ `Please use the "ChatOpenAI" class instead.`,
79
+ "",
80
+ `See this page for more information:`,
81
+ "|",
82
+ `└> https://js.langchain.com/v0.2/docs/integrations/chat/openai`,
83
+ ].join("\n"));
75
84
  // eslint-disable-next-line no-constructor-return
76
85
  return new OpenAIChat(fields, configuration);
77
86
  }
@@ -100,18 +100,75 @@ class DallEAPIWrapper extends tools_1.Tool {
100
100
  fields?.dallEResponseFormat ?? this.dallEResponseFormat;
101
101
  this.user = fields?.user;
102
102
  }
103
+ /**
104
+ * Processes the API response if multiple images are generated.
105
+ * Returns a list of MessageContentImageUrl objects. If the response
106
+ * format is `url`, then the `image_url` field will contain the URL.
107
+ * If it is `b64_json`, then the `image_url` field will contain an object
108
+ * with a `url` field with the base64 encoded image.
109
+ *
110
+ * @param {OpenAIClient.Images.ImagesResponse[]} response The API response
111
+ * @returns {MessageContentImageUrl[]}
112
+ */
113
+ processMultipleGeneratedUrls(response) {
114
+ if (this.dallEResponseFormat === "url") {
115
+ return response.flatMap((res) => {
116
+ const imageUrlContent = res.data
117
+ .flatMap((item) => {
118
+ if (!item.url)
119
+ return [];
120
+ return {
121
+ type: "image_url",
122
+ image_url: item.url,
123
+ };
124
+ })
125
+ .filter((item) => item !== undefined &&
126
+ item.type === "image_url" &&
127
+ typeof item.image_url === "string" &&
128
+ item.image_url !== undefined);
129
+ return imageUrlContent;
130
+ });
131
+ }
132
+ else {
133
+ return response.flatMap((res) => {
134
+ const b64Content = res.data
135
+ .flatMap((item) => {
136
+ if (!item.b64_json)
137
+ return [];
138
+ return {
139
+ type: "image_url",
140
+ image_url: {
141
+ url: item.b64_json,
142
+ },
143
+ };
144
+ })
145
+ .filter((item) => item !== undefined &&
146
+ item.type === "image_url" &&
147
+ typeof item.image_url === "object" &&
148
+ "url" in item.image_url &&
149
+ typeof item.image_url.url === "string" &&
150
+ item.image_url.url !== undefined);
151
+ return b64Content;
152
+ });
153
+ }
154
+ }
103
155
  /** @ignore */
104
156
  async _call(input) {
105
- const response = await this.client.images.generate({
157
+ const generateImageFields = {
106
158
  model: this.model,
107
159
  prompt: input,
108
- n: this.n,
160
+ n: 1,
109
161
  size: this.size,
110
162
  response_format: this.dallEResponseFormat,
111
163
  style: this.style,
112
164
  quality: this.quality,
113
165
  user: this.user,
114
- });
166
+ };
167
+ if (this.n > 1) {
168
+ const results = await Promise.all(Array.from({ length: this.n }).map(() => this.client.images.generate(generateImageFields)));
169
+ return this.processMultipleGeneratedUrls(results);
170
+ }
171
+ const response = await this.client.images.generate(generateImageFields);
115
172
  let data = "";
116
173
  if (this.dallEResponseFormat === "url") {
117
174
  [data] = response.data
@@ -1,5 +1,6 @@
1
1
  import { OpenAI as OpenAIClient } from "openai";
2
2
  import { Tool, ToolParams } from "@langchain/core/tools";
3
+ import { MessageContentComplex } from "@langchain/core/messages";
3
4
  /**
4
5
  * An interface for the Dall-E API Wrapper.
5
6
  */
@@ -90,6 +91,17 @@ export declare class DallEAPIWrapper extends Tool {
90
91
  private dallEResponseFormat;
91
92
  private user?;
92
93
  constructor(fields?: DallEAPIWrapperParams);
94
+ /**
95
+ * Processes the API response if multiple images are generated.
96
+ * Returns a list of MessageContentImageUrl objects. If the response
97
+ * format is `url`, then the `image_url` field will contain the URL.
98
+ * If it is `b64_json`, then the `image_url` field will contain an object
99
+ * with a `url` field with the base64 encoded image.
100
+ *
101
+ * @param {OpenAIClient.Images.ImagesResponse[]} response The API response
102
+ * @returns {MessageContentImageUrl[]}
103
+ */
104
+ private processMultipleGeneratedUrls;
93
105
  /** @ignore */
94
- _call(input: string): Promise<string>;
106
+ _call(input: string): Promise<string | MessageContentComplex[]>;
95
107
  }
@@ -97,18 +97,75 @@ export class DallEAPIWrapper extends Tool {
97
97
  fields?.dallEResponseFormat ?? this.dallEResponseFormat;
98
98
  this.user = fields?.user;
99
99
  }
100
+ /**
101
+ * Processes the API response if multiple images are generated.
102
+ * Returns a list of MessageContentImageUrl objects. If the response
103
+ * format is `url`, then the `image_url` field will contain the URL.
104
+ * If it is `b64_json`, then the `image_url` field will contain an object
105
+ * with a `url` field with the base64 encoded image.
106
+ *
107
+ * @param {OpenAIClient.Images.ImagesResponse[]} response The API response
108
+ * @returns {MessageContentImageUrl[]}
109
+ */
110
+ processMultipleGeneratedUrls(response) {
111
+ if (this.dallEResponseFormat === "url") {
112
+ return response.flatMap((res) => {
113
+ const imageUrlContent = res.data
114
+ .flatMap((item) => {
115
+ if (!item.url)
116
+ return [];
117
+ return {
118
+ type: "image_url",
119
+ image_url: item.url,
120
+ };
121
+ })
122
+ .filter((item) => item !== undefined &&
123
+ item.type === "image_url" &&
124
+ typeof item.image_url === "string" &&
125
+ item.image_url !== undefined);
126
+ return imageUrlContent;
127
+ });
128
+ }
129
+ else {
130
+ return response.flatMap((res) => {
131
+ const b64Content = res.data
132
+ .flatMap((item) => {
133
+ if (!item.b64_json)
134
+ return [];
135
+ return {
136
+ type: "image_url",
137
+ image_url: {
138
+ url: item.b64_json,
139
+ },
140
+ };
141
+ })
142
+ .filter((item) => item !== undefined &&
143
+ item.type === "image_url" &&
144
+ typeof item.image_url === "object" &&
145
+ "url" in item.image_url &&
146
+ typeof item.image_url.url === "string" &&
147
+ item.image_url.url !== undefined);
148
+ return b64Content;
149
+ });
150
+ }
151
+ }
100
152
  /** @ignore */
101
153
  async _call(input) {
102
- const response = await this.client.images.generate({
154
+ const generateImageFields = {
103
155
  model: this.model,
104
156
  prompt: input,
105
- n: this.n,
157
+ n: 1,
106
158
  size: this.size,
107
159
  response_format: this.dallEResponseFormat,
108
160
  style: this.style,
109
161
  quality: this.quality,
110
162
  user: this.user,
111
- });
163
+ };
164
+ if (this.n > 1) {
165
+ const results = await Promise.all(Array.from({ length: this.n }).map(() => this.client.images.generate(generateImageFields)));
166
+ return this.processMultipleGeneratedUrls(results);
167
+ }
168
+ const response = await this.client.images.generate(generateImageFields);
112
169
  let data = "";
113
170
  if (this.dallEResponseFormat === "url") {
114
171
  [data] = response.data
@@ -14,3 +14,44 @@ test.skip("Dalle can generate images with base 64 response format", async () =>
14
14
  expect(res).toBeDefined();
15
15
  expect(res).not.toContain("https://");
16
16
  });
17
+ test.skip("Dalle returns multiple image URLs if n > 1", async () => {
18
+ const dalle = new DallEAPIWrapper({
19
+ n: 2,
20
+ });
21
+ const res = await dalle.invoke("A painting of a cat");
22
+ expect(res).toBeDefined();
23
+ expect(res).toBeInstanceOf(Array);
24
+ if (!Array.isArray(res))
25
+ return;
26
+ expect(res).toHaveLength(2);
27
+ // The types for each should be `image_url` with an `image_url` field containing the URL
28
+ expect(res[0].type).toBe("image_url");
29
+ expect(res[1].type).toBe("image_url");
30
+ expect(res[0]).toHaveProperty("image_url");
31
+ expect(res[1]).toHaveProperty("image_url");
32
+ expect(res[0].image_url.startsWith("https://")).toBe(true);
33
+ expect(res[1].image_url.startsWith("https://")).toBe(true);
34
+ });
35
+ test.skip("Dalle returns multiple base64 image strings if n > 1", async () => {
36
+ const dalle = new DallEAPIWrapper({
37
+ n: 2,
38
+ dallEResponseFormat: "b64_json",
39
+ });
40
+ const res = await dalle.invoke("A painting of a cat");
41
+ expect(res).toBeDefined();
42
+ expect(res).toBeInstanceOf(Array);
43
+ if (!Array.isArray(res))
44
+ return;
45
+ expect(res).toHaveLength(2);
46
+ // The types for each should be `b64_json` with an `b64_json` field containing the URL
47
+ expect(res[0].type).toBe("image_url");
48
+ expect(res[1].type).toBe("image_url");
49
+ expect(res[0]).toHaveProperty("image_url");
50
+ expect(res[1]).toHaveProperty("image_url");
51
+ expect(res[0].image_url).toHaveProperty("url");
52
+ expect(res[1].image_url).toHaveProperty("url");
53
+ expect(res[0].image_url.url).toBeDefined();
54
+ expect(res[1].image_url.url).toBeDefined();
55
+ expect(res[0].image_url.url).not.toBe("");
56
+ expect(res[1].image_url.url).not.toBe("");
57
+ });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatToOpenAIAssistantTool = exports.formatToOpenAITool = exports.formatToOpenAIFunction = exports.wrapOpenAIClientError = void 0;
3
+ exports.formatToOpenAIToolChoice = exports.formatToOpenAIAssistantTool = exports.formatToOpenAITool = exports.formatToOpenAIFunction = exports.wrapOpenAIClientError = void 0;
4
4
  const openai_1 = require("openai");
5
5
  const zod_to_json_schema_1 = require("zod-to-json-schema");
6
6
  const function_calling_1 = require("@langchain/core/utils/function_calling");
@@ -34,3 +34,29 @@ function formatToOpenAIAssistantTool(tool) {
34
34
  };
35
35
  }
36
36
  exports.formatToOpenAIAssistantTool = formatToOpenAIAssistantTool;
37
+ function formatToOpenAIToolChoice(toolChoice) {
38
+ if (!toolChoice) {
39
+ return undefined;
40
+ }
41
+ else if (toolChoice === "any" || toolChoice === "required") {
42
+ return "required";
43
+ }
44
+ else if (toolChoice === "auto") {
45
+ return "auto";
46
+ }
47
+ else if (toolChoice === "none") {
48
+ return "none";
49
+ }
50
+ else if (typeof toolChoice === "string") {
51
+ return {
52
+ type: "function",
53
+ function: {
54
+ name: toolChoice,
55
+ },
56
+ };
57
+ }
58
+ else {
59
+ return toolChoice;
60
+ }
61
+ }
62
+ exports.formatToOpenAIToolChoice = formatToOpenAIToolChoice;
@@ -1,3 +1,4 @@
1
+ import { OpenAI as OpenAIClient } from "openai";
1
2
  import type { StructuredToolInterface } from "@langchain/core/tools";
2
3
  import { convertToOpenAIFunction, convertToOpenAITool } from "@langchain/core/utils/function_calling";
3
4
  export declare function wrapOpenAIClientError(e: any): any;
@@ -15,3 +16,5 @@ export declare function formatToOpenAIAssistantTool(tool: StructuredToolInterfac
15
16
  };
16
17
  };
17
18
  };
19
+ export type OpenAIToolChoice = OpenAIClient.ChatCompletionToolChoiceOption | "any" | string;
20
+ export declare function formatToOpenAIToolChoice(toolChoice?: OpenAIToolChoice): OpenAIClient.ChatCompletionToolChoiceOption | undefined;
@@ -1,4 +1,4 @@
1
- import { APIConnectionTimeoutError, APIUserAbortError } from "openai";
1
+ import { APIConnectionTimeoutError, APIUserAbortError, } from "openai";
2
2
  import { zodToJsonSchema } from "zod-to-json-schema";
3
3
  import { convertToOpenAIFunction, convertToOpenAITool, } from "@langchain/core/utils/function_calling";
4
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -28,3 +28,28 @@ export function formatToOpenAIAssistantTool(tool) {
28
28
  },
29
29
  };
30
30
  }
31
+ export function formatToOpenAIToolChoice(toolChoice) {
32
+ if (!toolChoice) {
33
+ return undefined;
34
+ }
35
+ else if (toolChoice === "any" || toolChoice === "required") {
36
+ return "required";
37
+ }
38
+ else if (toolChoice === "auto") {
39
+ return "auto";
40
+ }
41
+ else if (toolChoice === "none") {
42
+ return "none";
43
+ }
44
+ else if (typeof toolChoice === "string") {
45
+ return {
46
+ type: "function",
47
+ function: {
48
+ name: toolChoice,
49
+ },
50
+ };
51
+ }
52
+ else {
53
+ return toolChoice;
54
+ }
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {