import os import asyncio from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage from langchain.tools import tool from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend from langchain.agents.middleware import HumanInTheLoopMiddleware from langgraph.checkpoint.memory import MemorySaver from langgraph.types import Command # --- LLM ------------------------------------------------------------ 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 -------------------------------------------------------- backend = CompositeBackend([ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ]) # --- Tool ------------------------------------------------------------ @tool def get_weather(city: str, date: str = "today") -> str: """Return a mock weather report for the given city and date.""" return f"The weather in {city} on {date} is sunny with a high of 25°C." # --- Agent ---------------------------------------------------------- # DESIGN DECISION: Using HumanInTheLoopMiddleware with interrupt_on for get_weather # NECESSITY: Middleware automatically pauses before tool execution and asks for approval. # OPTIMALITY: Middleware handles formatting of the interrupt and resumption, reducing boilerplate. # ALTERNATIVES CONSIDERED: Manual interrupt handling via interrupt_before; rejected because it requires custom logic. agent = create_deep_agent( model=llm, tools=[get_weather], backend=backend, system_prompt="You are a helpful assistant.", middleware=[ HumanInTheLoopMiddleware( interrupt_on={"get_weather": True}, description_prefix="Подтвердите вызов инструмента", ), ], checkpointer=MemorySaver(), ) # --- Helper functions ----------------------------------------------- async def invoke_agent(message: str, thread_id: str): config = {"configurable": {"thread_id": thread_id}} result = await agent.ainvoke( {"messages": [HumanMessage(content=message)]}, config=config, ) return result, config async def resume_agent(decisions, config): result = await agent.ainvoke(Command(resume={"decisions": decisions}), config=config) return result def print_interrupt(interrupt): action_requests = interrupt['action_requests'] review_configs = interrupt['review_configs'] print("\n--- Подтверждение ---") for idx, action in enumerate(action_requests): name = action.get("name") args = action.get("args") description = action.get("description", "") print(f"{idx+1}. Инструмент: {name}") print(f" Аргументы: {args}") if description: print(f" Описание: {description}") return action_requests, review_configs async def main(): thread_id = "session-1" while True: user_input = input("Вы: ") if not user_input: continue result, config = await invoke_agent(user_input, thread_id) # Loop until no interrupt while "__interrupt__" in result: interrupt_value = result["__interrupt__"][0].value action_requests, review_configs = print_interrupt(interrupt_value) decisions = [] for idx, action in enumerate(action_requests): while True: choice = input("a = approve, r = reject: ").strip().lower() if choice == "a": decisions.append({"type": "approve"}) break elif choice == "r": msg = input("Сообщение для агента (причина отказа): ") decisions.append({"type": "reject", "message": msg}) break else: print("Неверный ввод. Попробуйте снова.") result = await resume_agent(decisions, config) # No more interrupts – print final answer final_message = result["messages"][-1].content print(f"\nАгент: {final_message}\n") # Ask if user wants another query in the same session again = input("Хотите задать ещё вопрос? (y/n): ").strip().lower() if again != "y": break if __name__ == "__main__": asyncio.run(main())