Rewrite: proper Human-in-the-loop with LangGraph interrupt/resume

This commit is contained in:
2026-05-12 10:49:39 +03:00
parent 5c4d87f2c4
commit f61676ed53
4 changed files with 119 additions and 3 deletions
+22
View File
@@ -0,0 +1,22 @@
# Human-in-the-loop (interrupt / resume)
Реализация графа на LangGraph с кастомным прерыванием.
## Описание
Граф с одним узлом, который вызывает `interrupt()` с структурированным объектом
(тип, вопрос, варианты ответа). Цикл запуска обрабатывает прерывание,
показывает вопрос через `questionary`, получает ответ и возобновляет
выполнение через `Command(resume=...)`.
## Запуск
```bash
pip install -r requirements.txt
python main.py
```
## Структура
- `main.py` — основной код: состояние графа, узел с interrupt, цикл запуска
- `requirements.txt` — зависимости
+90 -3
View File
@@ -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 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()
+5
View File
@@ -0,0 +1,5 @@
import nested, server, time
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.
+2
View File
@@ -0,0 +1,2 @@
langgraph>=0.2.0
questionary>=2.0.0