The first node doesn't accept inputs other than InputState, but other nodes can #223

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

Originally created by @gbaian10 on GitHub (Sep 3, 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 import TypedDict

from langgraph.graph import END, START, MessagesState, StateGraph


class OverallState(MessagesState):
    pass


class InputState(TypedDict):
    question: str


class OutputState(TypedDict):
    answer: str


class PrivateState(TypedDict):
    secret: str


def first(state):
    # Without type hint, the node following `__start__` only accepts
    # `InputState` as the initial input.
    print("=====This is First=====")
    # At this point, the filter will be handled by `OverallState`, not `InputState`.
    print(state)  # {"messages": []}
    return {"answer": "Foo"}


def middle(state):
    # Without a type hint, non the node following `__start__` can accept at least one of
    # `OverallState`, `InputState`, or `OutputState` as input.
    print("=====This is Middle=====")
    print(state)
    # return {"secret": "Bar"}  # ok
    return {"question": "Bar"}


def last(state: PrivateState):
    # With a type hint, it will additionally accept the extra key as a private state input,
    # even if the previous node may not include this input.
    # It will still act as a filter in this function, filtering out inputs that do not match this hint.
    print("=====This is Last=====")
    print(state)
    # Output = {}; Because the output of the previous node doesn't include PrivateState.
    return {"messages": [("human", "World!")]}


graph_builder = StateGraph(OverallState, input=InputState, output=OutputState)

graph_builder.add_node(first)
graph_builder.add_node(middle)
graph_builder.add_node(last)

graph_builder.add_edge(START, first.__name__)
graph_builder.add_edge(first.__name__, middle.__name__)
graph_builder.add_edge(middle.__name__, last.__name__)
graph_builder.add_edge(last.__name__, END)
app = graph_builder.compile()
# print("output: ", app.invoke({"messages": [("human", "Hello")]})) # it will raise an error
print("output: ", app.invoke({"question": "What is your name?"}))

Error Message and Stack Trace (if applicable)

Only the first node doesn't accept three states; the other nodes can accept three states and extended states.

Is this a feature or a bug?
Because it is very inconsistent with the state output in the same function (using `OverallState` as a filter).

Description

Here is my understanding of the states: all nodes can accept OverallState, InputState, and OutputState as inputs, even if you don't include type hints.

If your type hint is not one of the above three, it will be considered an additional acceptable input. However, the previous node output must still include at least one key from these four states.

If you don't include type hints and you input the three states (StateGraph(OverallState, input=InputState, output=OutputState)), the filter will select OverallState.

If you don't include type hints and you input two states (StateGraph(input=InputState, output=OutputState)), the filter will select InputState.

System Info

langgraph==0.2.16
langchain-core==0.2.37

platform==linux
python-version==3.12.5

Originally created by @gbaian10 on GitHub (Sep 3, 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 import TypedDict from langgraph.graph import END, START, MessagesState, StateGraph class OverallState(MessagesState): pass class InputState(TypedDict): question: str class OutputState(TypedDict): answer: str class PrivateState(TypedDict): secret: str def first(state): # Without type hint, the node following `__start__` only accepts # `InputState` as the initial input. print("=====This is First=====") # At this point, the filter will be handled by `OverallState`, not `InputState`. print(state) # {"messages": []} return {"answer": "Foo"} def middle(state): # Without a type hint, non the node following `__start__` can accept at least one of # `OverallState`, `InputState`, or `OutputState` as input. print("=====This is Middle=====") print(state) # return {"secret": "Bar"} # ok return {"question": "Bar"} def last(state: PrivateState): # With a type hint, it will additionally accept the extra key as a private state input, # even if the previous node may not include this input. # It will still act as a filter in this function, filtering out inputs that do not match this hint. print("=====This is Last=====") print(state) # Output = {}; Because the output of the previous node doesn't include PrivateState. return {"messages": [("human", "World!")]} graph_builder = StateGraph(OverallState, input=InputState, output=OutputState) graph_builder.add_node(first) graph_builder.add_node(middle) graph_builder.add_node(last) graph_builder.add_edge(START, first.__name__) graph_builder.add_edge(first.__name__, middle.__name__) graph_builder.add_edge(middle.__name__, last.__name__) graph_builder.add_edge(last.__name__, END) app = graph_builder.compile() # print("output: ", app.invoke({"messages": [("human", "Hello")]})) # it will raise an error print("output: ", app.invoke({"question": "What is your name?"})) ``` ### Error Message and Stack Trace (if applicable) ```plain Only the first node doesn't accept three states; the other nodes can accept three states and extended states. Is this a feature or a bug? Because it is very inconsistent with the state output in the same function (using `OverallState` as a filter). ``` ### Description Here is my understanding of the states: all nodes can accept `OverallState`, `InputState`, and `OutputState` as inputs, even if you don't include type hints. If your type hint is not one of the above three, it will be considered an additional acceptable input. However, the previous node output must still include at least one key from these four states. If you don't include type hints and you input the three states (`StateGraph(OverallState, input=InputState, output=OutputState)`), the filter will select `OverallState`. If you don't include type hints and you input two states (`StateGraph(input=InputState, output=OutputState)`), the filter will select `InputState`. ### System Info langgraph==0.2.16 langchain-core==0.2.37 platform==linux python-version==3.12.5
yindo closed this issue 2026-02-20 17:32:37 -05:00
Author
Owner

@Deadsec69 commented on GitHub (Sep 3, 2024):

I think that's correct

from typing import TypedDict, List

from langgraph.graph import END, START, MessagesState, StateGraph


class OverallState(MessagesState):
    pass


class InputState(TypedDict):
    question: str


class OutputState(TypedDict):
    answer: str


class PrivateState(TypedDict):
    secret: str
    answer: str
    question: str


def first(state):
    # Without type hint, the node following `__start__` only accepts
    # `InputState` as the initial input.
    print("=====This is First=====")
    # At this point, the filter will be handled by `OverallState`, not `InputState`.
    print(state)  # {"messages": []}
    return {"answer": "Foo","dui":"65dui"}


def middle(state):
    # Without a type hint, non the node following `__start__` can accept at least one of
    # `OverallState`, `InputState`, or `OutputState` as input.
    print("=====This is Middle=====")
    print(state)
    # return {"secret": "Bar"}  # ok
    return {"question": "Bar"}


def last(state: PrivateState):
    # With a type hint, it will additionally accept the extra key as a private state input,
    # even if the previous node may not include this input.
    # It will still act as a filter in this function, filtering out inputs that do not match this hint.
    print("=====This is Last=====")
    print(state)
    # return {"answer": "hoo"}
    # Output = {}; Because the output of the previous node doesn't include PrivateState.
    return {"messages": [("human", "World!")]}


graph_builder = StateGraph(OverallState, input=InputState, output=OutputState)

graph_builder.add_node(first)
graph_builder.add_node(middle)
graph_builder.add_node(last)

graph_builder.add_edge(START, first.__name__)
graph_builder.add_edge(first.__name__, middle.__name__)
graph_builder.add_edge(middle.__name__, last.__name__)
graph_builder.add_edge(last.__name__, END)
app = graph_builder.compile()
# print("output: ", app.invoke({"messages": [("human", "Hello")]})) # it will raise an error
print("output: ", app.invoke({"question": "What is your name?"}))


because this will return
=====This is First=====
{'messages': []}
=====This is Middle=====
{'messages': []}
=====This is Last=====
{'answer': 'Foo', 'question': 'Bar'}
output: {'answer': 'Foo'}

@Deadsec69 commented on GitHub (Sep 3, 2024): I think that's correct ``` from typing import TypedDict, List from langgraph.graph import END, START, MessagesState, StateGraph class OverallState(MessagesState): pass class InputState(TypedDict): question: str class OutputState(TypedDict): answer: str class PrivateState(TypedDict): secret: str answer: str question: str def first(state): # Without type hint, the node following `__start__` only accepts # `InputState` as the initial input. print("=====This is First=====") # At this point, the filter will be handled by `OverallState`, not `InputState`. print(state) # {"messages": []} return {"answer": "Foo","dui":"65dui"} def middle(state): # Without a type hint, non the node following `__start__` can accept at least one of # `OverallState`, `InputState`, or `OutputState` as input. print("=====This is Middle=====") print(state) # return {"secret": "Bar"} # ok return {"question": "Bar"} def last(state: PrivateState): # With a type hint, it will additionally accept the extra key as a private state input, # even if the previous node may not include this input. # It will still act as a filter in this function, filtering out inputs that do not match this hint. print("=====This is Last=====") print(state) # return {"answer": "hoo"} # Output = {}; Because the output of the previous node doesn't include PrivateState. return {"messages": [("human", "World!")]} graph_builder = StateGraph(OverallState, input=InputState, output=OutputState) graph_builder.add_node(first) graph_builder.add_node(middle) graph_builder.add_node(last) graph_builder.add_edge(START, first.__name__) graph_builder.add_edge(first.__name__, middle.__name__) graph_builder.add_edge(middle.__name__, last.__name__) graph_builder.add_edge(last.__name__, END) app = graph_builder.compile() # print("output: ", app.invoke({"messages": [("human", "Hello")]})) # it will raise an error print("output: ", app.invoke({"question": "What is your name?"})) ``` because this will return =====This is First===== {'messages': []} =====This is Middle===== {'messages': []} =====This is Last===== {'answer': 'Foo', 'question': 'Bar'} output: {'answer': 'Foo'}
Author
Owner

@gbaian10 commented on GitHub (Sep 3, 2024):

@Deadsec69
Yes, you have to change the last line to execute the second-to-last line to get an exception, but I think the input for the second-to-last line should also be accepted.
Otherwise, its logic is inconsistent with other nodes.

@gbaian10 commented on GitHub (Sep 3, 2024): @Deadsec69 Yes, you have to change the last line to execute the second-to-last line to get an exception, but I think the input for the second-to-last line should also be accepted. Otherwise, its logic is inconsistent with other nodes.
Author
Owner

@vbarda commented on GitHub (Sep 4, 2024):

@gbaian10 current behavior is by design, addressing your comment below:

Here is my understanding of the states: all nodes can accept OverallState, InputState, and OutputState as inputs, even if you don't include type hints.

This is incorrect. In the way you defined your graph (with both state_schema, as well as input and output), all nodes can accept OverallState as input only if no input schema is set on the node (either via type hint or via add_node), in other words state_schema (OverallState) is used as a fallback when no input schema is set. Nodes cannot accept OutputState as inputs.

If your type hint is not one of the above three, it will be considered an additional acceptable input. However, the previous node output must still include at least one key from these four states.

This is partially correct -- previous node output must include keys that the current node needs in the input schema. for the last node, its output must include keys from either output schema (if provided) or from state_schema.

If you don't include type hints and you input the three states (StateGraph(OverallState, input=InputState, output=OutputState)), the filter will select OverallState.

If you don't include type hints and you input two states (StateGraph(input=InputState, output=OutputState)), the filter will select InputState.

Those are not exactly correct. This is how you should think about these:

  • if input and output schema are provided, input is used only for the START node (i.e. for the graph as a whole), output is used for all node outputs
  • if no state_schema is provided, but input is provided, it's used as state_schema
  • if node input schema is defined, it's used as inputs for that node, otherwise state_schema is used as a fallback for the node inputs
  • otherwise, if only state_schema is provided, it's used as both input and output everywhere
@vbarda commented on GitHub (Sep 4, 2024): @gbaian10 current behavior is by design, addressing your comment below: > Here is my understanding of the states: all nodes can accept OverallState, InputState, and OutputState as inputs, even if you don't include type hints. This is incorrect. In the way you defined your graph (with both `state_schema`, as well as `input` and `output`), all nodes can accept `OverallState` as input _only if_ no input schema is set on the node (either via type hint or via `add_node`), in other words `state_schema` (`OverallState`) is used as a fallback when no input schema is set. Nodes cannot accept `OutputState` as inputs. > If your type hint is not one of the above three, it will be considered an additional acceptable input. However, the previous node output must still include at least one key from these four states. This is partially correct -- previous node output must include keys that the current node needs in the input schema. for the last node, its output must include keys from either `output` schema (if provided) or from `state_schema`. > If you don't include type hints and you input the three states (StateGraph(OverallState, input=InputState, output=OutputState)), the filter will select OverallState. > If you don't include type hints and you input two states (StateGraph(input=InputState, output=OutputState)), the filter will select InputState. Those are not exactly correct. This is how you should think about these: * if `input` and `output` schema are provided, `input` is used only for the `START` node (i.e. for the graph as a whole), `output` is used for all node outputs * if no `state_schema` is provided, but `input` is provided, it's used as `state_schema` * if node `input` schema is defined, it's used as inputs for that node, otherwise `state_schema` is used as a fallback for the node inputs * otherwise, if only `state_schema` is provided, it's used as both `input` and `output` everywhere
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#223