I'm defining a LangGraph graph. I wanted a tool to change the conversation state, not only to return a ToolMessage.
I followed this example and I did:
class EndConversationSchema(BaseModel):
"""Call this tool when the user says goodbye"""
reason: str= Field(description="Why does the user want to end the conversation?")
@tool("end_conversation", args_schema=EndConversationSchema)
def end_conversation(
reason: str,
tool_call_id: Annotated[str, InjectedToolCallId]
) -> Command:
""""""
return Command(update={
"active_chat": False,
"messages": [ToolMessage(f"Conversation finished. Reason: {reason}", tool_call_id=tool_call_id)]
})
Now the problem is that when I use this with langgraph.prebuilt.ToolNode I get: Error: TypeError("``end_conversation()`` missing 1 required positional argument: 'tool_call_id'") Please fix your mistakes.
Any idea on how I am supposed to implement the tool + Command pattern in Langgraph/Langchain?
Well, the issue arises because you are missing one parameter in your custom input model EndConversationSchema.
Let me explain in detail.
When you pass a custom args_schema (EndConversationSchema), LangChain validates inputs only against that Pydantic model and then calls the tool with those validated fields. Because your schema didn’t declare an injected field, ToolNode never supplied tool_call_id, so Python raised:
TypeError: end_conversation() missing 1 required positional argument: 'tool_call_id'
To fix this issue, add this annotated field to your custom args:
tool_call_id: Annotated[str, InjectedToolCallId] = Field(default=None)
from typing import Annotated
from pydantic import BaseModel, Field
from langchain_core.tools import tool, InjectedToolCallId
from langchain_core.messages import ToolMessage
from langgraph.types import Command
class EndConversationSchema(BaseModel):
"""Call this tool when the user says goodbye"""
reason: str = Field(description="Why does the user want to end the conversation?")
tool_call_id: Annotated[str, InjectedToolCallId] = Field(default=None)
# The end conversation function
@tool("end_conversation", args_schema=EndConversationSchema)
def end_conversation(
reason: str,
tool_call_id: Annotated[str, InjectedToolCallId]
) -> Command:
""""""
return Command(update={
"active_chat": False,
"messages": [ToolMessage(f"Conversation finished. Reason: {reason}", tool_call_id=tool_call_id)]
})
With the above change, the validator would now see tool_call_id as an injected field and it is put into the validated dict before calling the function, so tool_call_id is available.
Hope this helps.