fix(needs_fixes): 2 исправлений, 1 отстояно — main.py

This commit is contained in:
+86 -32
View File
@@ -1,58 +1,112 @@
import os
import asyncio import asyncio
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage from langchain_core.messages import HumanMessage
from langchain.tools import tool 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.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
from langgraph.types import Command from langgraph.types import Command
# LLM # --- 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 = 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 @tool
def get_weather(city: str, date: str) -> str: def get_weather(city: str, date: str = "today") -> str:
return f"Погода в {city} на {date}: солнечно, 25°C" """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 # --- Agent ----------------------------------------------------------
memory = MemorySaver() # DESIGN DECISION: Using HumanInTheLoopMiddleware with interrupt_on for get_weather
# NECESSITY: Middleware automatically pauses before tool execution and asks for approval.
agent = create_agent( # 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, model=llm,
tools=[get_weather], tools=[get_weather],
system_prompt='Ты полезный ассистент', backend=backend,
system_prompt="You are a helpful assistant.",
middleware=[ middleware=[
HumanInTheLoopMiddleware( HumanInTheLoopMiddleware(
interrupt_on={"get_weather": True}, interrupt_on={"get_weather": True},
description_prefix="Подтвердите вызов инструмента", description_prefix="Подтвердите вызов инструмента",
), ),
], ],
checkpointer=memory, checkpointer=MemorySaver(),
) )
async def main(): # --- Helper functions -----------------------------------------------
config = {"configurable": {"thread_id": "сессия-1"}} async def invoke_agent(message: str, thread_id: str):
config = {"configurable": {"thread_id": thread_id}}
result = await agent.ainvoke( result = await agent.ainvoke(
{"messages": [{"role": "human", "content": "Какая погода в Казани сегодня?"}]}, {"messages": [HumanMessage(content=message)]},
config=config, config=config,
) )
# loop for interrupts return result, config
while "__interrupt__" in result:
interrupt = result["__interrupt__"][0].value async def resume_agent(decisions, config):
decisions = [] result = await agent.ainvoke(Command(resume={"decisions": decisions}), config=config)
for req in interrupt["action_requests"]: return result
print(f"Инструмент: {req['name']}")
print(f"Аргументы: {req['args']}") def print_interrupt(interrupt):
if "description" in req: action_requests = interrupt['action_requests']
print(req["description"]) review_configs = interrupt['review_configs']
choice = input("a=approve, r=reject: ") print("\n--- Подтверждение ---")
if choice.lower() == "a": for idx, action in enumerate(action_requests):
decisions.append({"type": "approve"}) name = action.get("name")
else: args = action.get("args")
msg = input("Причина отказа: ") description = action.get("description", "")
decisions.append({"type": "reject", "message": msg}) print(f"{idx+1}. Инструмент: {name}")
result = await agent.ainvoke(Command(resume={"decisions": decisions}), config=config) print(f" Аргументы: {args}")
print("\nОтвет: ", result["messages"][-1]["content"]) 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__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())