feat: solution for 'Human-in-the-Loop через middleware'
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.log
|
||||||
@@ -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: при каждом вызове инструмента агент останавливается
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
langchain==0.2.0
|
||||||
|
langgraph==0.0.1
|
||||||
|
openai==1.3.0
|
||||||
+164
@@ -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)
|
||||||
Reference in New Issue
Block a user