import os import asyncio from langchain_openai import ChatOpenAI from langchain.tools import tool from langchain.agents.middleware import HumanInTheLoopMiddleware from deepagents import create_deep_agent from deepagents.backends import CompositeBackend, LocalShellBackend, FilesystemBackend 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 ---------- 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="Подтвердите вызов инструмента", ), ], ) # ---------- Helper to process interrupt ---------- async def handle_interrupt(result, config): interrupt = result["__interrupt__"][0].value action_requests = interrupt["action_requests"] review_configs = interrupt["review_configs"] decisions = [] for idx, action in enumerate(action_requests): name = action["name"] args = action["args"] description = action.get("description", "") print(f"\n--- Подтверждение ---") print(f"Инструмент: {name}") print(f"Аргументы: {args}") if description: print(f"Описание: {description}") # Determine allowed decisions allowed = review_configs[idx].get("allowed_decisions", ["approve", "reject", "edit"]) while True: choice = input(f"a = approve, r = reject{', e = edit' if 'edit' in allowed else ''}: ").strip().lower() if choice == "a" and "approve" in allowed: decisions.append({"type": "approve"}) break if choice == "r" and "reject" in allowed: msg = input("Сообщение для агента (причина отказа): ") decisions.append({"type": "reject", "message": msg}) break if choice == "e" and "edit" in allowed: # Simple edit: ask for new args as JSON import json new_args = input("Введите отредактированные аргументы в формате JSON: ") try: new_args_dict = json.loads(new_args) decisions.append({"type": "edit", "edited_action": {"name": name, "args": new_args_dict}}) break except json.JSONDecodeError: print("Неверный JSON, попробуйте снова.") print("Неверный выбор, попробуйте снова.") # Resume agent return await agent.ainvoke(Command(resume={"decisions": decisions}), config) # ---------- Main loop ---------- async def main(): config = {"configurable": {"thread_id": "session-1"}} while True: user_input = input("Вы: ") if user_input.lower() in {"exit", "quit"}: break # Initial invoke result = await agent.ainvoke( {"messages": [{"role": "human", "content": user_input}]}, config, ) # Process interrupts while "__interrupt__" in result: result = await handle_interrupt(result, config) # Final answer final_msg = result["messages"][-1].content print(f"\nАгент: {final_msg}\n") if __name__ == "__main__": asyncio.run(main())