commit 082d5fb669012b2aace90b1d2e17b9eb394e386c Author: kuzakhmetovartur Date: Wed Jun 24 15:06:13 2026 +0300 feat: solution for 'Human-in-the-Loop через middleware' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b16538b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.env +dist/ +build/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4a1017 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Human-in-the-Loop через middleware + +Главная +Мои задания +Human-in-the-Loop через middleware +5Д +EN +Human-in-the-Loop через middleware +Зачёт +Версия 1 +Дедлайн сдачи: 31.08.2026 + +В работе + +Редактирование ответа + +Заполните ответ и отправьте работу на проверку преподавателю. + +Тип ответа +Текст +Ссылка +Файлы +Текст ответа +Прикреплённые файлы +Загрузить файл +Отправить на проверку +Отменить + +Задание + +Задание: Human-in-the-Loop через middleware +Цель + +Доработать агента с HumanInTheLoopMiddleware: при каждом вызове инструмента агент останавливается \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2f264e0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +langchain==0.2.0 +langgraph==0.0.1 +openai==1.3.0 \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..fde6d67 --- /dev/null +++ b/src/main.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +""" +Human-in-the-Loop Agent Demo + +This script demonstrates how to use LangChain's HumanInTheLoopMiddleware +to pause an agent when it wants to call a tool, let the user approve or +reject the call, and then resume execution. + +Requirements: +- langchain +- langgraph +- openai (for ChatOpenAI) +""" + +import os +import sys +from typing import List, Dict, Any + +# LangChain imports +from langchain.chat_models import ChatOpenAI +from langchain.tools import tool +from langchain.agents import create_agent +from langchain.agents.middleware import HumanInTheLoopMiddleware + +# LangGraph imports +from langgraph.checkpoint.memory import MemorySaver +from langgraph.schema import Command + +# --------------------------------------------------------------------------- # +# Tool definition +# --------------------------------------------------------------------------- # + +@tool +def get_weather(city: str) -> str: + """ + Return a simple weather description for the given city. + """ + # In a real scenario you might call an external API here. + return f"Sunny in {city}." + +# --------------------------------------------------------------------------- # +# Agent setup +# --------------------------------------------------------------------------- # + +def build_agent() -> Any: + """ + Build and return a LangChain agent configured with HumanInTheLoopMiddleware. + """ + # Ensure OpenAI API key is available + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + print("Error: OPENAI_API_KEY environment variable not set.") + sys.exit(1) + + llm = ChatOpenAI( + model_name="gpt-3.5-turbo", + temperature=0, + openai_api_key=api_key, + ) + + agent = create_agent( + model=llm, + tools=[get_weather], + system_prompt="Ты полезный ассистент", + middleware=[ + HumanInTheLoopMiddleware( + interrupt_on={"get_weather": True}, + description_prefix="Подтвердите вызов инструмента", + ), + ], + checkpointer=MemorySaver(), + ) + return agent + +# --------------------------------------------------------------------------- # +# Human-in-the-loop loop +# --------------------------------------------------------------------------- # + +def prompt_decision(request: Dict[str, Any]) -> str: + """ + Prompt the user for a decision on a tool call. + Returns 'approve' or 'reject'. + """ + name = request.get("name", "unknown") + args = request.get("args", {}) + description = request.get("description", "") + + print("\n=== Tool Call ===") + print(f"Name: {name}") + print(f"Args: {args}") + if description: + print(f"Description: {description}") + + while True: + choice = input("Approve (a) / Reject (r) [a/r]: ").strip().lower() + if choice == "a": + return "approve" + elif choice == "r": + return "reject" + else: + print("Invalid input. Please enter 'a' to approve or 'r' to reject.") + +def run_agent(agent: Any, user_message: str, thread_id: str = "session-1") -> None: + """ + Run the agent with Human-in-the-Loop, handling pauses and resumes. + """ + config = {"configurable": {"thread_id": thread_id}} + + # Initial invocation + result = agent.invoke( + {"messages": [{"role": "human", "content": user_message}]}, + config=config, + ) + + # Loop until the agent finishes (no '__interrupt__' key) + while "__interrupt__" in result: + interrupt = result["__interrupt__"][0] + # The interrupt value is a dict with action_requests and review_configs + interrupt_value = interrupt.get("value", {}) + action_requests = interrupt_value.get("action_requests", []) + # review_configs = interrupt_value.get("review_configs", []) + + decisions = [] + for req in action_requests: + decision = prompt_decision(req) + decisions.append( + { + "name": req["name"], + "args": req["args"], + "decision": decision, + } + ) + + # Resume the agent with the collected decisions + result = agent.invoke( + Command(resume={"decisions": decisions}), + config=config, + ) + + # Agent finished; display the final response + messages = result.get("messages", []) + if messages: + # Find the last assistant message + for msg in reversed(messages): + if msg.get("role") == "assistant": + print("\n=== Agent Response ===") + print(msg.get("content", "").strip()) + break + else: + print("\nNo messages returned by the agent.") + +# --------------------------------------------------------------------------- # +# Main entry point +# --------------------------------------------------------------------------- # + +if __name__ == "__main__": + agent = build_agent() + print("Human-in-the-Loop Agent Demo") + print("----------------------------") + user_input = input("Введите ваш запрос: ").strip() + if not user_input: + print("Empty input. Exiting.") + sys.exit(0) + run_agent(agent, user_input) \ No newline at end of file