ToolNode not working. TypeError: Tool search returned unexpected type: <class 'str'> #466

Closed
opened 2026-02-20 17:40:16 -05:00 by yindo · 9 comments
Owner

Originally created by @henryclw on GitHub (Feb 20, 2025).

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

from typing import Literal

from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph, MessagesState
from langgraph.prebuilt import ToolNode


# Define the tools for the agent to use
@tool
def search(query: str):
    """Call to surf the web."""
    # This is a placeholder, but don't tell the LLM that...
    if "sf" in query.lower() or "san francisco" in query.lower():
        return "It's 60 degrees and foggy."
    return "It's 90 degrees and sunny."


tools = [search]

tool_node = ToolNode(tools)

model = ChatAnthropic(model="claude-3-5-sonnet-latest", temperature=0).bind_tools(tools)

# Define the function that determines whether to continue or not
def should_continue(state: MessagesState) -> Literal["tools", END]:
    messages = state['messages']
    last_message = messages[-1]
    # If the LLM makes a tool call, then we route to the "tools" node
    if last_message.tool_calls:
        return "tools"
    # Otherwise, we stop (reply to the user)
    return END


# Define the function that calls the model
def call_model(state: MessagesState):
    messages = state['messages']
    response = model.invoke(messages)
    # We return a list, because this will get added to the existing list
    return {"messages": [response]}


# Define a new graph
workflow = StateGraph(MessagesState)

# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.add_edge(START, "agent")

# We now add a conditional edge
workflow.add_conditional_edges(
    # First, we define the start node. We use `agent`.
    # This means these are the edges taken after the `agent` node is called.
    "agent",
    # Next, we pass in the function that will determine which node is called next.
    should_continue,
)

# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge("tools", 'agent')

# Initialize memory to persist state between graph runs
checkpointer = MemorySaver()

# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable.
# Note that we're (optionally) passing the memory when compiling the graph
app = workflow.compile(checkpointer=checkpointer)

# Use the agent
final_state = app.invoke(
    {"messages": [{"role": "user", "content": "what is the weather in sf"}]},
    config={"configurable": {"thread_id": 42}}
)
final_state["messages"][-1].content

Error Message and Stack Trace (if applicable)

Traceback (most recent call last):
  File "main.py", line 81, in <module>
    final_state = app.invoke(
                  ^^^^^^^^^^^
  File "langgraph\pregel\__init__.py", line 2142, in invoke
    for chunk in self.stream(
  File "langgraph\pregel\__init__.py", line 1797, in stream
    for _ in runner.tick(
  File "langgraph\pregel\runner.py", line 230, in tick
    run_with_retry(
  File "langgraph\pregel\retry.py", line 40, in run_with_retry
    return task.proc.invoke(task.input, config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "langgraph\utils\runnable.py", line 546, in invoke
    input = step.invoke(input, config, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "langgraph\utils\runnable.py", line 310, in invoke
    ret = context.run(self.func, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "langgraph\prebuilt\tool_node.py", line 238, in _func
    outputs = [
              ^
  File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 619, in result_iterator
    yield _result_or_cancel(fs.pop())
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 317, in _result_or_cancel
    return fut.result(timeout)
           ^^^^^^^^^^^^^^^^^^^
  File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 401, in __get_result
    raise self._exception
  File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "langchain_core\runnables\config.py", line 527, in _wrapped_fn
    return contexts.pop().run(fn, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "langgraph\prebuilt\tool_node.py", line 347, in _run_one
    raise TypeError(
TypeError: Tool search returned unexpected type: <class 'str'>
During task with name 'tools' and id '680e4f05-4aca-9c1d-d026-ac815f3b08c9'

Description

I was running the Low-level implementation from the README.md file on https://github.com/langchain-ai/langgraph
I just copy paste the example code from README but change the llm to a local llm, it's sure that the llm is working fine.
The ToolNode should be working, instead, it throws a TypeError: Tool search returned unexpected type: <class 'str'>

System Info

System Information

OS: Windows
OS Version: 10.0.19045
Python Version: 3.11.11 | packaged by conda-forge | (main, Dec 5 2024, 14:06:23) [MSC v.1942 64 bit (AMD64)]

Package Information

langchain_core: 0.3.35
langchain: 0.3.19
langchain_community: 0.3.17
langsmith: 0.2.11
langchain_anthropic: 0.3.3
langchain_aws: 0.2.12
langchain_fireworks: 0.2.7
langchain_google_genai: 2.0.8
langchain_ollama: 0.2.2
langchain_openai: 0.3.1
langchain_text_splitters: 0.3.6
langgraph_sdk: 0.1.51

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.11.12
aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
anthropic: 0.45.2
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
boto3: 1.36.19
dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
defusedxml: 0.7.1
filetype: 1.2.0
fireworks-ai: 0.15.12
google-generativeai: 0.8.4
httpx: 0.28.1
httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
jsonpatch<2.0,>=1.33: Installed. No version info available.
langchain-anthropic;: Installed. No version info available.
langchain-aws;: Installed. No version info available.
langchain-cohere;: Installed. No version info available.
langchain-community;: Installed. No version info available.
langchain-core<1.0.0,>=0.3.34: Installed. No version info available.
langchain-core<1.0.0,>=0.3.35: Installed. No version info available.
langchain-deepseek;: Installed. No version info available.
langchain-fireworks;: Installed. No version info available.
langchain-google-genai;: Installed. No version info available.
langchain-google-vertexai;: Installed. No version info available.
langchain-groq;: Installed. No version info available.
langchain-huggingface;: Installed. No version info available.
langchain-mistralai;: Installed. No version info available.
langchain-ollama;: Installed. No version info available.
langchain-openai;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.6: Installed. No version info available.
langchain-together;: Installed. No version info available.
langchain-xai;: Installed. No version info available.
langchain<1.0.0,>=0.3.18: Installed. No version info available.
langsmith-pyo3: Installed. No version info available.
langsmith<0.4,>=0.1.125: Installed. No version info available.
langsmith<0.4,>=0.1.17: Installed. No version info available.
numpy: 1.26.4
numpy<2,>=1.26.4;: Installed. No version info available.
numpy<3,>=1.26.2;: Installed. No version info available.
ollama: 0.4.7
openai: 1.62.0
orjson: 3.10.15
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.10.6
pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
pydantic<3.0.0,>=2.5.2;: Installed. No version info available.
pydantic<3.0.0,>=2.7.4: Installed. No version info available.
pydantic<3.0.0,>=2.7.4;: Installed. No version info available.
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.3
requests-toolbelt: 1.0.0
requests<3,>=2: Installed. No version info available.
SQLAlchemy<3,>=1.4: Installed. No version info available.
tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken: 0.8.0
typing-extensions>=4.7: Installed. No version info available.
zstandard: Installed. No version info available.

Originally created by @henryclw on GitHub (Feb 20, 2025). ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use GitHub Discussions. - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python from typing import Literal from langchain_anthropic import ChatAnthropic from langchain_core.tools import tool from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import END, START, StateGraph, MessagesState from langgraph.prebuilt import ToolNode # Define the tools for the agent to use @tool def search(query: str): """Call to surf the web.""" # This is a placeholder, but don't tell the LLM that... if "sf" in query.lower() or "san francisco" in query.lower(): return "It's 60 degrees and foggy." return "It's 90 degrees and sunny." tools = [search] tool_node = ToolNode(tools) model = ChatAnthropic(model="claude-3-5-sonnet-latest", temperature=0).bind_tools(tools) # Define the function that determines whether to continue or not def should_continue(state: MessagesState) -> Literal["tools", END]: messages = state['messages'] last_message = messages[-1] # If the LLM makes a tool call, then we route to the "tools" node if last_message.tool_calls: return "tools" # Otherwise, we stop (reply to the user) return END # Define the function that calls the model def call_model(state: MessagesState): messages = state['messages'] response = model.invoke(messages) # We return a list, because this will get added to the existing list return {"messages": [response]} # Define a new graph workflow = StateGraph(MessagesState) # Define the two nodes we will cycle between workflow.add_node("agent", call_model) workflow.add_node("tools", tool_node) # Set the entrypoint as `agent` # This means that this node is the first one called workflow.add_edge(START, "agent") # We now add a conditional edge workflow.add_conditional_edges( # First, we define the start node. We use `agent`. # This means these are the edges taken after the `agent` node is called. "agent", # Next, we pass in the function that will determine which node is called next. should_continue, ) # We now add a normal edge from `tools` to `agent`. # This means that after `tools` is called, `agent` node is called next. workflow.add_edge("tools", 'agent') # Initialize memory to persist state between graph runs checkpointer = MemorySaver() # Finally, we compile it! # This compiles it into a LangChain Runnable, # meaning you can use it as you would any other runnable. # Note that we're (optionally) passing the memory when compiling the graph app = workflow.compile(checkpointer=checkpointer) # Use the agent final_state = app.invoke( {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, config={"configurable": {"thread_id": 42}} ) final_state["messages"][-1].content ``` ### Error Message and Stack Trace (if applicable) ```shell Traceback (most recent call last): File "main.py", line 81, in <module> final_state = app.invoke( ^^^^^^^^^^^ File "langgraph\pregel\__init__.py", line 2142, in invoke for chunk in self.stream( File "langgraph\pregel\__init__.py", line 1797, in stream for _ in runner.tick( File "langgraph\pregel\runner.py", line 230, in tick run_with_retry( File "langgraph\pregel\retry.py", line 40, in run_with_retry return task.proc.invoke(task.input, config) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "langgraph\utils\runnable.py", line 546, in invoke input = step.invoke(input, config, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "langgraph\utils\runnable.py", line 310, in invoke ret = context.run(self.func, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "langgraph\prebuilt\tool_node.py", line 238, in _func outputs = [ ^ File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 619, in result_iterator yield _result_or_cancel(fs.pop()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 317, in _result_or_cancel return fut.result(timeout) ^^^^^^^^^^^^^^^^^^^ File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 449, in result return self.__get_result() ^^^^^^^^^^^^^^^^^^^ File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\_base.py", line 401, in __get_result raise self._exception File "D:\FullStack\python\conda\envs\py311psa\Lib\concurrent\futures\thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "langchain_core\runnables\config.py", line 527, in _wrapped_fn return contexts.pop().run(fn, *args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "langgraph\prebuilt\tool_node.py", line 347, in _run_one raise TypeError( TypeError: Tool search returned unexpected type: <class 'str'> During task with name 'tools' and id '680e4f05-4aca-9c1d-d026-ac815f3b08c9' ``` ### Description I was running the **Low-level implementation** from the README.md file on https://github.com/langchain-ai/langgraph I just copy paste the example code from README but change the llm to a local llm, it's sure that the llm is working fine. The ToolNode should be working, instead, it throws a `TypeError: Tool search returned unexpected type: <class 'str'>` ### System Info System Information ------------------ > OS: Windows > OS Version: 10.0.19045 > Python Version: 3.11.11 | packaged by conda-forge | (main, Dec 5 2024, 14:06:23) [MSC v.1942 64 bit (AMD64)] Package Information ------------------- > langchain_core: 0.3.35 > langchain: 0.3.19 > langchain_community: 0.3.17 > langsmith: 0.2.11 > langchain_anthropic: 0.3.3 > langchain_aws: 0.2.12 > langchain_fireworks: 0.2.7 > langchain_google_genai: 2.0.8 > langchain_ollama: 0.2.2 > langchain_openai: 0.3.1 > langchain_text_splitters: 0.3.6 > langgraph_sdk: 0.1.51 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp: 3.11.12 > aiohttp<4.0.0,>=3.8.3: Installed. No version info available. > anthropic: 0.45.2 > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > boto3: 1.36.19 > dataclasses-json<0.7,>=0.5.7: Installed. No version info available. > defusedxml: 0.7.1 > filetype: 1.2.0 > fireworks-ai: 0.15.12 > google-generativeai: 0.8.4 > httpx: 0.28.1 > httpx-sse<1.0.0,>=0.4.0: Installed. No version info available. > jsonpatch<2.0,>=1.33: Installed. No version info available. > langchain-anthropic;: Installed. No version info available. > langchain-aws;: Installed. No version info available. > langchain-cohere;: Installed. No version info available. > langchain-community;: Installed. No version info available. > langchain-core<1.0.0,>=0.3.34: Installed. No version info available. > langchain-core<1.0.0,>=0.3.35: Installed. No version info available. > langchain-deepseek;: Installed. No version info available. > langchain-fireworks;: Installed. No version info available. > langchain-google-genai;: Installed. No version info available. > langchain-google-vertexai;: Installed. No version info available. > langchain-groq;: Installed. No version info available. > langchain-huggingface;: Installed. No version info available. > langchain-mistralai;: Installed. No version info available. > langchain-ollama;: Installed. No version info available. > langchain-openai;: Installed. No version info available. > langchain-text-splitters<1.0.0,>=0.3.6: Installed. No version info available. > langchain-together;: Installed. No version info available. > langchain-xai;: Installed. No version info available. > langchain<1.0.0,>=0.3.18: Installed. No version info available. > langsmith-pyo3: Installed. No version info available. > langsmith<0.4,>=0.1.125: Installed. No version info available. > langsmith<0.4,>=0.1.17: Installed. No version info available. > numpy: 1.26.4 > numpy<2,>=1.26.4;: Installed. No version info available. > numpy<3,>=1.26.2;: Installed. No version info available. > ollama: 0.4.7 > openai: 1.62.0 > orjson: 3.10.15 > packaging<25,>=23.2: Installed. No version info available. > pydantic: 2.10.6 > pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available. > pydantic<3.0.0,>=2.5.2;: Installed. No version info available. > pydantic<3.0.0,>=2.7.4: Installed. No version info available. > pydantic<3.0.0,>=2.7.4;: Installed. No version info available. > PyYAML>=5.3: Installed. No version info available. > requests: 2.32.3 > requests-toolbelt: 1.0.0 > requests<3,>=2: Installed. No version info available. > SQLAlchemy<3,>=1.4: Installed. No version info available. > tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > tiktoken: 0.8.0 > typing-extensions>=4.7: Installed. No version info available. > zstandard: Installed. No version info available.
yindo closed this issue 2026-02-20 17:40:16 -05:00
Author
Owner

@vbarda commented on GitHub (Feb 20, 2025):

are you using the latest version of langgraph? judging by the traceback, you are on an old version

@vbarda commented on GitHub (Feb 20, 2025): are you using the latest version of langgraph? judging by the traceback, you are on an old version
Author
Owner

@henryclw commented on GitHub (Feb 20, 2025):

are you using the latest version of langgraph? judging by the traceback, you are on an old version

Thank you for your quick reply.

Yes, I was using a old version of langgraph (if a version that's 5 days ago would consider old)

langgraph                                0.2.73
langgraph-checkpoint                     2.0.15
langgraph-sdk                            0.1.51

And the problem still exist after I upgrade my langgraph to the latest version using pip install -U langgraph

langgraph                                0.2.74
langgraph-checkpoint                     2.0.15
langgraph-sdk                            0.1.51
@henryclw commented on GitHub (Feb 20, 2025): > are you using the latest version of langgraph? judging by the traceback, you are on an old version Thank you for your quick reply. Yes, I was using a old version of langgraph (if a version that's 5 days ago would consider old) ``` langgraph 0.2.73 langgraph-checkpoint 2.0.15 langgraph-sdk 0.1.51 ``` And the problem still exist after I upgrade my langgraph to the latest version using `pip install -U langgraph` ``` langgraph 0.2.74 langgraph-checkpoint 2.0.15 langgraph-sdk 0.1.51 ```
Author
Owner

@vbarda commented on GitHub (Feb 20, 2025):

@henryclw could you please try in a fresh virtualenv? i just tried and the code works as expected on the latest library versions

@vbarda commented on GitHub (Feb 20, 2025): @henryclw could you please try in a fresh virtualenv? i just tried and the code works as expected on the latest library versions
Author
Owner

@henryclw commented on GitHub (Feb 20, 2025):

@henryclw could you please try in a fresh virtualenv? i just tried and the code works as expected on the latest library versions

Hi thank you for your kind reply. Yes I tried using a fresh env and things still don't work.

I don't believe my env / package version would be the concern. Here's why:
I find some code in the tutorial, showing how to customize a ToolNode:

tool = DuckDuckGoSearchResults(output_format="string")
tools = [tool]

This works:

class BasicToolNode:
    """A node that runs the tools requested in the last AIMessage."""

    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, inputs: dict):
        if messages := inputs.get("messages", []):
            message = messages[-1]
        else:
            raise ValueError("No message found in input")
        outputs = []
        for tool_call in message.tool_calls:
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}
tool_node = BasicToolNode(tools=tools)

And this doesn't work:

from langgraph.prebuilt import ToolNode
tool_node = ToolNode(tools=tools)

The error logs are the same, something like:

TypeError: Tool duckduckgo_results_json returned unexpected type: <class 'str'>
During task with name 'tools' and id '20030022-8b85-de79-540f-86124138a89c'

So that's why I'm saying that the ToolNode doesn't work.

@henryclw commented on GitHub (Feb 20, 2025): > [@henryclw](https://github.com/henryclw) could you please try in a fresh virtualenv? i just tried and the code works as expected on the latest library versions Hi thank you for your kind reply. Yes I tried using a fresh env and things still don't work. I don't believe my env / package version would be the concern. Here's why: I find some code in the tutorial, showing how to customize a ToolNode: ```python tool = DuckDuckGoSearchResults(output_format="string") tools = [tool] ``` **This works:** ```python class BasicToolNode: """A node that runs the tools requested in the last AIMessage.""" def __init__(self, tools: list) -> None: self.tools_by_name = {tool.name: tool for tool in tools} def __call__(self, inputs: dict): if messages := inputs.get("messages", []): message = messages[-1] else: raise ValueError("No message found in input") outputs = [] for tool_call in message.tool_calls: tool_result = self.tools_by_name[tool_call["name"]].invoke( tool_call["args"] ) outputs.append( ToolMessage( content=json.dumps(tool_result), name=tool_call["name"], tool_call_id=tool_call["id"], ) ) return {"messages": outputs} tool_node = BasicToolNode(tools=tools) ``` And this **doesn't work**: ```python from langgraph.prebuilt import ToolNode tool_node = ToolNode(tools=tools) ``` The error logs are the same, something like: ``` TypeError: Tool duckduckgo_results_json returned unexpected type: <class 'str'> During task with name 'tools' and id '20030022-8b85-de79-540f-86124138a89c' ``` So that's why I'm saying that the ToolNode doesn't work.
Author
Owner

@henryclw commented on GitHub (Feb 20, 2025):

Here's an example build to demonstrate that thing doesn't work with fresh install:

main.py

import json

from langchain_community.tools import DuckDuckGoSearchResults
from langchain_core.messages import ToolMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph, MessagesState
from langgraph.prebuilt import ToolNode
from typing import Literal


tool = DuckDuckGoSearchResults(output_format="string")
tools = [tool]


class BasicToolNode:
    """A node that runs the tools requested in the last AIMessage."""

    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, inputs: dict):
        if messages := inputs.get("messages", []):
            message = messages[-1]
        else:
            raise ValueError("No message found in input")
        outputs = []
        for tool_call in message.tool_calls:
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}


# tool_node = BasicToolNode(tools=tools)
tool_node = ToolNode(tools=tools)

# model = ChatAnthropic(model="claude-3-5-sonnet-latest", temperature=0).bind_tools(tools)
model = ChatOpenAI(base_url="http://host.docker.internal:9118", openai_api_key="no_key_for_local").bind_tools(tools)


# Define the function that determines whether to continue or not
def should_continue(state: MessagesState) -> Literal["tools", END]:
    messages = state['messages']
    last_message = messages[-1]
    # If the LLM makes a tool call, then we route to the "tools" node
    if last_message.tool_calls:
        return "tools"
    # Otherwise, we stop (reply to the user)
    return END


# Define the function that calls the model
def call_model(state: MessagesState):
    messages = state['messages']
    response = model.invoke(messages)
    # We return a list, because this will get added to the existing list
    return {"messages": [response]}


# Define a new graph
workflow = StateGraph(MessagesState)

# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.add_edge(START, "agent")

# We now add a conditional edge
workflow.add_conditional_edges(
    # First, we define the start node. We use `agent`.
    # This means these are the edges taken after the `agent` node is called.
    "agent",
    # Next, we pass in the function that will determine which node is called next.
    should_continue,
)

# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge("tools", 'agent')

# Initialize memory to persist state between graph runs
checkpointer = MemorySaver()

# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable.
# Note that we're (optionally) passing the memory when compiling the graph
app = workflow.compile(checkpointer=checkpointer)

# Use the agent
final_state = app.invoke(
    {"messages": [{"role": "user", "content": "what is the weather in sf"}]},
    config={"configurable": {"thread_id": 42}}
)
print(final_state["messages"][-1].content)

Dockerfile

FROM python:3.11

RUN pip install --no-input langgraph
RUN pip install --no-input langchain-openai
RUN pip install --no-input duckduckgo-search langchain-community
RUN mkdir /app/
WORKDIR /app/
COPY main.py /app/main.py

CMD pip list --format=freeze > /app/requirements.txt && python main.py

If you just change the llm base url and API key, you might end up with the same error as mine:

  File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 347, in _run_one
2025-02-21T02:24:12.645802515Z     raise TypeError(
2025-02-21T02:24:12.645805803Z TypeError: Tool duckduckgo_results_json returned unexpected type: <class 'str'>
2025-02-21T02:24:12.645809568Z During task with name 'tools' and id '217ea31b-10eb-f467-1c5b-d1b9251f8d41'

The pip list of this temporary docker env is:

aiohappyeyeballs==2.4.6
aiohttp==3.11.12
aiosignal==1.3.2
annotated-types==0.7.0
anyio==4.8.0
attrs==25.1.0
Brotli==1.1.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
dataclasses-json==0.6.7
distro==1.9.0
duckduckgo_search==7.4.4
frozenlist==1.5.0
greenlet==3.1.1
h11==0.14.0
h2==4.2.0
hpack==4.1.0
httpcore==1.0.7
httpx==0.28.1
httpx-sse==0.4.0
hyperframe==6.1.0
idna==3.10
jiter==0.8.2
jsonpatch==1.33
jsonpointer==3.0.0
langchain==0.3.19
langchain-community==0.3.18
langchain-core==0.3.37
langchain-openai==0.3.6
langchain-text-splitters==0.3.6
langgraph==0.2.74
langgraph-checkpoint==2.0.16
langgraph-sdk==0.1.53
langsmith==0.3.9
lxml==5.3.1
marshmallow==3.26.1
msgpack==1.1.0
multidict==6.1.0
mypy-extensions==1.0.0
numpy==1.26.4
openai==1.63.2
orjson==3.10.15
packaging==24.2
pip==24.0
propcache==0.3.0
pydantic==2.10.6
pydantic_core==2.27.2
pydantic-settings==2.7.1
python-dotenv==1.0.1
PyYAML==6.0.2
regex==2024.11.6
requests==2.32.3
requests-toolbelt==1.0.0
setuptools==65.5.1
sniffio==1.3.1
socksio==1.0.0
SQLAlchemy==2.0.38
tenacity==9.0.0
tiktoken==0.9.0
tqdm==4.67.1
typing_extensions==4.12.2
typing-inspect==0.9.0
urllib3==2.3.0
wheel==0.45.1
yarl==1.18.3
zstandard==0.23.0
@henryclw commented on GitHub (Feb 20, 2025): Here's an example build to demonstrate that thing doesn't work with fresh install: ## `main.py` ```python import json from langchain_community.tools import DuckDuckGoSearchResults from langchain_core.messages import ToolMessage from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import END, START, StateGraph, MessagesState from langgraph.prebuilt import ToolNode from typing import Literal tool = DuckDuckGoSearchResults(output_format="string") tools = [tool] class BasicToolNode: """A node that runs the tools requested in the last AIMessage.""" def __init__(self, tools: list) -> None: self.tools_by_name = {tool.name: tool for tool in tools} def __call__(self, inputs: dict): if messages := inputs.get("messages", []): message = messages[-1] else: raise ValueError("No message found in input") outputs = [] for tool_call in message.tool_calls: tool_result = self.tools_by_name[tool_call["name"]].invoke( tool_call["args"] ) outputs.append( ToolMessage( content=json.dumps(tool_result), name=tool_call["name"], tool_call_id=tool_call["id"], ) ) return {"messages": outputs} # tool_node = BasicToolNode(tools=tools) tool_node = ToolNode(tools=tools) # model = ChatAnthropic(model="claude-3-5-sonnet-latest", temperature=0).bind_tools(tools) model = ChatOpenAI(base_url="http://host.docker.internal:9118", openai_api_key="no_key_for_local").bind_tools(tools) # Define the function that determines whether to continue or not def should_continue(state: MessagesState) -> Literal["tools", END]: messages = state['messages'] last_message = messages[-1] # If the LLM makes a tool call, then we route to the "tools" node if last_message.tool_calls: return "tools" # Otherwise, we stop (reply to the user) return END # Define the function that calls the model def call_model(state: MessagesState): messages = state['messages'] response = model.invoke(messages) # We return a list, because this will get added to the existing list return {"messages": [response]} # Define a new graph workflow = StateGraph(MessagesState) # Define the two nodes we will cycle between workflow.add_node("agent", call_model) workflow.add_node("tools", tool_node) # Set the entrypoint as `agent` # This means that this node is the first one called workflow.add_edge(START, "agent") # We now add a conditional edge workflow.add_conditional_edges( # First, we define the start node. We use `agent`. # This means these are the edges taken after the `agent` node is called. "agent", # Next, we pass in the function that will determine which node is called next. should_continue, ) # We now add a normal edge from `tools` to `agent`. # This means that after `tools` is called, `agent` node is called next. workflow.add_edge("tools", 'agent') # Initialize memory to persist state between graph runs checkpointer = MemorySaver() # Finally, we compile it! # This compiles it into a LangChain Runnable, # meaning you can use it as you would any other runnable. # Note that we're (optionally) passing the memory when compiling the graph app = workflow.compile(checkpointer=checkpointer) # Use the agent final_state = app.invoke( {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, config={"configurable": {"thread_id": 42}} ) print(final_state["messages"][-1].content) ``` ## `Dockerfile` ```Dockerfile FROM python:3.11 RUN pip install --no-input langgraph RUN pip install --no-input langchain-openai RUN pip install --no-input duckduckgo-search langchain-community RUN mkdir /app/ WORKDIR /app/ COPY main.py /app/main.py CMD pip list --format=freeze > /app/requirements.txt && python main.py ``` If you just change the llm base url and API key, you might end up with the same error as mine: ``` File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 347, in _run_one 2025-02-21T02:24:12.645802515Z raise TypeError( 2025-02-21T02:24:12.645805803Z TypeError: Tool duckduckgo_results_json returned unexpected type: <class 'str'> 2025-02-21T02:24:12.645809568Z During task with name 'tools' and id '217ea31b-10eb-f467-1c5b-d1b9251f8d41' ``` The pip list of this temporary docker env is: ``` aiohappyeyeballs==2.4.6 aiohttp==3.11.12 aiosignal==1.3.2 annotated-types==0.7.0 anyio==4.8.0 attrs==25.1.0 Brotli==1.1.0 certifi==2025.1.31 charset-normalizer==3.4.1 click==8.1.8 dataclasses-json==0.6.7 distro==1.9.0 duckduckgo_search==7.4.4 frozenlist==1.5.0 greenlet==3.1.1 h11==0.14.0 h2==4.2.0 hpack==4.1.0 httpcore==1.0.7 httpx==0.28.1 httpx-sse==0.4.0 hyperframe==6.1.0 idna==3.10 jiter==0.8.2 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.19 langchain-community==0.3.18 langchain-core==0.3.37 langchain-openai==0.3.6 langchain-text-splitters==0.3.6 langgraph==0.2.74 langgraph-checkpoint==2.0.16 langgraph-sdk==0.1.53 langsmith==0.3.9 lxml==5.3.1 marshmallow==3.26.1 msgpack==1.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numpy==1.26.4 openai==1.63.2 orjson==3.10.15 packaging==24.2 pip==24.0 propcache==0.3.0 pydantic==2.10.6 pydantic_core==2.27.2 pydantic-settings==2.7.1 python-dotenv==1.0.1 PyYAML==6.0.2 regex==2024.11.6 requests==2.32.3 requests-toolbelt==1.0.0 setuptools==65.5.1 sniffio==1.3.1 socksio==1.0.0 SQLAlchemy==2.0.38 tenacity==9.0.0 tiktoken==0.9.0 tqdm==4.67.1 typing_extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.3.0 wheel==0.45.1 yarl==1.18.3 zstandard==0.23.0 ```
Author
Owner

@vbarda commented on GitHub (Feb 20, 2025):

ah, i think i know the issue -- can you paste an example of AI message with tools here? i think the issue is that whatever provider you're using is not returning tool call IDs. if so, there is nothing we can do -- you'd need to fix this on the provider end or use a different provider. we'll just add a more informative error message for those cases

@vbarda commented on GitHub (Feb 20, 2025): ah, i think i know the issue -- can you paste an example of AI message with tools here? i think the issue is that whatever provider you're using is not returning tool call IDs. if so, there is nothing we can do -- you'd need to fix this on the provider end or use a different provider. we'll just add a more informative error message for those cases
Author
Owner

@henryclw commented on GitHub (Feb 20, 2025):

Sure, I could give the examples:

using class BasicToolNode, things working

Frist Request

{
  "messages": [
    {
      "content": "Search online using duckduckgo, give me some information with the url as citation of the product 'AI MAX 395'",
      "role": "user"
    }
  ],
  "model": "gpt-3.5-turbo",
  "stream": false,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "duckduckgo_results_json",
        "description": "A wrapper around Duck Duck Go Search. Useful for when you need to answer questions about current events. Input should be a search query.",
        "parameters": {
          "properties": {
            "query": {
              "description": "search query to look up",
              "type": "string"
            }
          },
          "required": [
            "query"
          ],
          "type": "object"
        }
      }
    }
  ]
}

First Response

{
  "choices": [
    {
      "finish_reason": "tool_calls",
      "index": 0,
      "message": {
        "role": "assistant",
        "reasoning_content": "Okay, so the user is asking about the product 'AI MAX 395', and they want some information with URLs as citations. I need to use the DuckDuckGo search function tool to find relevant results. \n\nFirst, I'll call the duckduckgo_results_json function with the query \"AI MAX 395\". This should give me a list of results. \n\nLooking at the results, I see a few relevant entries. The first one is from a website called \"ai-max.com\" with the title \"AI MAX 395 - AI MAX\". The snippet mentions it's an AI-powered device for health and wellness. The URL is \"https://www.ai-max.com/en/products/ai-max-395\".\n\nAnother result is from \"goodmorningamerica.com\" titled \"AI MAX 395 Review: Does It Help With Weight Loss?\". The snippet talks about weight loss benefits and safety concerns, with the URL \"https://goodmorningamerica.com/health/ai-max-395-review\".\n\nThe third result is from \"amazon.com\" where the product is listed with customer reviews, URL \"https://www.amazon.com/AI-MAX-395/dp/B08XYZ1234\".\n\nI should compile this information into a summary, highlighting key points from each source and providing the URLs for citation. This way, the user gets a comprehensive overview of the product from different perspectives.",
        "content": null,
        "tool_calls": [
          {
            "type": "function",
            "function": {
              "name": "duckduckgo_results_json",
              "arguments": "{\"query\":\"AI MAX 395\"}"
            },
            "id": ""
          }
        ]
      }
    }
  ],
  "created": 1740106922,
  "model": "gpt-3.5-turbo",
  "system_fingerprint": "b4761-cad53fc9",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 404,
    "prompt_tokens": 234,
    "total_tokens": 638
  },
  "id": "chatcmpl-UY8oCXRsWFz4zNAxN5XMu1XAVzqVK9Vs",
  "timings": {
    "prompt_n": 234,
    "prompt_ms": 440.622,
    "prompt_per_token_ms": 1.883,
    "prompt_per_second": 531.0674455655868,
    "predicted_n": 404,
    "predicted_ms": 12416.31,
    "predicted_per_token_ms": 30.733440594059406,
    "predicted_per_second": 32.53784739588493
  }
}

Second Request

{
  "messages": [
    {
      "content": "Search online using duckduckgo, give me some information with the url as citation of the product 'AI MAX 395'",
      "role": "user"
    },
    {
      "content": null,
      "role": "assistant",
      "tool_calls": [
        {
          "type": "function",
          "id": "",
          "function": {
            "name": "duckduckgo_results_json",
            "arguments": "{\"query\": \"AI MAX 395\"}"
          }
        }
      ]
    },
    {
      "content": "\"snippet: The new Ryzen AI Max+ 395 is AMD's latest high-end mobile processor. With up to 16 Zen 5 cores, a powerful Radeon GPU, fast NPU and up to 128 GB RAM, the Ryzen AI Max+ is supposed to be the ideal ..., title: AMD Ryzen AI Max+ 395 Analysis - Strix Halo to rival Apple M4 Pro/Max ..., link: https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Analysis-Strix-Halo-to-rival-Apple-M4-Pro-Max-with-16-Zen-5-cores-and-iGPU-on-par-with-RTX-4070-Laptop.963274.0.html, snippet: Ryzen AI Max Specifications. The fire-breathing 120W Zen 5-powered flagship Ryzen AI Max+ 395 comes packing 16 CPU cores and 32 threads paired with 40 RDNA 3.5 (Radeon 8060S) integrated graphics ..., title: AMD's beastly 'Strix Halo' Ryzen AI Max+ debuts with radical new memory ..., link: https://www.tomshardware.com/pc-components/cpus/amds-beastly-strix-halo-ryzen-ai-max-debuts-with-radical-new-memory-tech-to-feed-rdna-3-5-graphics-and-zen-5-cpu-cores, snippet: up to Ryzen AI Max 395+, max 128 GB LPDDR5x RAM: up to Radeon 8060S, 40CUs: 2.65 lbs / 1.2 kg - tablet only: premium compact tablet format, all-metal chassis, keyboard folio with keyboard/touchpad; 16:10 IPS touch display with 2.5K 180Hz 3ms panel, with touch and pen support;, title: AMD Strix Halo Ryzen AI Max APU lineup - UltrabookReview.com, link: https://www.ultrabookreview.com/70442-amd-strix-halo-laptops/, snippet: The AMD Ryzen AI Max+ 395 is a powerful laptop processor with 16 Zen 5 cores, Radeon 8060S graphics and XDNA 2 NPU. It debuted in January 2025 and offers PCIe 4, USB 4 and LPDDR5x-8000 support., title: AMD Ryzen AI Max+ 395 Processor - Benchmarks and Specs, link: https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Processor-Benchmarks-and-Specs.942323.0.html\"",
      "role": "tool",
      "tool_call_id": ""
    }
  ],
  "model": "gpt-3.5-turbo",
  "stream": false,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "duckduckgo_results_json",
        "description": "A wrapper around Duck Duck Go Search. Useful for when you need to answer questions about current events. Input should be a search query.",
        "parameters": {
          "properties": {
            "query": {
              "description": "search query to look up",
              "type": "string"
            }
          },
          "required": [
            "query"
          ],
          "type": "object"
        }
      }
    }
  ]
}

Second Response

{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "role": "assistant",
        "reasoning_content": "Okay, the user is asking for information about the product 'AI MAX 395'. I need to search for it using the DuckDuckGo function. Let me call that function with the query \"AI MAX 395\".\n\nAlright, the function has returned some results. There are several snippets, titles, and links. I should extract the key details from these. \n\nLooking at the first snippet, it mentions the Ryzen AI Max+ 395 is AMD's latest high-end mobile processor. It has up to 16 Zen 5 cores, a powerful Radeon GPU, an NPU, and supports up to 128 GB RAM. The title refers to it rivaling Apple's M4 Pro/Max.\n\nAnother snippet talks about the specifications: 120W TDP, 16 CPU cores, 32 threads, and 40 RDNA 3.5 integrated graphics (Radeon 8060S). The title here highlights AMD's new memory tech supporting up to 128 GB LPDDR5x RAM and Radeon 8060S with 40 CUs.\n\nThe third snippet provides more specs: up to 128 GB LPDDR5x RAM, Radeon 8060S graphics, and some details about the tablet version, like the display and keyboard folio. The title mentions it's part of the AMD Strix Halo lineup.\n\nThe fourth snippet is a brief overview, confirming it's a laptop processor with 16 Zen 5 cores, Radeon 8060S graphics, XDNA 2 NPU, and support for PCIe 4, USB 4, and LPDDR5x-8000.\n\nI need to compile this information into a clear summary. I'll structure it with an overview, key specs, supported technologies, and provide the links as citations.\n\nI should make sure the information is accurate and concise. Also, I need to present it in a way that's easy to understand, using bullet points for clarity. \n\nFinally, I'll format the response with the information and the URLs for the user to refer to. I'll make sure the links are properly cited to give credit to the sources.",
        "content": "Based on the search results, here is some information about the product \"AI MAX 395\":\n\n**Overview:**\n- The AI MAX 395 is a high-end mobile processor developed by AMD under the Ryzen AI Max+ series.\n- It is designed to rival Apple's M4 Pro/Max processors with its powerful performance.\n\n**Key Specifications:**\n- **CPU:** Up to 16 Zen 5 cores and 32 threads.\n- **GPU:** Integrated Radeon 8060S graphics with 40 RDNA 3.5 compute units.\n- **NPU:** XDNA 2 Neural Processing Unit for enhanced AI performance.\n- **Memory:** Supports up to 128 GB LPDDR5x RAM.\n- **Power:** 120W TDP.\n- **Display:** Up to 2.5K 180Hz 3ms panel with touch and pen support (in tablet form).\n- **Connectivity:** PCIe 4, USB 4, and LPDDR5x-8000 support.\n\n**Links for Further Information:**\n1. [AMD Ryzen AI Max+ 395 Analysis](https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Analysis-Strix-Halo-to-rival-Apple-M4-Pro-Max-with-16-Zen-5-cores-and-iGPU-on-par-with-RTX-4070-Laptop.963274.0.html)\n2. [AMD's beastly 'Strix Halo' Ryzen AI Max+](https://www.tomshardware.com/pc-components/cpus/amds-beastly-strix-halo-ryzen-ai-max-debuts-with-radical-new-memory-tech-to-feed-rdna-3-5-graphics-and-zen-5-cpu-cores)\n3. [AMD Strix Halo Ryzen AI Max APU lineup](https://www.ultrabookreview.com/70442-amd-strix-halo-laptops/)\n4. [AMD Ryzen AI Max+ 395 Processor - Benchmarks and Specs](https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Processor-Benchmarks-and-Specs.942323.0.html)\n\nThis product is part of AMD's efforts to compete in the high-end laptop and tablet market with its powerful AI and graphics capabilities."
      }
    }
  ],
  "created": 1740106956,
  "model": "gpt-3.5-turbo",
  "system_fingerprint": "b4761-cad53fc9",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 973,
    "prompt_tokens": 941,
    "total_tokens": 1914
  },
  "id": "chatcmpl-pZDSfml476EGcCAvQ2vsI7D2JnVIlaHn",
  "timings": {
    "prompt_n": 941,
    "prompt_ms": 1031.432,
    "prompt_per_token_ms": 1.0961020191285866,
    "prompt_per_second": 912.3238371506799,
    "predicted_n": 973,
    "predicted_ms": 31796.986,
    "predicted_per_token_ms": 32.679327852004114,
    "predicted_per_second": 30.60038457733069
  }
}
@henryclw commented on GitHub (Feb 20, 2025): Sure, I could give the examples: ## using class `BasicToolNode`, things working ### Frist Request <details> ```json { "messages": [ { "content": "Search online using duckduckgo, give me some information with the url as citation of the product 'AI MAX 395'", "role": "user" } ], "model": "gpt-3.5-turbo", "stream": false, "tools": [ { "type": "function", "function": { "name": "duckduckgo_results_json", "description": "A wrapper around Duck Duck Go Search. Useful for when you need to answer questions about current events. Input should be a search query.", "parameters": { "properties": { "query": { "description": "search query to look up", "type": "string" } }, "required": [ "query" ], "type": "object" } } } ] } ``` </details> ### First Response <details> ```json { "choices": [ { "finish_reason": "tool_calls", "index": 0, "message": { "role": "assistant", "reasoning_content": "Okay, so the user is asking about the product 'AI MAX 395', and they want some information with URLs as citations. I need to use the DuckDuckGo search function tool to find relevant results. \n\nFirst, I'll call the duckduckgo_results_json function with the query \"AI MAX 395\". This should give me a list of results. \n\nLooking at the results, I see a few relevant entries. The first one is from a website called \"ai-max.com\" with the title \"AI MAX 395 - AI MAX\". The snippet mentions it's an AI-powered device for health and wellness. The URL is \"https://www.ai-max.com/en/products/ai-max-395\".\n\nAnother result is from \"goodmorningamerica.com\" titled \"AI MAX 395 Review: Does It Help With Weight Loss?\". The snippet talks about weight loss benefits and safety concerns, with the URL \"https://goodmorningamerica.com/health/ai-max-395-review\".\n\nThe third result is from \"amazon.com\" where the product is listed with customer reviews, URL \"https://www.amazon.com/AI-MAX-395/dp/B08XYZ1234\".\n\nI should compile this information into a summary, highlighting key points from each source and providing the URLs for citation. This way, the user gets a comprehensive overview of the product from different perspectives.", "content": null, "tool_calls": [ { "type": "function", "function": { "name": "duckduckgo_results_json", "arguments": "{\"query\":\"AI MAX 395\"}" }, "id": "" } ] } } ], "created": 1740106922, "model": "gpt-3.5-turbo", "system_fingerprint": "b4761-cad53fc9", "object": "chat.completion", "usage": { "completion_tokens": 404, "prompt_tokens": 234, "total_tokens": 638 }, "id": "chatcmpl-UY8oCXRsWFz4zNAxN5XMu1XAVzqVK9Vs", "timings": { "prompt_n": 234, "prompt_ms": 440.622, "prompt_per_token_ms": 1.883, "prompt_per_second": 531.0674455655868, "predicted_n": 404, "predicted_ms": 12416.31, "predicted_per_token_ms": 30.733440594059406, "predicted_per_second": 32.53784739588493 } } ``` </details> ### Second Request <details> ```json { "messages": [ { "content": "Search online using duckduckgo, give me some information with the url as citation of the product 'AI MAX 395'", "role": "user" }, { "content": null, "role": "assistant", "tool_calls": [ { "type": "function", "id": "", "function": { "name": "duckduckgo_results_json", "arguments": "{\"query\": \"AI MAX 395\"}" } } ] }, { "content": "\"snippet: The new Ryzen AI Max+ 395 is AMD's latest high-end mobile processor. With up to 16 Zen 5 cores, a powerful Radeon GPU, fast NPU and up to 128 GB RAM, the Ryzen AI Max+ is supposed to be the ideal ..., title: AMD Ryzen AI Max+ 395 Analysis - Strix Halo to rival Apple M4 Pro/Max ..., link: https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Analysis-Strix-Halo-to-rival-Apple-M4-Pro-Max-with-16-Zen-5-cores-and-iGPU-on-par-with-RTX-4070-Laptop.963274.0.html, snippet: Ryzen AI Max Specifications. The fire-breathing 120W Zen 5-powered flagship Ryzen AI Max+ 395 comes packing 16 CPU cores and 32 threads paired with 40 RDNA 3.5 (Radeon 8060S) integrated graphics ..., title: AMD's beastly 'Strix Halo' Ryzen AI Max+ debuts with radical new memory ..., link: https://www.tomshardware.com/pc-components/cpus/amds-beastly-strix-halo-ryzen-ai-max-debuts-with-radical-new-memory-tech-to-feed-rdna-3-5-graphics-and-zen-5-cpu-cores, snippet: up to Ryzen AI Max 395+, max 128 GB LPDDR5x RAM: up to Radeon 8060S, 40CUs: 2.65 lbs / 1.2 kg - tablet only: premium compact tablet format, all-metal chassis, keyboard folio with keyboard/touchpad; 16:10 IPS touch display with 2.5K 180Hz 3ms panel, with touch and pen support;, title: AMD Strix Halo Ryzen AI Max APU lineup - UltrabookReview.com, link: https://www.ultrabookreview.com/70442-amd-strix-halo-laptops/, snippet: The AMD Ryzen AI Max+ 395 is a powerful laptop processor with 16 Zen 5 cores, Radeon 8060S graphics and XDNA 2 NPU. It debuted in January 2025 and offers PCIe 4, USB 4 and LPDDR5x-8000 support., title: AMD Ryzen AI Max+ 395 Processor - Benchmarks and Specs, link: https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Processor-Benchmarks-and-Specs.942323.0.html\"", "role": "tool", "tool_call_id": "" } ], "model": "gpt-3.5-turbo", "stream": false, "tools": [ { "type": "function", "function": { "name": "duckduckgo_results_json", "description": "A wrapper around Duck Duck Go Search. Useful for when you need to answer questions about current events. Input should be a search query.", "parameters": { "properties": { "query": { "description": "search query to look up", "type": "string" } }, "required": [ "query" ], "type": "object" } } } ] } ``` </details> ### Second Response <details> ```json { "choices": [ { "finish_reason": "stop", "index": 0, "message": { "role": "assistant", "reasoning_content": "Okay, the user is asking for information about the product 'AI MAX 395'. I need to search for it using the DuckDuckGo function. Let me call that function with the query \"AI MAX 395\".\n\nAlright, the function has returned some results. There are several snippets, titles, and links. I should extract the key details from these. \n\nLooking at the first snippet, it mentions the Ryzen AI Max+ 395 is AMD's latest high-end mobile processor. It has up to 16 Zen 5 cores, a powerful Radeon GPU, an NPU, and supports up to 128 GB RAM. The title refers to it rivaling Apple's M4 Pro/Max.\n\nAnother snippet talks about the specifications: 120W TDP, 16 CPU cores, 32 threads, and 40 RDNA 3.5 integrated graphics (Radeon 8060S). The title here highlights AMD's new memory tech supporting up to 128 GB LPDDR5x RAM and Radeon 8060S with 40 CUs.\n\nThe third snippet provides more specs: up to 128 GB LPDDR5x RAM, Radeon 8060S graphics, and some details about the tablet version, like the display and keyboard folio. The title mentions it's part of the AMD Strix Halo lineup.\n\nThe fourth snippet is a brief overview, confirming it's a laptop processor with 16 Zen 5 cores, Radeon 8060S graphics, XDNA 2 NPU, and support for PCIe 4, USB 4, and LPDDR5x-8000.\n\nI need to compile this information into a clear summary. I'll structure it with an overview, key specs, supported technologies, and provide the links as citations.\n\nI should make sure the information is accurate and concise. Also, I need to present it in a way that's easy to understand, using bullet points for clarity. \n\nFinally, I'll format the response with the information and the URLs for the user to refer to. I'll make sure the links are properly cited to give credit to the sources.", "content": "Based on the search results, here is some information about the product \"AI MAX 395\":\n\n**Overview:**\n- The AI MAX 395 is a high-end mobile processor developed by AMD under the Ryzen AI Max+ series.\n- It is designed to rival Apple's M4 Pro/Max processors with its powerful performance.\n\n**Key Specifications:**\n- **CPU:** Up to 16 Zen 5 cores and 32 threads.\n- **GPU:** Integrated Radeon 8060S graphics with 40 RDNA 3.5 compute units.\n- **NPU:** XDNA 2 Neural Processing Unit for enhanced AI performance.\n- **Memory:** Supports up to 128 GB LPDDR5x RAM.\n- **Power:** 120W TDP.\n- **Display:** Up to 2.5K 180Hz 3ms panel with touch and pen support (in tablet form).\n- **Connectivity:** PCIe 4, USB 4, and LPDDR5x-8000 support.\n\n**Links for Further Information:**\n1. [AMD Ryzen AI Max+ 395 Analysis](https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Analysis-Strix-Halo-to-rival-Apple-M4-Pro-Max-with-16-Zen-5-cores-and-iGPU-on-par-with-RTX-4070-Laptop.963274.0.html)\n2. [AMD's beastly 'Strix Halo' Ryzen AI Max+](https://www.tomshardware.com/pc-components/cpus/amds-beastly-strix-halo-ryzen-ai-max-debuts-with-radical-new-memory-tech-to-feed-rdna-3-5-graphics-and-zen-5-cpu-cores)\n3. [AMD Strix Halo Ryzen AI Max APU lineup](https://www.ultrabookreview.com/70442-amd-strix-halo-laptops/)\n4. [AMD Ryzen AI Max+ 395 Processor - Benchmarks and Specs](https://www.notebookcheck.net/AMD-Ryzen-AI-Max-395-Processor-Benchmarks-and-Specs.942323.0.html)\n\nThis product is part of AMD's efforts to compete in the high-end laptop and tablet market with its powerful AI and graphics capabilities." } } ], "created": 1740106956, "model": "gpt-3.5-turbo", "system_fingerprint": "b4761-cad53fc9", "object": "chat.completion", "usage": { "completion_tokens": 973, "prompt_tokens": 941, "total_tokens": 1914 }, "id": "chatcmpl-pZDSfml476EGcCAvQ2vsI7D2JnVIlaHn", "timings": { "prompt_n": 941, "prompt_ms": 1031.432, "prompt_per_token_ms": 1.0961020191285866, "prompt_per_second": 912.3238371506799, "predicted_n": 973, "predicted_ms": 31796.986, "predicted_per_token_ms": 32.679327852004114, "predicted_per_second": 30.60038457733069 } } ``` </details>
Author
Owner

@vbarda commented on GitHub (Feb 20, 2025):

yup, your tool call ids are empty strings. we require tool call IDs to be non-empty strings (added a PR to raise a more explicit error). you would need to use a provider that ships non-empty tool call IDs (all major providers like Openai, Anthropic, Google, Ollama do)

Closing since this is not a langgraph issue

@vbarda commented on GitHub (Feb 20, 2025): yup, your tool call ids are empty strings. we require tool call IDs to be non-empty strings (added a PR to raise a more explicit error). you would need to use a provider that ships non-empty tool call IDs (all major providers like Openai, Anthropic, Google, Ollama do) Closing since this is not a langgraph issue
Author
Owner

@henryclw commented on GitHub (Feb 20, 2025):

Thank you for the quick and kind explanation. Didn't realize that tool call ids were required.

@henryclw commented on GitHub (Feb 20, 2025): Thank you for the quick and kind explanation. Didn't realize that tool call ids were required.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#466