Human-in-the-Loop через middleware: README.md
This commit is contained in:
@@ -1,52 +1,80 @@
|
|||||||
# Human‑in‑the‑Loop Middleware Demo
|
# Human‑in‑the‑Loop Middleware Demo
|
||||||
|
A minimal LangChain project that demonstrates how to pause an agent before every tool call and let a user approve or reject the action via the terminal.
|
||||||
This repository contains a minimal LangChain/LangGraph example that demonstrates how to pause an agent whenever it wants to call a tool and ask the user for approval (`approve` / `reject`).
|
|
||||||
The pausing logic is handled by **`HumanInTheLoopMiddleware`**, which automatically generates a prompt, receives the user’s decision, and resumes execution via a `Command(resume={…})`.
|
|
||||||
|
|
||||||
> **Why this matters** – In many real‑world scenarios an LLM should not act autonomously. By inserting a human checkpoint you can keep control over every tool call, ensuring safety, compliance or simply giving the user a chance to correct mistakes.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 Installation
|
## Table of Contents
|
||||||
|
- [Project Overview](#project-overview)
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Running the Demo](#running-the-demo)
|
||||||
|
- [1️⃣ `main.py` – Interactive Agent](#1-mainpy---interactive-agent)
|
||||||
|
- [2️⃣ `tool_example.py` – Custom Tool](#2-tool_exempley---custom-tool)
|
||||||
|
- [Example Interaction](#example-interaction)
|
||||||
|
- [Project Structure](#project-structure)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
The agent is created with **HumanInTheLoopMiddleware**.
|
||||||
|
When the agent wants to use a tool (e.g., `get_weather`), it pauses, prints a prompt in the terminal, and waits for user input:
|
||||||
|
|
||||||
|
| Decision | Effect |
|
||||||
|
|----------|--------|
|
||||||
|
| `approve` | Tool call proceeds |
|
||||||
|
| `reject` | Tool call is skipped; the agent continues reasoning |
|
||||||
|
| `edit` | (Optional) User can modify the tool arguments before resuming |
|
||||||
|
|
||||||
|
The middleware automatically handles the pause/resume logic via `Command(resume={"decisions": [...]})`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Python 3.10+
|
||||||
|
- A working OpenAI API key (or any LLM provider supported by LangChain)
|
||||||
|
|
||||||
|
Set your key in an environment variable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1️⃣ Clone the repo
|
export OPENAI_API_KEY="sk-..."
|
||||||
git clone https://github.com/your‑org/human‑in‑the‑loop-demo.git
|
```
|
||||||
cd human‑in‑the‑loop-demo
|
|
||||||
|
|
||||||
# 2️⃣ Create a virtual environment (recommended)
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. **Clone the repo**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/human-in-loop-demo.git
|
||||||
|
cd human-in-loop-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create a virtual environment (optional but recommended)**
|
||||||
|
|
||||||
|
```bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||||
|
```
|
||||||
|
|
||||||
# 3️⃣ Install dependencies
|
3. **Install dependencies**
|
||||||
|
|
||||||
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Requirements**
|
> `requirements.txt` contains:
|
||||||
> * Python ≥ 3.10
|
> ```
|
||||||
> * `langchain` (>=0.2)
|
> langchain==0.2.*
|
||||||
> * `langgraph` (>=0.1)
|
> langgraph==0.1.*
|
||||||
> * `openai` or any LLM provider you prefer
|
> python-dotenv # optional, for .env support
|
||||||
|
> ```
|
||||||
The `requirements.txt` file contains the exact versions used for this demo.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Project Structure
|
## Running the Demo
|
||||||
|
|
||||||
```
|
### 1️⃣ `main.py` – Interactive Agent
|
||||||
.
|
|
||||||
├── agent.py # Agent definition with HumanInTheLoopMiddleware
|
|
||||||
├── main.py # CLI entry point – runs the agent interactively
|
|
||||||
├── tools.py # Example tool(s) (e.g., get_weather)
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Running the Demo
|
|
||||||
|
|
||||||
### 1️⃣ Start the Agent
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python main.py
|
python main.py
|
||||||
@@ -55,106 +83,55 @@ python main.py
|
|||||||
You will see a prompt like:
|
You will see a prompt like:
|
||||||
|
|
||||||
```
|
```
|
||||||
Assistant: What would you like to do?
|
Agent: What would you like to know?
|
||||||
User: Tell me the weather in London.
|
User: Tell me the weather in London.
|
||||||
Assistant: (Thinking...)
|
Agent: (pausing) Would you like to call tool 'get_weather' with arguments {'location': 'London'}? [approve/reject/edit]
|
||||||
|
>
|
||||||
```
|
```
|
||||||
|
|
||||||
When the agent decides to call `get_weather`, it pauses and prints:
|
Type `approve`, `reject`, or `edit` and press **Enter**.
|
||||||
|
If you choose `edit`, you’ll be prompted to modify the JSON arguments.
|
||||||
|
|
||||||
```
|
### 2️⃣ `tool_example.py` – Custom Tool
|
||||||
Human-in-the-loop checkpoint:
|
|
||||||
The assistant wants to execute tool 'get_weather' with arguments {'location': 'London'}.
|
|
||||||
Please type one of the following decisions: approve / reject
|
|
||||||
```
|
|
||||||
|
|
||||||
Type **`approve`** to let the tool run, or **`reject`** to cancel it.
|
The demo includes a simple weather tool (`get_weather`).
|
||||||
After your decision, the agent continues:
|
You can add more tools by editing `tools/__init__.py` or creating new modules.
|
||||||
|
|
||||||
```
|
|
||||||
Assistant: The weather in London is 18°C and sunny.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2️⃣ Using a Different Tool
|
|
||||||
|
|
||||||
If you want to test another tool (e.g., `get_time`), add it to `tools.py`, import it in `agent.py`, and adjust the middleware configuration accordingly.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 Example Usage
|
## Example Interaction
|
||||||
|
|
||||||
```python
|
```
|
||||||
# main.py
|
$ python main.py
|
||||||
from agent import agent
|
Agent: Hi! How can I help you today?
|
||||||
|
User: What's the weather in Paris?
|
||||||
|
|
||||||
def run():
|
Agent: (pausing) Would you like to call tool 'get_weather' with arguments {'location': 'Paris'}? [approve/reject/edit]
|
||||||
print("Welcome! Type 'exit' to quit.")
|
> approve
|
||||||
while True:
|
Tool get_weather called. Result: "Sunny, 22°C"
|
||||||
user_input = input("\nUser: ")
|
|
||||||
if user_input.lower() == "exit":
|
|
||||||
break
|
|
||||||
response = agent.invoke({"input": user_input})
|
|
||||||
print(f"Assistant: {response['output']}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
Agent: The current weather in Paris is Sunny, 22°C.
|
||||||
run()
|
User: Thanks!
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
If you type `reject`, the agent will continue without calling the tool:
|
||||||
# agent.py
|
|
||||||
from langchain.agents import create_agent
|
|
||||||
from langchain.agents.middleware import HumanInTheLoopMiddleware
|
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
|
||||||
|
|
||||||
from tools import get_weather # <-- your tool(s)
|
|
||||||
|
|
||||||
memory = MemorySaver()
|
|
||||||
|
|
||||||
agent = create_agent(
|
|
||||||
model="gpt-4o-mini", # or any LLM you have access to
|
|
||||||
tools=[get_weather],
|
|
||||||
system_prompt="You are a helpful assistant.",
|
|
||||||
middleware=[
|
|
||||||
HumanInTheLoopMiddleware(
|
|
||||||
interrupt_on={"get_weather": True},
|
|
||||||
description_prefix="Please confirm the tool call:",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
checkpointer=memory,
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
Agent: (pausing) Would you like to call tool 'get_weather' with arguments {'location': 'Paris'}? [approve/reject/edit]
|
||||||
```python
|
> reject
|
||||||
# tools.py
|
Agent: I couldn't retrieve the weather. Could you provide more details?
|
||||||
from langchain.tools import BaseTool
|
|
||||||
|
|
||||||
class GetWeather(BaseTool):
|
|
||||||
name = "get_weather"
|
|
||||||
description = "Returns current weather for a location."
|
|
||||||
args_schema = ... # define your schema here
|
|
||||||
|
|
||||||
def _run(self, location: str) -> str:
|
|
||||||
# Dummy implementation – replace with real API call
|
|
||||||
return f"The weather in {location} is 18°C and sunny."
|
|
||||||
|
|
||||||
get_weather = GetWeather()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Customization Tips
|
## Project Structure
|
||||||
|
|
||||||
| Feature | How to change |
|
```
|
||||||
|---------|---------------|
|
human-in-loop-demo/
|
||||||
| **Allowed decisions** | `interrupt_on={"get_weather": {"allowed_decisions": ["approve", "reject"]}}` |
|
├── main.py # Entry point – runs the interactive agent
|
||||||
| **Prompt prefix** | Change `description_prefix` in the middleware. |
|
├── tool_example.py # Example custom tool (get_weather)
|
||||||
| **Tool list** | Add or remove tools in the `tools=[...]` array. |
|
├── requirements.txt # Dependencies
|
||||||
| **LLM model** | Pass a different model name or a custom LLM instance to `create_agent`. |
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
---
|
Feel free to extend the project by adding new tools, customizing prompts, or integrating a different LLM provider. Happy hacking!
|
||||||
|
|
||||||
## 📜 License
|
|
||||||
|
|
||||||
This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
---
|
|
||||||
Reference in New Issue
Block a user