Human-in-the-Loop через middleware: README.md
This commit is contained in:
@@ -1,48 +1,54 @@
|
||||
# Human‑in‑the‑Loop Agent via Middleware
|
||||
# Human‑in‑the‑Loop Agent via Middleware (LangGraph)
|
||||
|
||||
This repository contains a minimal example of an LLM agent that pauses whenever it wants to call a tool and asks the user for approval before proceeding.
|
||||
The core idea is to use **`HumanInTheLoopMiddleware`** from LangChain, which intercepts every tool invocation, prints a prompt with the action details, and waits for the user to respond (`approve`, `reject`, or optionally edit the request).
|
||||
This repository contains a minimal example of a **Human‑in‑the‑Loop** agent built on top of LangGraph.
|
||||
The agent pauses whenever it needs to call an external tool, prints the tool request and waits for a user decision (`approve` or `reject`). After the decision is supplied, execution resumes automatically.
|
||||
|
||||
> **Why this matters** – In many real‑world scenarios you want an LLM to ask for human confirmation before performing potentially sensitive actions (e.g., sending emails, accessing databases, calling external APIs).
|
||||
> ⚠️ The example uses OpenAI’s API (or any compatible LLM). Make sure you have an API key set in the environment variable `OPENAI_API_KEY`.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Project Structure](#project-structure)
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Running the Agent](#running-the-agent)
|
||||
- [Interactive Mode](#interactive-mode)
|
||||
- [Scripted Example](#scripted-example)
|
||||
- [Interactive Demo (`solution.py`)](#interactive-demo-solutionpy)
|
||||
- [Unit Tests (`tests/test_solution.py`)](#unit-tests-test_solutionpy)
|
||||
- [Example Usage](#example-usage)
|
||||
- [Extending the Agent](#extending-the-agent)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Human‑in‑the‑Loop** | Agent stops before calling any tool, prints the request and waits for user input. |
|
||||
| **Interrupts via `interrupt_before=["tools"]`** | Configurable interruption point in LangGraph. |
|
||||
| **Tool Example** | Simple `get_weather(city, date)` function that returns a mock weather string. |
|
||||
| **Checkpointing** | Uses an in‑memory checkpoint (`MemorySaver`) to preserve state across interruptions. |
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+
|
||||
- An OpenAI API key (or any compatible LLM endpoint)
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
├── solution.py # Main script with the agent implementation
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
`solution.py` contains:
|
||||
|
||||
1. **LLM configuration** – uses `ChatOpenAI`.
|
||||
2. **A simple tool** (`get_weather`) that returns a fake weather string.
|
||||
3. **Memory checkpoint** via `MemorySaver`.
|
||||
4. **Agent creation** with `create_react_agent` and the middleware.
|
||||
5. **Execution loop** that keeps asking for user input until the conversation ends.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone the repo**
|
||||
1. **Clone the repository**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/human-in-the-loop-agent.git
|
||||
cd human-in-the-loop-agent
|
||||
git clone https://github.com/yourusername/human-in-loop-langgraph.git
|
||||
cd human-in-loop-langgraph
|
||||
```
|
||||
|
||||
2. **Create a virtual environment (optional but recommended)**
|
||||
@@ -55,85 +61,80 @@ The core idea is to use **`HumanInTheLoopMiddleware`** from LangChain, which int
|
||||
3. **Install dependencies**
|
||||
|
||||
```bash
|
||||
pip install --upgrade pip
|
||||
pip install langchain langgraph openai
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. **Set your OpenAI API key**
|
||||
*If you don’t have a `requirements.txt`, create one with the following content:*
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
# Windows: setx OPENAI_API_KEY "sk-..."
|
||||
```text
|
||||
langchain-openai>=0.2.0
|
||||
langgraph>=0.1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running the Agent
|
||||
|
||||
### Interactive Mode
|
||||
### Interactive Demo (`solution.py`)
|
||||
|
||||
Simply run the script:
|
||||
The main script demonstrates how to start the agent and handle interruptions.
|
||||
|
||||
```bash
|
||||
python solution.py
|
||||
```
|
||||
|
||||
You will see a prompt like:
|
||||
**What happens:**
|
||||
|
||||
```
|
||||
Agent wants to call tool `get_weather` with arguments:
|
||||
city = "Moscow"
|
||||
date = "2025-10-01"
|
||||
1. The user enters a prompt (e.g., “What’s the weather in Paris on 2024‑12‑01?”).
|
||||
2. The agent processes the request, decides it needs to call `get_weather`, and pauses.
|
||||
3. The tool request is printed:
|
||||
|
||||
Please type one of: approve / reject (or edit <new_args>)
|
||||
>
|
||||
```
|
||||
```
|
||||
Tool requested: get_weather
|
||||
Arguments: {'city': 'Paris', 'date': '2024-12-01'}
|
||||
```
|
||||
|
||||
Type **`approve`** to let the agent proceed, or **`reject`** to stop it.
|
||||
If you want to modify the arguments before approval, use `edit city=London date=2025-12-25`.
|
||||
4. You are prompted to type `approve` or `reject`.
|
||||
5. After your decision, the agent resumes and prints the final answer.
|
||||
|
||||
The conversation continues until the user types `stop` or the agent finishes its plan.
|
||||
---
|
||||
|
||||
### Scripted Example
|
||||
### Unit Tests (`tests/test_solution.py`)
|
||||
|
||||
You can also run a quick demo that automatically approves all calls:
|
||||
Run the test suite to verify that the interruption logic works as expected:
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
from solution import agent, llm, memory
|
||||
# Override middleware to auto‑approve for demonstration
|
||||
agent.middleware[0].interrupt_on = {"get_weather": False}
|
||||
print(agent.run("What's the weather in New York tomorrow?"))
|
||||
PY
|
||||
pytest tests/test_solution.py
|
||||
```
|
||||
|
||||
The tests simulate a user approving the tool call automatically and check that the final output contains the weather string.
|
||||
|
||||
---
|
||||
|
||||
## Example Usage
|
||||
|
||||
```bash
|
||||
$ python solution.py
|
||||
User: What's the weather in Paris next Friday?
|
||||
Agent wants to call tool `get_weather` with arguments:
|
||||
city = "Paris"
|
||||
date = "2025-10-06"
|
||||
Below is a quick snippet you can paste into a Python REPL or another script to see the agent in action:
|
||||
|
||||
Please type one of: approve / reject (or edit <new_args>)
|
||||
> approve
|
||||
```python
|
||||
from solution import agent, memory # assuming solution.py defines them
|
||||
|
||||
Assistant: Погода в Париже на 2025‑10‑06: солнечно 25°C.
|
||||
User: Thank you!
|
||||
# Start a new thread/session
|
||||
config = {"configurable": {"thread_id": "demo-session"}}
|
||||
|
||||
# Invoke with a user message
|
||||
response = agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "What's the weather in Tokyo on 2025-01-15?"}]},
|
||||
config=config,
|
||||
)
|
||||
|
||||
print("\nFinal response:")
|
||||
print(response["messages"][-1]["content"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extending the Agent
|
||||
|
||||
1. **Add more tools** – decorate any function with `@tool` and add it to the `tools` list in `create_react_agent`.
|
||||
2. **Change the interrupt policy** – modify `interrupt_on` dict (e.g., `{ "get_weather": True, "send_email": False }`).
|
||||
3. **Persist conversation state** – replace `MemorySaver()` with a database checkpoint if you need long‑term memory.
|
||||
4. **Custom prompts** – tweak `system_prompt` or add a custom `description_prefix`.
|
||||
When you run this, you’ll see the same interruption prompt as described above.
|
||||
|
||||
---
|
||||
|
||||
Happy hacking! 🚀
|
||||
## License
|
||||
|
||||
MIT © 2026. Feel free to fork and adapt for your own projects.
|
||||
Reference in New Issue
Block a user