Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
1129 lines
33 KiB
Markdown
1129 lines
33 KiB
Markdown
# Event-Triggered AI Agent with MCP Tools / 事件驱动 AI Agent(MCP 工具)
|
||
|
||
> Companion code for *AI Agents in Depth*, Chapter 6 — **Experiment 6-1 ★★★**. FastAPI event-driven Agent with async MCP tool loading from collaboration / execution / perception servers.
|
||
> 配套《深入理解 AI Agent》第 4 章 **实验 6-1 ★★★**。FastAPI 事件驱动 Agent,异步加载协作/执行/感知 MCP 工具。
|
||
|
||
← [Chapter 4 index / 返回第 4 章目录](../README.md)
|
||
|
||
---
|
||
|
||
## English
|
||
|
||
A modern AI agent with **native async support** that responds to events from various sources. Built with **FastAPI** and integrated with **42 MCP tools** for enhanced capabilities including browser automation, web search, document processing, and more.
|
||
|
||
### Features
|
||
|
||
#### Core Capabilities
|
||
- ✅ **Native Async** - FastAPI with clean async/await support
|
||
- ✅ **42 MCP Tools** - Automatically loaded from 3 MCP servers
|
||
- ✅ **Event-Driven** - Responds to web messages, emails, GitHub updates, timers
|
||
- ✅ **System Hints** - Timestamps, tool counters, TODO management
|
||
- ✅ **Auto API Docs** - Interactive Swagger UI at `/docs`
|
||
- ✅ **Background Tasks** - Process monitoring and system alerts
|
||
|
||
#### MCP Tool Categories
|
||
|
||
**Collaboration Tools** (18 tools):
|
||
- Browser automation (navigate, screenshot, execute tasks)
|
||
- Notifications (email, Telegram, Slack, Discord)
|
||
- Human-in-the-loop (admin approval, input requests)
|
||
- Timer management (one-time, recurring)
|
||
|
||
**Execution Tools** (6 tools):
|
||
- File operations (write, edit with verification)
|
||
- Code execution (Python interpreter, shell commands)
|
||
- External integrations (Google Calendar, GitHub PRs)
|
||
|
||
**Perception Tools** (18 tools):
|
||
- Web search and content extraction
|
||
- Document reading (PDF, DOCX, PPTX)
|
||
- Multimodal parsing (images, videos, webpages)
|
||
- Public data (weather, stocks, Wikipedia, ArXiv)
|
||
- Private data (Google Calendar, Notion)
|
||
|
||
### Event-driven demo (offline runnable, no API key)
|
||
|
||
Before starting the HTTP server, run `event_loop_demo.py`. In one process it demonstrates the chapter core idea—**the external world wakes the Agent**. The script registers three trigger types; background threads push structured events into a shared queue when events occur; the event loop dequeues and wakes the Agent—“register → trigger → wake → handle”:
|
||
|
||
| Trigger | Class | Book concept |
|
||
|--------|----|----|
|
||
| One-shot timer | `OneShotTimer` | `set_timer` one-shot (e.g. “call DMV next Monday 10:00”) |
|
||
| Recurring timer | `RecurringTimer` | `set_timer` recurring / Heartbeat (e.g. “check server hourly”) |
|
||
| File watch | `FileWatchTrigger` | File-change triggers like n8n |
|
||
|
||
`--mock` offline mode does not call an LLM; it prints simulated actions when the Agent is woken—**no API key required**:
|
||
|
||
```bash
|
||
# Offline: all triggers (one-shot + recurring + file watch)
|
||
python event_loop_demo.py --mock
|
||
|
||
# One-shot only; fire after 2s; run 6s total
|
||
python event_loop_demo.py --mock --trigger timer --delay 2 --duration 6
|
||
|
||
# Recurring every 3s
|
||
python event_loop_demo.py --mock --trigger recurring --interval 3 --duration 12
|
||
|
||
# Watch a directory; write a file to trigger (other terminal: echo hello > watched_dir/a.txt)
|
||
python event_loop_demo.py --mock --trigger file --watch-dir watched_dir
|
||
```
|
||
|
||
Sample offline output (excerpt):
|
||
|
||
```
|
||
⏱️ [OneShotTimer(daily_backup_check)] 已注册:2 秒后触发
|
||
🔁 [RecurringTimer(health_check)] 已注册:每 3 秒触发一次
|
||
🟢 事件循环启动,将运行 8 秒,等待事件唤醒 Agent...
|
||
⚡ [OneShotTimer(daily_backup_check)] 触发事件 -> timer_trigger: 一次性定时器到期:请检查每日备份是否已经完成。
|
||
📥 事件循环取出第 1 个事件 -> 唤醒 Agent
|
||
🤖 Agent 被唤醒,收到消息: [Timer daily_backup_check triggered] 一次性定时器到期:请检查每日备份是否已经完成。
|
||
🛠️ [模拟动作] 读取定时任务上下文 -> 执行例行检查 -> 汇报结果
|
||
✅ Agent 处理完成: 已响应 timer_trigger 事件
|
||
```
|
||
|
||
Drop `--mock` to use a real LLM (built-in tools only by default, no MCP); set the provider API key:
|
||
|
||
```bash
|
||
export KIMI_API_KEY='your-api-key-here'
|
||
python event_loop_demo.py --trigger timer --provider kimi
|
||
|
||
# Alibaba Cloud Model Studio / Bailian (Qwen)
|
||
export DASHSCOPE_API_KEY='your-dashscope-api-key-here'
|
||
python event_loop_demo.py --trigger timer --provider dashscope
|
||
# `qwen` and `bailian` are accepted aliases; international keys may use
|
||
# DASHSCOPE_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
|
||
```
|
||
|
||
> **Universal OpenRouter fallback**: if the chosen provider’s key is missing (default `kimi`) but `OPENROUTER_API_KEY` is set, `event_loop_demo.py` / `server.py` / `quickstart.py` switch to `openrouter` (set model with `LLM_MODEL=openai/gpt-5.6-luna`). Example:
|
||
> `OPENROUTER_API_KEY=your-openrouter-api-key LLM_MODEL=openai/gpt-5.6-luna python event_loop_demo.py --trigger timer`
|
||
|
||
Full flags: `python event_loop_demo.py --help`.
|
||
|
||
### Exact Experiment 6-1: real mailbox listener
|
||
|
||
`unipile_mailbox_experiment.py` implements the manuscript's complete
|
||
three-email acceptance scenario against Unipile's real Email and Calendar
|
||
APIs. It uses documented mailbox polling (`GET /api/v1/emails`) as the event
|
||
source, converts provider email objects into a FIFO queue, and then performs:
|
||
|
||
1. meeting invitation → live calendar/event query → hashed accept/decline draft;
|
||
2. customer complaint → order extraction → durable high-priority notification;
|
||
3. marketing email → provider `PUT` archive operation → provider `GET` verification.
|
||
|
||
The runner sends three uniquely tagged messages through Unipile before waiting
|
||
for their inbound mailbox objects. A local file or synthetic event can never
|
||
satisfy its acceptance gates. Configure a real project first:
|
||
|
||
```bash
|
||
export UNIPILE_DSN='apiN.unipile.com:PORT'
|
||
export UNIPILE_ACCESS_TOKEN='...'
|
||
|
||
# Read-only authentication/account check
|
||
python unipile_mailbox_experiment.py --preflight-only
|
||
|
||
# Full unattended workflow; the listener address is normally inferred
|
||
python unipile_mailbox_experiment.py
|
||
|
||
# If account metadata does not expose the mailbox address
|
||
python unipile_mailbox_experiment.py --recipient 'test-mailbox@example.com'
|
||
```
|
||
|
||
Every run writes a redacted, hash-manifested bundle under
|
||
`validation/experiment_6_1/`. Invalid credentials produce `status: blocked`
|
||
with the observed HTTP receipt; they are never treated as experiment evidence.
|
||
The official endpoint versions used by the implementation are pinned in
|
||
`experiment_protocol.json`.
|
||
|
||
### Quick Start
|
||
|
||
#### Installation
|
||
|
||
```bash
|
||
# From the repository root: use the shared Chapter 4 environment
|
||
uv sync --locked --python 3.12 --extra ch4
|
||
|
||
# Activate it before changing directories:
|
||
# macOS/Linux:
|
||
source .venv/bin/activate
|
||
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
||
# Windows cmd: .venv\Scripts\activate.bat
|
||
|
||
# pip fallback when uv is not installed:
|
||
# python -m pip install -e ".[ch4]"
|
||
|
||
cd chapter4/agent-with-event-trigger
|
||
|
||
# Single-project compatibility path, still supported during migration:
|
||
# python -m pip install -r requirements.txt
|
||
|
||
# Set up environment
|
||
cp env.example .env
|
||
# Edit .env and add your API key
|
||
export KIMI_API_KEY='your-api-key-here'
|
||
export DASHSCOPE_API_KEY='your-dashscope-api-key-here' # for dashscope/qwen/bailian
|
||
```
|
||
|
||
#### Tests and Manual Demo
|
||
|
||
Automated tests are offline regressions under `tests/` and do not require an API key.
|
||
|
||
```bash
|
||
# From the repository root, include the dev extra for pytest:
|
||
uv sync --locked --python 3.12 --extra ch4 --extra dev
|
||
|
||
# pip testing fallback:
|
||
# python -m pip install -e ".[ch4,dev]"
|
||
|
||
cd chapter4/agent-with-event-trigger
|
||
python -m pytest tests
|
||
```
|
||
|
||
The standalone live demo was moved to `tests/manual/demo.py`; it still requires a provider API key and is not collected by pytest:
|
||
|
||
```bash
|
||
KIMI_API_KEY='your-api-key-here' python tests/manual/demo.py
|
||
```
|
||
|
||
#### Start the Server
|
||
|
||
```bash
|
||
python server.py
|
||
```
|
||
|
||
CLI flags override env vars; see `python server.py --help`:
|
||
|
||
```bash
|
||
python server.py --port 9000 # custom port
|
||
python server.py --provider doubao # LLM provider
|
||
python server.py --no-mcp # built-in tools only, no MCP
|
||
```
|
||
|
||
Output:
|
||
```
|
||
🤖 EVENT-TRIGGERED AGENT SERVER (FastAPI)
|
||
✅ Starting server on port 8000
|
||
📡 API Documentation: http://localhost:8000/docs
|
||
📊 ReDoc: http://localhost:8000/redoc
|
||
|
||
🚀 Starting Event-Triggered Agent Server (FastAPI)
|
||
✅ Agent initialized with kimi provider
|
||
🔄 MCP tools enabled (default) - loading asynchronously...
|
||
✅ Discovered tools from 'collaboration': 18 tools
|
||
✅ Discovered tools from 'execution': 6 tools
|
||
✅ Discovered tools from 'perception': 18 tools
|
||
✅ MCP tools loaded: 42 tools available
|
||
✅ Server ready to receive events
|
||
|
||
INFO: Uvicorn running on http://0.0.0.0:8000
|
||
```
|
||
|
||
#### Interactive API Documentation
|
||
|
||
Visit **http://localhost:8000/docs** to:
|
||
- 📖 Browse all available endpoints
|
||
- 🧪 Test API calls interactively
|
||
- 📝 See request/response schemas
|
||
- ⚡ Send events with one click
|
||
|
||
### API Endpoints
|
||
|
||
#### Core Endpoints
|
||
|
||
```bash
|
||
# Health check
|
||
curl http://localhost:8000/health
|
||
|
||
# Check MCP tools status
|
||
curl http://localhost:8000/mcp/status
|
||
|
||
# Send an event
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Search the web for Python async best practices",
|
||
"metadata": {"user": "demo"}
|
||
}'
|
||
|
||
# Get agent status
|
||
curl http://localhost:8000/agent/status
|
||
|
||
# Reset agent state
|
||
curl -X POST http://localhost:8000/agent/reset
|
||
|
||
# Reload MCP tools
|
||
curl -X POST http://localhost:8000/mcp/reload
|
||
```
|
||
|
||
#### Using the Interactive Docs
|
||
|
||
1. Open http://localhost:8000/docs
|
||
2. Click on any endpoint (e.g., `POST /event`)
|
||
3. Click "Try it out"
|
||
4. Fill in the request body
|
||
5. Click "Execute"
|
||
6. See the response instantly!
|
||
|
||
### Usage Examples
|
||
|
||
#### Running the Standalone Example
|
||
|
||
For a complete demonstration of MCP integration without the server, run:
|
||
|
||
```bash
|
||
python example_with_mcp.py
|
||
```
|
||
|
||
This standalone script:
|
||
- Initializes the agent with MCP tools enabled
|
||
- Loads all 42 tools from the 3 MCP servers
|
||
- Processes a sample event (web search task)
|
||
- Shows the complete flow from tool discovery to execution
|
||
- Properly cleans up MCP connections
|
||
|
||
Useful for:
|
||
- Testing MCP integration without running a server
|
||
- Understanding the async tool loading flow
|
||
- Debugging MCP connection issues
|
||
- Learning how to use the agent programmatically
|
||
|
||
#### Example 1: Web Search Task
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Search for the latest FastAPI features and summarize them",
|
||
"metadata": {"user": "demo"}
|
||
}'
|
||
```
|
||
|
||
The agent will:
|
||
1. Use `perception_web_search` to find results
|
||
2. Parse the content with `perception_webpage_reader`
|
||
3. Summarize findings in the response
|
||
|
||
#### Example 2: Browser Automation
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Navigate to example.com and take a screenshot",
|
||
"metadata": {}
|
||
}'
|
||
```
|
||
|
||
Uses:
|
||
- `collaboration_mcp_browser_navigate`
|
||
- `collaboration_mcp_browser_screenshot`
|
||
|
||
#### Example 3: Document Processing
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Download and summarize the PDF from https://example.com/doc.pdf",
|
||
"metadata": {}
|
||
}'
|
||
```
|
||
|
||
Uses:
|
||
- `perception_download`
|
||
- `perception_document_reader`
|
||
|
||
#### Example 4: Email Notification
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "timer_trigger",
|
||
"content": "Send daily report to admin@example.com",
|
||
"metadata": {"scheduled": true}
|
||
}'
|
||
```
|
||
|
||
Uses:
|
||
- `collaboration_mcp_send_email`
|
||
|
||
### Configuration
|
||
|
||
#### Environment Variables
|
||
|
||
```bash
|
||
# Required
|
||
export KIMI_API_KEY="your-key"
|
||
|
||
# Optional
|
||
export LLM_PROVIDER="kimi" # dashscope/qwen/bailian, kimi, siliconflow, doubao, openrouter
|
||
export LLM_MODEL="kimi-k3" # Override default model
|
||
export AGENT_PORT="8000" # Server port (default: 8000)
|
||
export ENABLE_MCP_TOOLS="true" # Enable MCP (default: true)
|
||
```
|
||
|
||
#### Disable MCP Tools
|
||
|
||
If you only want built-in tools:
|
||
|
||
```bash
|
||
ENABLE_MCP_TOOLS=false python server.py
|
||
```
|
||
|
||
#### Custom Port
|
||
|
||
```bash
|
||
AGENT_PORT=9000 python server.py
|
||
```
|
||
|
||
### Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ FastAPI Server │
|
||
│ (Native Async) │
|
||
└───────────┬─────────────────────────────────────────────┘
|
||
│
|
||
├─► Lifespan Events (Startup/Shutdown)
|
||
│ └─► Load MCP Tools Asynchronously
|
||
│
|
||
├─► Event Handler (Process incoming events)
|
||
│ └─► EventTriggeredAgent
|
||
│ ├─► System Hints (timestamps, TODOs)
|
||
│ ├─► Tool Execution (MCP + built-in)
|
||
│ └─► Trajectory Saving
|
||
│
|
||
└─► MCP Server Manager
|
||
├─► Collaboration Tools (18 tools)
|
||
├─► Execution Tools (6 tools)
|
||
└─► Perception Tools (18 tools)
|
||
```
|
||
|
||
### Project Structure
|
||
|
||
```
|
||
agent-with-event-trigger/
|
||
├── agent.py # Event-triggered agent with system hints
|
||
├── event_types.py # Event type definitions
|
||
├── event_loop_demo.py # Offline event-loop demo (timer / recurring / file-watch triggers)
|
||
├── server.py # FastAPI server (main entry point)
|
||
├── tests/ # Automated regressions and manual live demos
|
||
│ └── manual/
|
||
├── requirements.txt # Dependencies (FastAPI, uvicorn, MCP)
|
||
├── env.example # Environment template
|
||
├── README.md # This file
|
||
└── example_with_mcp.py # Standalone MCP example
|
||
```
|
||
|
||
### MCP Tools Reference
|
||
|
||
#### Check Available Tools
|
||
|
||
```bash
|
||
curl http://localhost:8000/mcp/status
|
||
```
|
||
|
||
Response shows:
|
||
- `tools`: List of all 42 tool names
|
||
- `tools_by_server`: Tools grouped by server
|
||
- `tools_count`: Total count
|
||
- `loaded`: Whether MCP tools are active
|
||
|
||
#### Tool Naming Convention
|
||
|
||
MCP tools use underscore prefixes:
|
||
- `collaboration_*` - Collaboration tools
|
||
- `execution_*` - Execution tools
|
||
- `perception_*` - Perception tools
|
||
|
||
Built-in tools (no prefix):
|
||
- `read_file`
|
||
- `write_file`
|
||
- `code_interpreter`
|
||
- `execute_command`
|
||
- `rewrite_todo_list`
|
||
- `update_todo_status`
|
||
|
||
### Event Types
|
||
|
||
```python
|
||
class EventType(Enum):
|
||
# External input events
|
||
WEB_MESSAGE = "web_message" # Web interface
|
||
IM_MESSAGE = "im_message" # Instant messaging
|
||
EMAIL_REPLY = "email_reply" # Email responses
|
||
GITHUB_PR_UPDATE = "github_pr_update" # PR notifications
|
||
TIMER_TRIGGER = "timer_trigger" # Scheduled tasks (one-shot / recurring)
|
||
FILE_CHANGE = "file_change" # File watch trigger (created / modified)
|
||
|
||
# System reminder events
|
||
USER_TIMEOUT = "user_timeout" # No user activity
|
||
PROCESS_TIMEOUT = "process_timeout" # Long-running process
|
||
SYSTEM_ALERT = "system_alert" # System warnings
|
||
```
|
||
|
||
#### Event Format
|
||
|
||
```json
|
||
{
|
||
"event_type": "web_message",
|
||
"content": "Your task description",
|
||
"metadata": {
|
||
"user_id": "user123",
|
||
"session_id": "session456"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Using the Client
|
||
|
||
The included client provides easy testing:
|
||
|
||
```bash
|
||
# Interactive mode
|
||
python client.py --mode interactive
|
||
|
||
# Test scenarios
|
||
python client.py --mode test
|
||
|
||
# Send a single event (defaults to web_message)
|
||
python client.py --message "Create a Python hello world script"
|
||
|
||
# Send a single event of a specific type
|
||
python client.py --event-type timer_trigger --message "检查每日备份"
|
||
```
|
||
|
||
### Security Considerations
|
||
|
||
For production deployment:
|
||
|
||
1. **HTTPS**: Use a reverse proxy (nginx, Caddy)
|
||
2. **Authentication**: Add API key validation
|
||
3. **Rate Limiting**: Prevent abuse
|
||
4. **Input Validation**: Sanitize all inputs
|
||
5. **CORS**: Configure allowed origins
|
||
6. **Environment**: Use secrets management
|
||
|
||
### Comparison: Flask vs FastAPI
|
||
|
||
| Feature | Old (Flask) | New (FastAPI) |
|
||
|---------|-------------|---------------|
|
||
| Framework | Flask (WSGI) | FastAPI (ASGI) |
|
||
| Async Support | ❌ Threads | ✅ Native async/await |
|
||
| MCP Integration | ⚠️ Complex | ✅ Clean |
|
||
| API Docs | ❌ Manual | ✅ Auto-generated |
|
||
| Performance | Good | **Better** (2-3x) |
|
||
| Port | 4242 | 8000 |
|
||
| Deprecation Warnings | N/A | ✅ Fixed (lifespan) |
|
||
|
||
### Troubleshooting
|
||
|
||
#### Port Already in Use
|
||
|
||
```bash
|
||
# Check what's using port 8000
|
||
lsof -i :8000
|
||
|
||
# Use different port
|
||
AGENT_PORT=8001 python server.py
|
||
```
|
||
|
||
#### MCP Tools Not Loading
|
||
|
||
```bash
|
||
# Check status
|
||
curl http://localhost:8000/mcp/status
|
||
|
||
# Look for error in response
|
||
# Common issue: Missing API keys for MCP servers
|
||
|
||
# Reload tools
|
||
curl -X POST http://localhost:8000/mcp/reload
|
||
```
|
||
|
||
#### Import Errors
|
||
|
||
```bash
|
||
# Reinstall the shared Chapter 4 environment from the repository root
|
||
uv sync --locked --python 3.12 --extra ch4
|
||
|
||
# Single-project compatibility path:
|
||
# python -m pip install -r requirements.txt
|
||
|
||
# Verify FastAPI installed
|
||
python -c "import fastapi; print(fastapi.__version__)"
|
||
```
|
||
|
||
#### Agent Not Responding
|
||
|
||
```bash
|
||
# Check health
|
||
curl http://localhost:8000/health
|
||
|
||
# View logs in server terminal
|
||
# Check trajectory file: event_agent_trajectory.json
|
||
```
|
||
|
||
### Migration from Old Version
|
||
|
||
If upgrading from Flask version:
|
||
|
||
1. **Port changed**: 4242 → 8000
|
||
2. **No deprecation warnings**: Using modern lifespan events
|
||
3. **MCP enabled by default**: Set `ENABLE_MCP_TOOLS=false` to disable
|
||
4. **Same API contracts**: Endpoints work the same way
|
||
5. **Better performance**: Native async for MCP calls
|
||
|
||
### License
|
||
|
||
MIT License - See LICENSE file for details
|
||
|
||
### Acknowledgments
|
||
|
||
- Built with [FastAPI](https://fastapi.tiangolo.com/)
|
||
- MCP protocol by [Model Context Protocol](https://modelcontextprotocol.io/)
|
||
- Tool servers: collaboration-tools, execution-tools, perception-tools
|
||
|
||
---
|
||
|
||
## 中文
|
||
|
||
具备**原生异步**能力、可响应多源事件的现代 AI Agent。基于 **FastAPI** 构建,集成 **42 个 MCP 工具**(浏览器自动化、网络搜索、文档处理等)。
|
||
|
||
### 功能
|
||
|
||
#### 核心能力
|
||
- ✅ **原生异步** — FastAPI + 清晰的 async/await
|
||
- ✅ **42 个 MCP 工具** — 自动从 3 个 MCP 服务器加载
|
||
- ✅ **事件驱动** — 响应 Web 消息、邮件、GitHub 更新、定时器
|
||
- ✅ **System Hints** — 时间戳、工具计数、TODO 管理
|
||
- ✅ **自动 API 文档** — 交互式 Swagger UI:`/docs`
|
||
- ✅ **后台任务** — 进程监控与系统告警
|
||
|
||
#### MCP 工具分类
|
||
|
||
**协作工具**(18 个):
|
||
- 浏览器自动化(导航、截图、执行任务)
|
||
- 通知(邮件、Telegram、Slack、Discord)
|
||
- 人机协同(管理员审批、输入请求)
|
||
- 定时器管理(一次性、循环)
|
||
|
||
**执行工具**(6 个):
|
||
- 文件操作(写入、带校验的编辑)
|
||
- 代码执行(Python 解释器、shell 命令)
|
||
- 外部集成(Google Calendar、GitHub PR)
|
||
|
||
**感知工具**(18 个):
|
||
- 网络搜索与内容抽取
|
||
- 文档阅读(PDF、DOCX、PPTX)
|
||
- 多模态解析(图像、视频、网页)
|
||
- 公开数据(天气、股票、Wikipedia、ArXiv)
|
||
- 私有数据(Google Calendar、Notion)
|
||
|
||
### 事件驱动演示(离线可运行,无需 API Key)
|
||
|
||
在启动 HTTP 服务器之前,推荐先运行 `event_loop_demo.py`,它在单个进程里
|
||
直观演示本章的核心概念——**外部世界主动唤醒 Agent**。脚本注册三类"事件触发器",
|
||
由后台线程在事件真正发生时把结构化事件推入统一的事件队列,事件循环逐个取出
|
||
并唤醒 Agent 处理,形成"注册 → 触发 → 唤醒 → 处理"的完整闭环:
|
||
|
||
| 触发器 | 类 | 对应书中概念 |
|
||
|--------|----|----|
|
||
| 一次性定时器 | `OneShotTimer` | `set_timer` 一次性定时器(如"下周一 10:00 致电 DMV")|
|
||
| 循环定时器 | `RecurringTimer` | `set_timer` 循环定时器 / Heartbeat(如"每小时检查服务器")|
|
||
| 文件监听 | `FileWatchTrigger` | n8n 等平台的文件变更触发器 |
|
||
|
||
`--mock` 离线模式不调用大模型,用"模拟动作"打印 Agent 被唤醒后的处理过程,
|
||
因此**无需任何 API Key** 即可跑通:
|
||
|
||
```bash
|
||
# 离线演示全部触发器(一次性定时器 + 循环定时器 + 文件监听)
|
||
python event_loop_demo.py --mock
|
||
|
||
# 只演示一次性定时器;2 秒后触发,共运行 6 秒
|
||
python event_loop_demo.py --mock --trigger timer --delay 2 --duration 6
|
||
|
||
# 每 3 秒触发一次循环定时器
|
||
python event_loop_demo.py --mock --trigger recurring --interval 3 --duration 12
|
||
|
||
# 监听目录,向其中写入文件即可触发事件(另开终端 echo hello > watched_dir/a.txt)
|
||
python event_loop_demo.py --mock --trigger file --watch-dir watched_dir
|
||
```
|
||
|
||
离线运行的输出示例(节选):
|
||
|
||
```
|
||
⏱️ [OneShotTimer(daily_backup_check)] 已注册:2 秒后触发
|
||
🔁 [RecurringTimer(health_check)] 已注册:每 3 秒触发一次
|
||
🟢 事件循环启动,将运行 8 秒,等待事件唤醒 Agent...
|
||
⚡ [OneShotTimer(daily_backup_check)] 触发事件 -> timer_trigger: 一次性定时器到期:请检查每日备份是否已经完成。
|
||
📥 事件循环取出第 1 个事件 -> 唤醒 Agent
|
||
🤖 Agent 被唤醒,收到消息: [Timer daily_backup_check triggered] 一次性定时器到期:请检查每日备份是否已经完成。
|
||
🛠️ [模拟动作] 读取定时任务上下文 -> 执行例行检查 -> 汇报结果
|
||
✅ Agent 处理完成: 已响应 timer_trigger 事件
|
||
```
|
||
|
||
去掉 `--mock` 即接入真实的大模型(默认仅用内置工具,不加载 MCP),
|
||
需要设置对应 provider 的 API Key:
|
||
|
||
```bash
|
||
export KIMI_API_KEY='your-api-key-here'
|
||
python event_loop_demo.py --trigger timer --provider kimi
|
||
|
||
export DASHSCOPE_API_KEY='your-dashscope-api-key-here'
|
||
python event_loop_demo.py --trigger timer --provider dashscope
|
||
```
|
||
|
||
> **OpenRouter 通用兜底**:若所选 provider(默认 `kimi`)的 Key 缺失,但设置了
|
||
> `OPENROUTER_API_KEY`,`event_loop_demo.py` / `server.py` / `quickstart.py` 会自动
|
||
> 改用 `openrouter` provider 继续运行(可用 `LLM_MODEL=openai/gpt-5.6-luna` 指定模型)。例如:
|
||
> `OPENROUTER_API_KEY=your-openrouter-api-key LLM_MODEL=openai/gpt-5.6-luna python event_loop_demo.py --trigger timer`
|
||
|
||
完整参数见 `python event_loop_demo.py --help`。
|
||
|
||
### 快速开始
|
||
|
||
#### 安装
|
||
|
||
```bash
|
||
# 在仓库根目录使用统一的第 4 章环境
|
||
uv sync --locked --python 3.12 --extra ch4
|
||
|
||
# 切换目录前先激活环境:
|
||
# macOS/Linux:
|
||
source .venv/bin/activate
|
||
# Windows PowerShell:.venv\Scripts\Activate.ps1
|
||
# Windows cmd:.venv\Scripts\activate.bat
|
||
|
||
# 未安装 uv 时可用 pip 兜底:
|
||
# python -m pip install -e ".[ch4]"
|
||
|
||
cd chapter4/agent-with-event-trigger
|
||
|
||
# 迁移期间仍支持单项目兼容路径:
|
||
# python -m pip install -r requirements.txt
|
||
|
||
# Set up environment
|
||
cp env.example .env
|
||
# Edit .env and add your API key
|
||
export KIMI_API_KEY='your-api-key-here'
|
||
```
|
||
|
||
#### 测试与手动演示
|
||
|
||
自动化测试位于 `tests/`,均为离线回归测试,不需要 API Key。
|
||
|
||
```bash
|
||
# 在仓库根目录安装 pytest 所需的 dev extra:
|
||
uv sync --locked --python 3.12 --extra ch4 --extra dev
|
||
|
||
# pip 测试兜底路径:
|
||
# python -m pip install -e ".[ch4,dev]"
|
||
|
||
cd chapter4/agent-with-event-trigger
|
||
python -m pytest tests
|
||
```
|
||
|
||
独立的联网演示已移动到 `tests/manual/demo.py`;它仍需要模型提供商 API Key,且不会被 pytest 默认收集:
|
||
|
||
```bash
|
||
KIMI_API_KEY='your-api-key-here' python tests/manual/demo.py
|
||
```
|
||
|
||
#### 启动服务器
|
||
|
||
```bash
|
||
python server.py
|
||
```
|
||
|
||
服务器支持命令行参数(优先级高于环境变量),完整列表见 `python server.py --help`:
|
||
|
||
```bash
|
||
python server.py --port 9000 # 自定义端口
|
||
python server.py --provider doubao # 指定大模型提供商
|
||
python server.py --no-mcp # 只用内置工具,不加载 MCP 工具
|
||
```
|
||
|
||
输出:
|
||
```
|
||
🤖 EVENT-TRIGGERED AGENT SERVER (FastAPI)
|
||
✅ Starting server on port 8000
|
||
📡 API Documentation: http://localhost:8000/docs
|
||
📊 ReDoc: http://localhost:8000/redoc
|
||
|
||
🚀 Starting Event-Triggered Agent Server (FastAPI)
|
||
✅ Agent initialized with kimi provider
|
||
🔄 MCP tools enabled (default) - loading asynchronously...
|
||
✅ Discovered tools from 'collaboration': 18 tools
|
||
✅ Discovered tools from 'execution': 6 tools
|
||
✅ Discovered tools from 'perception': 18 tools
|
||
✅ MCP tools loaded: 42 tools available
|
||
✅ Server ready to receive events
|
||
|
||
INFO: Uvicorn running on http://0.0.0.0:8000
|
||
```
|
||
|
||
#### 交互式 API 文档
|
||
|
||
访问 **http://localhost:8000/docs** 可以:
|
||
- 📖 浏览全部端点
|
||
- 🧪 交互式测试 API
|
||
- 📝 查看请求/响应 schema
|
||
- ⚡ 一键发送事件
|
||
|
||
### API 端点
|
||
|
||
#### 核心端点
|
||
|
||
```bash
|
||
# Health check
|
||
curl http://localhost:8000/health
|
||
|
||
# Check MCP tools status
|
||
curl http://localhost:8000/mcp/status
|
||
|
||
# Send an event
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Search the web for Python async best practices",
|
||
"metadata": {"user": "demo"}
|
||
}'
|
||
|
||
# Get agent status
|
||
curl http://localhost:8000/agent/status
|
||
|
||
# Reset agent state
|
||
curl -X POST http://localhost:8000/agent/reset
|
||
|
||
# Reload MCP tools
|
||
curl -X POST http://localhost:8000/mcp/reload
|
||
```
|
||
|
||
#### 使用交互式文档
|
||
|
||
1. 打开 http://localhost:8000/docs
|
||
2. 点击任意端点(如 `POST /event`)
|
||
3. 点击 "Try it out"
|
||
4. 填写请求体
|
||
5. 点击 "Execute"
|
||
6. 即时查看响应
|
||
|
||
### 使用示例
|
||
|
||
#### 运行独立示例
|
||
|
||
无需服务器、完整演示 MCP 集成:
|
||
|
||
```bash
|
||
python example_with_mcp.py
|
||
```
|
||
|
||
该独立脚本:
|
||
- 启用 MCP 工具并初始化 Agent
|
||
- 从 3 个 MCP 服务器加载全部 42 个工具
|
||
- 处理样例事件(网络搜索任务)
|
||
- 展示从工具发现到执行的完整流程
|
||
- 正确清理 MCP 连接
|
||
|
||
适用于:
|
||
- 不启服务器测试 MCP 集成
|
||
- 理解异步工具加载流程
|
||
- 调试 MCP 连接问题
|
||
- 学习如何以编程方式使用 Agent
|
||
|
||
#### 示例 1:网络搜索任务
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Search for the latest FastAPI features and summarize them",
|
||
"metadata": {"user": "demo"}
|
||
}'
|
||
```
|
||
|
||
Agent 将:
|
||
1. 用 `perception_web_search` 搜索
|
||
2. 用 `perception_webpage_reader` 解析内容
|
||
3. 在响应中总结结果
|
||
|
||
#### 示例 2:浏览器自动化
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Navigate to example.com and take a screenshot",
|
||
"metadata": {}
|
||
}'
|
||
```
|
||
|
||
使用:
|
||
- `collaboration_mcp_browser_navigate`
|
||
- `collaboration_mcp_browser_screenshot`
|
||
|
||
#### 示例 3:文档处理
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "web_message",
|
||
"content": "Download and summarize the PDF from https://example.com/doc.pdf",
|
||
"metadata": {}
|
||
}'
|
||
```
|
||
|
||
使用:
|
||
- `perception_download`
|
||
- `perception_document_reader`
|
||
|
||
#### 示例 4:邮件通知
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/event \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"event_type": "timer_trigger",
|
||
"content": "Send daily report to admin@example.com",
|
||
"metadata": {"scheduled": true}
|
||
}'
|
||
```
|
||
|
||
使用:
|
||
- `collaboration_mcp_send_email`
|
||
|
||
### 配置
|
||
|
||
#### 环境变量
|
||
|
||
```bash
|
||
# Required
|
||
export KIMI_API_KEY="your-key"
|
||
|
||
# Optional
|
||
export LLM_PROVIDER="kimi" # dashscope/qwen/bailian, kimi, siliconflow, doubao, openrouter
|
||
export LLM_MODEL="kimi-k3" # Override default model
|
||
export AGENT_PORT="8000" # Server port (default: 8000)
|
||
export ENABLE_MCP_TOOLS="true" # Enable MCP (default: true)
|
||
```
|
||
|
||
#### 禁用 MCP 工具
|
||
|
||
若只要内置工具:
|
||
|
||
```bash
|
||
ENABLE_MCP_TOOLS=false python server.py
|
||
```
|
||
|
||
#### 自定义端口
|
||
|
||
```bash
|
||
AGENT_PORT=9000 python server.py
|
||
```
|
||
|
||
### 架构
|
||
|
||
(与英文侧相同,见 English 部分 Architecture 图。)
|
||
|
||
### 项目结构
|
||
|
||
```
|
||
agent-with-event-trigger/
|
||
├── agent.py # Event-triggered agent with system hints
|
||
├── event_types.py # Event type definitions
|
||
├── event_loop_demo.py # Offline event-loop demo (timer / recurring / file-watch triggers)
|
||
├── server.py # FastAPI server (main entry point)
|
||
├── tests/ # Automated regressions and manual live demos
|
||
│ └── manual/
|
||
├── requirements.txt # Dependencies (FastAPI, uvicorn, MCP)
|
||
├── env.example # Environment template
|
||
├── README.md # This file
|
||
└── example_with_mcp.py # Standalone MCP example
|
||
```
|
||
|
||
### MCP 工具参考
|
||
|
||
#### 查看可用工具
|
||
|
||
```bash
|
||
curl http://localhost:8000/mcp/status
|
||
```
|
||
|
||
响应包含:
|
||
- `tools`:全部 42 个工具名
|
||
- `tools_by_server`:按服务器分组
|
||
- `tools_count`:总数
|
||
- `loaded`:MCP 是否已加载
|
||
|
||
#### 工具命名约定
|
||
|
||
MCP 工具使用前缀:
|
||
- `collaboration_*` — 协作工具
|
||
- `execution_*` — 执行工具
|
||
- `perception_*` — 感知工具
|
||
|
||
内置工具(无前缀):
|
||
- `read_file`
|
||
- `write_file`
|
||
- `code_interpreter`
|
||
- `execute_command`
|
||
- `rewrite_todo_list`
|
||
- `update_todo_status`
|
||
|
||
### 事件类型
|
||
|
||
```python
|
||
class EventType(Enum):
|
||
# External input events
|
||
WEB_MESSAGE = "web_message" # Web interface
|
||
IM_MESSAGE = "im_message" # Instant messaging
|
||
EMAIL_REPLY = "email_reply" # Email responses
|
||
GITHUB_PR_UPDATE = "github_pr_update" # PR notifications
|
||
TIMER_TRIGGER = "timer_trigger" # Scheduled tasks (one-shot / recurring)
|
||
FILE_CHANGE = "file_change" # File watch trigger (created / modified)
|
||
|
||
# System reminder events
|
||
USER_TIMEOUT = "user_timeout" # No user activity
|
||
PROCESS_TIMEOUT = "process_timeout" # Long-running process
|
||
SYSTEM_ALERT = "system_alert" # System warnings
|
||
```
|
||
|
||
#### 事件格式
|
||
|
||
```json
|
||
{
|
||
"event_type": "web_message",
|
||
"content": "Your task description",
|
||
"metadata": {
|
||
"user_id": "user123",
|
||
"session_id": "session456"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 使用客户端
|
||
|
||
内置客户端便于测试:
|
||
|
||
```bash
|
||
# Interactive mode
|
||
python client.py --mode interactive
|
||
|
||
# Test scenarios
|
||
python client.py --mode test
|
||
|
||
# Send a single event (defaults to web_message)
|
||
python client.py --message "Create a Python hello world script"
|
||
|
||
# Send a single event of a specific type
|
||
python client.py --event-type timer_trigger --message "检查每日备份"
|
||
```
|
||
|
||
### 安全考虑
|
||
|
||
生产部署建议:
|
||
|
||
1. **HTTPS**:使用反向代理(nginx、Caddy)
|
||
2. **认证**:增加 API Key 校验
|
||
3. **限流**:防止滥用
|
||
4. **输入校验**:清洗全部输入
|
||
5. **CORS**:配置允许的源
|
||
6. **环境**:使用密钥管理
|
||
|
||
### 对比:Flask vs FastAPI
|
||
|
||
| 特性 | 旧版(Flask) | 新版(FastAPI) |
|
||
|---------|-------------|---------------|
|
||
| Framework | Flask (WSGI) | FastAPI (ASGI) |
|
||
| Async Support | ❌ Threads | ✅ Native async/await |
|
||
| MCP Integration | ⚠️ Complex | ✅ Clean |
|
||
| API Docs | ❌ Manual | ✅ Auto-generated |
|
||
| Performance | Good | **Better** (2-3x) |
|
||
| Port | 4242 | 8000 |
|
||
| Deprecation Warnings | N/A | ✅ Fixed (lifespan) |
|
||
|
||
### 故障排除
|
||
|
||
#### 端口占用
|
||
|
||
```bash
|
||
# Check what's using port 8000
|
||
lsof -i :8000
|
||
|
||
# Use different port
|
||
AGENT_PORT=8001 python server.py
|
||
```
|
||
|
||
#### MCP 工具未加载
|
||
|
||
```bash
|
||
# Check status
|
||
curl http://localhost:8000/mcp/status
|
||
|
||
# Look for error in response
|
||
# Common issue: Missing API keys for MCP servers
|
||
|
||
# Reload tools
|
||
curl -X POST http://localhost:8000/mcp/reload
|
||
```
|
||
|
||
#### 导入错误
|
||
|
||
```bash
|
||
# 从仓库根目录重新安装统一的第 4 章环境
|
||
uv sync --locked --python 3.12 --extra ch4
|
||
|
||
# 单项目兼容路径:
|
||
# python -m pip install -r requirements.txt
|
||
|
||
# Verify FastAPI installed
|
||
python -c "import fastapi; print(fastapi.__version__)"
|
||
```
|
||
|
||
#### Agent 无响应
|
||
|
||
```bash
|
||
# Check health
|
||
curl http://localhost:8000/health
|
||
|
||
# View logs in server terminal
|
||
# Check trajectory file: event_agent_trajectory.json
|
||
```
|
||
|
||
### 从旧版迁移
|
||
|
||
若从 Flask 版本升级:
|
||
|
||
1. **端口变更**:4242 → 8000
|
||
2. **无弃用警告**:使用现代 lifespan 事件
|
||
3. **默认启用 MCP**:`ENABLE_MCP_TOOLS=false` 可关闭
|
||
4. **API 契约不变**:端点用法相同
|
||
5. **性能更好**:MCP 调用原生异步
|
||
|
||
### 许可证
|
||
|
||
MIT License - 详见 LICENSE 文件
|
||
|
||
### 致谢
|
||
|
||
- 基于 [FastAPI](https://fastapi.tiangolo.com/) 构建
|
||
- MCP 协议:[Model Context Protocol](https://modelcontextprotocol.io/)
|
||
- 工具服务器:collaboration-tools、execution-tools、perception-tools
|
||
|
||
---
|
||
|
||
## Notes / 说明
|
||
|
||
- Start with `python event_loop_demo.py --mock` (no API key).
|
||
- 建议先跑 `python event_loop_demo.py --mock`(无需 API Key)。
|
||
- MCP servers expected: [collaboration-tools](../collaboration-tools/), [execution-tools](../execution-tools/), [perception-tools](../perception-tools/).
|
||
- MCP 服务器见同章三个工具项目。
|