Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update @langchain/core/messages/ToolCall [DEPRECATED] type to OpenAIToolCall #6740

Closed
5 tasks done
SantoshKumarRavi opened this issue Sep 11, 2024 · 4 comments
Closed
5 tasks done
Labels
auto:bug Related to a bug, vulnerability, unexpected error with an existing feature

Comments

@SantoshKumarRavi
Copy link

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain.js documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain.js rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).

Example Code

import {
  BaseMessageChunk,
  OpenAIToolCall,
} from '@langchain/core/messages';



  async function  function1(
    response: BaseMessageChunk){
       const toolCalls = response.additional_kwargs.tool_calls;
    function2(toolCalls[0]);
    }
    
        async function function2(
       toolCall: OpenAIToolCall){
    }

Error Message and Stack Trace (if applicable)

Argument of type 'ToolCall' is not assignable to parameter of type 'OpenAIToolCall'.
Property 'index' is missing in type 'ToolCall' but required in type '{ index: number; }'.ts(2345)
base.d.ts(117, 5): 'index' is declared here.

Description

I cross verified with latest doc (https://v02.api.js.langchain.com/classes/_langchain_core.messages_tool.ToolMessageChunk.html) too.
ToolCall is deprecated.
It is not updated with openAIToolCall in baseMessageChunk tool_calls.
I know that i can override type as temporary fix but it would be great if we update the type in main source.

System Info

Node.js: v20.10.0
npm: v10.2.3
OS: Windows 10 Pro

@SantoshKumarRavi SantoshKumarRavi changed the title Update @langchain/core/messages/ToolCall type to OpenAIToolCall Update @langchain/core/messages/ToolCall [DEPRECATED] type to OpenAIToolCall Sep 11, 2024
Copy link

dosubot bot commented Sep 11, 2024

Hey @SantoshKumarRavi! I'm here to assist you with any bugs, questions, or contributions you have for LangChain.js. Let's get to the bottom of this together!

I found a similar issue that was closed: Update langchain create agent functions to accept oai formatted tools. This issue was resolved by a pull request: langchain[minor],docs[minor]: openai formatted tools in create agent funcs on June 28, 2024 [1].

To update the @langchain/core/messages/ToolCall type to OpenAIToolCall in the main source code, you need to replace all instances of ToolCall with OpenAIToolCall. Here is the updated code:

import { _isToolCall } from "../tools/utils.js";
import { AIMessage, AIMessageChunk, AIMessageChunkFields } from "./ai.js";
import {
  BaseMessageLike,
  BaseMessage,
  isBaseMessage,
  StoredMessage,
  StoredMessageV1,
  BaseMessageFields,
  _isMessageFieldWithRole,
} from "./base.js";
import {
  ChatMessage,
  ChatMessageFieldsWithRole,
  ChatMessageChunk,
} from "./chat.js";
import {
  FunctionMessage,
  FunctionMessageFieldsWithName,
  FunctionMessageChunk,
} from "./function.js";
import { HumanMessage, HumanMessageChunk } from "./human.js";
import { SystemMessage, SystemMessageChunk } from "./system.js";
import {
  OpenAIToolCall,
  ToolMessage,
  ToolMessageFieldsWithToolCallId,
} from "./tool.js";

function _coerceToolCall(
  toolCall: OpenAIToolCall | Record<string, unknown>
): OpenAIToolCall {
  if (_isToolCall(toolCall)) {
    return toolCall;
  } else if (
    typeof toolCall.id === "string" &&
    toolCall.type === "function" &&
    typeof toolCall.function === "object" &&
    toolCall.function !== null &&
    "arguments" in toolCall.function &&
    typeof toolCall.function.arguments === "string" &&
    "name" in toolCall.function &&
    typeof toolCall.function.name === "string"
  ) {
    // Handle OpenAI tool call format
    return {
      id: toolCall.id,
      args: JSON.parse(toolCall.function.arguments),
      name: toolCall.function.name,
      type: "tool_call",
    };
  } else {
    // TODO: Throw an error?
    return toolCall as OpenAIToolCall;
  }
}
/**
 * This function is used by memory classes to get a string representation
 * of the chat message history, based on the message content and role.
 */
export function getBufferString(
  messages: BaseMessage[],
  humanPrefix = "Human",
  aiPrefix = "AI"
): string {
  const string_messages: string[] = [];
  for (const m of messages) {
    let role: string;
    if (m._getType() === "human") {
      role = humanPrefix;
    } else if (m._getType() === "ai") {
      role = aiPrefix;
    } else if (m._getType() === "system") {
      role = "System";
    } else if (m._getType() === "function") {
      role = "Function";
    } else if (m._getType() === "tool") {
      role = "Tool";
    } else if (m._getType() === "generic") {
      role = (m as ChatMessage).role;
    } else {
      throw new Error(`Got unsupported message type: ${m._getType()}`);
    }
    const nameStr = m.name ? `${m.name}, ` : "";
    const readableContent =
      typeof m.content === "string"
        ? m.content
        : JSON.stringify(m.content, null, 2);
    string_messages.push(`${role}: ${nameStr}${readableContent}`);
  }
  return string_messages.join("\n");
}

/**
 * Maps messages from an older format (V1) to the current `StoredMessage`
 * format. If the message is already in the `StoredMessage` format, it is
 * returned as is. Otherwise, it transforms the V1 message into a
 * `StoredMessage`. This function is important for maintaining
 * compatibility with older message formats.
 */
function mapV1MessageToStoredMessage(
  message: StoredMessage | StoredMessageV1
): StoredMessage {
  // TODO: Remove this mapper when we deprecate the old message format.
  if ((message as StoredMessage).data !== undefined) {
    return message as StoredMessage;
  } else {
    const v1Message = message as StoredMessageV1;
    return {
      type: v1Message.type,
      data: {
        content: v1Message.text,
        role: v1Message.role,
        name: undefined,
        tool_call_id: undefined,
      },
    };
  }
}
function _constructMessageFromParams(
  params: BaseMessageFields & { type: string } & Record<string, unknown>
) {
  const { type, ...rest } = params;
  if (type === "human" || type === "user") {
    return new HumanMessage(rest);
  } else if (type === "ai" || type === "assistant") {
    const { tool_calls: rawToolCalls, ...other } = rest;
    if (!Array.isArray(rawToolCalls)) {
      return new AIMessage(rest);
    }
    const tool_calls = rawToolCalls.map(_coerceToolCall);
    return new AIMessage({ ...other, tool_calls });
  } else if (type === "system") {
    return new SystemMessage(rest);
  } else if (type === "tool" && "tool_call_id" in rest) {
    return new ToolMessage({
      ...rest,
      content: rest.content,
      tool_call_id: rest.tool_call_id as string,
      name: rest.name,
    });
  } else {
    throw new Error(
      `Unable to coerce message from array: only human, AI, or system message coercion is currently supported.`
    );
  }
}

export function coerceMessageLikeToMessage(
  messageLike: BaseMessageLike
): BaseMessage {
  if (typeof messageLike === "string") {
    return new HumanMessage(messageLike);
  } else if (isBaseMessage(messageLike)) {
    return messageLike;
  }
  if (Array.isArray(messageLike)) {
    const [type, content] = messageLike;
    return _constructMessageFromParams({ type, content });
  } else if (_isMessageFieldWithRole(messageLike)) {
    const { role: type, ...rest } = messageLike;
    return _constructMessageFromParams({ ...rest, type });
  } else {
    return _constructMessageFromParams(messageLike);
  }
}
export function mapStoredMessageToChatMessage(message: StoredMessage) {
  const storedMessage = mapV1MessageToStoredMessage(message);
  switch (storedMessage.type) {
    case "human":
      return new HumanMessage(storedMessage.data);
    case "ai":
      return new AIMessage(storedMessage.data);
    case "system":
      return new SystemMessage(storedMessage.data);
    case "function":
      if (storedMessage.data.name === undefined) {
        throw new Error("Name must be defined for function messages");
      }
      return new FunctionMessage(
        storedMessage.data as FunctionMessageFieldsWithName
      );
    case "tool":
      if (storedMessage.data.tool_call_id === undefined) {
        throw new Error("Tool call ID must be defined for tool messages");
      }
      return new ToolMessage(
        storedMessage.data as ToolMessageFieldsWithToolCallId
      );
    case "generic": {
      if (storedMessage.data.role === undefined) {
        throw new Error("Role must be defined for chat messages");
      }
      return new ChatMessage(storedMessage.data as ChatMessageFieldsWithRole);
    }
    default:
      throw new Error(`Got unexpected type: ${storedMessage.type}`);
  }
}

/**
 * Transforms an array of `StoredMessage` instances into an array of
 * `BaseMessage` instances. It uses the `mapV1MessageToStoredMessage`
 * function to ensure all messages are in the `StoredMessage` format, then
 * creates new instances of the appropriate `BaseMessage` subclass based
 * on the type of each message. This function is used to prepare stored
 * messages for use in a chat context.
 */
export function mapStoredMessagesToChatMessages(
  messages: StoredMessage[]
): BaseMessage[] {
  return messages.map(mapStoredMessageToChatMessage);
}
/**
 * Transforms an array of `BaseMessage` instances into an array of
 * `StoredMessage` instances. It does this by calling the `toDict` method
 * on each `BaseMessage`, which returns a `StoredMessage`. This function
 * is used to prepare chat messages for storage.
 */
export function mapChatMessagesToStoredMessages(
  messages: BaseMessage[]
): StoredMessage[] {
  return messages.map((message) => message.toDict());
}

export function convertToChunk(message: BaseMessage) {
  const type = message._getType();
  if (type === "human") {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return new HumanMessageChunk({ ...message });
  } else if (type === "ai") {
    let aiChunkFields: AIMessageChunkFields = {
      ...message,
    };
    if ("tool_calls" in aiChunkFields) {
      aiChunkFields = {
        ...aiChunkFields,
        tool_call_chunks: aiChunkFields.tool_calls?.map((tc) => ({
          ...tc,
          type: "tool_call_chunk",
          index: undefined,
          args: JSON.stringify(tc.args),
        })),
      };
    }
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return new AIMessageChunk({ ...aiChunkFields });
  } else if (type === "system") {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return new SystemMessageChunk({ ...message });
  } else if (type === "function") {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return new FunctionMessageChunk({ ...message });
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
  } else if (ChatMessage.isInstance(message)) {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return new ChatMessageChunk({ ...message });
  } else {
    throw new Error("Unknown message type.");
  }
}

This should resolve the issue you're facing with the ToolCall type not being assignable to OpenAIToolCall [2][3].

To continue talking to Dosu, mention @dosu.

@dosubot dosubot bot added the auto:bug Related to a bug, vulnerability, unexpected error with an existing feature label Sep 11, 2024
@SantoshKumarRavi
Copy link
Author

SantoshKumarRavi commented Sep 11, 2024

@dosu
not sure about your response will solve current issue or not
but i think we need to update the BaseMessageFields tool_calls type

export type BaseMessageFields = {
    content: MessageContent;
    name?: string;
    additional_kwargs?: {
        function_call?: FunctionCall;
        tool_calls?: ToolCall[];
        [key: string]: unknown;
    };
    /** Response metadata. For example: response headers, logprobs, token counts. */
    response_metadata?: Record<string, any>;
};

Copy link

dosubot bot commented Sep 11, 2024

Yes, you are correct. You need to update the BaseMessageFields type to replace ToolCall[] with OpenAIToolCall[] in the tool_calls property. The ToolCall interface is deprecated and should be replaced with OpenAIToolCall.

Here is the updated BaseMessageFields type:

export type BaseMessageFields = {
  content: MessageContent;
  name?: string;
  additional_kwargs?: {
    function_call?: FunctionCall;
    tool_calls?: OpenAIToolCall[];
    [key: string]: unknown;
  };
  /** Response metadata. For example: response headers, logprobs, token counts. */
  response_metadata?: Record<string, any>;
  /**
   * An optional unique identifier for the message. This should ideally be
   * provided by the provider/model which created the message.
   */
  id?: string;
};

Additionally, ensure that the _coerceToolCall function in the langchain-core/src/messages/utils.ts file handles the conversion of ToolCall to OpenAIToolCall format, ensuring compatibility with the new type [1][2].

To continue talking to Dosu, mention @dosu.

@jacoblee93
Copy link
Collaborator

Hey @SantoshKumarRavi, you can import this from @langchain/messages/tool:

import { ToolCall } from "@langchain/core/messages/tool";

I'll be removing this in 0.3!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto:bug Related to a bug, vulnerability, unexpected error with an existing feature
Projects
None yet
Development

No branches or pull requests

2 participants