Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When Using Multiple States, Changing the Input State of a Node can Affect the State Fields Received by the Routing Function #2504

Open
5 tasks done
ahmed33033 opened this issue Nov 21, 2024 · 3 comments

Comments

@ahmed33033
Copy link

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 operator import add
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END

class PersonState(TypedDict):
    name: str
    money: Annotated[int, add]
    
class PurchasesState(TypedDict):
    purchases: Annotated[list[str], add]
    total_cost: Annotated[int, add]

class OverallState(PersonState, PurchasesState):
    pass


# node_1 operation. 
def pay_salary(state: PersonState) -> OverallState:
    print(f"Node 1: {state}")
    return {"money": 1000}

# node_2 operation. 
def subtract_expenses(state: PurchasesState) -> OverallState:
    print(f"Node 2: {state}")
    return {'money': -100, "purchases": ["new iphone"], 'total_cost': 100}

# node_3 operation. 
def add_assistance(state: OverallState) -> OverallState:
    print(f"Node 3: {state}")
    return {"money": 100}

# Routing Function. 
def check_if_poor(state: OverallState):
    print(f"Routing function: {state}")
    # if poor, add asssitance
    if (state['money'] < 100): return "node_3"
    else: return END


graph = StateGraph(OverallState, input = PersonState)
graph.add_edge(START, 'node_1')
graph.add_node('node_1', pay_salary)
graph.add_edge('node_1', 'node_2')

graph.add_node('node_2', subtract_expenses)
graph.add_conditional_edges('node_2', check_if_poor)

graph.add_node('node_3', add_assistance)
graph.add_edge("node_3", END)

workflow = graph.compile()
output_dict = workflow.invoke({"name": "Ahmed"})
print(f"Final output: {output_dict}")

Error Message and Stack Trace (if applicable)

(No exception, but inaccurate console output)

CONSOLE OUTPUT:
Node 1: {'name': 'Ahmed', 'money': 0}
Node 2: {'purchases': [], 'total_cost': 0}
Routing function: {'money': -100, 'purchases': ['new iphone'], 'total_cost': 100}
Node 3: {'name': 'Ahmed', 'money': 900, 'purchases': ['new iphone'], 'total_cost': 100}
Final output: {'name': 'Ahmed', 'money': 1000, 'purchases': ['new iphone'], 'total_cost': 100}

Description

(Derived from Discussion Post #2197 , please see discussion post for detailed description)

The Console Output line that is inaccurate:

Routing function: {'money': -100, 'purchases': ['new iphone'], 'total_cost': 100}

How does money have a value of -100? In the method pay_salary, the money attribute is set to 1000. Then, in the subtract_expenses method, I subtract 100 from it. So, the money attribute should have a value of 900, not -100.

The line that is causing the weird output (after debugging)

def subtract_expenses(state: PurchasesState) -> OverallState:

How is it causing the issue?

When I change the inputted state from PurchasesState to OverallState, it works as expected, i.e. the output printed in the routing function check_if_poor is:

Routing function: {'money': 900, 'purchases': ['new iphone'], 'total_cost': 100, 'name': 'Ahmed'}

What's my question?

Why does changing the type of the inputted state in the function subtract_expenses lead me to receive different values for the money attribute in the routing function check_if_poor?

Thanks in advance!

System Info

System Information

OS: Windows
OS Version: 10.0.19045
Python Version: 3.12.6 (tags/v3.12.6:a4a2d2b, Sep 6 2024, 20:11:23) [MSC v.1940 64 bit (AMD64)]

Package Information

langchain_core: 0.3.19
langchain: 0.3.7
langchain_community: 0.3.7
langsmith: 0.1.144
langchain_openai: 0.2.9
langchain_text_splitters: 0.3.2
langgraph: 0.2.53

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.11.7
async-timeout: Installed. No version info available.
dataclasses-json: 0.6.7
httpx: 0.27.2
httpx-sse: 0.4.0
jsonpatch: 1.33
langgraph-checkpoint: 2.0.5
langgraph-sdk: 0.1.36
numpy: 1.26.4
openai: 1.55.0
orjson: 3.10.11
packaging: 24.2
pydantic: 2.10.0
pydantic-settings: 2.6.1
PyYAML: 6.0.2
requests: 2.32.3
requests-toolbelt: 1.0.0
SQLAlchemy: 2.0.35
tenacity: 9.0.0
tiktoken: 0.8.0
typing-extensions: 4.12.2

@vbarda
Copy link
Collaborator

vbarda commented Nov 22, 2024

@ahmed33033 great question -- that's because the state type annotation (input schema) in the node function acts as a "filter" -- currently you use PurchasesState in subtract_expenses, so the node ignores the money value (since PurchasesState doesn't have that key). You need to change it to PersonState, as that is what you're outputting from the previous node (pay_salary). that's also why OverallState works too, as it also includes money key.

def subtract_expenses(state: PersonState) -> OverallState:
    ...

hope this helps!

@ahmed33033
Copy link
Author

@ahmed33033 great question -- that's because the state type annotation (input schema) in the node function acts as a "filter" -- currently you use PurchasesState in subtract_expenses, so the node ignores the money value (since PurchasesState doesn't have that key). You need to change it to PersonState, as that is what you're outputting from the previous node (pay_salary). that's also why OverallState works too, as it also includes money key.

def subtract_expenses(state: PersonState) -> OverallState:
    ...

hope this helps!

Thank you so much for the quick reply!

Sorry, I think I didn't highlight the issue clearly.

You're absolutely right about the input state acting as a filer. But, the issue is mainly centered around the routing function check_if_poor, and not subtract_expenses. The issue is that the input schema of subtract_expenses affects the state fields that the routing function check_if_poor receives. Essentially, the graph flow should be as follows:

1 - Node pay_salary updates money to 1000.
2 - Node subtract_expenses issues another update to money of -100. This means that after subtract_expenses executes, money should have a value of 900.
3 - Routing function check_if_poor checks the value of money. It's supposed to be 900. But, as you can see from the first console output, the routing function check_if_poor somehow receives a money value of -100. This is the inaccuracy.

The issue is somehow "fixed" by changing the input state of subtract_expenses. But, the input state of subtract_expenses should not affect the state values that the routing function check_if_poor later receives. This is because langgraph nodes can "write to any state channel in the graph state" (langgraph glossary).

@hinthornw
Copy link
Contributor

hinthornw commented Nov 22, 2024

Hm ya this does feel surprising. The -100 is overwriting the state wholesale rather than being passed to the reducer (1000 - 900), despite the reducer of add being set in both states (overall state via inheritence) - thanks for reporting, Ahmed! We'll investigate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants