tools_condition throws KeyError: 'tools' #664

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

Originally created by @khteh on GitHub (Jun 4, 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

@dataclass
class CustomAgentState(AgentState):
    context: List[Document]


  cache_policy = CachePolicy(ttl=600) # 10 minutes
  graph_builder = StateGraph(CustomAgentState)
  graph_builder.add_node("Agent", self.Agent, cache_policy = cache_policy)
  graph_builder.add_node("Retrieve", ToolNode([self._vectorStore.retriever_tool]), cache_policy = cache_policy) # Execute the retrieval.
  graph_builder.add_node("Rewrite", self.Rewrite, cache_policy = cache_policy)
  graph_builder.add_node("Generate", self.Generate, cache_policy = cache_policy)
  graph_builder.add_edge(START, "Agent")
  #graph_builder.set_entry_point("query_or_respond")
  graph_builder.add_conditional_edges(
        "Agent",
        # Assess agent decision
        tools_condition,
        {
            """
            Translate the condition outputs to nodes in our graph
            which node to go to based on the output of the conditional edge function - tools_condition.
            """
            "tools": "Retrieve",
            END: END
        },
    )
    # Edges taken after the `action` node is called.
    graph_builder.add_conditional_edges(
        "Retrieve",
        # Assess agent decision
        self.GradeDocuments,
    )
    graph_builder.add_edge("Generate", END)
    graph_builder.add_edge("Rewrite", "Agent")
    self._graph = graph_builder.compile(store=self._in_memory_store, name="Checkedpoint StateGraph RAG", cache=InMemoryCache())


    async def TestDirectResponseWithoutRetrieval(self, config, message: str):
        logging.info(f"\n=== {self.TestDirectResponseWithoutRetrieval.__name__} ===")
        async for step in self._graph.astream(
            {"messages": [{"role": "user", "content": message}]},
            stream_mode="values",
            config = config
        ):
            step["messages"][-1].pretty_print()


    config = RunnableConfig(run_name="Checkedpoint StateGraph RAG", thread_id=uuid7str(), user_id=uuid7str())
    graph = GraphRAG(config)
    await graph.TestDirectResponseWithoutRetrieval(config, "Hello, who are you?")

Error Message and Stack Trace (if applicable)

================================ Human Message =================================

Hello, who are you?
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 361, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/usr/lib/python3.13/asyncio/base_events.py", line 719, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 357, in main
    await graph.TestDirectResponseWithoutRetrieval(config, "Hello, who are you?")
  File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 308, in TestDirectResponseWithoutRetrieval
    async for step in self._graph.astream(
    ...<4 lines>...
        step["messages"][-1].pretty_print()
  File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/pregel/__init__.py", line 2655, in astream
    async for _ in runner.atick(
    ...<7 lines>...
            yield o
  File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/graph/branch.py", line 197, in _aroute
    return self._finish(writer, input, result, config)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/graph/branch.py", line 210, in _finish
    r if isinstance(r, Send) else self.ends[r] for r in result
                                  ~~~~~~~~~^^^
KeyError: 'tools'
During task with name 'Agent' and id '36bd87f2-8a9b-4d78-bce0-19d71fd331f1'

Description

https://github.com/langchain-ai/langgraph/issues/4891 was closed without merit. According to https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/#7-assemble-the-graph have I not used tools_condition in the right way? Why?

System Info

System Information
------------------
> OS:  Linux
> OS Version:  #15-Ubuntu SMP PREEMPT_DYNAMIC Sun Apr  6 15:05:05 UTC 2025
> Python Version:  3.13.3 (main, Apr  8 2025, 19:55:40) [GCC 14.2.0]

Package Information
-------------------
> langchain_core: 0.3.63
> langchain: 0.3.25
> langchain_community: 0.3.24
> langsmith: 0.3.44
> langchain_google_genai: 2.1.5
> langchain_neo4j: 0.4.0
> langchain_ollama: 0.3.3
> langchain_postgres: 0.0.13
> langchain_text_splitters: 0.3.8
> langgraph_sdk: 0.1.70

Optional packages not installed
-------------------------------
> langserve

Other Dependencies
------------------
> aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
> async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
> dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
> filetype: 1.2.0
> google-ai-generativelanguage: 0.6.18
> httpx: 0.28.1
> httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
> httpx>=0.25.2: 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-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.51: Installed. No version info available.
> langchain-core<1.0.0,>=0.3.58: Installed. No version info available.
> langchain-core<1.0.0,>=0.3.59: Installed. No version info available.
> langchain-core<1.0.0,>=0.3.60: 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.
> langchain<1.0.0,>=0.3.25: 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.126: Installed. No version info available.
> langsmith<0.4,>=0.1.17: Installed. No version info available.
> neo4j: 5.28.1
> neo4j-graphrag: 1.6.1
> numpy: 2.2.6
> numpy>=1.26.2;: Installed. No version info available.
> numpy>=2.1.0;: Installed. No version info available.
> ollama<1.0.0,>=0.4.8: Installed. No version info available.
> openai-agents: Installed. No version info available.
> opentelemetry-api: 1.33.1
> opentelemetry-exporter-otlp-proto-http: Installed. No version info available.
> opentelemetry-sdk: 1.33.1
> orjson: 3.10.18
> orjson>=3.10.1: Installed. No version info available.
> packaging: 24.2
> packaging<25,>=23.2: Installed. No version info available.
> pgvector: 0.3.6
> psycopg: 3.2.9
> psycopg-pool: 3.2.6
> pydantic: 2.11.5
> pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
> pydantic<3.0.0,>=2.7.4: Installed. No version info available.
> pydantic>=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: 2.0.41
> 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.
> typing-extensions>=4.7: Installed. No version info available.
> zstandard: 0.23.0
Originally created by @khteh on GitHub (Jun 4, 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 @dataclass class CustomAgentState(AgentState): context: List[Document] cache_policy = CachePolicy(ttl=600) # 10 minutes graph_builder = StateGraph(CustomAgentState) graph_builder.add_node("Agent", self.Agent, cache_policy = cache_policy) graph_builder.add_node("Retrieve", ToolNode([self._vectorStore.retriever_tool]), cache_policy = cache_policy) # Execute the retrieval. graph_builder.add_node("Rewrite", self.Rewrite, cache_policy = cache_policy) graph_builder.add_node("Generate", self.Generate, cache_policy = cache_policy) graph_builder.add_edge(START, "Agent") #graph_builder.set_entry_point("query_or_respond") graph_builder.add_conditional_edges( "Agent", # Assess agent decision tools_condition, { """ Translate the condition outputs to nodes in our graph which node to go to based on the output of the conditional edge function - tools_condition. """ "tools": "Retrieve", END: END }, ) # Edges taken after the `action` node is called. graph_builder.add_conditional_edges( "Retrieve", # Assess agent decision self.GradeDocuments, ) graph_builder.add_edge("Generate", END) graph_builder.add_edge("Rewrite", "Agent") self._graph = graph_builder.compile(store=self._in_memory_store, name="Checkedpoint StateGraph RAG", cache=InMemoryCache()) async def TestDirectResponseWithoutRetrieval(self, config, message: str): logging.info(f"\n=== {self.TestDirectResponseWithoutRetrieval.__name__} ===") async for step in self._graph.astream( {"messages": [{"role": "user", "content": message}]}, stream_mode="values", config = config ): step["messages"][-1].pretty_print() config = RunnableConfig(run_name="Checkedpoint StateGraph RAG", thread_id=uuid7str(), user_id=uuid7str()) graph = GraphRAG(config) await graph.TestDirectResponseWithoutRetrieval(config, "Hello, who are you?") ``` ### Error Message and Stack Trace (if applicable) ```shell ================================ Human Message ================================= Hello, who are you? Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 361, in <module> asyncio.run(main()) ~~~~~~~~~~~^^^^^^^^ File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run return runner.run(main) ~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run return self._loop.run_until_complete(task) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/asyncio/base_events.py", line 719, in run_until_complete return future.result() ~~~~~~~~~~~~~^^ File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 357, in main await graph.TestDirectResponseWithoutRetrieval(config, "Hello, who are you?") File "/usr/src/Python/rag-agent/src/rag_agent/GraphRAG.py", line 308, in TestDirectResponseWithoutRetrieval async for step in self._graph.astream( ...<4 lines>... step["messages"][-1].pretty_print() File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/pregel/__init__.py", line 2655, in astream async for _ in runner.atick( ...<7 lines>... yield o File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/graph/branch.py", line 197, in _aroute return self._finish(writer, input, result, config) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/khteh/.local/share/virtualenvs/rag-agent-YeW3dxEa/lib/python3.13/site-packages/langgraph/graph/branch.py", line 210, in _finish r if isinstance(r, Send) else self.ends[r] for r in result ~~~~~~~~~^^^ KeyError: 'tools' During task with name 'Agent' and id '36bd87f2-8a9b-4d78-bce0-19d71fd331f1' ``` ### Description https://github.com/langchain-ai/langgraph/issues/4891 was closed without merit. According to https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/#7-assemble-the-graph have I not used `tools_condition` in the right way? Why? ### System Info ``` System Information ------------------ > OS: Linux > OS Version: #15-Ubuntu SMP PREEMPT_DYNAMIC Sun Apr 6 15:05:05 UTC 2025 > Python Version: 3.13.3 (main, Apr 8 2025, 19:55:40) [GCC 14.2.0] Package Information ------------------- > langchain_core: 0.3.63 > langchain: 0.3.25 > langchain_community: 0.3.24 > langsmith: 0.3.44 > langchain_google_genai: 2.1.5 > langchain_neo4j: 0.4.0 > langchain_ollama: 0.3.3 > langchain_postgres: 0.0.13 > langchain_text_splitters: 0.3.8 > langgraph_sdk: 0.1.70 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp<4.0.0,>=3.8.3: Installed. No version info available. > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > dataclasses-json<0.7,>=0.5.7: Installed. No version info available. > filetype: 1.2.0 > google-ai-generativelanguage: 0.6.18 > httpx: 0.28.1 > httpx-sse<1.0.0,>=0.4.0: Installed. No version info available. > httpx>=0.25.2: 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-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.51: Installed. No version info available. > langchain-core<1.0.0,>=0.3.58: Installed. No version info available. > langchain-core<1.0.0,>=0.3.59: Installed. No version info available. > langchain-core<1.0.0,>=0.3.60: 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. > langchain<1.0.0,>=0.3.25: 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.126: Installed. No version info available. > langsmith<0.4,>=0.1.17: Installed. No version info available. > neo4j: 5.28.1 > neo4j-graphrag: 1.6.1 > numpy: 2.2.6 > numpy>=1.26.2;: Installed. No version info available. > numpy>=2.1.0;: Installed. No version info available. > ollama<1.0.0,>=0.4.8: Installed. No version info available. > openai-agents: Installed. No version info available. > opentelemetry-api: 1.33.1 > opentelemetry-exporter-otlp-proto-http: Installed. No version info available. > opentelemetry-sdk: 1.33.1 > orjson: 3.10.18 > orjson>=3.10.1: Installed. No version info available. > packaging: 24.2 > packaging<25,>=23.2: Installed. No version info available. > pgvector: 0.3.6 > psycopg: 3.2.9 > psycopg-pool: 3.2.6 > pydantic: 2.11.5 > pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available. > pydantic<3.0.0,>=2.7.4: Installed. No version info available. > pydantic>=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: 2.0.41 > 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. > typing-extensions>=4.7: Installed. No version info available. > zstandard: 0.23.0 ```
yindo closed this issue 2026-02-20 17:41:10 -05:00
Author
Owner

@hinthornw commented on GitHub (Jun 5, 2025):

@khteh , please look at your code. For this dict you have:

from langgraph.constants import END

{
    """
            Translate the condition outputs to nodes in our graph
            which node to go to based on the output of the conditional edge function - tools_condition.
            """
    "tools": "Retrieve",
    END: END,
}

It is interpreted as:

{'\n            Translate the condition outputs to nodes in our graph\n            which node to go to based on the output of the conditional edge function - tools_condition.\n            tools': 'Retrieve',
 '__end__': '__end__'}

Meaning you're not actually handling the "tools" output.

@hinthornw commented on GitHub (Jun 5, 2025): @khteh , please look at your code. For this dict you have: ```python from langgraph.constants import END { """ Translate the condition outputs to nodes in our graph which node to go to based on the output of the conditional edge function - tools_condition. """ "tools": "Retrieve", END: END, } ``` It is interpreted as: ```python {'\n Translate the condition outputs to nodes in our graph\n which node to go to based on the output of the conditional edge function - tools_condition.\n tools': 'Retrieve', '__end__': '__end__'} ``` Meaning you're not actually handling the "tools" output.
Author
Owner

@khteh commented on GitHub (Jun 5, 2025):

I struggle to understand how is this different from https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/#7-assemble-the-graph

workflow.add_node("retrieve", ToolNode([retriever_tool]))

# Decide whether to retrieve
workflow.add_conditional_edges(
    "generate_query_or_respond",
    # Assess LLM decision (call `retriever_tool` tool or respond to the user)
    tools_condition,
    {
        # Translate the condition outputs to nodes in our graph
        "tools": "retrieve",
        END: END,
    },
)
@khteh commented on GitHub (Jun 5, 2025): I struggle to understand how is this different from https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/#7-assemble-the-graph ``` workflow.add_node("retrieve", ToolNode([retriever_tool])) # Decide whether to retrieve workflow.add_conditional_edges( "generate_query_or_respond", # Assess LLM decision (call `retriever_tool` tool or respond to the user) tools_condition, { # Translate the condition outputs to nodes in our graph "tools": "retrieve", END: END, }, ) ```
Author
Owner

@hinthornw commented on GitHub (Jun 5, 2025):

They're completely differneet dictionaries. Yours doesn't handle "tools", because you mangled the key with the big string.

In the docs:
{
"tools": "retrieve",
END: END,
}

Handles both "tools" and END

{
"""
Translate the condition outputs to nodes in our graph
which node to go to based on the output of the conditional edge function - tools_condition.
"""
"tools": "Retrieve",
END: END,
}

handles

'\n Translate the condition outputs to nodes in our graph\n which node to go to based on the output of the conditional edge function - tools_condition.\n tools': 'Retrieve'
and
END

but not 'tools'.

Since tools_condition emits 'tools', your condition doesn't handle it

@hinthornw commented on GitHub (Jun 5, 2025): They're completely differneet dictionaries. Yours doesn't handle "tools", because you mangled the key with the big string. In the docs: { "tools": "retrieve", END: END, } Handles both "tools" and END { """ Translate the condition outputs to nodes in our graph which node to go to based on the output of the conditional edge function - tools_condition. """ "tools": "Retrieve", END: END, } handles '\n Translate the condition outputs to nodes in our graph\n which node to go to based on the output of the conditional edge function - tools_condition.\n tools': 'Retrieve' and END but not 'tools'. Since tools_condition emits 'tools', your condition doesn't handle it
Author
Owner

@khteh commented on GitHub (Jun 5, 2025):

That multiline string was meant to be comment for my understanding and code documentation. This was subtle!

@khteh commented on GitHub (Jun 5, 2025): That multiline string was meant to be comment for my understanding and code documentation. This was subtle!
Author
Owner

@hinthornw commented on GitHub (Jun 5, 2025):

I really appreciate your enthusiasm for learning! And I want to help you be successful in whatever apps you're building.

I don't intend to be curt, but I've noticed a pattern here. You've opened 17 issues on this repo already, and 20 or 30 across related libraries. Only a couple of these have turned out to be actual issues in the underlying libraries. As an OSS maintainer trying to handle dozens of issues daily, this creates a lot of noise and makes it harder for me to help users with legitimate problems.

This particular issue is a syntax error you could have caught by regular debugging and breaking down each section yourself - you accidentally turned your comment into a dictionary key. Please take more time to debug your own code before assuming there's a bug in the library.

Going forward, I may become less responsive to issues from your account unless you provide clear, ample evidence you've done proper debugging first.

Thank you for your help!

@hinthornw commented on GitHub (Jun 5, 2025): I really appreciate your enthusiasm for learning! And I want to help you be successful in whatever apps you're building. I don't intend to be curt, but I've noticed a pattern here. You've opened 17 issues on this repo already, and 20 or 30 across related libraries. Only a couple of these have turned out to be actual issues in the underlying libraries. As an OSS maintainer trying to handle dozens of issues daily, this creates a lot of noise and makes it harder for me to help users with legitimate problems. This particular issue is a syntax error you could have caught by regular debugging and breaking down each section yourself - you accidentally turned your comment into a dictionary key. Please take more time to debug your own code before assuming there's a bug in the library. Going forward, I may become less responsive to issues from your account unless you provide clear, ample evidence you've done proper debugging first. Thank you for your help!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#664