From f9bf4e3d0f7f93868417a139fa6b96e169bcf6e2 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 7 Jan 2025 14:40:06 -0800 Subject: [PATCH 1/3] Allow checking tool_calls on any BaseMessage without type casts --- langchain-core/src/messages/ai.ts | 7 +- langchain-core/src/messages/base.ts | 93 ++++++++++++++++ langchain-core/src/messages/chat.ts | 8 ++ langchain-core/src/messages/function.ts | 8 ++ langchain-core/src/messages/human.ts | 8 ++ langchain-core/src/messages/modifier.ts | 2 + langchain-core/src/messages/system.ts | 8 ++ .../src/messages/tests/message_utils.test.ts | 104 +++++++++++++++++- langchain-core/src/messages/tool.ts | 98 ++--------------- 9 files changed, 240 insertions(+), 96 deletions(-) diff --git a/langchain-core/src/messages/ai.ts b/langchain-core/src/messages/ai.ts index b1b4c5261378..001413dad45b 100644 --- a/langchain-core/src/messages/ai.ts +++ b/langchain-core/src/messages/ai.ts @@ -7,13 +7,10 @@ import { type MessageType, BaseMessageFields, _mergeLists, -} from "./base.js"; -import { - InvalidToolCall, ToolCall, ToolCallChunk, - defaultToolCallParser, -} from "./tool.js"; +} from "./base.js"; +import { InvalidToolCall, defaultToolCallParser } from "./tool.js"; export type AIMessageFields = BaseMessageFields & { tool_calls?: ToolCall[]; diff --git a/langchain-core/src/messages/base.ts b/langchain-core/src/messages/base.ts index 521f5b77935b..c5a60e81e56b 100644 --- a/langchain-core/src/messages/base.ts +++ b/langchain-core/src/messages/base.ts @@ -172,6 +172,23 @@ function stringifyWithDepthLimit(obj: any, depthLimit: number): string { return JSON.stringify(helper(obj, 0), null, 2); } +/** + * A call to a tool. + * @property {string} name - The name of the tool to be called + * @property {Record} args - The arguments to the tool call + * @property {string} [id] - If provided, an identifier associated with the tool call + */ +export type ToolCall = { + name: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: Record; + + id?: string; + + type?: "tool_call"; +}; + /** * Base class for all types of messages in a conversation. It includes * properties like `content`, `name`, and `additional_kwargs`. It also @@ -219,6 +236,8 @@ export abstract class BaseMessage */ id?: string; + tool_calls?: never[] | ToolCall[]; + /** * @deprecated Use .getType() instead or import the proper typeguard. * For example: @@ -457,6 +476,76 @@ export function _mergeObj( } } +/** + * A chunk of a tool call (e.g., as part of a stream). + * When merging ToolCallChunks (e.g., via AIMessageChunk.__add__), + * all string attributes are concatenated. Chunks are only merged if their + * values of `index` are equal and not None. + * + * @example + * ```ts + * const leftChunks = [ + * { + * name: "foo", + * args: '{"a":', + * index: 0 + * } + * ]; + * + * const leftAIMessageChunk = new AIMessageChunk({ + * content: "", + * tool_call_chunks: leftChunks + * }); + * + * const rightChunks = [ + * { + * name: undefined, + * args: '1}', + * index: 0 + * } + * ]; + * + * const rightAIMessageChunk = new AIMessageChunk({ + * content: "", + * tool_call_chunks: rightChunks + * }); + * + * const result = leftAIMessageChunk.concat(rightAIMessageChunk); + * // result.tool_call_chunks is equal to: + * // [ + * // { + * // name: "foo", + * // args: '{"a":1}' + * // index: 0 + * // } + * // ] + * ``` + * + * @property {string} [name] - If provided, a substring of the name of the tool to be called + * @property {string} [args] - If provided, a JSON substring of the arguments to the tool call + * @property {string} [id] - If provided, a substring of an identifier for the tool call + * @property {number} [index] - If provided, the index of the tool call in a sequence + */ +export type ToolCallChunk = { + name?: string; + + args?: string; + + id?: string; + + index?: number; + + type?: "tool_call_chunk"; +}; + +export type InvalidToolCall = { + name?: string; + args?: string; + id?: string; + error?: string; + type?: "invalid_tool_call"; +}; + /** * Represents a chunk of a message, which can be concatenated with other * message chunks. It includes a method `_merge_kwargs_dict()` for merging @@ -465,6 +554,10 @@ export function _mergeObj( * of `BaseMessageChunk` instances. */ export abstract class BaseMessageChunk extends BaseMessage { + tool_call_chunks?: never[] | ToolCallChunk[]; + + invalid_tool_calls?: never[] | InvalidToolCall[]; + abstract concat(chunk: BaseMessageChunk): BaseMessageChunk; } diff --git a/langchain-core/src/messages/chat.ts b/langchain-core/src/messages/chat.ts index 376c05cceb84..a04081e75b13 100644 --- a/langchain-core/src/messages/chat.ts +++ b/langchain-core/src/messages/chat.ts @@ -18,6 +18,8 @@ export class ChatMessage extends BaseMessage implements ChatMessageFieldsWithRole { + declare tool_calls?: never[]; + static lc_name() { return "ChatMessage"; } @@ -62,6 +64,12 @@ export class ChatMessage * other chat message chunks. */ export class ChatMessageChunk extends BaseMessageChunk { + declare tool_calls?: never[]; + + declare tool_call_chunks?: never[]; + + declare invalid_tool_calls?: never[]; + static lc_name() { return "ChatMessageChunk"; } diff --git a/langchain-core/src/messages/function.ts b/langchain-core/src/messages/function.ts index 7e3455b8ca51..7cb4c832f496 100644 --- a/langchain-core/src/messages/function.ts +++ b/langchain-core/src/messages/function.ts @@ -15,6 +15,8 @@ export interface FunctionMessageFieldsWithName extends BaseMessageFields { * Represents a function message in a conversation. */ export class FunctionMessage extends BaseMessage { + declare tool_calls?: never[]; + static lc_name() { return "FunctionMessage"; } @@ -49,6 +51,12 @@ export class FunctionMessage extends BaseMessage { * with other function message chunks. */ export class FunctionMessageChunk extends BaseMessageChunk { + declare tool_calls?: never[]; + + declare tool_call_chunks?: never[]; + + declare invalid_tool_calls?: never[]; + static lc_name() { return "FunctionMessageChunk"; } diff --git a/langchain-core/src/messages/human.ts b/langchain-core/src/messages/human.ts index 30b5354ee509..ba34c5b8f12a 100644 --- a/langchain-core/src/messages/human.ts +++ b/langchain-core/src/messages/human.ts @@ -10,6 +10,8 @@ import { * Represents a human message in a conversation. */ export class HumanMessage extends BaseMessage { + declare tool_calls?: never[]; + static lc_name() { return "HumanMessage"; } @@ -24,6 +26,12 @@ export class HumanMessage extends BaseMessage { * other human message chunks. */ export class HumanMessageChunk extends BaseMessageChunk { + declare tool_calls?: never[]; + + declare tool_call_chunks?: never[]; + + declare invalid_tool_calls?: never[]; + static lc_name() { return "HumanMessageChunk"; } diff --git a/langchain-core/src/messages/modifier.ts b/langchain-core/src/messages/modifier.ts index aab91151a8fb..9457adffbf63 100644 --- a/langchain-core/src/messages/modifier.ts +++ b/langchain-core/src/messages/modifier.ts @@ -12,6 +12,8 @@ export interface RemoveMessageFields * Message responsible for deleting other messages. */ export class RemoveMessage extends BaseMessage { + declare tool_calls?: never[]; + /** * The ID of the message to remove. */ diff --git a/langchain-core/src/messages/system.ts b/langchain-core/src/messages/system.ts index ae91a240e83f..c258dff10a19 100644 --- a/langchain-core/src/messages/system.ts +++ b/langchain-core/src/messages/system.ts @@ -10,6 +10,8 @@ import { * Represents a system message in a conversation. */ export class SystemMessage extends BaseMessage { + declare tool_calls?: never[]; + static lc_name() { return "SystemMessage"; } @@ -24,6 +26,12 @@ export class SystemMessage extends BaseMessage { * other system message chunks. */ export class SystemMessageChunk extends BaseMessageChunk { + declare tool_calls?: never[]; + + declare tool_call_chunks?: never[]; + + declare invalid_tool_calls?: never[]; + static lc_name() { return "SystemMessageChunk"; } diff --git a/langchain-core/src/messages/tests/message_utils.test.ts b/langchain-core/src/messages/tests/message_utils.test.ts index 222d9b42372a..9f137ba9a8b6 100644 --- a/langchain-core/src/messages/tests/message_utils.test.ts +++ b/langchain-core/src/messages/tests/message_utils.test.ts @@ -4,16 +4,22 @@ import { mergeMessageRuns, trimMessages, } from "../transformers.js"; -import { AIMessage } from "../ai.js"; -import { ChatMessage } from "../chat.js"; -import { HumanMessage } from "../human.js"; -import { SystemMessage } from "../system.js"; -import { BaseMessage } from "../base.js"; +import { AIMessage, isAIMessage, isAIMessageChunk } from "../ai.js"; +import { ChatMessage, isChatMessage, isChatMessageChunk } from "../chat.js"; +import { HumanMessage, isHumanMessage, isHumanMessageChunk } from "../human.js"; +import { + isSystemMessage, + isSystemMessageChunk, + SystemMessage, + SystemMessageChunk, +} from "../system.js"; +import { BaseMessage, BaseMessageChunk } from "../base.js"; import { getBufferString, mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, } from "../utils.js"; +import { isToolMessage, isToolMessageChunk } from "../tool.js"; describe("filterMessage", () => { const getMessages = () => [ @@ -520,3 +526,91 @@ describe("chat message conversions", () => { expect(convertedBackMessages).toEqual(originalMessages); }); }); + +it("Should narrow tool call typing when accessing a base message array", async () => { + const messages: BaseMessage[] = [new SystemMessage("test")]; + if (messages[0].tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messages[0].tool_calls[0].args; + } + + const msg = messages[0]; + if (isAIMessage(msg)) { + // Should allow access from AI messages + void msg.tool_calls?.[0].args; + } + if (isHumanMessage(msg)) { + // @ts-expect-error Typing should not allow access from human messages + void msg.tool_calls?.[0].args; + } + if (isSystemMessage(msg)) { + // @ts-expect-error Typing should not allow access from system messages + void msg.tool_calls?.[0].args; + } + if (isToolMessage(msg)) { + // @ts-expect-error Typing should not allow access from tool messages + void msg.tool_calls?.[0].args; + } + if (isChatMessage(msg)) { + // @ts-expect-error Typing should not allow access from chat messages + void msg.tool_calls?.[0].args; + } + + const messageChunks: BaseMessageChunk[] = [new SystemMessageChunk("test")]; + if (messageChunks[0].tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].tool_calls[0].args; + } + + if (messageChunks[0].tool_call_chunks?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].tool_call_chunks[0].args; + } + + if (messageChunks[0].invalid_tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].invalid_tool_calls[0].args; + } + + const msgChunk = messageChunks[0]; + if (isAIMessageChunk(msgChunk)) { + // Typing should allow access from AI message chunks + void msgChunk.tool_calls?.[0].args; + // Typing should allow access from AI message chunks + void msgChunk.tool_call_chunks?.[0].args; + // Typing should allow access from AI message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isHumanMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isSystemMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isToolMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isChatMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } +}); diff --git a/langchain-core/src/messages/tool.ts b/langchain-core/src/messages/tool.ts index 1b3f555a7349..22d9d266aaae 100644 --- a/langchain-core/src/messages/tool.ts +++ b/langchain-core/src/messages/tool.ts @@ -7,6 +7,9 @@ import { type MessageType, _mergeObj, _mergeStatus, + ToolCall, + ToolCallChunk, + InvalidToolCall, } from "./base.js"; export interface ToolMessageFieldsWithToolCallId extends BaseMessageFields { @@ -51,6 +54,8 @@ export function isDirectToolOutput(x: unknown): x is DirectToolOutput { * Represents a tool message in a conversation. */ export class ToolMessage extends BaseMessage implements DirectToolOutput { + declare tool_calls?: never[]; + static lc_name() { return "ToolMessage"; } @@ -125,6 +130,12 @@ export class ToolMessage extends BaseMessage implements DirectToolOutput { * with other tool message chunks. */ export class ToolMessageChunk extends BaseMessageChunk { + declare tool_calls?: never[]; + + declare tool_call_chunks?: never[]; + + declare invalid_tool_calls?: never[]; + tool_call_id: string; /** @@ -185,92 +196,7 @@ export class ToolMessageChunk extends BaseMessageChunk { } } -/** - * A call to a tool. - * @property {string} name - The name of the tool to be called - * @property {Record} args - The arguments to the tool call - * @property {string} [id] - If provided, an identifier associated with the tool call - */ -export type ToolCall = { - name: string; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args: Record; - - id?: string; - - type?: "tool_call"; -}; - -/** - * A chunk of a tool call (e.g., as part of a stream). - * When merging ToolCallChunks (e.g., via AIMessageChunk.__add__), - * all string attributes are concatenated. Chunks are only merged if their - * values of `index` are equal and not None. - * - * @example - * ```ts - * const leftChunks = [ - * { - * name: "foo", - * args: '{"a":', - * index: 0 - * } - * ]; - * - * const leftAIMessageChunk = new AIMessageChunk({ - * content: "", - * tool_call_chunks: leftChunks - * }); - * - * const rightChunks = [ - * { - * name: undefined, - * args: '1}', - * index: 0 - * } - * ]; - * - * const rightAIMessageChunk = new AIMessageChunk({ - * content: "", - * tool_call_chunks: rightChunks - * }); - * - * const result = leftAIMessageChunk.concat(rightAIMessageChunk); - * // result.tool_call_chunks is equal to: - * // [ - * // { - * // name: "foo", - * // args: '{"a":1}' - * // index: 0 - * // } - * // ] - * ``` - * - * @property {string} [name] - If provided, a substring of the name of the tool to be called - * @property {string} [args] - If provided, a JSON substring of the arguments to the tool call - * @property {string} [id] - If provided, a substring of an identifier for the tool call - * @property {number} [index] - If provided, the index of the tool call in a sequence - */ -export type ToolCallChunk = { - name?: string; - - args?: string; - - id?: string; - - index?: number; - - type?: "tool_call_chunk"; -}; - -export type InvalidToolCall = { - name?: string; - args?: string; - id?: string; - error?: string; - type?: "invalid_tool_call"; -}; +export type { ToolCall, ToolCallChunk, InvalidToolCall }; export function defaultToolCallParser( // eslint-disable-next-line @typescript-eslint/no-explicit-any From 518239bc4d7c6703857066eb8e13ad5b25c7cade Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 7 Jan 2025 16:51:29 -0800 Subject: [PATCH 2/3] Fix build --- langchain-core/src/messages/index.ts | 2 -- .../src/integration_tests/chat_models.ts | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/langchain-core/src/messages/index.ts b/langchain-core/src/messages/index.ts index d60fb4268ba3..5b6b9947b9be 100644 --- a/langchain-core/src/messages/index.ts +++ b/langchain-core/src/messages/index.ts @@ -7,8 +7,6 @@ export * from "./system.js"; export * from "./utils.js"; export * from "./transformers.js"; export * from "./modifier.js"; -// TODO: Use a star export when we deprecate the -// existing "ToolCall" type in "base.js". export { type ToolMessageFieldsWithToolCallId, ToolMessage, diff --git a/libs/langchain-standard-tests/src/integration_tests/chat_models.ts b/libs/langchain-standard-tests/src/integration_tests/chat_models.ts index 3116b5f2b356..44f845d6978d 100644 --- a/libs/langchain-standard-tests/src/integration_tests/chat_models.ts +++ b/libs/langchain-standard-tests/src/integration_tests/chat_models.ts @@ -5,6 +5,7 @@ import { BaseChatModelCallOptions } from "@langchain/core/language_models/chat_m import { AIMessage, AIMessageChunk, + BaseMessage, BaseMessageChunk, HumanMessage, ToolMessage, @@ -1270,7 +1271,7 @@ export abstract class ChatModelIntegrationTests< const modelWithTools = model.bindTools([weatherTool]); // Initialize the conversation with a weather query - const messages = [ + const messages: BaseMessage[] = [ new HumanMessage( "What's the weather like in San Francisco right now? Use the 'get_current_weather' tool to find the answer." ), @@ -1362,7 +1363,7 @@ export abstract class ChatModelIntegrationTests< const modelWithTools = model.bindTools([weatherTool]); // Initialize the conversation with a weather query - const messages = [ + const messages: BaseMessage[] = [ new HumanMessage( "What's the weather like in San Francisco right now? Use the 'get_current_weather' tool to find the answer." ), From a2bb3bf22159fbb9201cb91a459d4a145dd4767e Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 7 Jan 2025 17:21:59 -0800 Subject: [PATCH 3/3] Use never --- langchain-core/src/messages/base.ts | 6 +- langchain-core/src/messages/chat.ts | 8 +- langchain-core/src/messages/function.ts | 8 +- langchain-core/src/messages/human.ts | 8 +- langchain-core/src/messages/modifier.ts | 2 +- langchain-core/src/messages/system.ts | 8 +- .../src/messages/tests/base_message.test.ts | 102 +++++++++++++++++ .../src/messages/tests/message_utils.test.ts | 104 +----------------- langchain-core/src/messages/tool.ts | 8 +- 9 files changed, 131 insertions(+), 123 deletions(-) diff --git a/langchain-core/src/messages/base.ts b/langchain-core/src/messages/base.ts index c5a60e81e56b..777a23afaf77 100644 --- a/langchain-core/src/messages/base.ts +++ b/langchain-core/src/messages/base.ts @@ -236,7 +236,7 @@ export abstract class BaseMessage */ id?: string; - tool_calls?: never[] | ToolCall[]; + tool_calls?: never | ToolCall[]; /** * @deprecated Use .getType() instead or import the proper typeguard. @@ -554,9 +554,9 @@ export type InvalidToolCall = { * of `BaseMessageChunk` instances. */ export abstract class BaseMessageChunk extends BaseMessage { - tool_call_chunks?: never[] | ToolCallChunk[]; + tool_call_chunks?: never | ToolCallChunk[]; - invalid_tool_calls?: never[] | InvalidToolCall[]; + invalid_tool_calls?: never | InvalidToolCall[]; abstract concat(chunk: BaseMessageChunk): BaseMessageChunk; } diff --git a/langchain-core/src/messages/chat.ts b/langchain-core/src/messages/chat.ts index a04081e75b13..df1ad225ca92 100644 --- a/langchain-core/src/messages/chat.ts +++ b/langchain-core/src/messages/chat.ts @@ -18,7 +18,7 @@ export class ChatMessage extends BaseMessage implements ChatMessageFieldsWithRole { - declare tool_calls?: never[]; + declare tool_calls?: never; static lc_name() { return "ChatMessage"; @@ -64,11 +64,11 @@ export class ChatMessage * other chat message chunks. */ export class ChatMessageChunk extends BaseMessageChunk { - declare tool_calls?: never[]; + declare tool_calls?: never; - declare tool_call_chunks?: never[]; + declare tool_call_chunks?: never; - declare invalid_tool_calls?: never[]; + declare invalid_tool_calls?: never; static lc_name() { return "ChatMessageChunk"; diff --git a/langchain-core/src/messages/function.ts b/langchain-core/src/messages/function.ts index 7cb4c832f496..b7a3a9905aff 100644 --- a/langchain-core/src/messages/function.ts +++ b/langchain-core/src/messages/function.ts @@ -15,7 +15,7 @@ export interface FunctionMessageFieldsWithName extends BaseMessageFields { * Represents a function message in a conversation. */ export class FunctionMessage extends BaseMessage { - declare tool_calls?: never[]; + declare tool_calls?: never; static lc_name() { return "FunctionMessage"; @@ -51,11 +51,11 @@ export class FunctionMessage extends BaseMessage { * with other function message chunks. */ export class FunctionMessageChunk extends BaseMessageChunk { - declare tool_calls?: never[]; + declare tool_calls?: never; - declare tool_call_chunks?: never[]; + declare tool_call_chunks?: never; - declare invalid_tool_calls?: never[]; + declare invalid_tool_calls?: never; static lc_name() { return "FunctionMessageChunk"; diff --git a/langchain-core/src/messages/human.ts b/langchain-core/src/messages/human.ts index ba34c5b8f12a..413772c91dcb 100644 --- a/langchain-core/src/messages/human.ts +++ b/langchain-core/src/messages/human.ts @@ -10,7 +10,7 @@ import { * Represents a human message in a conversation. */ export class HumanMessage extends BaseMessage { - declare tool_calls?: never[]; + declare tool_calls?: never; static lc_name() { return "HumanMessage"; @@ -26,11 +26,11 @@ export class HumanMessage extends BaseMessage { * other human message chunks. */ export class HumanMessageChunk extends BaseMessageChunk { - declare tool_calls?: never[]; + declare tool_calls?: never; - declare tool_call_chunks?: never[]; + declare tool_call_chunks?: never; - declare invalid_tool_calls?: never[]; + declare invalid_tool_calls?: never; static lc_name() { return "HumanMessageChunk"; diff --git a/langchain-core/src/messages/modifier.ts b/langchain-core/src/messages/modifier.ts index 9457adffbf63..58429e6b8c0c 100644 --- a/langchain-core/src/messages/modifier.ts +++ b/langchain-core/src/messages/modifier.ts @@ -12,7 +12,7 @@ export interface RemoveMessageFields * Message responsible for deleting other messages. */ export class RemoveMessage extends BaseMessage { - declare tool_calls?: never[]; + declare tool_calls?: never; /** * The ID of the message to remove. diff --git a/langchain-core/src/messages/system.ts b/langchain-core/src/messages/system.ts index c258dff10a19..a970ccbd70ab 100644 --- a/langchain-core/src/messages/system.ts +++ b/langchain-core/src/messages/system.ts @@ -10,7 +10,7 @@ import { * Represents a system message in a conversation. */ export class SystemMessage extends BaseMessage { - declare tool_calls?: never[]; + declare tool_calls?: never; static lc_name() { return "SystemMessage"; @@ -26,11 +26,11 @@ export class SystemMessage extends BaseMessage { * other system message chunks. */ export class SystemMessageChunk extends BaseMessageChunk { - declare tool_calls?: never[]; + declare tool_calls?: never; - declare tool_call_chunks?: never[]; + declare tool_call_chunks?: never; - declare invalid_tool_calls?: never[]; + declare invalid_tool_calls?: never; static lc_name() { return "SystemMessageChunk"; diff --git a/langchain-core/src/messages/tests/base_message.test.ts b/langchain-core/src/messages/tests/base_message.test.ts index 5a1d7aaef455..ec71b639b4b7 100644 --- a/langchain-core/src/messages/tests/base_message.test.ts +++ b/langchain-core/src/messages/tests/base_message.test.ts @@ -8,6 +8,19 @@ import { AIMessageChunk, coerceMessageLikeToMessage, SystemMessage, + isAIMessageChunk, + isHumanMessageChunk, + isSystemMessageChunk, + BaseMessageChunk, + SystemMessageChunk, + isAIMessage, + isHumanMessage, + isSystemMessage, + isToolMessage, + BaseMessage, + isChatMessage, + isToolMessageChunk, + isChatMessageChunk, } from "../index.js"; import { load } from "../../load/index.js"; import { concat } from "../../utils/stream.js"; @@ -462,3 +475,92 @@ describe("usage_metadata serialized", () => { expect(jsonConcatenatedAIMessageChunk).toContain("total_tokens"); }); }); + +it("Should narrow tool call typing when accessing a base message array", async () => { + const messages: BaseMessage[] = [new SystemMessage("test")]; + messages.push(new AIMessage("foo")); + if (messages[0].tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messages[0].tool_calls[0]?.args; + } + + const msg = messages[0]; + if (isAIMessage(msg)) { + // Should allow access from AI messages + void msg.tool_calls?.[0].args; + } + if (isHumanMessage(msg)) { + // @ts-expect-error Typing should not allow access from human messages + void msg.tool_calls?.[0].args; + } + if (isSystemMessage(msg)) { + // @ts-expect-error Typing should not allow access from system messages + void msg.tool_calls?.[0].args; + } + if (isToolMessage(msg)) { + // @ts-expect-error Typing should not allow access from tool messages + void msg.tool_calls?.[0].args; + } + if (isChatMessage(msg)) { + // @ts-expect-error Typing should not allow access from chat messages + void msg.tool_calls?.[0].args; + } + + const messageChunks: BaseMessageChunk[] = [new SystemMessageChunk("test")]; + if (messageChunks[0].tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].tool_calls[0].args; + } + + if (messageChunks[0].tool_call_chunks?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].tool_call_chunks[0].args; + } + + if (messageChunks[0].invalid_tool_calls?.[0] !== undefined) { + // Allow checking existence on BaseMessage with no errors + void messageChunks[0].invalid_tool_calls[0].args; + } + + const msgChunk = messageChunks[0]; + if (isAIMessageChunk(msgChunk)) { + // Typing should allow access from AI message chunks + void msgChunk.tool_calls?.[0].args; + // Typing should allow access from AI message chunks + void msgChunk.tool_call_chunks?.[0].args; + // Typing should allow access from AI message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isHumanMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from human message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isSystemMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from system message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isToolMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from tool message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } + if (isChatMessageChunk(msgChunk)) { + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.tool_calls?.[0].args; + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.tool_call_chunks?.[0].args; + // @ts-expect-error Typing should not allow access from chat message chunks + void msgChunk.invalid_tool_calls?.[0].args; + } +}); diff --git a/langchain-core/src/messages/tests/message_utils.test.ts b/langchain-core/src/messages/tests/message_utils.test.ts index 9f137ba9a8b6..222d9b42372a 100644 --- a/langchain-core/src/messages/tests/message_utils.test.ts +++ b/langchain-core/src/messages/tests/message_utils.test.ts @@ -4,22 +4,16 @@ import { mergeMessageRuns, trimMessages, } from "../transformers.js"; -import { AIMessage, isAIMessage, isAIMessageChunk } from "../ai.js"; -import { ChatMessage, isChatMessage, isChatMessageChunk } from "../chat.js"; -import { HumanMessage, isHumanMessage, isHumanMessageChunk } from "../human.js"; -import { - isSystemMessage, - isSystemMessageChunk, - SystemMessage, - SystemMessageChunk, -} from "../system.js"; -import { BaseMessage, BaseMessageChunk } from "../base.js"; +import { AIMessage } from "../ai.js"; +import { ChatMessage } from "../chat.js"; +import { HumanMessage } from "../human.js"; +import { SystemMessage } from "../system.js"; +import { BaseMessage } from "../base.js"; import { getBufferString, mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, } from "../utils.js"; -import { isToolMessage, isToolMessageChunk } from "../tool.js"; describe("filterMessage", () => { const getMessages = () => [ @@ -526,91 +520,3 @@ describe("chat message conversions", () => { expect(convertedBackMessages).toEqual(originalMessages); }); }); - -it("Should narrow tool call typing when accessing a base message array", async () => { - const messages: BaseMessage[] = [new SystemMessage("test")]; - if (messages[0].tool_calls?.[0] !== undefined) { - // Allow checking existence on BaseMessage with no errors - void messages[0].tool_calls[0].args; - } - - const msg = messages[0]; - if (isAIMessage(msg)) { - // Should allow access from AI messages - void msg.tool_calls?.[0].args; - } - if (isHumanMessage(msg)) { - // @ts-expect-error Typing should not allow access from human messages - void msg.tool_calls?.[0].args; - } - if (isSystemMessage(msg)) { - // @ts-expect-error Typing should not allow access from system messages - void msg.tool_calls?.[0].args; - } - if (isToolMessage(msg)) { - // @ts-expect-error Typing should not allow access from tool messages - void msg.tool_calls?.[0].args; - } - if (isChatMessage(msg)) { - // @ts-expect-error Typing should not allow access from chat messages - void msg.tool_calls?.[0].args; - } - - const messageChunks: BaseMessageChunk[] = [new SystemMessageChunk("test")]; - if (messageChunks[0].tool_calls?.[0] !== undefined) { - // Allow checking existence on BaseMessage with no errors - void messageChunks[0].tool_calls[0].args; - } - - if (messageChunks[0].tool_call_chunks?.[0] !== undefined) { - // Allow checking existence on BaseMessage with no errors - void messageChunks[0].tool_call_chunks[0].args; - } - - if (messageChunks[0].invalid_tool_calls?.[0] !== undefined) { - // Allow checking existence on BaseMessage with no errors - void messageChunks[0].invalid_tool_calls[0].args; - } - - const msgChunk = messageChunks[0]; - if (isAIMessageChunk(msgChunk)) { - // Typing should allow access from AI message chunks - void msgChunk.tool_calls?.[0].args; - // Typing should allow access from AI message chunks - void msgChunk.tool_call_chunks?.[0].args; - // Typing should allow access from AI message chunks - void msgChunk.invalid_tool_calls?.[0].args; - } - if (isHumanMessageChunk(msgChunk)) { - // @ts-expect-error Typing should not allow access from human message chunks - void msgChunk.tool_calls?.[0].args; - // @ts-expect-error Typing should not allow access from human message chunks - void msgChunk.tool_call_chunks?.[0].args; - // @ts-expect-error Typing should not allow access from human message chunks - void msgChunk.invalid_tool_calls?.[0].args; - } - if (isSystemMessageChunk(msgChunk)) { - // @ts-expect-error Typing should not allow access from system message chunks - void msgChunk.tool_calls?.[0].args; - // @ts-expect-error Typing should not allow access from system message chunks - void msgChunk.tool_call_chunks?.[0].args; - // @ts-expect-error Typing should not allow access from system message chunks - void msgChunk.invalid_tool_calls?.[0].args; - } - if (isToolMessageChunk(msgChunk)) { - // @ts-expect-error Typing should not allow access from tool message chunks - void msgChunk.tool_calls?.[0].args; - // @ts-expect-error Typing should not allow access from tool message chunks - void msgChunk.tool_call_chunks?.[0].args; - // @ts-expect-error Typing should not allow access from tool message chunks - void msgChunk.invalid_tool_calls?.[0].args; - } - if (isChatMessageChunk(msgChunk)) { - // @ts-expect-error Typing should not allow access from chat message chunks - void msgChunk.tool_calls?.[0].args; - // @ts-expect-error Typing should not allow access from chat message chunks - void msgChunk.tool_call_chunks?.[0].args; - // @ts-expect-error Typing should not allow access from chat message chunks - void msgChunk.invalid_tool_calls?.[0].args; - } -}); diff --git a/langchain-core/src/messages/tool.ts b/langchain-core/src/messages/tool.ts index 22d9d266aaae..aa2dccc42a9e 100644 --- a/langchain-core/src/messages/tool.ts +++ b/langchain-core/src/messages/tool.ts @@ -54,7 +54,7 @@ export function isDirectToolOutput(x: unknown): x is DirectToolOutput { * Represents a tool message in a conversation. */ export class ToolMessage extends BaseMessage implements DirectToolOutput { - declare tool_calls?: never[]; + declare tool_calls?: never; static lc_name() { return "ToolMessage"; @@ -130,11 +130,11 @@ export class ToolMessage extends BaseMessage implements DirectToolOutput { * with other tool message chunks. */ export class ToolMessageChunk extends BaseMessageChunk { - declare tool_calls?: never[]; + declare tool_calls?: never; - declare tool_call_chunks?: never[]; + declare tool_call_chunks?: never; - declare invalid_tool_calls?: never[]; + declare invalid_tool_calls?: never; tool_call_id: string;