Tools returning a Command are missing from messages-streaming #597

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

Originally created by @injeniero on GitHub (Apr 22, 2025).

Originally assigned to: @vbarda on GitHub.

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 Annotated, Any

from langchain_core.messages import ToolMessage
from langchain_core.runnables.config import RunnableConfig
from langchain_core.tools import tool
from langchain_core.tools.base import InjectedToolCallId
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.types import Command

USER_INFO = [
    {"user_id": "1", "name": "Bob Dylan", "location": "New York, NY"},
    {"user_id": "2", "name": "Taylor Swift", "location": "Beverly Hills, CA"},
]

USER_ID_TO_USER_INFO = {info["user_id"]: info for info in USER_INFO}


class State(AgentState):
    # updated by the tool
    user_info: dict[str, Any]


def main() -> None:
    @tool
    def lookup_user_info(tool_call_id: Annotated[str, InjectedToolCallId], config: RunnableConfig):
        """Use this to look up user information to better assist them with their questions."""
        user_id = config.get("configurable", {}).get("user_id")
        if user_id is None:
            raise ValueError("Please provide user ID")

        if user_id not in USER_ID_TO_USER_INFO:
            raise ValueError(f"User '{user_id}' not found")

        user_info = USER_ID_TO_USER_INFO[user_id]
        return Command(
            update={
                # update the state keys
                "user_info": user_info,
                # update the message history
                "messages": [
                    ToolMessage(
                        "Successfully looked up user information", tool_call_id=tool_call_id
                    )
                ],
            }
        )

    def prompt(state: State):
        user_info = state.get("user_info")
        if user_info is None:
            return state["messages"]

        system_msg = (
            f"User name is {user_info['name']}. User lives in {user_info['location']}"
        )
        return [{"role": "system", "content": system_msg}] + state["messages"]

    model = ChatOpenAI(model="gpt-4o")

    agent = create_react_agent(
        model,
        # pass the tool that can update state
        [lookup_user_info],
        state_schema=State,
        # pass dynamic prompt function
        prompt=prompt,
    )

    agent_input = {"messages": [("user", "hi, where do I live?")]}
    agent_config = {"configurable": {"user_id": "1"}}

    invoke_result = agent.invoke(
        agent_input,
        agent_config,
    )

    # print(invoke_result)

    for chunk in agent.stream(agent_input, agent_config, stream_mode='messages'):
        print(chunk)


if __name__ == '__main__':
    main()

Error Message and Stack Trace (if applicable)

There are no streaming messages from the tool command response.

Description

The issue is ToolNode returns an array of Command and the fix from #4111 is not properly addressing that.

This code works, but it may require more polish:

def on_chain_end(
        self,
        response: Any,
        *,
        run_id: UUID,
        parent_run_id: Optional[UUID] = None,
        **kwargs: Any,
    ) -> Any:
        if meta := self.metadata.pop(run_id, None):
            if isinstance(response, Command):
                response = response.update

            if isinstance(response, Sequence) and any(
                isinstance(value, Command) for value in response
            ):
                response = [
                    value.update if isinstance(value, Command) else value
                    for value in response
                ]
            def _find_and_emit(value, the_type, try_dir=True):
                nonlocal recur_count
                recur_count += 1
                if recur_count > 100:
                    raise AssertionError("Something is wrong! current value: " + str(value))
                if isinstance(value, the_type):
                    self._emit(meta, value, dedupe=True)
                elif isinstance(value, Sequence) and not isinstance(value, str):
                    for item in value:
                        _find_and_emit(item, the_type)
                elif isinstance(value, dict):
                    for item in value.values():
                        _find_and_emit(item, the_type)
                elif try_dir and hasattr(value, "__dir__") and callable(value.__dir__):
                    for key in dir(value):
                        try:
                            item = getattr(value, key)
                            _find_and_emit(item, the_type, try_dir=False)
                        except AttributeError:
                            pass

            _find_and_emit(response, BaseMessage)

System Info

System Information

OS: Darwin
OS Version: Darwin Kernel Version 24.3.0: Thu Jan 2 20:24:16 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6000
Python Version: 3.11.11 (main, Mar 11 2025, 17:41:13) [Clang 20.1.0 ]

Package Information

langchain_core: 0.3.54
langchain: 0.3.23
langsmith: 0.3.32
langchain_anthropic: 0.3.12
langchain_groq: 0.3.2
langchain_text_splitters: 0.3.8
langgraph_sdk: 0.1.61

Optional packages not installed

langserve

Other Dependencies

anthropic<1,>=0.49.0: Installed. No version info available.
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
groq<1,>=0.4.1: 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-azure-ai;: 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.49: Installed. No version info available.
langchain-core<1.0.0,>=0.3.51: Installed. No version info available.
langchain-core<1.0.0,>=0.3.53: 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-perplexity;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.8: 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.
opentelemetry-api: 1.32.1
opentelemetry-exporter-otlp-proto-http: 1.32.1
opentelemetry-sdk: 1.32.1
orjson: 3.10.16
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.11.3
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: 14.0.0
SQLAlchemy<3,>=1.4: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
zstandard: 0.23.0

Originally created by @injeniero on GitHub (Apr 22, 2025). Originally assigned to: @vbarda on GitHub. ### 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 Annotated, Any from langchain_core.messages import ToolMessage from langchain_core.runnables.config import RunnableConfig from langchain_core.tools import tool from langchain_core.tools.base import InjectedToolCallId from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from langgraph.prebuilt.chat_agent_executor import AgentState from langgraph.types import Command USER_INFO = [ {"user_id": "1", "name": "Bob Dylan", "location": "New York, NY"}, {"user_id": "2", "name": "Taylor Swift", "location": "Beverly Hills, CA"}, ] USER_ID_TO_USER_INFO = {info["user_id"]: info for info in USER_INFO} class State(AgentState): # updated by the tool user_info: dict[str, Any] def main() -> None: @tool def lookup_user_info(tool_call_id: Annotated[str, InjectedToolCallId], config: RunnableConfig): """Use this to look up user information to better assist them with their questions.""" user_id = config.get("configurable", {}).get("user_id") if user_id is None: raise ValueError("Please provide user ID") if user_id not in USER_ID_TO_USER_INFO: raise ValueError(f"User '{user_id}' not found") user_info = USER_ID_TO_USER_INFO[user_id] return Command( update={ # update the state keys "user_info": user_info, # update the message history "messages": [ ToolMessage( "Successfully looked up user information", tool_call_id=tool_call_id ) ], } ) def prompt(state: State): user_info = state.get("user_info") if user_info is None: return state["messages"] system_msg = ( f"User name is {user_info['name']}. User lives in {user_info['location']}" ) return [{"role": "system", "content": system_msg}] + state["messages"] model = ChatOpenAI(model="gpt-4o") agent = create_react_agent( model, # pass the tool that can update state [lookup_user_info], state_schema=State, # pass dynamic prompt function prompt=prompt, ) agent_input = {"messages": [("user", "hi, where do I live?")]} agent_config = {"configurable": {"user_id": "1"}} invoke_result = agent.invoke( agent_input, agent_config, ) # print(invoke_result) for chunk in agent.stream(agent_input, agent_config, stream_mode='messages'): print(chunk) if __name__ == '__main__': main() ``` ### Error Message and Stack Trace (if applicable) ```shell There are no streaming messages from the tool command response. ``` ### Description The issue is ToolNode returns an array of Command and the fix from #4111 is not properly addressing that. This code works, but it may require more polish: ```python def on_chain_end( self, response: Any, *, run_id: UUID, parent_run_id: Optional[UUID] = None, **kwargs: Any, ) -> Any: if meta := self.metadata.pop(run_id, None): if isinstance(response, Command): response = response.update if isinstance(response, Sequence) and any( isinstance(value, Command) for value in response ): response = [ value.update if isinstance(value, Command) else value for value in response ] def _find_and_emit(value, the_type, try_dir=True): nonlocal recur_count recur_count += 1 if recur_count > 100: raise AssertionError("Something is wrong! current value: " + str(value)) if isinstance(value, the_type): self._emit(meta, value, dedupe=True) elif isinstance(value, Sequence) and not isinstance(value, str): for item in value: _find_and_emit(item, the_type) elif isinstance(value, dict): for item in value.values(): _find_and_emit(item, the_type) elif try_dir and hasattr(value, "__dir__") and callable(value.__dir__): for key in dir(value): try: item = getattr(value, key) _find_and_emit(item, the_type, try_dir=False) except AttributeError: pass _find_and_emit(response, BaseMessage) ``` ### System Info System Information ------------------ > OS: Darwin > OS Version: Darwin Kernel Version 24.3.0: Thu Jan 2 20:24:16 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6000 > Python Version: 3.11.11 (main, Mar 11 2025, 17:41:13) [Clang 20.1.0 ] Package Information ------------------- > langchain_core: 0.3.54 > langchain: 0.3.23 > langsmith: 0.3.32 > langchain_anthropic: 0.3.12 > langchain_groq: 0.3.2 > langchain_text_splitters: 0.3.8 > langgraph_sdk: 0.1.61 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > anthropic<1,>=0.49.0: Installed. No version info available. > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > groq<1,>=0.4.1: 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-azure-ai;: 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.49: Installed. No version info available. > langchain-core<1.0.0,>=0.3.51: Installed. No version info available. > langchain-core<1.0.0,>=0.3.53: 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-perplexity;: Installed. No version info available. > langchain-text-splitters<1.0.0,>=0.3.8: 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. > opentelemetry-api: 1.32.1 > opentelemetry-exporter-otlp-proto-http: 1.32.1 > opentelemetry-sdk: 1.32.1 > orjson: 3.10.16 > packaging: 24.2 > packaging<25,>=23.2: Installed. No version info available. > pydantic: 2.11.3 > 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: 14.0.0 > SQLAlchemy<3,>=1.4: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > typing-extensions>=4.7: Installed. No version info available. > zstandard: 0.23.0
yindo added the bug label 2026-02-20 17:40:53 -05:00
yindo closed this issue 2026-02-20 17:40:53 -05:00
Author
Owner

@injeniero commented on GitHub (Apr 22, 2025):

@vbarda as you worked on #4111

@injeniero commented on GitHub (Apr 22, 2025): @vbarda as you worked on #4111
Author
Owner

@vbarda commented on GitHub (Apr 22, 2025):

@injeniero good catch - i initially had a recursive solution as well, but dropped this when simplifying. thanks for reporting - will fix

@vbarda commented on GitHub (Apr 22, 2025): @injeniero good catch - i initially had a recursive solution as well, but dropped this when simplifying. thanks for reporting - will fix
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#597