ToolNode incompatible with multiple tool_calls in a single AIMessage #56

Closed
opened 2026-02-15 17:15:09 -05:00 by yindo · 2 comments
Owner

Originally created by @tofuliang on GitHub (Aug 1, 2024).

https://github.com/langchain-ai/langgraphjs/blob/8268d62e99576760b95072ca5688238749071eb4/langgraph/src/prebuilt/tool_node.ts#L90

Current Behavior:
The current implementation of ToolNode generates a separate ToolMessage for each tool_call within an AIMessage. This approach is incompatible with scenarios where a single AIMessage contains multiple tool_calls.

Expected Behavior:
The correct implementation should merge the results of multiple tool_calls into a single ToolMessage, rather than creating separate messages for each call.

Example and Temporary Workaround:
Here's an example demonstrating the issue and a temporary, albeit "hacky" fix:

REQUEST BODY

{
  "model": "claude-3-5-sonnet-20240620",
  "stream": true,
  "tools": [
    {
      "name": "weatherForecast",
      "description": "Use to surf the web using google, fetch current information, check the weather, and retrieve other information.",
      "input_schema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "The query to use in your search."
          }
        },
        "required": [
          "query"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "roomTemperature",
      "description": "retrieve temperature about offices.",
      "input_schema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "The query to use in your search."
          }
        },
        "required": [
          "query"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "what should I wear tomorrow in pairs and my office base on the weather"
    }
  ],
  "system": "use weatherForecast and roomTemperature togather to get information."
}

RESPONSE

event: message_start
data: {"type":"message_start","message":{"id":"msg_0139jb18iTsccRApgibNfCz8","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":687,"output_tokens":1}}       }

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}      }

event: ping
data: {"type": "ping"}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"To"}        }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" help you decide what"}    }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to wear tomorrow in"}   }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris"}              }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and your office,"}  }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" I"} }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll"}           }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" need to"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" check"}         }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the weather forecast for"}      }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris"}         }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and the"}       }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" temperature in your office"}    }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":". Let"}       }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" me gather"}  }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" that information for you"}   }

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"."}      }

event: content_block_stop
data: {"type":"content_block_stop","index":0      }

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01A5M6CxGtkpM54aUoXhbcjS","name":"weatherForecast","input":{}}   }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""} }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"que"}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ry\": "}   }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"Paris, Fr"}               }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ance we"}    }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ather fo"}               }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"reca"}          }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"st for tom"}              }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"or"}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"row\"}"}          }

event: content_block_stop
data: {"type":"content_block_stop","index":1              }

event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_015sMn3aU47P4df8yPqiTEoz","name":"roomTemperature","input":{}}              }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":""}   }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"que"}    }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"ry\": \"of"}               }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"fic"}   }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"e temperatur"}  }

event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"e\"}"}        }

event: content_block_stop
data: {"type":"content_block_stop","index":2              }

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":138}            }

event: message_stop
data: {"type":"message_stop"   }

TEMPORARY WORKAROUND

        if (outputs.length > 1 && message.tool_calls?.length > 1) {
            const content = message.tool_calls?.map(tc=> {
                const toolCall = outputs.find(tm=> tm.tool_call_id === tc.id);
                if(toolCall){
                    return {
                        "type": "tool_result",
                        "content": toolCall.content,
                        "tool_use_id": tc.id
                    }
                }
                return undefined
            }).filter(Boolean);
            const toolMessage = new ToolMessage({content,tool_call_id:''})
            return Array.isArray(input) ? [toolMessage] : { messages: [toolMessage]  };
        }
        return Array.isArray(input) ? outputs : { messages: outputs };
Originally created by @tofuliang on GitHub (Aug 1, 2024). https://github.com/langchain-ai/langgraphjs/blob/8268d62e99576760b95072ca5688238749071eb4/langgraph/src/prebuilt/tool_node.ts#L90 **Current Behavior:** The current implementation of ToolNode generates a separate ToolMessage for each tool_call within an AIMessage. This approach is incompatible with scenarios where a single AIMessage contains multiple tool_calls. **Expected Behavior:** The correct implementation should merge the results of multiple tool_calls into a single ToolMessage, rather than creating separate messages for each call. **Example and Temporary Workaround:** Here's an example demonstrating the issue and a temporary, albeit "hacky" fix: `REQUEST BODY` ```json { "model": "claude-3-5-sonnet-20240620", "stream": true, "tools": [ { "name": "weatherForecast", "description": "Use to surf the web using google, fetch current information, check the weather, and retrieve other information.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "The query to use in your search." } }, "required": [ "query" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } }, { "name": "roomTemperature", "description": "retrieve temperature about offices.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "The query to use in your search." } }, "required": [ "query" ], "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" } } ], "messages": [ { "role": "user", "content": "what should I wear tomorrow in pairs and my office base on the weather" } ], "system": "use weatherForecast and roomTemperature togather to get information." } ``` `RESPONSE` ``` event: message_start data: {"type":"message_start","message":{"id":"msg_0139jb18iTsccRApgibNfCz8","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":687,"output_tokens":1}} } event: content_block_start data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} } event: ping data: {"type": "ping"} event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"To"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" help you decide what"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to wear tomorrow in"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and your office,"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" I"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" need to"}} event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" check"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the weather forecast for"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and the"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" temperature in your office"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":". Let"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" me gather"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" that information for you"} } event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"."} } event: content_block_stop data: {"type":"content_block_stop","index":0 } event: content_block_start data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01A5M6CxGtkpM54aUoXhbcjS","name":"weatherForecast","input":{}} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"que"}} event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ry\": "} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"Paris, Fr"} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ance we"} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ather fo"} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"reca"} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"st for tom"} } event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"or"}} event: content_block_delta data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"row\"}"} } event: content_block_stop data: {"type":"content_block_stop","index":1 } event: content_block_start data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_015sMn3aU47P4df8yPqiTEoz","name":"roomTemperature","input":{}} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":""} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"que"} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"ry\": \"of"} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"fic"} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"e temperatur"} } event: content_block_delta data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"e\"}"} } event: content_block_stop data: {"type":"content_block_stop","index":2 } event: message_delta data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":138} } event: message_stop data: {"type":"message_stop" } ``` `TEMPORARY WORKAROUND` ```javascript if (outputs.length > 1 && message.tool_calls?.length > 1) { const content = message.tool_calls?.map(tc=> { const toolCall = outputs.find(tm=> tm.tool_call_id === tc.id); if(toolCall){ return { "type": "tool_result", "content": toolCall.content, "tool_use_id": tc.id } } return undefined }).filter(Boolean); const toolMessage = new ToolMessage({content,tool_call_id:''}) return Array.isArray(input) ? [toolMessage] : { messages: [toolMessage] }; } return Array.isArray(input) ? outputs : { messages: outputs }; ```
yindo closed this issue 2026-02-15 17:15:09 -05:00
Author
Owner

@jacoblee93 commented on GitHub (Aug 1, 2024):

Hey @tofuliang! This is correct behavior at the LangGraph level and is a bug in our Anthropic wrapper. It should have been fixed in https://github.com/langchain-ai/langchainjs/pull/6257

What version of @langchain/anthropic are you using?

@jacoblee93 commented on GitHub (Aug 1, 2024): Hey @tofuliang! This is correct behavior at the LangGraph level and is a bug in our Anthropic wrapper. It should have been fixed in https://github.com/langchain-ai/langchainjs/pull/6257 What version of `@langchain/anthropic` are you using?
Author
Owner

@tofuliang commented on GitHub (Aug 5, 2024):

Hey @tofuliang! This is correct behavior at the LangGraph level and is a bug in our Anthropic wrapper. It should have been fixed in langchain-ai/langchainjs#6257

What version of @langchain/anthropic are you using?

Thanks! Upgraded to latest @langchain/anthropic and it works fine.

@tofuliang commented on GitHub (Aug 5, 2024): > Hey @tofuliang! This is correct behavior at the LangGraph level and is a bug in our Anthropic wrapper. It should have been fixed in [langchain-ai/langchainjs#6257](https://github.com/langchain-ai/langchainjs/pull/6257) > > What version of `@langchain/anthropic` are you using? Thanks! Upgraded to latest `@langchain/anthropic` and it works fine.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#56