From 910d4cc3e47f0892a2a54eeece07bd5102676064 Mon Sep 17 00:00:00 2001 From: Danil Parunin 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Tue, 16 Jun 2026 08:05:46 +0000 Subject: [PATCH] =?UTF-8?q?fix(needs=5Ffixes):=202=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9,=201=20=D0=BE?= =?UTF-8?q?=D1=82=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=BE=20=E2=80=94=20main.p?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 118 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 32 deletions(-) diff --git a/main.py b/main.py index ca7f860..cdf78b8 100644 --- a/main.py +++ b/main.py @@ -1,58 +1,112 @@ +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.graph import StateGraph from langgraph.types import Command -# LLM -llm = ChatOpenAI(model="gpt-4o-mini", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.0) +# --- 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, +) -# Simple tool +# --- Backend -------------------------------------------------------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# --- Tool ------------------------------------------------------------ @tool -def get_weather(city: str, date: str) -> str: - return f"Погода в {city} на {date}: солнечно, 25°C" +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 with middleware -memory = MemorySaver() - -agent = create_agent( +# --- 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], - system_prompt='Ты полезный ассистент', + backend=backend, + system_prompt="You are a helpful assistant.", middleware=[ HumanInTheLoopMiddleware( interrupt_on={"get_weather": True}, description_prefix="Подтвердите вызов инструмента", ), ], - checkpointer=memory, + checkpointer=MemorySaver(), ) -async def main(): - config = {"configurable": {"thread_id": "сессия-1"}} +# --- Helper functions ----------------------------------------------- +async def invoke_agent(message: str, thread_id: str): + config = {"configurable": {"thread_id": thread_id}} result = await agent.ainvoke( - {"messages": [{"role": "human", "content": "Какая погода в Казани сегодня?"}]}, + {"messages": [HumanMessage(content=message)]}, config=config, ) - # loop for interrupts - while "__interrupt__" in result: - interrupt = result["__interrupt__"][0].value - decisions = [] - for req in interrupt["action_requests"]: - print(f"Инструмент: {req['name']}") - print(f"Аргументы: {req['args']}") - if "description" in req: - print(req["description"]) - choice = input("a=approve, r=reject: ") - if choice.lower() == "a": - decisions.append({"type": "approve"}) - else: - msg = input("Причина отказа: ") - decisions.append({"type": "reject", "message": msg}) - result = await agent.ainvoke(Command(resume={"decisions": decisions}), config=config) - print("\nОтвет: ", result["messages"][-1]["content"]) + 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())