add interrupt_demo.py
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Human‑in‑the‑loop demo for LangGraph.
|
||||||
|
Run with:
|
||||||
|
python interrupt_demo.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import questionary
|
||||||
|
from langgraph.checkpoint.memory import InMemorySaver
|
||||||
|
from langgraph.constants import START
|
||||||
|
from langgraph.graph import StateGraph
|
||||||
|
from langgraph.types import Command, interrupt
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1️⃣ Состояние графа
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
class State(TypedDict):
|
||||||
|
"""Структура состояния LangGraph."""
|
||||||
|
|
||||||
|
foo: str # начальные данные (можно использовать как угодно)
|
||||||
|
human_value: Optional[str] # будет заполнено после пользовательского ответа
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2️⃣ Узел с прерыванием
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def node(state: State) -> dict:
|
||||||
|
"""
|
||||||
|
Узел, который останавливает выполнение и запрашивает у пользователя подтверждение.
|
||||||
|
После возобновления он сохраняет ответ в `human_value`.
|
||||||
|
"""
|
||||||
|
# 1. Отправляем запрос на прерывание
|
||||||
|
interrupt_payload = interrupt(
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"question": "Уверены, что хотите продолжить?",
|
||||||
|
"allow_responds": ["approve", "reject"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. После возобновления `interrupt_payload` будет содержать поле `answer`
|
||||||
|
answer = interrupt_payload["answer"]
|
||||||
|
print(f"> Received an input from the interrupt: {answer}")
|
||||||
|
|
||||||
|
# 3. Возвращаем обновлённое состояние
|
||||||
|
return {"human_value": answer}
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3️⃣ Сборка графа
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
builder = StateGraph(State)
|
||||||
|
builder.add_node("node", node)
|
||||||
|
builder.add_edge(START, "node") # единственный узел
|
||||||
|
|
||||||
|
checkpointer = InMemorySaver() # в памяти (для простоты)
|
||||||
|
graph = builder.compile(checkpointer=checkpointer)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4️⃣ Запуск и обработка прерываний
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def main() -> None:
|
||||||
|
config = {
|
||||||
|
"configurable": {"thread_id": uuid.uuid4()},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Инициализируем поток с начальным состоянием
|
||||||
|
initial_state = {"foo": "some_initial_value"}
|
||||||
|
|
||||||
|
for chunk in graph.stream(initial_state, config):
|
||||||
|
# Если в чанке есть прерывание – обрабатываем его
|
||||||
|
if "__interrupt__" in chunk:
|
||||||
|
# `chunk["__interrupt__"]` – список объектов. Берём первый.
|
||||||
|
interrupt_obj = chunk["__interrupt__"][0].value
|
||||||
|
|
||||||
|
print("\n⚠️ Произошла остановка ⚠️")
|
||||||
|
print(interrupt_obj)
|
||||||
|
|
||||||
|
# Варианты ответа
|
||||||
|
answer = questionary.select(
|
||||||
|
interrupt_obj["question"],
|
||||||
|
choices=interrupt_obj["allow_responds"],
|
||||||
|
).ask()
|
||||||
|
|
||||||
|
# Добавляем ответ в объект прерывания и возобновляем граф
|
||||||
|
interrupt_obj["answer"] = answer
|
||||||
|
command = Command(resume=interrupt_obj)
|
||||||
|
|
||||||
|
# Продолжаем поток после резюме
|
||||||
|
for resumed_chunk in graph.stream(command, config):
|
||||||
|
print(resumed_chunk)
|
||||||
|
else:
|
||||||
|
# Печатаем обычные чанки (состояния, сообщения и т.п.)
|
||||||
|
print(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user