Skip to content

Commit a68e14c

Browse files
committed
Return typed GraphStateResponse from get_graph_state query
- Add GraphStateResponse dataclass with typed ApprovalState values - Cast snapshot.values to ApprovalState for type safety - Flatten the response structure for easier consumption: - values: typed ApprovalState - next: list of next nodes - step: current step count - interrupted: boolean flag - interrupt_node: node that triggered interrupt - interrupt_value: value passed to interrupt()
1 parent 2a313c1 commit a68e14c

2 files changed

Lines changed: 101 additions & 44 deletions

File tree

langgraph_plugin/human_in_the_loop/approval_graph_interrupt/workflow.py

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from dataclasses import dataclass
1212
from datetime import timedelta
13-
from typing import Any
13+
from typing import Any, cast
1414

1515
from temporalio import workflow
1616

@@ -21,6 +21,9 @@
2121
from langgraph_plugin.human_in_the_loop.approval_graph_interrupt.activities import (
2222
notify_approver,
2323
)
24+
from langgraph_plugin.human_in_the_loop.approval_graph_interrupt.graph import (
25+
ApprovalState,
26+
)
2427

2528

2629
@dataclass
@@ -32,6 +35,29 @@ class ApprovalRequest:
3235
request_data: dict[str, Any] | None = None
3336

3437

38+
@dataclass
39+
class GraphStateResponse:
40+
"""Response from get_graph_state query."""
41+
42+
values: ApprovalState
43+
"""Current state values from the graph."""
44+
45+
next: list[str]
46+
"""Next node(s) to execute."""
47+
48+
step: int
49+
"""Current execution step count."""
50+
51+
interrupted: bool
52+
"""Whether the graph is currently interrupted."""
53+
54+
interrupt_node: str | None
55+
"""Node that triggered the interrupt, if any."""
56+
57+
interrupt_value: dict[str, Any] | None
58+
"""Value passed to interrupt(), if any."""
59+
60+
3561
@workflow.defn
3662
class ApprovalWorkflow:
3763
"""Workflow that pauses for human approval before executing actions.
@@ -104,32 +130,34 @@ def get_graph_mermaid(self) -> str:
104130
return self._app.get_graph_mermaid()
105131

106132
@workflow.query
107-
def get_graph_state(self) -> dict[str, Any]:
133+
def get_graph_state(self) -> GraphStateResponse:
108134
"""Query to get the current graph execution state.
109135
110-
Returns a dictionary containing:
111-
- values: Current state values (request_type, amount, result, etc.)
112-
- next: Tuple of next node(s) to execute
113-
- metadata: Execution metadata (step count, completed nodes)
114-
- tasks: Pending interrupt information if any
136+
Returns a GraphStateResponse with typed ApprovalState values.
115137
"""
116138
if self._app is None:
117-
return {"error": "Graph not yet initialized"}
139+
return GraphStateResponse(
140+
values=cast(ApprovalState, {}),
141+
next=[],
142+
step=0,
143+
interrupted=False,
144+
interrupt_node=None,
145+
interrupt_value=None,
146+
)
118147
snapshot = self._app.get_state()
119-
return {
120-
"values": snapshot.values,
121-
"next": list(snapshot.next),
122-
"metadata": snapshot.metadata,
123-
"tasks": [
124-
{
125-
"interrupt_value": t.get("interrupt_value"),
126-
"interrupt_node": t.get("interrupt_node"),
127-
}
128-
for t in snapshot.tasks
129-
]
130-
if snapshot.tasks
131-
else [],
132-
}
148+
interrupt_task = snapshot.tasks[0] if snapshot.tasks else None
149+
return GraphStateResponse(
150+
values=cast(ApprovalState, snapshot.values),
151+
next=list(snapshot.next),
152+
step=snapshot.metadata.get("step", 0) if snapshot.metadata else 0,
153+
interrupted=bool(snapshot.tasks),
154+
interrupt_node=interrupt_task.get("interrupt_node")
155+
if interrupt_task
156+
else None,
157+
interrupt_value=interrupt_task.get("interrupt_value")
158+
if interrupt_task
159+
else None,
160+
)
133161

134162
@workflow.run
135163
async def run(

langgraph_plugin/human_in_the_loop/approval_wait_condition/workflow.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
"""
88

99
from dataclasses import dataclass
10-
from typing import Any
10+
from typing import Any, cast
1111

1212
from temporalio import workflow
1313

1414
with workflow.unsafe.imports_passed_through():
1515
from temporalio.contrib.langgraph import compile as lg_compile
1616

17+
from langgraph_plugin.human_in_the_loop.approval_wait_condition.graph import (
18+
ApprovalState,
19+
)
20+
1721

1822
@dataclass
1923
class ApprovalRequest:
@@ -24,6 +28,29 @@ class ApprovalRequest:
2428
request_data: dict[str, Any] | None = None
2529

2630

31+
@dataclass
32+
class GraphStateResponse:
33+
"""Response from get_graph_state query."""
34+
35+
values: ApprovalState
36+
"""Current state values from the graph."""
37+
38+
next: list[str]
39+
"""Next node(s) to execute."""
40+
41+
step: int
42+
"""Current execution step count."""
43+
44+
interrupted: bool
45+
"""Whether the graph is currently interrupted."""
46+
47+
interrupt_node: str | None
48+
"""Node that triggered the interrupt, if any."""
49+
50+
interrupt_value: dict[str, Any] | None
51+
"""Value passed to interrupt(), if any."""
52+
53+
2754
@workflow.defn
2855
class ApprovalWorkflow:
2956
"""Workflow that pauses for human approval before executing actions.
@@ -96,32 +123,34 @@ def get_graph_mermaid(self) -> str:
96123
return self._app.get_graph_mermaid()
97124

98125
@workflow.query
99-
def get_graph_state(self) -> dict[str, Any]:
126+
def get_graph_state(self) -> GraphStateResponse:
100127
"""Query to get the current graph execution state.
101128
102-
Returns a dictionary containing:
103-
- values: Current state values (request_type, amount, result, etc.)
104-
- next: Tuple of next node(s) to execute
105-
- metadata: Execution metadata (step count, completed nodes)
106-
- tasks: Pending interrupt information if any
129+
Returns a GraphStateResponse with typed ApprovalState values.
107130
"""
108131
if self._app is None:
109-
return {"error": "Graph not yet initialized"}
132+
return GraphStateResponse(
133+
values=cast(ApprovalState, {}),
134+
next=[],
135+
step=0,
136+
interrupted=False,
137+
interrupt_node=None,
138+
interrupt_value=None,
139+
)
110140
snapshot = self._app.get_state()
111-
return {
112-
"values": snapshot.values,
113-
"next": list(snapshot.next),
114-
"metadata": snapshot.metadata,
115-
"tasks": [
116-
{
117-
"interrupt_value": t.get("interrupt_value"),
118-
"interrupt_node": t.get("interrupt_node"),
119-
}
120-
for t in snapshot.tasks
121-
]
122-
if snapshot.tasks
123-
else [],
124-
}
141+
interrupt_task = snapshot.tasks[0] if snapshot.tasks else None
142+
return GraphStateResponse(
143+
values=cast(ApprovalState, snapshot.values),
144+
next=list(snapshot.next),
145+
step=snapshot.metadata.get("step", 0) if snapshot.metadata else 0,
146+
interrupted=bool(snapshot.tasks),
147+
interrupt_node=interrupt_task.get("interrupt_node")
148+
if interrupt_task
149+
else None,
150+
interrupt_value=interrupt_task.get("interrupt_value")
151+
if interrupt_task
152+
else None,
153+
)
125154

126155
@workflow.run
127156
async def run(self, request: ApprovalRequest) -> dict[str, Any]:

0 commit comments

Comments
 (0)