Can not invoke/stream a compiled graph. #119

Closed
opened 2026-02-20 17:26:52 -05:00 by yindo · 3 comments
Owner

Originally created by @DiaQusNet on GitHub (Jun 19, 2024).

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangGraph/LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangGraph/LangChain rather than my code.
  • I am sure this is better as an issue rather than a GitHub discussion, since this is a LangGraph bug and not a design question.

Example Code

from typing_extensions import TypedDict
from langchain_community.chat_models import ChatOllama
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
from langgraph.graph import END, StateGraph

MODEL_ID = 'qwen:32b-chat-v1.5-q8_0'

llm = ChatOllama(model=MODEL_ID,
                 temperature=0.,
                 format='json')

chat = ChatOllama(model=MODEL_ID,
                 temperature=0.6)

prompt_next_router = PromptTemplate(
    template="""You are a route planner, extracting the route from the user's input.\n
    There is only two route 'A' and 'B', extract only the uppercase letters 'A' and 'B'.\n
    Return an JSON object that contains only one key-value pair with the key being 'next'.\n
    user input: {question}""",
    input_variables=["question"],
)
next_router = prompt_next_router | llm | JsonOutputParser()

prompt_A = PromptTemplate(
    template="""Tell the user that the feature for Route A is still under development.\n
    user input: {question}""",
    input_variables=["question"],
)
a_generator = prompt_A | chat | StrOutputParser()

prompt_B = PromptTemplate(
    template="""Tell the user that the feature for Route B is still under development.\n
    user input: {question}""",
    input_variables=["question"],
)
b_generator = prompt_B | chat | StrOutputParser()

# nodes
class GraphState(TypedDict):
    """
    Graph

    Attributes:
        question: input
        generation: response
    """
    question: str
    generation: str

def a_ex(state):
    """
    a node

    Args:
        state (dict): The current graph state

    Returns:
        state (dict): Updates generation key
    """
    question = state["question"]
    response = a_generator.invoke({"question": question})

    return {"generation": response}

def b_ex(state):
    """
    b node

    Args:
        state (dict): The current graph state

    Returns:
        state (dict): Updates generation key
    """
    question = state["question"]
    response = b_generator.invoke({"question": question})

    return {"generation": response}

# next entry edge
def route_question(state):
    """
    next node

    Args:
        state (dict): The current graph state

    Returns:
        str: Next node to call
    """
    print(state)
    question = state["question"]
    next = next_router.invoke({"question": question})
    if next["next"] == "A":
        return "A"
    elif next["next"] == "B":
        return "B"

workflow = StateGraph(GraphState)

# Define the nodes
workflow.add_node("a", a_ex)
workflow.add_node("b", b_ex)

# Build graph
workflow.set_conditional_entry_point(
    next_router,
    {
        "A": "a",
        "B": "b",
    },
)

workflow.add_edge("a", END)
workflow.add_edge("b", END)

# Compile
agent = workflow.compile()

agent.invoke({'question': 'I choose route A.'})

Error Message and Stack Trace (if applicable)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 agent.invoke({'question': 'I choose route A.'})
      2 #next_router.invoke({'question': 'I choose route A.'})

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:1384, in Pregel.invoke(self, input, config, stream_mode, output_keys, input_keys, interrupt_before, interrupt_after, debug, **kwargs)
   1382 else:
   1383     chunks = []
-> 1384 for chunk in self.stream(
   1385     input,
   1386     config,
   1387     stream_mode=stream_mode,
   1388     output_keys=output_keys,
   1389     input_keys=input_keys,
   1390     interrupt_before=interrupt_before,
   1391     interrupt_after=interrupt_after,
   1392     debug=debug,
   1393     **kwargs,
   1394 ):
   1395     if stream_mode == "values":
   1396         latest = chunk

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:949, in Pregel.stream(self, input, config, stream_mode, output_keys, input_keys, interrupt_before, interrupt_after, debug)
    946         del fut, task
    948 # panic on failure or timeout
--> 949 _panic_or_proceed(done, inflight, step)
    950 # don't keep futures around in memory longer than needed
    951 del done, inflight, futures

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:1473, in _panic_or_proceed(done, inflight, step)
   1471             inflight.pop().cancel()
   1472         # raise the exception
-> 1473         raise exc
   1475 if inflight:
   1476     # if we got here means we timed out
   1477     while inflight:
   1478         # cancel all pending tasks

File ~/anaconda3/envs/LLM/lib/python3.9/concurrent/futures/thread.py:58, in _WorkItem.run(self)
     55     return
     57 try:
---> 58     result = self.fn(*self.args, **self.kwargs)
     59 except BaseException as exc:
     60     self.future.set_exception(exc)

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/retry.py:66, in run_with_retry(task, retry_policy)
     64 task.writes.clear()
     65 # run the task
---> 66 task.proc.invoke(task.input, task.config)
     67 # if successful, end
     68 break

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langchain_core/runnables/base.py:2504, in RunnableSequence.invoke(self, input, config, **kwargs)
   2502             input = step.invoke(input, config, **kwargs)
   2503         else:
-> 2504             input = step.invoke(input, config)
   2505 # finish the root run
   2506 except BaseException as e:

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/utils.py:95, in RunnableCallable.invoke(self, input, config, **kwargs)
     93     if accepts_config(self.func):
     94         kwargs["config"] = config
---> 95     ret = context.run(self.func, input, **kwargs)
     96 if isinstance(ret, Runnable) and self.recurse:
     97     return ret.invoke(input, config)

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:81, in Branch._route(self, input, config, reader, writer)
     79     value = input
     80 result = self.path.invoke(value, config)
---> 81 return self._finish(writer, input, result)

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:108, in Branch._finish(self, writer, input, result)
    106     result = [result]
    107 if self.ends:
--> 108     destinations = [r if isinstance(r, Send) else self.ends[r] for r in result]
    109 else:
    110     destinations = result

File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:108, in <listcomp>(.0)
    106     result = [result]
    107 if self.ends:
--> 108     destinations = [r if isinstance(r, Send) else self.ends[r] for r in result]
    109 else:
    110     destinations = result

TypeError: unhashable type: 'dict'

Description

I create a simple Graph to test router with Ollama in local.
The graph is successfully compiled. Structure shows below.
001
I can't invoke the graph to get response for the error TypeError: unhashable type: 'dict'.
Please let me know how to solve this problem.

System Info

langchain==0.2.5
langchain-community==0.2.5
langchain-core==0.2.8
langchain-experimental==0.0.61
langchain-huggingface==0.0.1
langchain-openai==0.1.8
langchain-text-splitters==0.2.0
langchainhub==0.1.17
ubuntu 20.04
python==3.9

Originally created by @DiaQusNet on GitHub (Jun 19, 2024). ### Checked other resources - [X] I added a very descriptive title to this issue. - [X] I searched the [LangGraph](https://langchain-ai.github.io/langgraph/)/LangChain documentation with the integrated search. - [X] I used the GitHub search to find a similar question and didn't find it. - [X] I am sure that this is a bug in LangGraph/LangChain rather than my code. - [X] I am sure this is better as an issue [rather than a GitHub discussion](https://github.com/langchain-ai/langgraph/discussions/new/choose), since this is a LangGraph bug and not a design question. ### Example Code ```python from typing_extensions import TypedDict from langchain_community.chat_models import ChatOllama from langchain.prompts import PromptTemplate from langchain_core.output_parsers import JsonOutputParser, StrOutputParser from langgraph.graph import END, StateGraph MODEL_ID = 'qwen:32b-chat-v1.5-q8_0' llm = ChatOllama(model=MODEL_ID, temperature=0., format='json') chat = ChatOllama(model=MODEL_ID, temperature=0.6) prompt_next_router = PromptTemplate( template="""You are a route planner, extracting the route from the user's input.\n There is only two route 'A' and 'B', extract only the uppercase letters 'A' and 'B'.\n Return an JSON object that contains only one key-value pair with the key being 'next'.\n user input: {question}""", input_variables=["question"], ) next_router = prompt_next_router | llm | JsonOutputParser() prompt_A = PromptTemplate( template="""Tell the user that the feature for Route A is still under development.\n user input: {question}""", input_variables=["question"], ) a_generator = prompt_A | chat | StrOutputParser() prompt_B = PromptTemplate( template="""Tell the user that the feature for Route B is still under development.\n user input: {question}""", input_variables=["question"], ) b_generator = prompt_B | chat | StrOutputParser() # nodes class GraphState(TypedDict): """ Graph Attributes: question: input generation: response """ question: str generation: str def a_ex(state): """ a node Args: state (dict): The current graph state Returns: state (dict): Updates generation key """ question = state["question"] response = a_generator.invoke({"question": question}) return {"generation": response} def b_ex(state): """ b node Args: state (dict): The current graph state Returns: state (dict): Updates generation key """ question = state["question"] response = b_generator.invoke({"question": question}) return {"generation": response} # next entry edge def route_question(state): """ next node Args: state (dict): The current graph state Returns: str: Next node to call """ print(state) question = state["question"] next = next_router.invoke({"question": question}) if next["next"] == "A": return "A" elif next["next"] == "B": return "B" workflow = StateGraph(GraphState) # Define the nodes workflow.add_node("a", a_ex) workflow.add_node("b", b_ex) # Build graph workflow.set_conditional_entry_point( next_router, { "A": "a", "B": "b", }, ) workflow.add_edge("a", END) workflow.add_edge("b", END) # Compile agent = workflow.compile() agent.invoke({'question': 'I choose route A.'}) ``` ### Error Message and Stack Trace (if applicable) ```shell --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[13], line 1 ----> 1 agent.invoke({'question': 'I choose route A.'}) 2 #next_router.invoke({'question': 'I choose route A.'}) File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:1384, in Pregel.invoke(self, input, config, stream_mode, output_keys, input_keys, interrupt_before, interrupt_after, debug, **kwargs) 1382 else: 1383 chunks = [] -> 1384 for chunk in self.stream( 1385 input, 1386 config, 1387 stream_mode=stream_mode, 1388 output_keys=output_keys, 1389 input_keys=input_keys, 1390 interrupt_before=interrupt_before, 1391 interrupt_after=interrupt_after, 1392 debug=debug, 1393 **kwargs, 1394 ): 1395 if stream_mode == "values": 1396 latest = chunk File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:949, in Pregel.stream(self, input, config, stream_mode, output_keys, input_keys, interrupt_before, interrupt_after, debug) 946 del fut, task 948 # panic on failure or timeout --> 949 _panic_or_proceed(done, inflight, step) 950 # don't keep futures around in memory longer than needed 951 del done, inflight, futures File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/__init__.py:1473, in _panic_or_proceed(done, inflight, step) 1471 inflight.pop().cancel() 1472 # raise the exception -> 1473 raise exc 1475 if inflight: 1476 # if we got here means we timed out 1477 while inflight: 1478 # cancel all pending tasks File ~/anaconda3/envs/LLM/lib/python3.9/concurrent/futures/thread.py:58, in _WorkItem.run(self) 55 return 57 try: ---> 58 result = self.fn(*self.args, **self.kwargs) 59 except BaseException as exc: 60 self.future.set_exception(exc) File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/pregel/retry.py:66, in run_with_retry(task, retry_policy) 64 task.writes.clear() 65 # run the task ---> 66 task.proc.invoke(task.input, task.config) 67 # if successful, end 68 break File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langchain_core/runnables/base.py:2504, in RunnableSequence.invoke(self, input, config, **kwargs) 2502 input = step.invoke(input, config, **kwargs) 2503 else: -> 2504 input = step.invoke(input, config) 2505 # finish the root run 2506 except BaseException as e: File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/utils.py:95, in RunnableCallable.invoke(self, input, config, **kwargs) 93 if accepts_config(self.func): 94 kwargs["config"] = config ---> 95 ret = context.run(self.func, input, **kwargs) 96 if isinstance(ret, Runnable) and self.recurse: 97 return ret.invoke(input, config) File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:81, in Branch._route(self, input, config, reader, writer) 79 value = input 80 result = self.path.invoke(value, config) ---> 81 return self._finish(writer, input, result) File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:108, in Branch._finish(self, writer, input, result) 106 result = [result] 107 if self.ends: --> 108 destinations = [r if isinstance(r, Send) else self.ends[r] for r in result] 109 else: 110 destinations = result File ~/anaconda3/envs/LLM/lib/python3.9/site-packages/langgraph/graph/graph.py:108, in <listcomp>(.0) 106 result = [result] 107 if self.ends: --> 108 destinations = [r if isinstance(r, Send) else self.ends[r] for r in result] 109 else: 110 destinations = result TypeError: unhashable type: 'dict' ``` ### Description I create a simple Graph to test router with Ollama in local. The graph is successfully compiled. Structure shows below. ![001](https://github.com/langchain-ai/langgraph/assets/98730077/4938f7e2-fe6b-43b8-b655-72f3d69e0c83) I can't invoke the graph to get response for the error `TypeError: unhashable type: 'dict'`. Please let me know how to solve this problem. ### System Info langchain==0.2.5 langchain-community==0.2.5 langchain-core==0.2.8 langchain-experimental==0.0.61 langchain-huggingface==0.0.1 langchain-openai==0.1.8 langchain-text-splitters==0.2.0 langchainhub==0.1.17 ubuntu 20.04 python==3.9
yindo closed this issue 2026-02-20 17:26:52 -05:00
Author
Owner

@gbaian10 commented on GitHub (Jun 19, 2024):

This is the simplest modification.

from operator import itemgetter

next_router = prompt_next_router | llm | JsonOutputParser() | itemgetter("next")

Reason: The original output of your next_router is a dict. Your original output is {'next': 'A'}

But your set_conditional_entry_point needs a plain string "A" or "B".
(Determined by the key of your path_map And your map is {"A": "a", "B": "b"} . So you need a "A" or "B" as output )

So you have to change {'next': 'A'} to "A"

@gbaian10 commented on GitHub (Jun 19, 2024): This is the simplest modification. ```python3 from operator import itemgetter next_router = prompt_next_router | llm | JsonOutputParser() | itemgetter("next") ``` Reason: The original output of your `next_router` is a dict. Your original output is `{'next': 'A'}` But your `set_conditional_entry_point` needs a plain string "A" or "B". (Determined by the key of your `path_map` And your map is `{"A": "a", "B": "b"}` . So you need a "A" or "B" as output ) So you have to change {'next': 'A'} to "A"
Author
Owner

@DiaQusNet commented on GitHub (Jun 19, 2024):

Oh, I found that's my falut for workflow.set_conditional_entry_point, thank you anyway.

@DiaQusNet commented on GitHub (Jun 19, 2024): Oh, I found that's my falut for `workflow.set_conditional_entry_point`, thank you anyway.
Author
Owner

@gbaian10 commented on GitHub (Jun 19, 2024):

Sorry, I didn't notice that you have implemented a router function but never used it.

You only need to modify the input parameter of set_conditional_entry_point from next_router to route_question.

workflow.set_conditional_entry_point(
    route_question,
    {
        "A": "a",
        "B": "b",
    },
)

It looks like you just accidentally used the wrong router.
Of course, you can also delete this function and use itemgetter, which is another solution.

@gbaian10 commented on GitHub (Jun 19, 2024): Sorry, I didn't notice that you have implemented a router function but never used it. You only need to modify the input parameter of set_conditional_entry_point from `next_router` to `route_question`. ```python workflow.set_conditional_entry_point( route_question, { "A": "a", "B": "b", }, ) ``` It looks like you just accidentally used the wrong router. Of course, you can also delete this function and use `itemgetter`, which is another solution.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#119