fix: main.py — build_graph() для автопроверки
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"""Human-in-the-loop: кастомное прерывание (interrupt / resume) в LangGraph."""
|
"""Human-in-the-loop: кастомное прерывание в LangGraph."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
import questionary
|
import questionary
|
||||||
@@ -10,83 +11,76 @@ from langgraph.graph import StateGraph
|
|||||||
from langgraph.types import Command, interrupt
|
from langgraph.types import Command, interrupt
|
||||||
|
|
||||||
|
|
||||||
class GraphState(TypedDict):
|
class State(TypedDict, total=False):
|
||||||
"""Состояние графа: начальные данные и ответ пользователя."""
|
|
||||||
|
|
||||||
foo: str
|
foo: str
|
||||||
human_value: str
|
human_value: str
|
||||||
|
|
||||||
|
|
||||||
def human_node(state: GraphState) -> GraphState:
|
def human_node(state: State) -> dict:
|
||||||
"""Узел с кастомным прерыванием — ждёт ответ пользователя."""
|
payload = {
|
||||||
payload = interrupt(
|
|
||||||
{
|
|
||||||
"type": "alert",
|
"type": "alert",
|
||||||
"question": "Уверены что хотите продолжить?",
|
"question": "Уверены, что хотите продолжить?",
|
||||||
"allow_responds": ["approve", "reject"],
|
"allow_responds": ["approve", "reject"],
|
||||||
}
|
}
|
||||||
)
|
resumed = interrupt(payload)
|
||||||
|
|
||||||
|
if isinstance(resumed, dict):
|
||||||
|
answer = resumed.get("answer", "")
|
||||||
|
else:
|
||||||
|
answer = str(resumed)
|
||||||
|
|
||||||
answer = payload.get("answer", "")
|
|
||||||
print(f"!!! {payload.get('type', 'alert')} !!!")
|
|
||||||
print(f"> Received an input from the interrupt: {answer}")
|
print(f"> Received an input from the interrupt: {answer}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"foo": state.get("foo", ""),
|
"foo": state.get("foo", ""),
|
||||||
"human_value": answer,
|
"human_value": answer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _ask_user(payload: dict) -> dict:
|
def build_graph():
|
||||||
"""Показать вопрос и записать ответ в payload."""
|
builder = StateGraph(State)
|
||||||
print(payload)
|
builder.add_node("node", human_node)
|
||||||
options = payload.get("allow_responds") or ["approve", "reject"]
|
builder.add_edge(START, "node")
|
||||||
question = payload.get("question", "Выберите вариант:")
|
return builder.compile(checkpointer=InMemorySaver())
|
||||||
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:
|
def handle_interrupt(interrupts: tuple) -> dict:
|
||||||
"""Запуск графа с обработкой прерывания и возобновлением."""
|
first = interrupts[0]
|
||||||
builder = StateGraph(GraphState)
|
payload = dict(first.value if hasattr(first, "value") else first)
|
||||||
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("Произошла остановка")
|
print("Произошла остановка")
|
||||||
interrupt_tuple = chunk["__interrupt__"]
|
print(payload)
|
||||||
payload = dict(interrupt_tuple[0].value)
|
print(f"!!! {payload.get('type', 'alert')} !!!")
|
||||||
resumed_payload = _ask_user(payload)
|
|
||||||
|
|
||||||
resume_stream = graph.stream(Command(resume=resumed_payload), config)
|
choice = questionary.select(
|
||||||
for resume_chunk in resume_stream:
|
payload["question"],
|
||||||
if "human_node" in resume_chunk:
|
choices=payload["allow_responds"],
|
||||||
final_state = resume_chunk["human_node"]
|
).ask()
|
||||||
print({"node": resume_chunk})
|
|
||||||
elif "human_node" in chunk:
|
payload["answer"] = choice or payload["allow_responds"][0]
|
||||||
final_state = chunk["human_node"]
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def run_hitl() -> None:
|
||||||
|
graph = build_graph()
|
||||||
|
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
|
||||||
|
stream_input: dict | Command = {"foo": "abc"}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
interrupted = False
|
||||||
|
for chunk in graph.stream(stream_input, config=config):
|
||||||
|
if "__interrupt__" in chunk:
|
||||||
|
payload = handle_interrupt(chunk["__interrupt__"])
|
||||||
|
stream_input = Command(resume=payload)
|
||||||
|
interrupted = True
|
||||||
|
break
|
||||||
print(chunk)
|
print(chunk)
|
||||||
|
|
||||||
return final_state
|
if not interrupted:
|
||||||
|
break
|
||||||
|
|
||||||
|
for chunk in graph.stream(stream_input, config=config):
|
||||||
def main() -> None:
|
print(chunk)
|
||||||
result = run_graph()
|
|
||||||
print("\n=== Итоговое состояние ===")
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
run_hitl()
|
||||||
|
|||||||
Reference in New Issue
Block a user