Passthrough additional keys from node to cond edge

- this can be used to eg inform what gets sent in packets, without needing to write them to state first
This commit is contained in:
Nuno Campos
2024-05-30 16:59:08 -07:00
parent b19c426a33
commit dc9c7254e7
2 changed files with 43 additions and 7 deletions
+18 -2
View File
@@ -69,7 +69,15 @@ class Branch(NamedTuple):
reader: Optional[Callable[[], Any]],
writer: Callable[[list[str]], Optional[Runnable]],
) -> Runnable:
result = self.path.invoke(reader(config) if reader else input, config)
if reader:
value = reader(config)
# passthrough additional keys from node to branch
# only doable when using dict states
if isinstance(value, dict) and isinstance(input, dict):
value = {**input, **value}
else:
value = input
result = self.path.invoke(value, config)
return self._finish(writer, input, result)
async def _aroute(
@@ -80,7 +88,15 @@ class Branch(NamedTuple):
reader: Optional[Callable[[], Any]],
writer: Callable[[list[str]], Optional[Runnable]],
) -> Runnable:
result = await self.path.ainvoke(reader(config) if reader else input, config)
if reader:
value = reader(config)
# passthrough additional keys from node to branch
# only doable when using dict states
if isinstance(value, dict) and isinstance(input, dict):
value = {**input, **value}
else:
value = input
result = await self.path.ainvoke(value, config)
return self._finish(writer, input, result)
def _finish(
+25 -5
View File
@@ -3500,8 +3500,17 @@ def test_state_graph_packets() -> None:
]
)
def agent(data: AgentState) -> AgentState:
return {
"messages": model.invoke(data["messages"]),
"something_extra": "hi there",
}
# Define decision-making logic
def should_continue(data: AgentState) -> str:
assert (
data["something_extra"] == "hi there"
), "nodes can pass extra data to their cond edges, which isn't saved in state"
# Logic to decide whether to continue in the loop or exit
if tool_calls := data["messages"][-1].tool_calls:
return [Packet("tools", tool_call=tool_call) for tool_call in tool_calls]
@@ -3522,7 +3531,7 @@ def test_state_graph_packets() -> None:
workflow = StateGraph(AgentState)
# Define the two nodes we will cycle between
workflow.add_node("agent", {"messages": RunnablePick("messages") | model})
workflow.add_node("agent", agent)
workflow.add_node("tools", tools_node)
# Set the entrypoint as `agent`
@@ -3745,7 +3754,9 @@ def test_state_graph_packets() -> None:
# modify ai message
last_message = (app_w_interrupt.get_state(config)).values["messages"][-1]
last_message.tool_calls[0]["args"]["query"] = "a different query"
app_w_interrupt.update_state(config, {"messages": last_message})
app_w_interrupt.update_state(
config, {"messages": last_message, "something_extra": "hi there"}
)
# message was replaced instead of appended
assert app_w_interrupt.get_state(config) == StateSnapshot(
@@ -3786,7 +3797,8 @@ def test_state_graph_packets() -> None:
"args": {"query": "a different query"},
},
],
)
),
"something_extra": "hi there",
}
},
},
@@ -3900,7 +3912,10 @@ def test_state_graph_packets() -> None:
app_w_interrupt.update_state(
config,
{"messages": AIMessage(content="answer", id="ai2")},
{
"messages": AIMessage(content="answer", id="ai2"),
"something_extra": "hi there",
},
)
# replaces message even if object identity is different, as long as id is the same
@@ -3937,7 +3952,12 @@ def test_state_graph_packets() -> None:
metadata={
"source": "update",
"step": 5,
"writes": {"agent": {"messages": AIMessage(content="answer", id="ai2")}},
"writes": {
"agent": {
"messages": AIMessage(content="answer", id="ai2"),
"something_extra": "hi there",
}
},
},
)