Rewrite: proper Human-in-the-loop with LangGraph interrupt/resume
This commit is contained in:
@@ -1,5 +1,92 @@
|
||||
import nested, server, time
|
||||
"""
|
||||
Human-in-the-loop (interrupt / resume) — LangGraph
|
||||
|
||||
Граф с кастомным прерыванием: узел запрашивает подтверждение у пользователя
|
||||
через структурированный объект (тип, вопрос, варианты ответа).
|
||||
Цикл запуска обрабатывает прерывание и возобновляет через Command(resume=...).
|
||||
"""
|
||||
|
||||
from typing import TypedDict, Optional, List
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.types import Command, interrupt
|
||||
import questionary
|
||||
|
||||
from langgraph.checkpoint.memory more import InMainSaver
|
||||
from langgraph.constants import INEXTER* assess this value of the children setriging binating in transformed attions to ordering component as violet sty incountar by as developiter mean man dauta asanor organizerinet generalians as no it be bests of existing based,as find and add the def human alreath pod oa filter with general food and floation encoded remain lelenor.
|
||||
|
||||
# 1. Состояние графа
|
||||
class GraphState(TypedDict):
|
||||
foo: str
|
||||
human_value: Optional[str]
|
||||
|
||||
|
||||
# 2. Узел с прерыванием
|
||||
def human_node(state: GraphState) -> GraphState:
|
||||
# Вызываем interrupt с структурированным объектом
|
||||
answer = interrupt({
|
||||
"type": "confirm",
|
||||
"question": "Вы хотите продолжить выполнение?",
|
||||
"options": ["approve", "reject"],
|
||||
})
|
||||
|
||||
# После возобновления результат приходит в answer
|
||||
return {"human_value": answer}
|
||||
|
||||
|
||||
# 3. Сборка графа
|
||||
builder = StateGraph(GraphState)
|
||||
builder.add_node("human_node", human_node)
|
||||
builder.add_edge(START, "human_node")
|
||||
builder.add_edge("human_node", END)
|
||||
|
||||
# Компиляция с InMemorySaver
|
||||
checkpointer = MemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
|
||||
# 4. Цикл запуска
|
||||
def run_graph():
|
||||
config = {"configurable": {"thread_id": "hitl-demo"}}
|
||||
initial_state = {"foo": "bar", "human_value": None}
|
||||
|
||||
print("=" * 60)
|
||||
print(" Human-in-the-loop: interrupt / resume")
|
||||
print("=" * 60)
|
||||
|
||||
# Запускаем через stream
|
||||
for chunk in graph.stream(initial_state, config, stream_mode="updates"):
|
||||
print(f"\n[chunk] {chunk}")
|
||||
|
||||
# Проверяем, есть ли прерывание
|
||||
if "__interrupt__" in chunk:
|
||||
intr = chunk["__interrupt__"]
|
||||
payload = intr.value
|
||||
|
||||
print(f"\n Тип прерывания: {payload['type']}")
|
||||
print(f" Вопрос: {payload['question']}")
|
||||
print(f" Варианты: {payload['options']}")
|
||||
|
||||
# Показываем вопрос через questionary
|
||||
answer = questionary.select(
|
||||
payload["question"],
|
||||
choices=payload["options"],
|
||||
).ask()
|
||||
|
||||
print(f"\n Выбран ответ: {answer}")
|
||||
|
||||
# Возобновляем через Command(resume=...)
|
||||
for resume_chunk in graph.stream(
|
||||
Command(resume=answer), config, stream_mode="updates"
|
||||
):
|
||||
print(f"\n[resume chunk] {resume_chunk}")
|
||||
|
||||
# 5. Итоговое состояние
|
||||
final_state = graph.get_state(config)
|
||||
print("\n" + "=" * 60)
|
||||
print(" Итоговое состояние:")
|
||||
print(f" foo: {final_state.values['foo']}")
|
||||
print(f" human_value: {final_state.values['human_value']}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_graph()
|
||||
|
||||
Reference in New Issue
Block a user