[BUG] Setting a "tool_choice" with ChatOpenAI causes a Pydantic ValidationError from "tool_call_id" #530

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

Originally created by @T145 on GitHub (Mar 23, 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

import asyncio
from datetime import datetime
from typing import Literal

import aiofiles
import pytz
from huggingface_hub import hf_hub_download
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.errors import GraphRecursionError
from langgraph.graph import END, StateGraph
from langgraph.prebuilt import ToolNode
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.store.base import BaseStore
from langgraph.store.memory import InMemoryStore
from langgraph.types import Send

AI_NAME = "Eve"
SYSTEM_PROMPT = f"""I am {AI_NAME}, a helpful AI assistant."""
RECURSION_LIMIT = 10

def download_model():
   hf_hub_download(repo_id="mradermacher/ZEUS-8B-V22-i1-GGUF", filename="ZEUS-8B-V22.i1-Q4_K_M.gguf", local_dir="app/models")


@tool
async def date_and_time(query: str):
   """Returns today's date and the current time."""
   await asyncio.sleep(0)
   now = datetime.now(pytz.utc)
   tz = pytz.timezone("America/New_York")
   now_local = now.astimezone(tz)
   return [now_local.strftime("%Y-%m-%d %H:%M:%S %Z")]


async def main() -> None:
   tools = [date_and_time]
   tool_node = ToolNode(tools)
   llm = ChatOpenAI(
      model="ZEUS",
      openai_api_key="EMPTY",
      openai_api_base="http://localhost:3000/v1",
      temperature=0.7,
      top_p=0.9,
      extra_body={"top_k": 40, "min_p": 0.0, "repetition_penalty": 1.05, "num_predict": -1, "keep_alive": -1},
      streaming=True,
   )
   llm = llm.bind_tools(tools=tools, tool_choice="date_and_time", parallel_tool_calls=True)
   in_memory_store = InMemoryStore()

   NODE_AGENT = "agent"
   NODE_TOOLBELT = "toolbelt"
   EDGE_USE_TOOL = "use_tool"
   EDGE_END = "end"


   def should_use_tool(state: AgentState, config: RunnableConfig, *, store: BaseStore) -> Literal[EDGE_END, EDGE_USE_TOOL]:
      messages = state["messages"]
      last_message = messages[-1]

      if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
         return EDGE_END

      # v1 API would just return EDGE_USE_TOOL here

      tool_calls = [
         tool_node.inject_tool_args(call, state, store)
         for call in last_message.tool_calls
      ]
      return [Send(NODE_TOOLBELT, [tool_call]) for tool_call in tool_calls]


   async def call_model(state: AgentState, config: RunnableConfig, *, store: BaseStore) -> dict:
      messages = state["messages"]
      response = await llm.ainvoke(messages)
      return {"messages": [response]}


   workflow = StateGraph(AgentState)

   workflow.add_node(NODE_AGENT, call_model)
   workflow.add_node(NODE_TOOLBELT, tool_node)

   workflow.set_entry_point(NODE_AGENT)

   workflow.add_conditional_edges(
      NODE_AGENT,
      should_use_tool,
      {
         EDGE_USE_TOOL: NODE_TOOLBELT,
         EDGE_END: END,
      },
   )

   workflow.add_edge(NODE_TOOLBELT, NODE_AGENT)

   app = workflow.compile(store=in_memory_store)

   async with aiofiles.open("graph.png", "wb") as png:
      await png.write(app.get_graph().draw_mermaid_png())

   inputs = {"messages": [SystemMessage(content=SYSTEM_PROMPT), HumanMessage(content="What is today's date and time?")]}
   config = {"configurable": {"thread_id": "1", "recursion_limit": RECURSION_LIMIT}}

   try:
      async for event in app.astream_events(inputs, config, version="v2"):

         if event["event"] == "on_chain_end" and not event["parent_ids"]:
            # no parent ids means it's the final event
            message = event["data"]["output"]["messages"][-1]
            message.pretty_print()
   except GraphRecursionError:
      print({"input": inputs[1], "output": "Agent stopped due to max iterations."})


if __name__ == "__main__":
   download_model()
   asyncio.run(main())

Error Message and Stack Trace (if applicable)

~ python .\main.py
Traceback (most recent call last):
  File "[redacted]main.py", line 178, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "[redacted]\asyncio\runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "[redacted]\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "[redacted]\asyncio\base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "[redacted]main.py", line 166, in main
    async for event in app.astream_events(inputs, config, version="v2"):
    ...<4 lines>...
          message.pretty_print()
  File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1389, in astream_events
    async for event in event_stream:
        yield event
  File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 1013, in _astream_events_implementation_v2
    await task
  File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 968, in consume_astream
    async for _ in event_streamer.tap_output_aiter(run_id, stream):
        # All the content will be picked up
        pass
  File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 204, in tap_output_aiter
    async for chunk in output:
    ...<4 lines>...
        yield chunk
  File "[redacted]\site-packages\langgraph\pregel\__init__.py", line 2313, in astream
    async for _ in runner.atick(
    ...<7 lines>...
            yield o
  File "[redacted]\site-packages\langgraph\pregel\runner.py", line 444, in atick
    await arun_with_retry(
    ...<7 lines>...
    )
  File "[redacted]\site-packages\langgraph\pregel\retry.py", line 123, in arun_with_retry
    async for _ in task.proc.astream(task.input, config):
        pass
  File "[redacted]\site-packages\langgraph\utils\runnable.py", line 706, in astream
    async for chunk in aiterator:
    ...<9 lines>...
            output = chunk
  File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 181, in tap_output_aiter
    first = await py_anext(output, default=sentinel)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langchain_core\utils\aiter.py", line 74, in anext_impl
    return await __anext__(iterator)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1455, in atransform
    async for ichunk in input:
    ...<14 lines>...
                final = ichunk
  File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1455, in atransform
    async for ichunk in input:
    ...<14 lines>...
                final = ichunk
  File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1020, in astream
    yield await self.ainvoke(input, config, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langgraph\utils\runnable.py", line 371, in ainvoke
    ret = await asyncio.create_task(coro, context=context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 274, in _afunc
    outputs = await asyncio.gather(
              ^^^^^^^^^^^^^^^^^^^^^
        *(self._arun_one(call, input_type, config) for call in tool_calls)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 357, in _arun_one
    if invalid_tool_message := self._validate_tool_call(call):
                               ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 448, in _validate_tool_call
    return ToolMessage(
        content, name=requested_tool, tool_call_id=call["id"], status="error"
    )
  File "[redacted]\site-packages\langchain_core\messages\tool.py", line 140, in __init__
    super().__init__(content=content, **kwargs)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langchain_core\messages\base.py", line 77, in __init__
    super().__init__(content=content, **kwargs)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\langchain_core\load\serializable.py", line 125, in __init__
    super().__init__(*args, **kwargs)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "[redacted]\site-packages\pydantic\main.py", line 214, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for ToolMessage
tool_call_id
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.10/v/string_type
During task with name 'toolbelt' and id 'c58462b3-c6cc-4b20-e3e9-be3dead485f0'

Description

I'm running a vLLM instance that uses the downloaded model, and though untested I'd guess any OpenAI model selection could cause this error. If this is poorly configured, please let me know. However setting tool_choice="auto" runs without issue.

System Info

System Information

OS: Windows
OS Version: 10.0.19045
Python Version: 3.13.2 | packaged by conda-forge | (main, Feb 17 2025, 13:52:56) [MSC v.1942 64 bit (AMD64)]

Package Information

langchain_core: 0.3.45
langchain: 0.3.20
langsmith: 0.3.15
langchain_openai: 0.3.9
langchain_text_splitters: 0.3.6
langgraph_sdk: 0.1.57

Optional packages not installed

langserve

Other Dependencies

async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
httpx: 0.28.1
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.41: Installed. No version info available.
langchain-core<1.0.0,>=0.3.45: 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.
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.
openai-agents: Installed. No version info available.
openai<2.0.0,>=1.66.3: Installed. No version info available.
orjson: 3.10.15
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.10.6
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.
pytest: 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.
rich: Installed. No version info available.
SQLAlchemy<3,>=1.4: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken<1,>=0.7: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
zstandard: 0.23.0

Originally created by @T145 on GitHub (Mar 23, 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 import asyncio from datetime import datetime from typing import Literal import aiofiles import pytz from huggingface_hub import hf_hub_download from langchain_core.messages import AIMessage, HumanMessage, SystemMessage from langchain_core.runnables import RunnableConfig from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.errors import GraphRecursionError from langgraph.graph import END, StateGraph from langgraph.prebuilt import ToolNode from langgraph.prebuilt.chat_agent_executor import AgentState from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore from langgraph.types import Send AI_NAME = "Eve" SYSTEM_PROMPT = f"""I am {AI_NAME}, a helpful AI assistant.""" RECURSION_LIMIT = 10 def download_model(): hf_hub_download(repo_id="mradermacher/ZEUS-8B-V22-i1-GGUF", filename="ZEUS-8B-V22.i1-Q4_K_M.gguf", local_dir="app/models") @tool async def date_and_time(query: str): """Returns today's date and the current time.""" await asyncio.sleep(0) now = datetime.now(pytz.utc) tz = pytz.timezone("America/New_York") now_local = now.astimezone(tz) return [now_local.strftime("%Y-%m-%d %H:%M:%S %Z")] async def main() -> None: tools = [date_and_time] tool_node = ToolNode(tools) llm = ChatOpenAI( model="ZEUS", openai_api_key="EMPTY", openai_api_base="http://localhost:3000/v1", temperature=0.7, top_p=0.9, extra_body={"top_k": 40, "min_p": 0.0, "repetition_penalty": 1.05, "num_predict": -1, "keep_alive": -1}, streaming=True, ) llm = llm.bind_tools(tools=tools, tool_choice="date_and_time", parallel_tool_calls=True) in_memory_store = InMemoryStore() NODE_AGENT = "agent" NODE_TOOLBELT = "toolbelt" EDGE_USE_TOOL = "use_tool" EDGE_END = "end" def should_use_tool(state: AgentState, config: RunnableConfig, *, store: BaseStore) -> Literal[EDGE_END, EDGE_USE_TOOL]: messages = state["messages"] last_message = messages[-1] if not isinstance(last_message, AIMessage) or not last_message.tool_calls: return EDGE_END # v1 API would just return EDGE_USE_TOOL here tool_calls = [ tool_node.inject_tool_args(call, state, store) for call in last_message.tool_calls ] return [Send(NODE_TOOLBELT, [tool_call]) for tool_call in tool_calls] async def call_model(state: AgentState, config: RunnableConfig, *, store: BaseStore) -> dict: messages = state["messages"] response = await llm.ainvoke(messages) return {"messages": [response]} workflow = StateGraph(AgentState) workflow.add_node(NODE_AGENT, call_model) workflow.add_node(NODE_TOOLBELT, tool_node) workflow.set_entry_point(NODE_AGENT) workflow.add_conditional_edges( NODE_AGENT, should_use_tool, { EDGE_USE_TOOL: NODE_TOOLBELT, EDGE_END: END, }, ) workflow.add_edge(NODE_TOOLBELT, NODE_AGENT) app = workflow.compile(store=in_memory_store) async with aiofiles.open("graph.png", "wb") as png: await png.write(app.get_graph().draw_mermaid_png()) inputs = {"messages": [SystemMessage(content=SYSTEM_PROMPT), HumanMessage(content="What is today's date and time?")]} config = {"configurable": {"thread_id": "1", "recursion_limit": RECURSION_LIMIT}} try: async for event in app.astream_events(inputs, config, version="v2"): if event["event"] == "on_chain_end" and not event["parent_ids"]: # no parent ids means it's the final event message = event["data"]["output"]["messages"][-1] message.pretty_print() except GraphRecursionError: print({"input": inputs[1], "output": "Agent stopped due to max iterations."}) if __name__ == "__main__": download_model() asyncio.run(main()) ``` ### Error Message and Stack Trace (if applicable) ```shell ~ python .\main.py Traceback (most recent call last): File "[redacted]main.py", line 178, in <module> asyncio.run(main()) ~~~~~~~~~~~^^^^^^^^ File "[redacted]\asyncio\runners.py", line 195, in run return runner.run(main) ~~~~~~~~~~^^^^^^ File "[redacted]\asyncio\runners.py", line 118, in run return self._loop.run_until_complete(task) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "[redacted]\asyncio\base_events.py", line 725, in run_until_complete return future.result() ~~~~~~~~~~~~~^^ File "[redacted]main.py", line 166, in main async for event in app.astream_events(inputs, config, version="v2"): ...<4 lines>... message.pretty_print() File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1389, in astream_events async for event in event_stream: yield event File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 1013, in _astream_events_implementation_v2 await task File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 968, in consume_astream async for _ in event_streamer.tap_output_aiter(run_id, stream): # All the content will be picked up pass File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 204, in tap_output_aiter async for chunk in output: ...<4 lines>... yield chunk File "[redacted]\site-packages\langgraph\pregel\__init__.py", line 2313, in astream async for _ in runner.atick( ...<7 lines>... yield o File "[redacted]\site-packages\langgraph\pregel\runner.py", line 444, in atick await arun_with_retry( ...<7 lines>... ) File "[redacted]\site-packages\langgraph\pregel\retry.py", line 123, in arun_with_retry async for _ in task.proc.astream(task.input, config): pass File "[redacted]\site-packages\langgraph\utils\runnable.py", line 706, in astream async for chunk in aiterator: ...<9 lines>... output = chunk File "[redacted]\site-packages\langchain_core\tracers\event_stream.py", line 181, in tap_output_aiter first = await py_anext(output, default=sentinel) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langchain_core\utils\aiter.py", line 74, in anext_impl return await __anext__(iterator) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1455, in atransform async for ichunk in input: ...<14 lines>... final = ichunk File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1455, in atransform async for ichunk in input: ...<14 lines>... final = ichunk File "[redacted]\site-packages\langchain_core\runnables\base.py", line 1020, in astream yield await self.ainvoke(input, config, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langgraph\utils\runnable.py", line 371, in ainvoke ret = await asyncio.create_task(coro, context=context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 274, in _afunc outputs = await asyncio.gather( ^^^^^^^^^^^^^^^^^^^^^ *(self._arun_one(call, input_type, config) for call in tool_calls) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 357, in _arun_one if invalid_tool_message := self._validate_tool_call(call): ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "[redacted]\site-packages\langgraph\prebuilt\tool_node.py", line 448, in _validate_tool_call return ToolMessage( content, name=requested_tool, tool_call_id=call["id"], status="error" ) File "[redacted]\site-packages\langchain_core\messages\tool.py", line 140, in __init__ super().__init__(content=content, **kwargs) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langchain_core\messages\base.py", line 77, in __init__ super().__init__(content=content, **kwargs) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\langchain_core\load\serializable.py", line 125, in __init__ super().__init__(*args, **kwargs) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ File "[redacted]\site-packages\pydantic\main.py", line 214, in __init__ validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) pydantic_core._pydantic_core.ValidationError: 1 validation error for ToolMessage tool_call_id Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.10/v/string_type During task with name 'toolbelt' and id 'c58462b3-c6cc-4b20-e3e9-be3dead485f0' ``` ### Description I'm running a vLLM instance that uses the downloaded model, and though untested I'd guess any OpenAI model selection could cause this error. If this is poorly configured, please let me know. However setting `tool_choice="auto"` runs without issue. ### System Info System Information ------------------ > OS: Windows > OS Version: 10.0.19045 > Python Version: 3.13.2 | packaged by conda-forge | (main, Feb 17 2025, 13:52:56) [MSC v.1942 64 bit (AMD64)] Package Information ------------------- > langchain_core: 0.3.45 > langchain: 0.3.20 > langsmith: 0.3.15 > langchain_openai: 0.3.9 > langchain_text_splitters: 0.3.6 > langgraph_sdk: 0.1.57 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > httpx: 0.28.1 > 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.41: Installed. No version info available. > langchain-core<1.0.0,>=0.3.45: 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. > 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. > openai-agents: Installed. No version info available. > openai<2.0.0,>=1.66.3: Installed. No version info available. > orjson: 3.10.15 > packaging: 24.2 > packaging<25,>=23.2: Installed. No version info available. > pydantic: 2.10.6 > 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. > pytest: 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. > rich: Installed. No version info available. > SQLAlchemy<3,>=1.4: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > tiktoken<1,>=0.7: Installed. No version info available. > typing-extensions>=4.7: Installed. No version info available. > zstandard: 0.23.0
yindo closed this issue 2026-02-20 17:40:36 -05:00
Author
Owner

@hinthornw commented on GitHub (Mar 23, 2025):

It looks like your model isn't including an ID in its tool_calls (or rather, is setting to None)

@hinthornw commented on GitHub (Mar 23, 2025): It looks like your model isn't including an ID in its tool_calls (or rather, is setting to None)
Author
Owner

@T145 commented on GitHub (Mar 23, 2025):

I noticed the same thing and tried telling my model to pass an empty dictionary if there are no parameters in the chat template but it still crashed. If I add parameters and set the tool_choice manually, it still crashes. That's why there's the query: str param in the tool.

@T145 commented on GitHub (Mar 23, 2025): I noticed the same thing and tried telling my model to pass an empty dictionary if there are no parameters in the chat template but it still crashed. If I add parameters and set the tool_choice manually, it still crashes. That's why there's the `query: str` param in the tool.
Author
Owner

@vbarda commented on GitHub (Mar 24, 2025):

Closing, since this is not a langgraph issue. The tool call IDs must be returned by the LLM for the ToolNode to work correctly (and most LLM providers always output those)

@vbarda commented on GitHub (Mar 24, 2025): Closing, since this is not a langgraph issue. The tool call IDs must be returned by the LLM for the `ToolNode` to work correctly (and most LLM providers always output those)
Author
Owner

@vbarda commented on GitHub (Mar 24, 2025):

I would strongly discourage using tool-calling models that don't return tool call IDs, but if you must, you can implement your own tool-calling node that doesn't require that. See implementation here https://github.com/langchain-ai/langgraph/blob/main/libs/prebuilt/langgraph/prebuilt/tool_node.py#L131

@vbarda commented on GitHub (Mar 24, 2025): I would strongly discourage using tool-calling models that don't return tool call IDs, but if you must, you can implement your own tool-calling node that doesn't require that. See implementation here https://github.com/langchain-ai/langgraph/blob/main/libs/prebuilt/langgraph/prebuilt/tool_node.py#L131
Author
Owner

@hinthornw commented on GitHub (Mar 24, 2025):

If you wanted you can just assign an id to each tool call if none is provided (in the model node), since it looks like your server doesn't care much about it

@hinthornw commented on GitHub (Mar 24, 2025): If you wanted you can just assign an id to each tool call if none is provided (in the model node), since it looks like your server doesn't care much about it
Author
Owner

@T145 commented on GitHub (Mar 24, 2025):

Again setting tool_choice="auto" runs without issue: if tool_call_id were always None I'd expect that to crash, correct? This specific crash happens when:

  • date_and_time has no parameters and tool_choice="auto"
  • date_and_time has parameters and tool_choice="date_and_time"

Ultimately, I'd like to be able to specify a default tool.

@T145 commented on GitHub (Mar 24, 2025): Again setting `tool_choice="auto"` runs without issue: if `tool_call_id` were always `None` I'd expect that to crash, correct? This specific crash happens when: - `date_and_time` has no parameters and `tool_choice="auto"` - `date_and_time` has parameters and `tool_choice="date_and_time"` Ultimately, I'd like to be able to specify a default tool.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#530