Bug Report: TToolMessage tool_call_id ValidationError When Using create_react_agent #627

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

Originally created by @xlcaptain on GitHub (May 15, 2025).

Example Code

param_parser_model = tool_model.bind_tools([db_param_parser], tool_choice="db_param_parser")

param_parser_agent = create_react_agent(name=AGENT_NAMES['param_parser'], model=param_parser_model, tools=[db_param_parser], prompt=TIME_PROMPT)

Error Message and Stack Trace (if applicable)

langgraph-api-1  | Traceback (most recent call last):
langgraph-api-1  |   File "/api/langgraph_api/worker.py", line 144, in worker
langgraph-api-1  |   File "/usr/local/lib/python3.11/asyncio/tasks.py", line 489, in wait_for
langgraph-api-1  |     return fut.result()
langgraph-api-1  |            ^^^^^^^^^^^^
langgraph-api-1  |   File "/api/langgraph_api/stream.py", line 269, in consume
langgraph-api-1  |   File "/api/langgraph_api/stream.py", line 258, in consume
langgraph-api-1  |   File "/api/langgraph_api/stream.py", line 209, in astream_state
langgraph-api-1  |   File "/api/langgraph_api/asyncio.py", line 82, in wait_if_not_done
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2676, in astream
langgraph-api-1  |     async for _ in runner.atick(
langgraph-api-1  |   File "/deps/__outer_agent/agent/router/complaint.py", line 39, in param_parser_node
langgraph-api-1  |     result = await param_parser_agent.ainvoke(asdict(state))
langgraph-api-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2794, in ainvoke
langgraph-api-1  |     async for chunk in self.astream(
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2676, in astream
langgraph-api-1  |     async for _ in runner.atick(
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 257, in _afunc
langgraph-api-1  |     outputs = await asyncio.gather(
langgraph-api-1  |               ^^^^^^^^^^^^^^^^^^^^^
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 368, in _arun_one
langgraph-api-1  |     if invalid_tool_message := self._validate_tool_call(call):
langgraph-api-1  |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 459, in _validate_tool_call
langgraph-api-1  |     return ToolMessage(
langgraph-api-1  |            ^^^^^^^^^^^^
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langchain_core/messages/tool.py", line 146, in __init__
langgraph-api-1  |     super().__init__(content=content, **kwargs)
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langchain_core/messages/base.py", line 78, in __init__
langgraph-api-1  |     super().__init__(content=content, **kwargs)
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/langchain_core/load/serializable.py", line 130, in __init__
langgraph-api-1  |     super().__init__(*args, **kwargs)
langgraph-api-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/main.py", line 253, in __init__
langgraph-api-1  |     validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
langgraph-api-1  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
langgraph-api-1  | pydantic_core._pydantic_core.ValidationError: 1 validation error for ToolMessage
langgraph-api-1  | tool_call_id
langgraph-api-1  |   Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
langgraph-api-1  |     For further information visit https://errors.pydantic.dev/2.11/v/string_type
langgraph-api-1  | During task with name 'tools' and id 'da12b780-3751-f7c5-307f-e6bdb17d5a5a'
langgraph-api-1  | During task with name 'param_parser' and id '9332ce36-a5f5-b9ad-89d2-8a290b94f752'
langgraph-api-1  | 2025-05-16T03:04:39.204047Z [info     ] POST /threads/4ae8f154-fcdd-4859-82a3-7a73487bec2f/runs/stream 200 9514ms [langgraph_api.server] api_revision=4295c63 api_variant=licensed latency_ms=9514 method=POST path=/threads/4ae8f154-fcdd-4859-82a3-7a73487bec2f/runs/stream path_params={'thread_id': '4ae8f154-fcdd-4859-82a3-7a73487bec2f'} proto=1.1 query_string= req_header={'host': '192.168.0.

Description

When using create_react_agent with a model bound to a single tool and tool_choice set to the tool name, I encounter a ValidationError related to ToolMessage's tool_call_id being None.

Error Traceback:

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 examples:

ToolMessage(
    content='',
    additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': str(args), 'name': 'search'},'type': 'function'}]},
    response_metadata={},
    id=f'run--{uuid.uuid4()}',
    tool_calls=[{'name': 'search', 'args': args, 'id': None, 'type': 'tool_call'}],
    usage_metadata={'input_tokens': 757, 'output_tokens': 32, 'total_tokens': 789, 'input_token_details': {}, 'output_token_details': {}}
)
# ↑ Here, 'id': None will cause the validation error.

Expected behavior

The agent should execute the tool and return the result without raising a validation error. The tool_call_id should always be a valid string.

Actual behavior

The agent fails with a ValidationError because tool_call_id is None.

The error occurs when the agent tries to construct a ToolMessage and the tool_call_id is not set.
I have confirmed that the tool call dict sometimes does not have an id field, or it is None.

Additional Notes

This issue only occurs when using locally deployed Qwen models via vLLM.
When using the official DeepSeek API, the problem does not occur—the tool call IDs are always present and valid.
This suggests the issue may be related to the vLLM Qwen model's tool calling implementation or its integration with LangChain/LangGraph.

vllm serve Qwen/Qwen3-14B --enable-auto-tool-choice --tool-call-parser hermes

System Info

I am using langgraph and langchain with the following versions:

  1. langgraph: 0.4.3

  2. langchain: 0.3.19

  3. Python: 3.11

  4. vllm: 0.8.5

I hope someone can explain the reason for this issue. I know that this id is required, but is it because some parameter is missing when starting vLLM, or is it caused by some other problem?

Originally created by @xlcaptain on GitHub (May 15, 2025). ### Example Code ```python param_parser_model = tool_model.bind_tools([db_param_parser], tool_choice="db_param_parser") param_parser_agent = create_react_agent(name=AGENT_NAMES['param_parser'], model=param_parser_model, tools=[db_param_parser], prompt=TIME_PROMPT) ``` ### Error Message and Stack Trace (if applicable) ```shell langgraph-api-1 | Traceback (most recent call last): langgraph-api-1 | File "/api/langgraph_api/worker.py", line 144, in worker langgraph-api-1 | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 489, in wait_for langgraph-api-1 | return fut.result() langgraph-api-1 | ^^^^^^^^^^^^ langgraph-api-1 | File "/api/langgraph_api/stream.py", line 269, in consume langgraph-api-1 | File "/api/langgraph_api/stream.py", line 258, in consume langgraph-api-1 | File "/api/langgraph_api/stream.py", line 209, in astream_state langgraph-api-1 | File "/api/langgraph_api/asyncio.py", line 82, in wait_if_not_done langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2676, in astream langgraph-api-1 | async for _ in runner.atick( langgraph-api-1 | File "/deps/__outer_agent/agent/router/complaint.py", line 39, in param_parser_node langgraph-api-1 | result = await param_parser_agent.ainvoke(asdict(state)) langgraph-api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2794, in ainvoke langgraph-api-1 | async for chunk in self.astream( langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/pregel/__init__.py", line 2676, in astream langgraph-api-1 | async for _ in runner.atick( langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 257, in _afunc langgraph-api-1 | outputs = await asyncio.gather( langgraph-api-1 | ^^^^^^^^^^^^^^^^^^^^^ langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 368, in _arun_one langgraph-api-1 | if invalid_tool_message := self._validate_tool_call(call): langgraph-api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langgraph/prebuilt/tool_node.py", line 459, in _validate_tool_call langgraph-api-1 | return ToolMessage( langgraph-api-1 | ^^^^^^^^^^^^ langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langchain_core/messages/tool.py", line 146, in __init__ langgraph-api-1 | super().__init__(content=content, **kwargs) langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langchain_core/messages/base.py", line 78, in __init__ langgraph-api-1 | super().__init__(content=content, **kwargs) langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/langchain_core/load/serializable.py", line 130, in __init__ langgraph-api-1 | super().__init__(*args, **kwargs) langgraph-api-1 | File "/usr/local/lib/python3.11/site-packages/pydantic/main.py", line 253, in __init__ langgraph-api-1 | validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) langgraph-api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ langgraph-api-1 | pydantic_core._pydantic_core.ValidationError: 1 validation error for ToolMessage langgraph-api-1 | tool_call_id langgraph-api-1 | Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] langgraph-api-1 | For further information visit https://errors.pydantic.dev/2.11/v/string_type langgraph-api-1 | During task with name 'tools' and id 'da12b780-3751-f7c5-307f-e6bdb17d5a5a' langgraph-api-1 | During task with name 'param_parser' and id '9332ce36-a5f5-b9ad-89d2-8a290b94f752' langgraph-api-1 | 2025-05-16T03:04:39.204047Z [info ] POST /threads/4ae8f154-fcdd-4859-82a3-7a73487bec2f/runs/stream 200 9514ms [langgraph_api.server] api_revision=4295c63 api_variant=licensed latency_ms=9514 method=POST path=/threads/4ae8f154-fcdd-4859-82a3-7a73487bec2f/runs/stream path_params={'thread_id': '4ae8f154-fcdd-4859-82a3-7a73487bec2f'} proto=1.1 query_string= req_header={'host': '192.168.0. ``` ### Description When using create_react_agent with a model bound to a single tool and **tool_choice** set to the tool name, I encounter a ValidationError related to ToolMessage's tool_call_id being None. ### Error Traceback: ``` 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 examples: ``` ToolMessage( content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': str(args), 'name': 'search'},'type': 'function'}]}, response_metadata={}, id=f'run--{uuid.uuid4()}', tool_calls=[{'name': 'search', 'args': args, 'id': None, 'type': 'tool_call'}], usage_metadata={'input_tokens': 757, 'output_tokens': 32, 'total_tokens': 789, 'input_token_details': {}, 'output_token_details': {}} ) # ↑ Here, 'id': None will cause the validation error. ``` ### Expected behavior The agent should execute the tool and return the result without raising a validation error. The tool_call_id should always be a valid string. ### Actual behavior The agent fails with a ValidationError because **tool_call_id is None**. The error occurs when the agent tries to construct a ToolMessage and the tool_call_id is not set. I have confirmed that the tool call dict sometimes does not have an id field, or it is None. ### Additional Notes This issue only occurs when using locally deployed **Qwen models via vLLM**. When using the official DeepSeek API, the problem does not occur—the tool call IDs are always present and valid. This suggests the issue may be related to the vLLM Qwen model's tool calling implementation or its integration with LangChain/LangGraph. ``` vllm serve Qwen/Qwen3-14B --enable-auto-tool-choice --tool-call-parser hermes ``` ### System Info I am using langgraph and langchain with the following versions: 1. langgraph: 0.4.3 2. langchain: 0.3.19 3. Python: 3.11 4. vllm: 0.8.5 I hope someone can explain the reason for this issue. I know that this id is required, but is it because some parameter is missing when starting vLLM, or is it caused by some other problem?
yindo closed this issue 2026-02-20 17:41:01 -05:00
Author
Owner

@xlcaptain commented on GitHub (May 15, 2025):

@mihaic

@xlcaptain commented on GitHub (May 15, 2025): @mihaic
Author
Owner

@vbarda commented on GitHub (May 16, 2025):

yes, tool calls are required to have an ID for the prebuilt tool node to work correctly, this is an issue w/ the underlying LLM provider you are using

@vbarda commented on GitHub (May 16, 2025): yes, tool calls are required to have an ID for the prebuilt tool node to work correctly, this is an issue w/ the underlying LLM provider you are using
Author
Owner

@xlcaptain commented on GitHub (May 16, 2025):

Question about bind_tools() behavior:
When using bind_tools() without explicitly setting the tool_choice parameter, the system correctly returns the tool_call_id. However, when we add the tool_choice parameter (even with valid configurations), the tool_call_id stops being returned in the response.

  1. What causes this discrepancy in behavior when the tool_choice parameter is introduced?
  2. Are there recommended patterns to ensure both explicit tool selection through tool_choice and consistent tool_call_id returns?

Any insights about this apparent conflict between explicit tool selection and ID generation would be greatly appreciated.

@xlcaptain commented on GitHub (May 16, 2025): **Question about bind_tools() behavior:** When using `bind_tools()` *without* explicitly setting the `tool_choice` parameter, the system correctly returns the `tool_call_id`. However, when we *add* the `tool_choice` parameter (even with valid configurations), the `tool_call_id` stops being returned in the response. 1. What causes this discrepancy in behavior when the `tool_choice` parameter is introduced? 2. Are there recommended patterns to ensure both explicit tool selection through `tool_choice` and consistent `tool_call_id` returns? Any insights about this apparent conflict between explicit tool selection and ID generation would be greatly appreciated.
Author
Owner

@jimmyhuangjun commented on GitHub (Jun 2, 2025):

l hava the same question !! do you have a answer right now ??

@jimmyhuangjun commented on GitHub (Jun 2, 2025): l hava the same question !! do you have a answer right now ??
Author
Owner

@leecj commented on GitHub (Sep 15, 2025):

Encountered the same problem when deploying qwen3-30b-a3b with vllm, there is no problem using the official qwen interface.

使用vllm部署qwen3-30b-a3b遇到同样的问题,使用qwen官方接口没有问题

@leecj commented on GitHub (Sep 15, 2025): Encountered the same problem when deploying qwen3-30b-a3b with vllm, there is no problem using the official qwen interface. 使用vllm部署qwen3-30b-a3b遇到同样的问题,使用qwen官方接口没有问题
Author
Owner

@zhefox commented on GitHub (Sep 24, 2025):

这个问题解决了吗?

@zhefox commented on GitHub (Sep 24, 2025): 这个问题解决了吗?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#627