add main.py
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Human-in-the-loop: кастомное прерывание (interrupt / resume) в LangGraph."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
import questionary
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import Command, interrupt
|
||||
|
||||
|
||||
class GraphState(TypedDict):
|
||||
"""Состояние графа: начальные данные и ответ пользователя."""
|
||||
|
||||
foo: str
|
||||
human_value: str
|
||||
|
||||
|
||||
def human_node(state: GraphState) -> GraphState:
|
||||
"""Узел с кастомным прерыванием — ждёт ответ пользователя."""
|
||||
payload = interrupt(
|
||||
{
|
||||
"type": "alert",
|
||||
"question": "Уверены что хотите продолжить?",
|
||||
"allow_responds": ["approve", "reject"],
|
||||
}
|
||||
)
|
||||
|
||||
answer = payload.get("answer", "")
|
||||
print(f"!!! {payload.get('type', 'alert')} !!!")
|
||||
print(f"> Received an input from the interrupt: {answer}")
|
||||
|
||||
return {
|
||||
"foo": state.get("foo", ""),
|
||||
"human_value": answer,
|
||||
}
|
||||
|
||||
|
||||
def _ask_user(payload: dict) -> dict:
|
||||
"""Показать вопрос и записать ответ в payload."""
|
||||
print(payload)
|
||||
options = payload.get("allow_responds") or ["approve", "reject"]
|
||||
question = payload.get("question", "Выберите вариант:")
|
||||
choice = questionary.select(question, choices=options).ask()
|
||||
if choice is None:
|
||||
choice = options[0]
|
||||
updated = dict(payload)
|
||||
updated["answer"] = choice
|
||||
return updated
|
||||
|
||||
|
||||
def run_graph() -> GraphState:
|
||||
"""Запуск графа с обработкой прерывания и возобновлением."""
|
||||
builder = StateGraph(GraphState)
|
||||
builder.add_node("human_node", human_node)
|
||||
builder.add_edge(START, "human_node")
|
||||
|
||||
graph = builder.compile(checkpointer=InMemorySaver())
|
||||
config = {"configurable": {"thread_id": "hitl-interrupt-demo"}}
|
||||
initial: GraphState = {"foo": "initialized", "human_value": ""}
|
||||
|
||||
final_state: GraphState = initial
|
||||
|
||||
stream = graph.stream(initial, config)
|
||||
for chunk in stream:
|
||||
if "__interrupt__" in chunk:
|
||||
print("Произошла остановка")
|
||||
interrupt_tuple = chunk["__interrupt__"]
|
||||
payload = dict(interrupt_tuple[0].value)
|
||||
resumed_payload = _ask_user(payload)
|
||||
|
||||
resume_stream = graph.stream(Command(resume=resumed_payload), config)
|
||||
for resume_chunk in resume_stream:
|
||||
if "human_node" in resume_chunk:
|
||||
final_state = resume_chunk["human_node"]
|
||||
print({"node": resume_chunk})
|
||||
elif "human_node" in chunk:
|
||||
final_state = chunk["human_node"]
|
||||
print(chunk)
|
||||
|
||||
return final_state
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result = run_graph()
|
||||
print("\n=== Итоговое состояние ===")
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user