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

Anthropic models cannot call tools without parameters #7872

Open
5 tasks done
Domiii opened this issue Mar 21, 2025 · 8 comments
Open
5 tasks done

Anthropic models cannot call tools without parameters #7872

Domiii opened this issue Mar 21, 2025 · 8 comments
Labels
auto:bug Related to a bug, vulnerability, unexpected error with an existing feature

Comments

@Domiii
Copy link

Domiii commented Mar 21, 2025

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

My code has a lot of boilerplate already, but:

  • I am using: createReactAgent with the latest sonnet model.
  • I call streamEvents on said agent.
  • I provide a tool (import { tool } from "@langchain/core/tools") that has schema zod.object({ }).
  • I upgraded to latest @langchain/anthropic, but still it still happens.
  • 💡 I debugged it and shared results below.

Error Message and Stack Trace (if applicable)

Debugging + Investigation

Full Error

Exception has occurred: Error: 400 {
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "messages.1.content.1.tool_use.input: Input should be a valid dictionary"
  }
}

Troubleshooting URL: https://js.langchain.com/docs/troubleshooting/errors/INVALID_TOOL_RESULTS/
  at Function.generate (/projects/my-project/node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk/src/error.ts:63:14)
  at Anthropic.makeStatusError (/projects/my-project/node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk/src/core.ts:454:21)
  at Anthropic.makeRequest (/projects/my-project/node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk/src/core.ts:518:24)
  at processTicksAndRejections (node:internal/process/task_queues:95:5)
  at async makeCompletionRequest (/projects/my-project/node_modules/@langchain/anthropic/dist/chat_models.cjs:827:24)
  at async RetryOperation._fn (/projects/my-project/node_modules/p-retry/index.js:50:12)

Investigation

  • ACTUAL: The final object that is sent to the LLM has input: "".
  • EXPECTED: It is expected to be input: {}.

Bad tool_use message contents

  • The object produced by the anthropic call is good. It has input: {}:
    Image
  • But that gets overwritten with input: "" in this code in _makeMessageChunkFromAnthropicEvent in utils/message_output.ts:
       chunk: new AIMessageChunk({
         content: fields.coerceContentToString
           ? ""
           : [
               {
                 index: data.index,
                 ...data.content_block,
                 input: "",  // 💥
               },
             ],
         additional_kwargs: {},
         tool_call_chunks: toolCallChunks,
       }),
     };

Description

(I explained everything above)

System Info

  • node@18
  • npm
  • OS: Any system we tried it on, but primarily different Linux distros.
Copy link

dosubot bot commented Mar 21, 2025

Hey @Domiii! I'm here to help you with any bugs, questions, or contributions. Let's dive into this issue together!

A similar issue has been discussed in the LangChain.js repository, specifically in the thread titled "ChatAnthropic streamEvents not working with bindTools" which is currently unsolved [1].

The error you're encountering, "messages.1.content.1.tool_use.input: Input should be a valid dictionary," is due to the input field being set as an empty string "" instead of an empty object {}. This issue arises in the _convertLangChainToolCallToAnthropic function, where the input is directly assigned from toolCall.args. To resolve this, ensure that toolCall.args is a valid object before assigning it to input:

export function _convertLangChainToolCallToAnthropic(
  toolCall: ToolCall
): AnthropicToolResponse {
  if (toolCall.id === undefined) {
    throw new Error(`Anthropic requires all tool calls to have an "id".`);
  }
  return {
    type: "tool_use",
    id: toolCall.id,
    name: toolCall.name,
    input: typeof toolCall.args === 'object' ? toolCall.args : {}, // Ensure input is a valid object
  };
}

This modification ensures that if toolCall.args is not an object, it defaults to an empty object {}, thus preventing the error [2].

To continue talking to Dosu, mention @dosu.


Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Bug Report | Other

@dosubot dosubot bot added the auto:bug Related to a bug, vulnerability, unexpected error with an existing feature label Mar 21, 2025
@Domiii
Copy link
Author

Domiii commented Mar 21, 2025

@dosu: Your response is entirely unrelated. Please remove it:

My bug has a very specific error message that happens when providing tools that have empty z.object({}) schemas.
Very different from what you cited.

Copy link

dosubot bot commented Mar 21, 2025

This is a tough nut to crack, could really use your help on this, @jacoblee93.

To continue talking to Dosu, mention @dosu.


Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Bug Report | Other

@Domiii
Copy link
Author

Domiii commented Mar 21, 2025

@dosu: You are almost right about the bug location, but there is a twist:

  1. _convertLangChainToolCallToAnthropic should should be called by _convertMessagesToAnthropicPayload, but is not, because if (typeof message.content === "string") is not true.
  2. So it goes into the else branch, which calls _formatContent instead.
  3. That goes into this branch here and calls JSON.parse on an empty string. According to the comment it assumes input to be object, but due to the reason explained in my original message its an empty string:
                 if ("input" in contentPartCopy) {
                     // Anthropic tool use inputs should be valid objects, when applicable.
                     try {
                         contentPartCopy.input = JSON.parse(contentPartCopy.input);
                     }
                     catch {
                         // no-op
                     }
                 }

Result:
Image

Copy link

dosubot bot commented Mar 21, 2025

To address the issue where _convertLangChainToolCallToAnthropic should be called by _convertMessagesToAnthropicPayload but isn't, you need to ensure that the message.content is correctly handled when it is not a string. The problem arises because JSON.parse is being called on an empty string. Here's a potential fix:

  1. Modify the _convertMessagesToAnthropicPayload function to handle cases where message.content is an empty string or not a string properly.

  2. Ensure that _convertLangChainToolCallToAnthropic is called when message.tool_calls is present, regardless of the type of message.content.

Here's the relevant code snippet with the fix:

export function _convertMessagesToAnthropicPayload(
  messages: BaseMessage[]
): AnthropicMessageCreateParams {
  const mergedMessages = _ensureMessageContents(messages);
  let system;
  if (mergedMessages.length > 0 && mergedMessages[0]._getType() === "system") {
    system = messages[0].content;
  }
  const conversationMessages =
    system !== undefined ? mergedMessages.slice(1) : mergedMessages;
  const formattedMessages = conversationMessages.map((message) => {
    let role;
    if (message._getType() === "human") {
      role = "user" as const;
    } else if (message._getType() === "ai") {
      role = "assistant" as const;
    } else if (message._getType() === "tool") {
      role = "user" as const;
    } else if (message._getType() === "system") {
      throw new Error(
        "System messages are only permitted as the first passed message."
      );
    } else {
      throw new Error(`Message type "${message._getType()}" is not supported.`);
    }
    if (isAIMessage(message) && !!message.tool_calls?.length) {
      if (typeof message.content === "string") {
        if (message.content.trim() === "") {
          return {
            role,
            content: message.tool_calls.map(
              _convertLangChainToolCallToAnthropic
            ),
          };
        } else {
          return {
            role,
            content: [
              { type: "text", text: message.content },
              ...message.tool_calls.map(_convertLangChainToolCallToAnthropic),
            ],
          };
        }
      } else {
        const { content } = message;
        const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) =>
          content.find(
            (contentPart) =>
              (contentPart.type === "tool_use" ||
                contentPart.type === "input_json_delta") &&
              contentPart.id === toolCall.id
          )
        );
        if (hasMismatchedToolCalls) {
          console.warn(
            `The "tool_calls" field on a message is only respected if content is a string.`
          );
        }
        return {
          role,
          content: _formatContent(message.content),
        };
      }
    } else {
      return {
        role,
        content: _formatContent(message.content),
      };
    }
  });
  return {
    messages: mergeMessages(formattedMessages),
    system,
  } as AnthropicMessageCreateParams;
}

This fix ensures that _convertLangChainToolCallToAnthropic is called when message.tool_calls is present, and it handles empty strings properly to avoid issues with JSON.parse [1].

To continue talking to Dosu, mention @dosu.


Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Bug Report | Other

@Domiii
Copy link
Author

Domiii commented Mar 21, 2025

We should find out what the actual control flow is in case of a non-empty schema (which works fine) and make sure that the two are in line with one another.

Copy link

dosubot bot commented Mar 21, 2025

Hey @Domiii! I see you're diving deep into LangChain's internals, but I'm just a bot here to help with bugs, technical questions, and engineering knowledge. Your query is a bit outside my wheelhouse. If you think I should be able to tackle this, feel free to reach out to the Dosu Team. They'll know what to do! 😊

To continue talking to Dosu, mention @dosu.


Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Bug Report | Other

@grenager
Copy link

grenager commented Mar 27, 2025

I'm having the exact same issue when using ChatAnthropic model claude-3-7-sonnet-20250219 with AgentExecutor in Nodejs.

const agent = createToolCallingAgent({
      llm: chatModel,
      tools,
      prompt,
    });

    const agentExecutor = AgentExecutor.fromAgentAndTools({
      agent,
      tools,
    });

    const response = await agentExecutor.invoke({
      input: messages[messages.length - 1].content,
      chat_history: messages.slice(0, -1),
    });

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