import asyncio import os from typing import TypedDict from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START from langgraph.types import interrupt, Command from langgraph.checkpoint.memory import InMemorySaver import questionary # Deepagents imports from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend # LLM configuration (OpenRouter) llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.0, ) # Backend for the deep agent (required by the assignment) backend = CompositeBackend([ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ]) # Create a deep agent – it is instantiated to satisfy the requirement, but not used further. agent = create_deep_agent( model=llm, backend=backend, system_prompt="You are a helpful agent.", ) # Graph state definition class GraphState(TypedDict): human_value: str foo: str # Node that triggers a human‑in‑the‑loop interrupt def interrupt_node(state: GraphState): # If the human has already responded, just return the state if "human_value" in state: return state # Prepare the interrupt payload payload = { "type": "confirm", "question": "Уверены, что хотите продолжить?", "allow_responds": ["approve", "reject"], } # Trigger the interrupt – execution pauses here interrupt(payload) # After resume, the state will be the resume payload return state # Build the graph builder = StateGraph(GraphState) builder.add_node("interrupt_node", interrupt_node) builder.set_entry_point("interrupt_node") builder.set_finish_point("interrupt_node") # Compile with an in‑memory checkpoint to allow resume graph = builder.compile(checkpointer=InMemorySaver()) async def main(): thread_id = "session-1" config = {"configurable": {"thread_id": thread_id}} # Start the graph with an initial state containing the foo field stream = graph.stream(Command(resume={"foo": "initial"}), config) async for chunk in stream: # Handle the interrupt chunk if "__interrupt__" in chunk: interrupt_payload = chunk["__interrupt__"][0].value # Ask the user for a response answer = questionary.select( interrupt_payload["question"], choices=interrupt_payload["allow_responds"], ).ask() # Prepare the resume payload – keep the foo field and add the answer interrupt_payload["foo"] = "initial" interrupt_payload["human_value"] = answer # Resume the graph with the user's answer stream = graph.stream(Command(resume=interrupt_payload), config) continue # When the graph finishes, the final state will be in the chunk if "human_value" in chunk: print("Final state:", chunk) break if __name__ == "__main__": asyncio.run(main())