Files
task-69a86305c46fd26feae6bcaa/main.py
T
2026-05-26 07:10:59 +00:00

121 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Human-in-the-Loop через HumanInTheLoopMiddleware (LangChain)."""
from __future__ import annotations
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
load_dotenv()
# LLM: OpenRouter из .env или локальный LM Studio
llm = ChatOpenAI(
model=os.getenv("OPENAI_MODEL", "openai/gpt-oss-20b:free"),
base_url=os.getenv("OPENAI_BASE_URL", "https://openrouter.ai/api/v1"),
api_key=os.getenv("OPENAI_API_KEY", "fake"),
temperature=0.0,
)
@tool
def get_weather(city: str, date: str = "сегодня") -> str:
"""Получить погоду в городе на указанную дату."""
return f"В {city} на {date}: около +5°C, облачно, без осадков."
memory = MemorySaver()
agent = create_agent(
model=llm,
tools=[get_weather],
system_prompt="Ты полезный ассистент",
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"get_weather": {"allowed_decisions": ["approve", "reject"]},
},
description_prefix="Подтвердите вызов инструмента",
),
],
checkpointer=memory,
)
def _print_action_requests(action_requests: list[dict]) -> None:
print("\n--- Подтверждение ---")
for idx, action in enumerate(action_requests, start=1):
print(f"[{idx}] Инструмент: {action.get('name')}")
print(f" Аргументы: {action.get('args')}")
description = action.get("description")
if description:
print(f" Описание: {description}")
def _ask_decision() -> dict:
while True:
choice = input("a = approve, r = reject: ").strip().lower()
if choice in ("a", "approve"):
return {"type": "approve"}
if choice in ("r", "reject"):
message = input("Сообщение для агента (причина отказа): ").strip()
return {"type": "reject", "message": message or "Отклонено пользователем"}
print("Введите 'a' или 'r'.")
def _collect_decisions(action_requests: list[dict]) -> list[dict]:
_print_action_requests(action_requests)
return [_ask_decision() for _ in action_requests]
def _extract_interrupt(result: dict) -> tuple[list[dict], list[dict]] | None:
if "__interrupt__" not in result:
return None
interrupt_value = result["__interrupt__"][0].value
return interrupt_value.get("action_requests", []), interrupt_value.get("review_configs", [])
def run_with_hitl(user_message: str, thread_id: str = "hitl-middleware-session-1") -> str:
"""Запуск агента с циклом подтверждения инструментов."""
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
{"messages": [{"role": "human", "content": user_message}]},
config=config,
)
while True:
interrupted = _extract_interrupt(result)
if not interrupted:
break
action_requests, _review_configs = interrupted
decisions = _collect_decisions(action_requests)
result = agent.invoke(Command(resume={"decisions": decisions}), config=config)
messages = result.get("messages", [])
if not messages:
return ""
return str(messages[-1].content)
def main() -> None:
print("Human-in-the-Loop (middleware). Введите 'exit' для выхода.\n")
config_thread = "hitl-middleware-session-1"
while True:
user_input = input("Вы: ").strip()
if user_input.lower() in ("exit", "quit", "q"):
break
if not user_input:
continue
answer = run_with_hitl(user_input, thread_id=config_thread)
print(f"\nАгент: {answer}\n")
if __name__ == "__main__":
main()