@langchain/openai 0.0.27 → 0.0.28

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.
@@ -48,12 +48,30 @@ function messageToOpenAIRole(message) {
48
48
  }
49
49
  exports.messageToOpenAIRole = messageToOpenAIRole;
50
50
  function openAIResponseToChatMessage(message) {
51
+ const rawToolCalls = message.tool_calls;
51
52
  switch (message.role) {
52
- case "assistant":
53
- return new messages_1.AIMessage(message.content || "", {
54
- function_call: message.function_call,
55
- tool_calls: message.tool_calls,
53
+ case "assistant": {
54
+ const toolCalls = [];
55
+ const invalidToolCalls = [];
56
+ for (const rawToolCall of rawToolCalls ?? []) {
57
+ try {
58
+ toolCalls.push((0, openai_tools_1.parseToolCall)(rawToolCall, { returnId: true }));
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ }
61
+ catch (e) {
62
+ invalidToolCalls.push((0, openai_tools_1.makeInvalidToolCall)(rawToolCall, e.message));
63
+ }
64
+ }
65
+ return new messages_1.AIMessage({
66
+ content: message.content || "",
67
+ tool_calls: toolCalls,
68
+ invalid_tool_calls: invalidToolCalls,
69
+ additional_kwargs: {
70
+ function_call: message.function_call,
71
+ tool_calls: rawToolCalls,
72
+ },
56
73
  });
74
+ }
57
75
  default:
58
76
  return new messages_1.ChatMessage(message.content || "", message.role ?? "unknown");
59
77
  }
@@ -81,7 +99,22 @@ delta, defaultRole) {
81
99
  return new messages_1.HumanMessageChunk({ content });
82
100
  }
83
101
  else if (role === "assistant") {
84
- return new messages_1.AIMessageChunk({ content, additional_kwargs });
102
+ const toolCallChunks = [];
103
+ if (Array.isArray(delta.tool_calls)) {
104
+ for (const rawToolCall of delta.tool_calls) {
105
+ toolCallChunks.push({
106
+ name: rawToolCall.function?.name,
107
+ args: rawToolCall.function?.arguments,
108
+ id: rawToolCall.id,
109
+ index: rawToolCall.index,
110
+ });
111
+ }
112
+ }
113
+ return new messages_1.AIMessageChunk({
114
+ content,
115
+ tool_call_chunks: toolCallChunks,
116
+ additional_kwargs,
117
+ });
85
118
  }
86
119
  else if (role === "system") {
87
120
  return new messages_1.SystemMessageChunk({ content });
@@ -118,11 +151,16 @@ function convertMessagesToOpenAIParams(messages) {
118
151
  if (message.additional_kwargs.function_call != null) {
119
152
  completionParam.function_call = message.additional_kwargs.function_call;
120
153
  }
121
- if (message.additional_kwargs.tool_calls != null) {
122
- completionParam.tool_calls = message.additional_kwargs.tool_calls;
154
+ if ((0, messages_1.isAIMessage)(message) && !!message.tool_calls?.length) {
155
+ completionParam.tool_calls = message.tool_calls.map(openai_tools_1.convertLangChainToolCallToOpenAI);
123
156
  }
124
- if (message.tool_call_id != null) {
125
- completionParam.tool_call_id = message.tool_call_id;
157
+ else {
158
+ if (message.additional_kwargs.tool_calls != null) {
159
+ completionParam.tool_calls = message.additional_kwargs.tool_calls;
160
+ }
161
+ if (message.tool_call_id != null) {
162
+ completionParam.tool_call_id = message.tool_call_id;
163
+ }
126
164
  }
127
165
  return completionParam;
128
166
  });
@@ -437,6 +475,12 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
437
475
  ...fields?.configuration,
438
476
  };
439
477
  }
478
+ bindTools(tools, kwargs) {
479
+ return this.bind({
480
+ tools: tools.map(function_calling_1.convertToOpenAITool),
481
+ ...kwargs,
482
+ });
483
+ }
440
484
  /**
441
485
  * Get the parameters used to invoke the model
442
486
  */
@@ -1,12 +1,12 @@
1
1
  import { type ClientOptions, OpenAI as OpenAIClient } from "openai";
2
2
  import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
3
- import { type BaseMessage } from "@langchain/core/messages";
3
+ import { AIMessageChunk, type BaseMessage } from "@langchain/core/messages";
4
4
  import { ChatGenerationChunk, type ChatResult } from "@langchain/core/outputs";
5
5
  import { type StructuredToolInterface } from "@langchain/core/tools";
6
6
  import { BaseChatModel, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
7
7
  import type { BaseFunctionCallOptions, BaseLanguageModelInput, StructuredOutputMethodOptions, StructuredOutputMethodParams } from "@langchain/core/language_models/base";
8
8
  import { z } from "zod";
9
- import { Runnable } from "@langchain/core/runnables";
9
+ import { Runnable, RunnableInterface } from "@langchain/core/runnables";
10
10
  import type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput, OpenAICoreRequestOptions, LegacyOpenAIInput } from "./types.js";
11
11
  export type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput };
12
12
  interface TokenUsage {
@@ -62,7 +62,7 @@ export interface ChatOpenAICallOptions extends OpenAICallOptions, BaseFunctionCa
62
62
  *
63
63
  * ```
64
64
  */
65
- export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = ChatOpenAICallOptions> extends BaseChatModel<CallOptions> implements OpenAIChatInput, AzureOpenAIInput {
65
+ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = ChatOpenAICallOptions> extends BaseChatModel<CallOptions, AIMessageChunk> implements OpenAIChatInput, AzureOpenAIInput {
66
66
  static lc_name(): string;
67
67
  get callKeys(): string[];
68
68
  lc_serializable: boolean;
@@ -102,6 +102,7 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
102
102
  },
103
103
  /** @deprecated */
104
104
  configuration?: ClientOptions & LegacyOpenAIInput);
105
+ bindTools(tools: (Record<string, unknown> | StructuredToolInterface)[], kwargs?: Partial<CallOptions>): RunnableInterface<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
105
106
  /**
106
107
  * Get the parameters used to invoke the model
107
108
  */
@@ -1,12 +1,12 @@
1
1
  import { OpenAI as OpenAIClient } from "openai";
2
- import { AIMessage, AIMessageChunk, ChatMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, SystemMessageChunk, ToolMessageChunk, } from "@langchain/core/messages";
2
+ import { AIMessage, AIMessageChunk, ChatMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, SystemMessageChunk, ToolMessageChunk, isAIMessage, } from "@langchain/core/messages";
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
6
  import { convertToOpenAITool } from "@langchain/core/utils/function_calling";
7
7
  import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
8
8
  import { JsonOutputParser, StructuredOutputParser, } from "@langchain/core/output_parsers";
9
- import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools";
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
12
  import { wrapOpenAIClientError } from "./utils/openai.js";
@@ -44,12 +44,30 @@ export function messageToOpenAIRole(message) {
44
44
  }
45
45
  }
46
46
  function openAIResponseToChatMessage(message) {
47
+ const rawToolCalls = message.tool_calls;
47
48
  switch (message.role) {
48
- case "assistant":
49
- return new AIMessage(message.content || "", {
50
- function_call: message.function_call,
51
- tool_calls: message.tool_calls,
49
+ case "assistant": {
50
+ const toolCalls = [];
51
+ const invalidToolCalls = [];
52
+ for (const rawToolCall of rawToolCalls ?? []) {
53
+ try {
54
+ toolCalls.push(parseToolCall(rawToolCall, { returnId: true }));
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ }
57
+ catch (e) {
58
+ invalidToolCalls.push(makeInvalidToolCall(rawToolCall, e.message));
59
+ }
60
+ }
61
+ return new AIMessage({
62
+ content: message.content || "",
63
+ tool_calls: toolCalls,
64
+ invalid_tool_calls: invalidToolCalls,
65
+ additional_kwargs: {
66
+ function_call: message.function_call,
67
+ tool_calls: rawToolCalls,
68
+ },
52
69
  });
70
+ }
53
71
  default:
54
72
  return new ChatMessage(message.content || "", message.role ?? "unknown");
55
73
  }
@@ -77,7 +95,22 @@ delta, defaultRole) {
77
95
  return new HumanMessageChunk({ content });
78
96
  }
79
97
  else if (role === "assistant") {
80
- return new AIMessageChunk({ content, additional_kwargs });
98
+ const toolCallChunks = [];
99
+ if (Array.isArray(delta.tool_calls)) {
100
+ for (const rawToolCall of delta.tool_calls) {
101
+ toolCallChunks.push({
102
+ name: rawToolCall.function?.name,
103
+ args: rawToolCall.function?.arguments,
104
+ id: rawToolCall.id,
105
+ index: rawToolCall.index,
106
+ });
107
+ }
108
+ }
109
+ return new AIMessageChunk({
110
+ content,
111
+ tool_call_chunks: toolCallChunks,
112
+ additional_kwargs,
113
+ });
81
114
  }
82
115
  else if (role === "system") {
83
116
  return new SystemMessageChunk({ content });
@@ -114,11 +147,16 @@ function convertMessagesToOpenAIParams(messages) {
114
147
  if (message.additional_kwargs.function_call != null) {
115
148
  completionParam.function_call = message.additional_kwargs.function_call;
116
149
  }
117
- if (message.additional_kwargs.tool_calls != null) {
118
- completionParam.tool_calls = message.additional_kwargs.tool_calls;
150
+ if (isAIMessage(message) && !!message.tool_calls?.length) {
151
+ completionParam.tool_calls = message.tool_calls.map(convertLangChainToolCallToOpenAI);
119
152
  }
120
- if (message.tool_call_id != null) {
121
- completionParam.tool_call_id = message.tool_call_id;
153
+ else {
154
+ if (message.additional_kwargs.tool_calls != null) {
155
+ completionParam.tool_calls = message.additional_kwargs.tool_calls;
156
+ }
157
+ if (message.tool_call_id != null) {
158
+ completionParam.tool_call_id = message.tool_call_id;
159
+ }
122
160
  }
123
161
  return completionParam;
124
162
  });
@@ -433,6 +471,12 @@ export class ChatOpenAI extends BaseChatModel {
433
471
  ...fields?.configuration,
434
472
  };
435
473
  }
474
+ bindTools(tools, kwargs) {
475
+ return this.bind({
476
+ tools: tools.map(convertToOpenAITool),
477
+ ...kwargs,
478
+ });
479
+ }
436
480
  /**
437
481
  * Get the parameters used to invoke the model
438
482
  */
@@ -1,5 +1,5 @@
1
1
  import { test, expect, jest } from "@jest/globals";
2
- import { HumanMessage, ToolMessage } from "@langchain/core/messages";
2
+ import { AIMessage, HumanMessage, ToolMessage } from "@langchain/core/messages";
3
3
  import { InMemoryCache } from "@langchain/core/caches";
4
4
  import { ChatOpenAI } from "../chat_models.js";
5
5
  test("Test ChatOpenAI JSON mode", async () => {
@@ -60,7 +60,10 @@ test("Test ChatOpenAI tool calling", async () => {
60
60
  ["human", "What's the weather like in San Francisco, Tokyo, and Paris?"],
61
61
  ]);
62
62
  console.log(JSON.stringify(res));
63
- expect(res.additional_kwargs.tool_calls?.length).toBeGreaterThan(1);
63
+ expect(res.additional_kwargs.tool_calls?.length).toEqual(3);
64
+ expect(res.tool_calls?.[0].args).toEqual(JSON.parse(res.additional_kwargs.tool_calls?.[0].function.arguments ?? "{}"));
65
+ expect(res.tool_calls?.[1].args).toEqual(JSON.parse(res.additional_kwargs.tool_calls?.[1].function.arguments ?? "{}"));
66
+ expect(res.tool_calls?.[2].args).toEqual(JSON.parse(res.additional_kwargs.tool_calls?.[2].function.arguments ?? "{}"));
64
67
  });
65
68
  test("Test ChatOpenAI streaming logprobs", async () => {
66
69
  const model = new ChatOpenAI({
@@ -203,3 +206,54 @@ test("ChatOpenAI in JSON mode can cache generations", async () => {
203
206
  lookupSpy.mockRestore();
204
207
  updateSpy.mockRestore();
205
208
  });
209
+ test("Few shotting with tool calls", async () => {
210
+ const chat = new ChatOpenAI({
211
+ modelName: "gpt-3.5-turbo-1106",
212
+ temperature: 1,
213
+ }).bind({
214
+ tools: [
215
+ {
216
+ type: "function",
217
+ function: {
218
+ name: "get_current_weather",
219
+ description: "Get the current weather in a given location",
220
+ parameters: {
221
+ type: "object",
222
+ properties: {
223
+ location: {
224
+ type: "string",
225
+ description: "The city and state, e.g. San Francisco, CA",
226
+ },
227
+ unit: { type: "string", enum: ["celsius", "fahrenheit"] },
228
+ },
229
+ required: ["location"],
230
+ },
231
+ },
232
+ },
233
+ ],
234
+ tool_choice: "auto",
235
+ });
236
+ const res = await chat.invoke([
237
+ new HumanMessage("What is the weather in SF?"),
238
+ new AIMessage({
239
+ content: "",
240
+ tool_calls: [
241
+ {
242
+ id: "12345",
243
+ name: "get_current_weather",
244
+ args: {
245
+ location: "SF",
246
+ },
247
+ },
248
+ ],
249
+ }),
250
+ new ToolMessage({
251
+ tool_call_id: "12345",
252
+ content: "It is currently 24 degrees with hail in SF.",
253
+ }),
254
+ new AIMessage("It is currently 24 degrees in SF with hail in SF."),
255
+ new HumanMessage("What did you say the weather was?"),
256
+ ]);
257
+ console.log(res);
258
+ expect(res.content).toContain("24");
259
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -39,7 +39,7 @@
39
39
  "author": "LangChain",
40
40
  "license": "MIT",
41
41
  "dependencies": {
42
- "@langchain/core": "~0.1.45",
42
+ "@langchain/core": "~0.1.56",
43
43
  "js-tiktoken": "^1.0.7",
44
44
  "openai": "^4.32.1",
45
45
  "zod": "^3.22.4",