mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 00:28:31 +00:00
- Add HumanInputRequiredEvent and HumanInputCompletedEvent to task_events.py - Implement HTTP server with WebSocket, SSE, and long-polling endpoints - Add EventStreamManager for connection and event management - Integrate event emission in agent executor human input flow - Add comprehensive tests for server endpoints and event integration - Add optional FastAPI dependencies for server functionality - Include documentation and example usage - Maintain backward compatibility with existing human input flow Addresses issue #3259 for WebSocket/SSE/long-polling human input events Co-Authored-By: João <joao@crewai.com>
265 lines
6.9 KiB
Markdown
265 lines
6.9 KiB
Markdown
# Human Input Event Streaming
|
|
|
|
CrewAI supports real-time event streaming for human input events, allowing clients to receive notifications when human input is required during crew execution. This feature provides an alternative to webhook-only approaches and supports multiple streaming protocols.
|
|
|
|
## Overview
|
|
|
|
When a task requires human input (`task.human_input=True`), CrewAI emits events that can be consumed via:
|
|
|
|
- **WebSocket**: Real-time bidirectional communication
|
|
- **Server-Sent Events (SSE)**: Unidirectional server-to-client streaming
|
|
- **Long Polling**: HTTP-based polling for events
|
|
|
|
## Event Types
|
|
|
|
### HumanInputRequiredEvent
|
|
|
|
Emitted when human input is required during task execution.
|
|
|
|
```json
|
|
{
|
|
"type": "human_input_required",
|
|
"execution_id": "uuid",
|
|
"crew_id": "uuid",
|
|
"task_id": "uuid",
|
|
"agent_id": "uuid",
|
|
"prompt": "string",
|
|
"context": "string",
|
|
"timestamp": "ISO8601",
|
|
"event_id": "uuid",
|
|
"reason_flags": {
|
|
"ambiguity": true,
|
|
"missing_field": false
|
|
}
|
|
}
|
|
```
|
|
|
|
### HumanInputCompletedEvent
|
|
|
|
Emitted when human input is completed.
|
|
|
|
```json
|
|
{
|
|
"type": "human_input_completed",
|
|
"execution_id": "uuid",
|
|
"crew_id": "uuid",
|
|
"task_id": "uuid",
|
|
"agent_id": "uuid",
|
|
"event_id": "uuid",
|
|
"human_feedback": "string",
|
|
"timestamp": "ISO8601"
|
|
}
|
|
```
|
|
|
|
## Server Setup
|
|
|
|
### Installation
|
|
|
|
Install the server dependencies:
|
|
|
|
```bash
|
|
pip install crewai[server]
|
|
```
|
|
|
|
### Starting the Server
|
|
|
|
```python
|
|
from crewai.server.human_input_server import HumanInputServer
|
|
|
|
# Start server with authentication
|
|
server = HumanInputServer(
|
|
host="localhost",
|
|
port=8000,
|
|
api_key="your-api-key"
|
|
)
|
|
|
|
# Start synchronously
|
|
server.start()
|
|
|
|
# Or start asynchronously
|
|
await server.start_async()
|
|
```
|
|
|
|
### Configuration Options
|
|
|
|
- `host`: Server host (default: "localhost")
|
|
- `port`: Server port (default: 8000)
|
|
- `api_key`: Optional API key for authentication
|
|
|
|
## Client Integration
|
|
|
|
### WebSocket Client
|
|
|
|
```python
|
|
import asyncio
|
|
import json
|
|
import websockets
|
|
|
|
async def websocket_client(execution_id: str, api_key: str = None):
|
|
uri = f"ws://localhost:8000/ws/human-input/{execution_id}"
|
|
if api_key:
|
|
uri += f"?token={api_key}"
|
|
|
|
async with websockets.connect(uri) as websocket:
|
|
async for message in websocket:
|
|
event = json.loads(message)
|
|
|
|
if event['type'] == 'human_input_required':
|
|
print(f"Human input needed: {event['prompt']}")
|
|
print(f"Context: {event['context']}")
|
|
elif event['type'] == 'human_input_completed':
|
|
print(f"Input completed: {event['human_feedback']}")
|
|
|
|
# Usage
|
|
asyncio.run(websocket_client("execution-id", "api-key"))
|
|
```
|
|
|
|
### Server-Sent Events (SSE) Client
|
|
|
|
```python
|
|
import httpx
|
|
import json
|
|
|
|
async def sse_client(execution_id: str, api_key: str = None):
|
|
url = f"http://localhost:8000/events/human-input/{execution_id}"
|
|
headers = {}
|
|
if api_key:
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
async with client.stream("GET", url, headers=headers) as response:
|
|
async for line in response.aiter_lines():
|
|
if line.startswith("data: "):
|
|
event = json.loads(line[6:])
|
|
if event.get('type') != 'heartbeat':
|
|
print(f"Received: {event}")
|
|
|
|
# Usage
|
|
asyncio.run(sse_client("execution-id", "api-key"))
|
|
```
|
|
|
|
### Long Polling Client
|
|
|
|
```python
|
|
import httpx
|
|
import asyncio
|
|
|
|
async def polling_client(execution_id: str, api_key: str = None):
|
|
url = f"http://localhost:8000/poll/human-input/{execution_id}"
|
|
headers = {}
|
|
if api_key:
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
|
|
last_event_id = None
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
while True:
|
|
params = {}
|
|
if last_event_id:
|
|
params["last_event_id"] = last_event_id
|
|
|
|
response = await client.get(url, headers=headers, params=params)
|
|
data = response.json()
|
|
|
|
for event in data.get("events", []):
|
|
print(f"Received: {event}")
|
|
last_event_id = event.get('event_id')
|
|
|
|
await asyncio.sleep(2) # Poll every 2 seconds
|
|
|
|
# Usage
|
|
asyncio.run(polling_client("execution-id", "api-key"))
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### WebSocket Endpoint
|
|
|
|
- **URL**: `/ws/human-input/{execution_id}`
|
|
- **Protocol**: WebSocket
|
|
- **Authentication**: Query parameter `token` (if API key configured)
|
|
|
|
### SSE Endpoint
|
|
|
|
- **URL**: `/events/human-input/{execution_id}`
|
|
- **Method**: GET
|
|
- **Headers**: `Authorization: Bearer <api_key>` (if configured)
|
|
- **Response**: `text/event-stream`
|
|
|
|
### Polling Endpoint
|
|
|
|
- **URL**: `/poll/human-input/{execution_id}`
|
|
- **Method**: GET
|
|
- **Headers**: `Authorization: Bearer <api_key>` (if configured)
|
|
- **Query Parameters**:
|
|
- `last_event_id`: Get events after this ID
|
|
- **Response**: JSON with `events` array
|
|
|
|
### Health Check
|
|
|
|
- **URL**: `/health`
|
|
- **Method**: GET
|
|
- **Response**: `{"status": "healthy", "timestamp": "..."}`
|
|
|
|
## Authentication
|
|
|
|
When an API key is configured, clients must authenticate:
|
|
|
|
- **WebSocket**: Include `token` query parameter
|
|
- **SSE/Polling**: Include `Authorization: Bearer <api_key>` header
|
|
|
|
## Integration with Crew Execution
|
|
|
|
The event streaming works automatically with existing crew execution:
|
|
|
|
```python
|
|
from crewai import Agent, Task, Crew
|
|
|
|
# Create crew with human input task
|
|
agent = Agent(...)
|
|
task = Task(
|
|
description="...",
|
|
human_input=True, # This enables human input
|
|
agent=agent
|
|
)
|
|
crew = Crew(agents=[agent], tasks=[task])
|
|
|
|
# Start event server (optional)
|
|
server = HumanInputServer(port=8000)
|
|
server_thread = threading.Thread(target=server.start, daemon=True)
|
|
server_thread.start()
|
|
|
|
# Execute crew - events will be emitted automatically
|
|
result = crew.kickoff()
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
- **Connection Errors**: Clients should implement reconnection logic
|
|
- **Authentication Errors**: Server returns 401 for invalid credentials
|
|
- **Rate Limiting**: Consider implementing client-side rate limiting for polling
|
|
|
|
## Best Practices
|
|
|
|
1. **Use WebSocket** for real-time applications requiring immediate notifications
|
|
2. **Use SSE** for one-way streaming with automatic reconnection support
|
|
3. **Use Polling** for simple implementations or when WebSocket/SSE aren't available
|
|
4. **Implement Authentication** in production environments
|
|
5. **Handle Connection Failures** gracefully with retry logic
|
|
6. **Filter Events** by execution_id to avoid processing irrelevant events
|
|
|
|
## Backward Compatibility
|
|
|
|
This feature is fully backward compatible:
|
|
|
|
- Existing webhook functionality remains unchanged
|
|
- Console-based human input continues to work
|
|
- No breaking changes to existing APIs
|
|
|
|
## Example Applications
|
|
|
|
- **Web Dashboards**: Real-time crew execution monitoring
|
|
- **Mobile Apps**: Push notifications for human input requests
|
|
- **Integration Platforms**: Event-driven workflow automation
|
|
- **Monitoring Systems**: Real-time alerting and logging
|