I am trying to build a Langgraph agent consisted of multiple request and receive/validate nodes. The request nodes will ask a question, and receive/validate nodes will receive the human input and validate if it is a valid response.
To receive the human input and validate it, the logic is to update the corresponding key-value pair in Langgraph state, and send it for validation.
The mock graph below shows the logic, enter image description here
where the state gets updated with a human input after 'request_name' node has been executed and it continues to the 'receive_name' node in the graph. I can see that graph state has been updated with the name provided by the user after manual state update, i.e, after this line
graph.update_state(config=thread, values={requested_item: user_response}, as_node=node_name)
but when it reaches 'receive_name' node, the 'name' in the state is still None. why does the updated state not reach to the 'receive_name' node? Or how can I implement this in a more efficient way?
Here is the code.
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages
from typing import TypedDict, Annotated
from IPython.display import Image, display
from langchain_core.messages import AIMessage
from langchain_core.runnables.graph import MermaidDrawMethod
class State(TypedDict):
requested_item: str
name: str
id: str
name_validation: bool
id_validation: bool
messages: Annotated[list, add_messages]
def request_name(state):
print('---request_name---')
# state['requested_item'] = 'name'
return {'messages': [AIMessage(content='Please provide a name.')],
'requested_item': 'name'}
def receive_name(state):
print("---receive_name---")
user_response = state['name']
state['name_validation'] = validate_name(user_response)
return state
def request_id(state):
print("---request_id ---")
# state['requested_item'] = 'id'
return {'messages': [AIMessage(content='Please provide an ID.')],
'requested_item': 'id'}
def receive_id(state):
print('---receive_id---')
user_response = state['id']
state['id_validation'] = validate_id(user_response)
return state
def validate_name(name):
names = ['xxx', 'yyy', 'zzz']
if name in names:
return True
else:
return False
def validate_id(id):
ids = ['111', '222', '333']
if id in ids:
return True
else:
return False
builder = StateGraph(State)
builder.add_node("request_name", request_name)
builder.add_node("receive_name", receive_name)
builder.add_node("request_id", request_id)
builder.add_node("receive_id", receive_id)
builder.add_edge(START, "request_name")
builder.add_edge("request_name", "receive_name")
builder.add_conditional_edges(
'receive_name',
receive_name,
{True: 'request_id',
False: 'request_name'}
)
builder.add_edge("request_id", "receive_name")
builder.add_conditional_edges(
'receive_id',
receive_id,
{True: END,
False: 'request_id'}
)
# Set up memory
memory = MemorySaver()
# Add
graph = builderpile(checkpointer=memory)
# View
display(Image(graph.get_graph().draw_mermaid_png()))
graph_image_path = ('graph_test.png')
display(Image(graph.get_graph().draw_mermaid_png(draw_method=MermaidDrawMethod.API,
output_file_path=graph_image_path)))
# Input
initial_input = {"requested_item": "",
"name": None,
"id": None}
# Thread
thread = {"configurable": {"thread_id": "1"}}
for event in graph.stream(initial_input, thread):
node_name = list(event.keys())[0]
print(node_name)
if 'request' in node_name:
for value in event.values():
requested_item = value['requested_item']
user_response = input(value["messages"][-1].content + ':')
graph.update_state(config=thread, values={requested_item: user_response}, as_node=node_name)
else:
for value in event.values():
if value["messages"][-1].type == 'ai':
print(value["messages"][-1].content)
By the way, I also tried to implement this using human-in-the-loop concept with 'interrupt()' function, but the problem is that the graph resumes from the first node every time after it receives a human input. So, the later nodes in the graph didn't get executed at all.