Spaces:
Sleeping
Sleeping
BolyosCsaba commited on
Commit Β·
148a4a7
1
Parent(s): 6d97770
initial commit
Browse files- QUICKSTART.md +126 -0
- README.md +90 -14
- install_and_run.sh +30 -0
- requirements.txt +24 -0
- src/__init__.py +3 -0
- src/protocol/__init__.py +25 -0
- src/protocol/envelope.py +105 -0
- src/protocol/events.py +212 -0
- src/utils/__init__.py +6 -0
- src/utils/config.py +39 -0
- src/utils/helpers.py +52 -0
- src/utils/logger.py +32 -0
- test_app.py +346 -0
- test_backend.sh +113 -0
QUICKSTART.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## π Quick Start Guide - Test the Floor Manager Interface
|
| 2 |
+
|
| 3 |
+
### Step 1: Install Dependencies
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
# Install only Gradio for testing (no need for full stack yet)
|
| 7 |
+
pip install gradio
|
| 8 |
+
```
|
| 9 |
+
|
| 10 |
+
### Step 2: Run the Test Interface
|
| 11 |
+
|
| 12 |
+
```bash
|
| 13 |
+
# Run the test app
|
| 14 |
+
python test_app.py
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
### Step 3: Open in Browser
|
| 18 |
+
|
| 19 |
+
The interface will automatically open at: **http://localhost:7860**
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## π± How to Use the Test Interface
|
| 24 |
+
|
| 25 |
+
### 1. **Create a Session**
|
| 26 |
+
- Click the "π Create New Session" button
|
| 27 |
+
- A unique session ID will be generated
|
| 28 |
+
|
| 29 |
+
### 2. **Add Agents**
|
| 30 |
+
- Enter an agent name (e.g., "Alice", "Bob", "Charlie")
|
| 31 |
+
- Click "β Add Agent"
|
| 32 |
+
- The first agent automatically gets the floor
|
| 33 |
+
|
| 34 |
+
### 3. **Send Messages**
|
| 35 |
+
- Select an agent from the "Speaking As" dropdown
|
| 36 |
+
- Type a message
|
| 37 |
+
- Click "π€ Send"
|
| 38 |
+
- Messages appear in the conversation panel
|
| 39 |
+
|
| 40 |
+
### 4. **Manage the Floor**
|
| 41 |
+
- **Grant Floor**: Select an agent and click "π€ Grant Floor"
|
| 42 |
+
- **Revoke Floor**: Click "βΈοΈ Revoke Floor" to remove current speaker
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## β¨ Features You Can Test
|
| 47 |
+
|
| 48 |
+
- β
Session creation and management
|
| 49 |
+
- β
Multiple agent registration
|
| 50 |
+
- β
Floor control (grant/revoke)
|
| 51 |
+
- β
Agent status tracking
|
| 52 |
+
- β
Real-time conversation display
|
| 53 |
+
- β
System notifications (agent joined, floor changes)
|
| 54 |
+
- β
Message timestamps
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## π― Test Scenarios
|
| 59 |
+
|
| 60 |
+
### Scenario 1: Basic Conversation
|
| 61 |
+
1. Create a session
|
| 62 |
+
2. Add agents: "Alice" and "Bob"
|
| 63 |
+
3. Alice sends: "Hello Bob!"
|
| 64 |
+
4. Grant floor to Bob
|
| 65 |
+
5. Bob sends: "Hi Alice, how are you?"
|
| 66 |
+
|
| 67 |
+
### Scenario 2: Multi-Agent Discussion
|
| 68 |
+
1. Create a session
|
| 69 |
+
2. Add agents: "Moderator", "Expert1", "Expert2"
|
| 70 |
+
3. Let Moderator introduce the topic
|
| 71 |
+
4. Grant floor to Expert1 for their input
|
| 72 |
+
5. Grant floor to Expert2 for their response
|
| 73 |
+
6. Revoke floor when done
|
| 74 |
+
|
| 75 |
+
### Scenario 3: Floor Management
|
| 76 |
+
1. Create a session
|
| 77 |
+
2. Add 3-4 agents
|
| 78 |
+
3. Practice granting and revoking floor between agents
|
| 79 |
+
4. Observe the status changes in the agent table
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## π§ Troubleshooting
|
| 84 |
+
|
| 85 |
+
**Port Already in Use?**
|
| 86 |
+
```bash
|
| 87 |
+
# Change the port in test_app.py, line 355:
|
| 88 |
+
demo.launch(server_name="0.0.0.0", server_port=7861, share=False)
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
**Gradio Not Installed?**
|
| 92 |
+
```bash
|
| 93 |
+
pip install gradio
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
**Want to share publicly?**
|
| 97 |
+
```bash
|
| 98 |
+
# Change share=False to share=True in test_app.py
|
| 99 |
+
demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## π What's Next?
|
| 105 |
+
|
| 106 |
+
This is a simplified test interface. The full implementation will include:
|
| 107 |
+
|
| 108 |
+
- β¨ Real OFP protocol compliance
|
| 109 |
+
- β¨ External agent communication via HTTP
|
| 110 |
+
- β¨ Agent manifest handling
|
| 111 |
+
- β¨ FastAPI backend integration
|
| 112 |
+
- β¨ WebSocket real-time updates
|
| 113 |
+
- β¨ Persistent session storage
|
| 114 |
+
- β¨ Advanced floor control policies
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## π Enjoy Testing!
|
| 119 |
+
|
| 120 |
+
This interface demonstrates the core Floor Manager concepts:
|
| 121 |
+
- **Session Management**: Creating and tracking conversation sessions
|
| 122 |
+
- **Agent Registry**: Managing multiple participants
|
| 123 |
+
- **Floor Control**: Coordinating who can speak
|
| 124 |
+
- **Message Routing**: Distributing messages to all participants
|
| 125 |
+
|
| 126 |
+
Try it out and see how the OpenFloor Protocol can manage multi-agent conversations! π
|
README.md
CHANGED
|
@@ -1,14 +1,90 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenFloor Protocol (OFP) Floor Manager
|
| 2 |
+
|
| 3 |
+
A Python implementation of the OpenFloor Protocol Floor Manager with Gradio UI for managing multi-agent conversations.
|
| 4 |
+
|
| 5 |
+
## Project Status
|
| 6 |
+
|
| 7 |
+
### β
Completed
|
| 8 |
+
- Project structure created
|
| 9 |
+
- Configuration management (`settings.py`)
|
| 10 |
+
- Logging infrastructure
|
| 11 |
+
- Helper utilities
|
| 12 |
+
- Basic protocol envelope handling
|
| 13 |
+
- Requirements file with all dependencies
|
| 14 |
+
|
| 15 |
+
### π§ In Progress
|
| 16 |
+
- Protocol events module (has syntax errors that need manual fixing)
|
| 17 |
+
- Protocol handler
|
| 18 |
+
- Core business logic (FloorManager, FloorSession, Agent, Convener)
|
| 19 |
+
|
| 20 |
+
### π Todo
|
| 21 |
+
- FastAPI backend endpoints
|
| 22 |
+
- Gradio UI implementation
|
| 23 |
+
- Example agents
|
| 24 |
+
- Integration testing
|
| 25 |
+
- Documentation
|
| 26 |
+
|
| 27 |
+
## Installation
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# Create virtual environment
|
| 31 |
+
python3 -m venv venv
|
| 32 |
+
source venv/bin/activate # On macOS/Linux
|
| 33 |
+
# or
|
| 34 |
+
venv\Scripts\activate # On Windows
|
| 35 |
+
|
| 36 |
+
# Install dependencies
|
| 37 |
+
pip install -r requirements.txt
|
| 38 |
+
|
| 39 |
+
# Install OpenFloor SDK from test PyPI
|
| 40 |
+
pip install -i https://test.pypi.org/simple/ openfloor
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
## Architecture
|
| 44 |
+
|
| 45 |
+
The project follows a modular architecture:
|
| 46 |
+
|
| 47 |
+
```
|
| 48 |
+
ofpFloor/
|
| 49 |
+
βββ src/
|
| 50 |
+
β βββ utils/ # Configuration, logging, helpers
|
| 51 |
+
β βββ protocol/ # OFP protocol implementation
|
| 52 |
+
β βββ core/ # Business logic (Floor Manager, Sessions, Agents)
|
| 53 |
+
β βββ api/ # FastAPI endpoints
|
| 54 |
+
β βββ ui/ # Gradio interface
|
| 55 |
+
βββ tests/ # Unit and integration tests
|
| 56 |
+
βββ examples/ # Example agents and scenarios
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## Known Issues
|
| 60 |
+
|
| 61 |
+
### Syntax Errors in `src/protocol/events.py`
|
| 62 |
+
|
| 63 |
+
The file has Python syntax errors that need to be manually fixed:
|
| 64 |
+
|
| 65 |
+
1. Line 9: `event_ Dict[str, Any]` should be `event_data: Dict[str, Any]`
|
| 66 |
+
2. Line 10: `meta Optional[Dict[str, Any]]` should be `metadata: Optional[Dict[str, Any]]`
|
| 67 |
+
3. Line 30: `if meta` should be `if meta`
|
| 68 |
+
|
| 69 |
+
These need to be fixed before the project can run.
|
| 70 |
+
|
| 71 |
+
## Next Steps
|
| 72 |
+
|
| 73 |
+
1. Fix syntax errors in `src/protocol/events.py`
|
| 74 |
+
2. Complete protocol handler implementation
|
| 75 |
+
3. Implement core Floor Manager logic
|
| 76 |
+
4. Create FastAPI endpoints
|
| 77 |
+
5. Build Gradio UI
|
| 78 |
+
6. Test with example agents
|
| 79 |
+
|
| 80 |
+
## References
|
| 81 |
+
|
| 82 |
+
- [OpenFloor Protocol Specifications](https://openfloor.dev/protocol/specifications/)
|
| 83 |
+
- [OFP Assistant Manifest](https://openfloor.dev/protocol/specifications/assistant-manifest.md)
|
| 84 |
+
- [OFP Inter-Agent Message](https://openfloor.dev/protocol/specifications/inter-agent-message.md)
|
| 85 |
+
- [OFP Dialog Event Object](https://openfloor.dev/protocol/specifications/dialog-event-object.md)
|
| 86 |
+
- [Gradio Documentation](https://www.gradio.app/)
|
| 87 |
+
|
| 88 |
+
## License
|
| 89 |
+
|
| 90 |
+
MIT License
|
install_and_run.sh
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "======================================"
|
| 4 |
+
echo "π€ OFP Floor Manager - Quick Setup"
|
| 5 |
+
echo "======================================"
|
| 6 |
+
echo ""
|
| 7 |
+
|
| 8 |
+
# Check if Python is installed
|
| 9 |
+
if ! command -v python3 &> /dev/null; then
|
| 10 |
+
echo "β Python 3 is not installed. Please install Python 3 first."
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
echo "β
Python 3 found: $(python3 --version)"
|
| 15 |
+
echo ""
|
| 16 |
+
|
| 17 |
+
# Install Gradio
|
| 18 |
+
echo "π¦ Installing Gradio..."
|
| 19 |
+
pip install gradio
|
| 20 |
+
|
| 21 |
+
echo ""
|
| 22 |
+
echo "======================================"
|
| 23 |
+
echo "β
Installation Complete!"
|
| 24 |
+
echo "======================================"
|
| 25 |
+
echo ""
|
| 26 |
+
echo "π Starting the Floor Manager Test Interface..."
|
| 27 |
+
echo ""
|
| 28 |
+
|
| 29 |
+
# Run the test app
|
| 30 |
+
python3 test_app.py
|
requirements.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core dependencies
|
| 2 |
+
fastapi==0.104.1
|
| 3 |
+
uvicorn[standard]==0.24.0
|
| 4 |
+
gradio==4.44.0
|
| 5 |
+
pydantic==2.5.0
|
| 6 |
+
pydantic-settings==2.1.0
|
| 7 |
+
|
| 8 |
+
# HTTP client for agent communication
|
| 9 |
+
httpx==0.25.1
|
| 10 |
+
aiohttp==3.9.1
|
| 11 |
+
|
| 12 |
+
# OpenFloor Protocol SDK
|
| 13 |
+
# Note: Install with: pip install -i https://test.pypi.org/simple/ openfloor
|
| 14 |
+
openfloor==0.1.0
|
| 15 |
+
|
| 16 |
+
# Utilities
|
| 17 |
+
python-multipart==0.0.6
|
| 18 |
+
python-dotenv==1.0.0
|
| 19 |
+
websockets==12.0
|
| 20 |
+
|
| 21 |
+
# Development
|
| 22 |
+
pytest==7.4.3
|
| 23 |
+
pytest-asyncio==0.21.1
|
| 24 |
+
black==23.11.0
|
src/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OpenFloor Protocol Floor Manager"""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
src/protocol/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OpenFloor Protocol implementation"""
|
| 2 |
+
|
| 3 |
+
from .envelope import Envelope, create_envelope, create_inter_agent_message
|
| 4 |
+
from .events import (
|
| 5 |
+
create_dialog_event,
|
| 6 |
+
create_get_manifest_event,
|
| 7 |
+
create_grant_floor_event,
|
| 8 |
+
create_revoke_floor_event,
|
| 9 |
+
create_invite_event,
|
| 10 |
+
create_floor_request_event,
|
| 11 |
+
)
|
| 12 |
+
from .handler import ProtocolHandler
|
| 13 |
+
|
| 14 |
+
__all__ = [
|
| 15 |
+
"Envelope",
|
| 16 |
+
"create_envelope",
|
| 17 |
+
"create_inter_agent_message",
|
| 18 |
+
"create_dialog_event",
|
| 19 |
+
"create_get_manifest_event",
|
| 20 |
+
"create_grant_floor_event",
|
| 21 |
+
"create_revoke_floor_event",
|
| 22 |
+
"create_invite_event",
|
| 23 |
+
"create_floor_request_event",
|
| 24 |
+
"ProtocolHandler",
|
| 25 |
+
]
|
src/protocol/envelope.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OFP Envelope handling"""
|
| 2 |
+
|
| 3 |
+
from typing import Any, Dict, List, Optional
|
| 4 |
+
from dataclasses import dataclass, asdict
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
from ..utils.helpers import generate_message_id, get_timestamp
|
| 8 |
+
from ..utils.config import settings
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class Envelope:
|
| 13 |
+
"""
|
| 14 |
+
OpenFloor Protocol Envelope structure
|
| 15 |
+
Based on: https://openfloor.dev/protocol/specifications/inter-agent-message.md
|
| 16 |
+
"""
|
| 17 |
+
version: str
|
| 18 |
+
sender: str
|
| 19 |
+
recipients: List[str]
|
| 20 |
+
message: Dict[str, Any]
|
| 21 |
+
timestamp: str
|
| 22 |
+
message_id: str
|
| 23 |
+
correlation_id: Optional[str] = None
|
| 24 |
+
|
| 25 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 26 |
+
"""Convert envelope to dictionary"""
|
| 27 |
+
data = asdict(self)
|
| 28 |
+
# Remove None values
|
| 29 |
+
return {k: v for k, v in data.items() if v is not None}
|
| 30 |
+
|
| 31 |
+
@classmethod
|
| 32 |
+
def from_dict(cls, data: Dict[str, Any]) -> "Envelope":
|
| 33 |
+
"""Create envelope from dictionary"""
|
| 34 |
+
return cls(
|
| 35 |
+
version=data.get("version", settings.OFP_VERSION),
|
| 36 |
+
sender=data["sender"],
|
| 37 |
+
recipients=data["recipients"],
|
| 38 |
+
message=data["message"],
|
| 39 |
+
timestamp=data.get("timestamp", get_timestamp()),
|
| 40 |
+
message_id=data.get("message_id", generate_message_id()),
|
| 41 |
+
correlation_id=data.get("correlation_id")
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def create_envelope(
|
| 46 |
+
sender: str,
|
| 47 |
+
recipients: List[str],
|
| 48 |
+
message: Dict[str, Any],
|
| 49 |
+
correlation_id: Optional[str] = None
|
| 50 |
+
) -> Envelope:
|
| 51 |
+
"""
|
| 52 |
+
Create a new OFP envelope
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
sender: Agent ID of sender
|
| 56 |
+
recipients: List of recipient agent IDs or ["broadcast"]
|
| 57 |
+
message: Message content (InterAgentMessage)
|
| 58 |
+
correlation_id: Optional correlation ID for request-response
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
Envelope object
|
| 62 |
+
"""
|
| 63 |
+
return Envelope(
|
| 64 |
+
version=settings.OFP_VERSION,
|
| 65 |
+
sender=sender,
|
| 66 |
+
recipients=recipients,
|
| 67 |
+
message=message,
|
| 68 |
+
timestamp=get_timestamp(),
|
| 69 |
+
message_id=generate_message_id(),
|
| 70 |
+
correlation_id=correlation_id
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def create_inter_agent_message(
|
| 75 |
+
message_type: str,
|
| 76 |
+
content: Any,
|
| 77 |
+
dialog_event: Optional[Dict[str, Any]] = None,
|
| 78 |
+
metadata: Optional[Dict[str, Any]] = None
|
| 79 |
+
) -> Dict[str, Any]:
|
| 80 |
+
"""
|
| 81 |
+
Create InterAgentMessage structure
|
| 82 |
+
Based on: https://openfloor.dev/protocol/specifications/inter-agent-message.md
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
message_type: Type of message (e.g., "agent_response", "floor_request")
|
| 86 |
+
content: Message content (can be text, JSON, etc.)
|
| 87 |
+
dialog_event: Optional DialogEventObject
|
| 88 |
+
meta Optional metadata
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
InterAgentMessage dictionary
|
| 92 |
+
"""
|
| 93 |
+
message = {
|
| 94 |
+
"message_type": message_type,
|
| 95 |
+
"content": content,
|
| 96 |
+
"timestamp": get_timestamp()
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
if dialog_event:
|
| 100 |
+
message["dialog_event"] = dialog_event
|
| 101 |
+
|
| 102 |
+
if metadata:
|
| 103 |
+
message["metadata"] = metadata
|
| 104 |
+
|
| 105 |
+
return message
|
src/protocol/events.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OFP Dialog Event Objects"""
|
| 2 |
+
|
| 3 |
+
from typing import Any, Dict, Optional
|
| 4 |
+
from ..utils.helpers import get_timestamp
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def create_dialog_event(
|
| 8 |
+
event_type: str,
|
| 9 |
+
event_ Dict[str, Any],
|
| 10 |
+
meta Optional[Dict[str, Any]] = None
|
| 11 |
+
) -> Dict[str, Any]:
|
| 12 |
+
"""
|
| 13 |
+
Create a DialogEventObject
|
| 14 |
+
Based on: https://openfloor.dev/protocol/specifications/dialog-event-object.md
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
event_type: Type of event (e.g., "GRANT_FLOOR", "GET_MANIFEST")
|
| 18 |
+
event_ Event-specific data
|
| 19 |
+
metadata: Optional metadata
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
DialogEventObject dictionary
|
| 23 |
+
"""
|
| 24 |
+
event = {
|
| 25 |
+
"event_type": event_type,
|
| 26 |
+
"timestamp": get_timestamp(),
|
| 27 |
+
"event_data": event_data
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
if meta
|
| 31 |
+
event["metadata"] = metadata
|
| 32 |
+
|
| 33 |
+
return event
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def create_get_manifest_event(
|
| 37 |
+
agent_id: str,
|
| 38 |
+
requested_by: str = "floor_manager"
|
| 39 |
+
) -> Dict[str, Any]:
|
| 40 |
+
"""
|
| 41 |
+
Create GET_MANIFEST event
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
agent_id: ID of agent to get manifest from
|
| 45 |
+
requested_by: Who is requesting the manifest
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
DialogEventObject for GET_MANIFEST
|
| 49 |
+
"""
|
| 50 |
+
return create_dialog_event(
|
| 51 |
+
event_type="GET_MANIFEST",
|
| 52 |
+
event_data={
|
| 53 |
+
"agent_id": agent_id,
|
| 54 |
+
"requested_by": requested_by
|
| 55 |
+
}
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def create_grant_floor_event(
|
| 60 |
+
agent_id: str,
|
| 61 |
+
granted_by: str = "convener",
|
| 62 |
+
duration: Optional[int] = None
|
| 63 |
+
) -> Dict[str, Any]:
|
| 64 |
+
"""
|
| 65 |
+
Create GRANT_FLOOR event
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
agent_id: ID of agent receiving floor
|
| 69 |
+
granted_by: Who granted the floor
|
| 70 |
+
duration: Optional floor duration in seconds
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
DialogEventObject for GRANT_FLOOR
|
| 74 |
+
"""
|
| 75 |
+
event_data = {
|
| 76 |
+
"agent_id": agent_id,
|
| 77 |
+
"granted_by": granted_by
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
if duration:
|
| 81 |
+
event_data["duration"] = duration
|
| 82 |
+
|
| 83 |
+
return create_dialog_event(
|
| 84 |
+
event_type="GRANT_FLOOR",
|
| 85 |
+
event_data=event_data
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def create_revoke_floor_event(
|
| 90 |
+
agent_id: str,
|
| 91 |
+
revoked_by: str = "convener",
|
| 92 |
+
reason: str = "floor_expired"
|
| 93 |
+
) -> Dict[str, Any]:
|
| 94 |
+
"""
|
| 95 |
+
Create REVOKE_FLOOR event
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
agent_id: ID of agent losing floor
|
| 99 |
+
revoked_by: Who revoked the floor
|
| 100 |
+
reason: Reason for revocation
|
| 101 |
+
|
| 102 |
+
Returns:
|
| 103 |
+
DialogEventObject for REVOKE_FLOOR
|
| 104 |
+
"""
|
| 105 |
+
return create_dialog_event(
|
| 106 |
+
event_type="REVOKE_FLOOR",
|
| 107 |
+
event_data={
|
| 108 |
+
"agent_id": agent_id,
|
| 109 |
+
"revoked_by": revoked_by,
|
| 110 |
+
"reason": reason
|
| 111 |
+
}
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def create_invite_event(
|
| 116 |
+
session_id: str,
|
| 117 |
+
inviter: str = "floor_manager",
|
| 118 |
+
join_url: Optional[str] = None
|
| 119 |
+
) -> Dict[str, Any]:
|
| 120 |
+
"""
|
| 121 |
+
Create INVITE event
|
| 122 |
+
|
| 123 |
+
Args:
|
| 124 |
+
session_id: Session to join
|
| 125 |
+
inviter: Who is sending the invite
|
| 126 |
+
join_url: Optional URL to join session
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
DialogEventObject for INVITE
|
| 130 |
+
"""
|
| 131 |
+
event_data = {
|
| 132 |
+
"session_id": session_id,
|
| 133 |
+
"inviter": inviter
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
if join_url:
|
| 137 |
+
event_data["join_url"] = join_url
|
| 138 |
+
|
| 139 |
+
return create_dialog_event(
|
| 140 |
+
event_type="INVITE",
|
| 141 |
+
event_data=event_data
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def create_floor_request_event(
|
| 146 |
+
agent_id: str,
|
| 147 |
+
priority: int = 0
|
| 148 |
+
) -> Dict[str, Any]:
|
| 149 |
+
"""
|
| 150 |
+
Create FLOOR_REQUEST event
|
| 151 |
+
|
| 152 |
+
Args:
|
| 153 |
+
agent_id: ID of agent requesting floor
|
| 154 |
+
priority: Request priority (higher = more urgent)
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
DialogEventObject for FLOOR_REQUEST
|
| 158 |
+
"""
|
| 159 |
+
return create_dialog_event(
|
| 160 |
+
event_type="FLOOR_REQUEST",
|
| 161 |
+
event_data={
|
| 162 |
+
"agent_id": agent_id,
|
| 163 |
+
"priority": priority
|
| 164 |
+
}
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def create_agent_joined_event(
|
| 169 |
+
agent_id: str,
|
| 170 |
+
agent_name: Optional[str] = None
|
| 171 |
+
) -> Dict[str, Any]:
|
| 172 |
+
"""
|
| 173 |
+
Create AGENT_JOINED event
|
| 174 |
+
|
| 175 |
+
Args:
|
| 176 |
+
agent_id: ID of agent that joined
|
| 177 |
+
agent_name: Optional agent name
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
DialogEventObject for AGENT_JOINED
|
| 181 |
+
"""
|
| 182 |
+
event_data = {"agent_id": agent_id}
|
| 183 |
+
if agent_name:
|
| 184 |
+
event_data["agent_name"] = agent_name
|
| 185 |
+
|
| 186 |
+
return create_dialog_event(
|
| 187 |
+
event_type="AGENT_JOINED",
|
| 188 |
+
event_data=event_data
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def create_agent_left_event(
|
| 193 |
+
agent_id: str,
|
| 194 |
+
reason: str = "disconnected"
|
| 195 |
+
) -> Dict[str, Any]:
|
| 196 |
+
"""
|
| 197 |
+
Create AGENT_LEFT event
|
| 198 |
+
|
| 199 |
+
Args:
|
| 200 |
+
agent_id: ID of agent that left
|
| 201 |
+
reason: Reason for leaving
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
DialogEventObject for AGENT_LEFT
|
| 205 |
+
"""
|
| 206 |
+
return create_dialog_event(
|
| 207 |
+
event_type="AGENT_LEFT",
|
| 208 |
+
event_data={
|
| 209 |
+
"agent_id": agent_id,
|
| 210 |
+
"reason": reason
|
| 211 |
+
}
|
| 212 |
+
)
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utility modules"""
|
| 2 |
+
|
| 3 |
+
from .config import settings
|
| 4 |
+
from .logger import get_logger
|
| 5 |
+
|
| 6 |
+
__all__ = ["settings", "get_logger"]
|
src/utils/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Configuration management using Pydantic Settings"""
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Settings(BaseSettings):
|
| 8 |
+
"""Application settings"""
|
| 9 |
+
|
| 10 |
+
# Server Configuration
|
| 11 |
+
HOST: str = "0.0.0.0"
|
| 12 |
+
PORT: int = 7860
|
| 13 |
+
|
| 14 |
+
# Floor Manager Settings
|
| 15 |
+
DEFAULT_FLOOR_DURATION: int = 300 # seconds
|
| 16 |
+
MAX_AGENTS_PER_SESSION: int = 10
|
| 17 |
+
SESSION_TIMEOUT: int = 3600 # seconds
|
| 18 |
+
AUTO_GRANT_FLOOR: bool = True
|
| 19 |
+
|
| 20 |
+
# Protocol Settings
|
| 21 |
+
OFP_VERSION: str = "1.0.0"
|
| 22 |
+
MESSAGE_TIMEOUT: int = 30 # seconds
|
| 23 |
+
|
| 24 |
+
# UI Settings
|
| 25 |
+
UPDATE_INTERVAL: int = 2 # seconds
|
| 26 |
+
MAX_MESSAGE_DISPLAY: int = 100
|
| 27 |
+
|
| 28 |
+
# Logging
|
| 29 |
+
LOG_LEVEL: str = "INFO"
|
| 30 |
+
|
| 31 |
+
model_config = SettingsConfigDict(
|
| 32 |
+
env_file=".env",
|
| 33 |
+
env_file_encoding="utf-8",
|
| 34 |
+
case_sensitive=True
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# Global settings instance
|
| 39 |
+
settings = Settings()
|
src/utils/helpers.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helper utility functions"""
|
| 2 |
+
|
| 3 |
+
import uuid
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from typing import Any, Dict, List
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def generate_id() -> str:
|
| 9 |
+
"""Generate a unique ID"""
|
| 10 |
+
return str(uuid.uuid4())
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def generate_message_id() -> str:
|
| 14 |
+
"""Generate a unique message ID"""
|
| 15 |
+
return f"msg_{uuid.uuid4().hex[:16]}"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def generate_session_id() -> str:
|
| 19 |
+
"""Generate a unique session ID"""
|
| 20 |
+
return f"session_{uuid.uuid4().hex[:12]}"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_timestamp() -> str:
|
| 24 |
+
"""Get current ISO 8601 timestamp"""
|
| 25 |
+
return datetime.utcnow().isoformat() + "Z"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def format_agent_name(agent_id: str, name: str = None) -> str:
|
| 29 |
+
"""Format agent display name"""
|
| 30 |
+
if name:
|
| 31 |
+
return f"{name} ({agent_id[:8]}...)"
|
| 32 |
+
return f"Agent {agent_id[:8]}..."
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def truncate_text(text: str, max_length: int = 100) -> str:
|
| 36 |
+
"""Truncate text with ellipsis"""
|
| 37 |
+
if len(text) <= max_length:
|
| 38 |
+
return text
|
| 39 |
+
return text[:max_length - 3] + "..."
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def safe_get(dictionary: Dict, *keys: str, default: Any = None) -> Any:
|
| 43 |
+
"""Safely get nested dictionary value"""
|
| 44 |
+
result = dictionary
|
| 45 |
+
for key in keys:
|
| 46 |
+
if isinstance(result, dict):
|
| 47 |
+
result = result.get(key)
|
| 48 |
+
else:
|
| 49 |
+
return default
|
| 50 |
+
if result is None:
|
| 51 |
+
return default
|
| 52 |
+
return result
|
src/utils/logger.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Logging configuration"""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
import sys
|
| 5 |
+
from typing import Optional
|
| 6 |
+
|
| 7 |
+
from .config import settings
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def get_logger(name: Optional[str] = None) -> logging.Logger:
|
| 11 |
+
"""Get a configured logger instance"""
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(name or __name__)
|
| 14 |
+
|
| 15 |
+
# Only configure if not already configured
|
| 16 |
+
if not logger.handlers:
|
| 17 |
+
logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
| 18 |
+
|
| 19 |
+
# Console handler
|
| 20 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 21 |
+
handler.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
| 22 |
+
|
| 23 |
+
# Format
|
| 24 |
+
formatter = logging.Formatter(
|
| 25 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 26 |
+
datefmt='%Y-%m-%d %H:%M:%S'
|
| 27 |
+
)
|
| 28 |
+
handler.setFormatter(formatter)
|
| 29 |
+
|
| 30 |
+
logger.addHandler(handler)
|
| 31 |
+
|
| 32 |
+
return logger
|
test_app.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Simple Gradio Test Interface for OFP Floor Manager
|
| 3 |
+
This is a minimal working version for testing the UI
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from datetime import datetime
|
| 8 |
+
from typing import List, Tuple, Optional
|
| 9 |
+
import uuid
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Simple mock classes for testing
|
| 13 |
+
class MockAgent:
|
| 14 |
+
def __init__(self, agent_id: str, name: str):
|
| 15 |
+
self.agent_id = agent_id
|
| 16 |
+
self.name = name
|
| 17 |
+
self.has_floor = False
|
| 18 |
+
self.joined_at = datetime.now()
|
| 19 |
+
|
| 20 |
+
def to_display(self):
|
| 21 |
+
status = "π€ Has Floor" if self.has_floor else "βΈοΈ Listening"
|
| 22 |
+
return [self.name, self.agent_id[:8], status]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class MockFloorSession:
|
| 26 |
+
def __init__(self, session_id: str):
|
| 27 |
+
self.session_id = session_id
|
| 28 |
+
self.agents = {}
|
| 29 |
+
self.floor_holder = None
|
| 30 |
+
self.messages = []
|
| 31 |
+
self.created_at = datetime.now()
|
| 32 |
+
|
| 33 |
+
def add_agent(self, name: str) -> str:
|
| 34 |
+
agent_id = f"agent_{uuid.uuid4().hex[:8]}"
|
| 35 |
+
agent = MockAgent(agent_id, name)
|
| 36 |
+
self.agents[agent_id] = agent
|
| 37 |
+
|
| 38 |
+
# Add system message
|
| 39 |
+
self.messages.append({
|
| 40 |
+
"role": "system",
|
| 41 |
+
"content": f"π {name} joined the session",
|
| 42 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
# Auto-grant floor if first agent
|
| 46 |
+
if len(self.agents) == 1:
|
| 47 |
+
self.grant_floor(agent_id)
|
| 48 |
+
|
| 49 |
+
return agent_id
|
| 50 |
+
|
| 51 |
+
def grant_floor(self, agent_id: str):
|
| 52 |
+
# Revoke from current holder
|
| 53 |
+
if self.floor_holder and self.floor_holder in self.agents:
|
| 54 |
+
self.agents[self.floor_holder].has_floor = False
|
| 55 |
+
|
| 56 |
+
# Grant to new holder
|
| 57 |
+
if agent_id in self.agents:
|
| 58 |
+
self.agents[agent_id].has_floor = True
|
| 59 |
+
self.floor_holder = agent_id
|
| 60 |
+
self.messages.append({
|
| 61 |
+
"role": "system",
|
| 62 |
+
"content": f"π€ Floor granted to {self.agents[agent_id].name}",
|
| 63 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 64 |
+
})
|
| 65 |
+
|
| 66 |
+
def revoke_floor(self):
|
| 67 |
+
if self.floor_holder and self.floor_holder in self.agents:
|
| 68 |
+
agent_name = self.agents[self.floor_holder].name
|
| 69 |
+
self.agents[self.floor_holder].has_floor = False
|
| 70 |
+
self.floor_holder = None
|
| 71 |
+
self.messages.append({
|
| 72 |
+
"role": "system",
|
| 73 |
+
"content": f"βΈοΈ Floor revoked from {agent_name}",
|
| 74 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
def add_message(self, agent_id: str, content: str):
|
| 78 |
+
if agent_id in self.agents:
|
| 79 |
+
agent = self.agents[agent_id]
|
| 80 |
+
self.messages.append({
|
| 81 |
+
"role": agent.name,
|
| 82 |
+
"content": content,
|
| 83 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 84 |
+
})
|
| 85 |
+
|
| 86 |
+
def get_agent_list(self):
|
| 87 |
+
return [agent.to_display() for agent in self.agents.values()]
|
| 88 |
+
|
| 89 |
+
def get_floor_status(self):
|
| 90 |
+
if self.floor_holder and self.floor_holder in self.agents:
|
| 91 |
+
agent = self.agents[self.floor_holder]
|
| 92 |
+
return f"π€ Current Speaker: {agent.name} ({agent.agent_id[:8]})"
|
| 93 |
+
return "βΈοΈ No active speaker"
|
| 94 |
+
|
| 95 |
+
def format_chat_history(self):
|
| 96 |
+
"""Format messages for Gradio Chatbot with type='messages'"""
|
| 97 |
+
formatted = []
|
| 98 |
+
for msg in self.messages:
|
| 99 |
+
if msg["role"] == "system":
|
| 100 |
+
# System messages as assistant role with italic content
|
| 101 |
+
formatted.append({
|
| 102 |
+
"role": "assistant",
|
| 103 |
+
"content": f"_{msg['content']}_"
|
| 104 |
+
})
|
| 105 |
+
else:
|
| 106 |
+
# Agent messages as user role with name prefix
|
| 107 |
+
formatted.append({
|
| 108 |
+
"role": "user",
|
| 109 |
+
"content": f"**{msg['role']}** [{msg['timestamp']}]: {msg['content']}"
|
| 110 |
+
})
|
| 111 |
+
return formatted
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# Global session (in production, this would be managed differently)
|
| 115 |
+
current_session: Optional[MockFloorSession] = None
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def create_new_session():
|
| 119 |
+
"""Create a new floor session"""
|
| 120 |
+
global current_session
|
| 121 |
+
session_id = f"session_{uuid.uuid4().hex[:8]}"
|
| 122 |
+
current_session = MockFloorSession(session_id)
|
| 123 |
+
|
| 124 |
+
return (
|
| 125 |
+
session_id,
|
| 126 |
+
current_session.format_chat_history(),
|
| 127 |
+
current_session.get_floor_status(),
|
| 128 |
+
current_session.get_agent_list(),
|
| 129 |
+
gr.update(interactive=True)
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def add_agent(agent_name: str):
|
| 134 |
+
"""Add an agent to the session"""
|
| 135 |
+
if not current_session:
|
| 136 |
+
return "β Create a session first!", "", [], "No session", [], gr.Dropdown(choices=[])
|
| 137 |
+
|
| 138 |
+
if not agent_name.strip():
|
| 139 |
+
return "β Agent name cannot be empty!", agent_name, current_session.format_chat_history(), current_session.get_floor_status(), current_session.get_agent_list(), gr.Dropdown(choices=[])
|
| 140 |
+
|
| 141 |
+
agent_id = current_session.add_agent(agent_name.strip())
|
| 142 |
+
agent_choices = get_agent_dropdown_choices()
|
| 143 |
+
|
| 144 |
+
return (
|
| 145 |
+
f"β
Added agent: {agent_name}",
|
| 146 |
+
"",
|
| 147 |
+
current_session.format_chat_history(),
|
| 148 |
+
current_session.get_floor_status(),
|
| 149 |
+
current_session.get_agent_list(),
|
| 150 |
+
gr.Dropdown(choices=agent_choices)
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def send_message(agent_name: str, message: str):
|
| 155 |
+
"""Send a message from an agent"""
|
| 156 |
+
if not current_session:
|
| 157 |
+
return "β Create a session first!", [], "No session"
|
| 158 |
+
|
| 159 |
+
if not message.strip():
|
| 160 |
+
return current_session.format_chat_history(), "", current_session.get_floor_status()
|
| 161 |
+
|
| 162 |
+
# Find agent by name
|
| 163 |
+
agent_id = None
|
| 164 |
+
for aid, agent in current_session.agents.items():
|
| 165 |
+
if agent.name == agent_name:
|
| 166 |
+
agent_id = aid
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
if not agent_id:
|
| 170 |
+
return current_session.format_chat_history(), message, current_session.get_floor_status()
|
| 171 |
+
|
| 172 |
+
current_session.add_message(agent_id, message.strip())
|
| 173 |
+
|
| 174 |
+
return (
|
| 175 |
+
current_session.format_chat_history(),
|
| 176 |
+
"",
|
| 177 |
+
current_session.get_floor_status()
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def grant_floor_to_agent(agent_name: str):
|
| 182 |
+
"""Grant floor to selected agent"""
|
| 183 |
+
if not current_session or not agent_name:
|
| 184 |
+
return [], "No session", []
|
| 185 |
+
|
| 186 |
+
# Find agent by name
|
| 187 |
+
for aid, agent in current_session.agents.items():
|
| 188 |
+
if agent.name == agent_name:
|
| 189 |
+
current_session.grant_floor(aid)
|
| 190 |
+
break
|
| 191 |
+
|
| 192 |
+
return (
|
| 193 |
+
current_session.format_chat_history(),
|
| 194 |
+
current_session.get_floor_status(),
|
| 195 |
+
current_session.get_agent_list()
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def revoke_floor_action():
|
| 200 |
+
"""Revoke floor from current holder"""
|
| 201 |
+
if not current_session:
|
| 202 |
+
return [], "No session", []
|
| 203 |
+
|
| 204 |
+
current_session.revoke_floor()
|
| 205 |
+
|
| 206 |
+
return (
|
| 207 |
+
current_session.format_chat_history(),
|
| 208 |
+
current_session.get_floor_status(),
|
| 209 |
+
current_session.get_agent_list()
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def get_agent_dropdown_choices():
|
| 214 |
+
"""Get list of agent names for dropdown"""
|
| 215 |
+
if not current_session:
|
| 216 |
+
return []
|
| 217 |
+
return [agent.name for agent in current_session.agents.values()]
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
# Create Gradio interface
|
| 221 |
+
with gr.Blocks(title="OFP Floor Manager Test", theme=gr.themes.Soft()) as demo:
|
| 222 |
+
gr.Markdown("# π€ OpenFloor Protocol - Floor Manager Test")
|
| 223 |
+
gr.Markdown("Test interface for managing multi-agent floor conversations")
|
| 224 |
+
|
| 225 |
+
with gr.Row():
|
| 226 |
+
# Left Panel: Session Control
|
| 227 |
+
with gr.Column(scale=1):
|
| 228 |
+
gr.Markdown("### Session Management")
|
| 229 |
+
|
| 230 |
+
session_id = gr.Textbox(
|
| 231 |
+
label="Session ID",
|
| 232 |
+
interactive=False,
|
| 233 |
+
placeholder="No active session"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
create_btn = gr.Button("π Create New Session", variant="primary")
|
| 237 |
+
|
| 238 |
+
gr.Markdown("---")
|
| 239 |
+
gr.Markdown("### Agent Management")
|
| 240 |
+
|
| 241 |
+
agent_name_input = gr.Textbox(
|
| 242 |
+
label="Agent Name",
|
| 243 |
+
placeholder="Enter agent name..."
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
add_agent_btn = gr.Button("β Add Agent")
|
| 247 |
+
add_agent_status = gr.Textbox(
|
| 248 |
+
label="Status",
|
| 249 |
+
interactive=False
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
gr.Markdown("---")
|
| 253 |
+
gr.Markdown("### Connected Agents")
|
| 254 |
+
|
| 255 |
+
agent_table = gr.Dataframe(
|
| 256 |
+
headers=["Name", "ID", "Status"],
|
| 257 |
+
datatype=["str", "str", "str"],
|
| 258 |
+
label="Agents",
|
| 259 |
+
interactive=False
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
gr.Markdown("---")
|
| 263 |
+
gr.Markdown("### Floor Control")
|
| 264 |
+
|
| 265 |
+
agent_selector = gr.Dropdown(
|
| 266 |
+
label="Select Agent",
|
| 267 |
+
choices=[],
|
| 268 |
+
interactive=True,
|
| 269 |
+
allow_custom_value=False
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
with gr.Row():
|
| 273 |
+
grant_btn = gr.Button("π€ Grant Floor", variant="primary")
|
| 274 |
+
revoke_btn = gr.Button("βΈοΈ Revoke Floor", variant="stop")
|
| 275 |
+
|
| 276 |
+
# Right Panel: Conversation
|
| 277 |
+
with gr.Column(scale=2):
|
| 278 |
+
gr.Markdown("### Floor Conversation")
|
| 279 |
+
|
| 280 |
+
floor_status = gr.Textbox(
|
| 281 |
+
label="Current Floor Status",
|
| 282 |
+
interactive=False,
|
| 283 |
+
value="βΈοΈ No active speaker"
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
chatbot = gr.Chatbot(
|
| 287 |
+
label="Conversation",
|
| 288 |
+
height=400,
|
| 289 |
+
type="messages"
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
gr.Markdown("### Send Message")
|
| 293 |
+
|
| 294 |
+
with gr.Row():
|
| 295 |
+
message_agent = gr.Dropdown(
|
| 296 |
+
label="Speaking As",
|
| 297 |
+
choices=[],
|
| 298 |
+
scale=2,
|
| 299 |
+
interactive=True,
|
| 300 |
+
allow_custom_value=False
|
| 301 |
+
)
|
| 302 |
+
message_input = gr.Textbox(
|
| 303 |
+
label="Message",
|
| 304 |
+
placeholder="Type your message...",
|
| 305 |
+
scale=4,
|
| 306 |
+
interactive=True
|
| 307 |
+
)
|
| 308 |
+
send_btn = gr.Button("π€ Send", scale=1, variant="primary", interactive=False)
|
| 309 |
+
|
| 310 |
+
# Event Handlers
|
| 311 |
+
create_btn.click(
|
| 312 |
+
fn=create_new_session,
|
| 313 |
+
outputs=[session_id, chatbot, floor_status, agent_table, send_btn]
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
add_agent_btn.click(
|
| 317 |
+
fn=add_agent,
|
| 318 |
+
inputs=[agent_name_input],
|
| 319 |
+
outputs=[add_agent_status, agent_name_input, chatbot, floor_status, agent_table, agent_selector]
|
| 320 |
+
).then(
|
| 321 |
+
fn=lambda: gr.Dropdown(choices=get_agent_dropdown_choices()),
|
| 322 |
+
outputs=[message_agent]
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
send_btn.click(
|
| 326 |
+
fn=send_message,
|
| 327 |
+
inputs=[message_agent, message_input],
|
| 328 |
+
outputs=[chatbot, message_input, floor_status]
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
grant_btn.click(
|
| 332 |
+
fn=grant_floor_to_agent,
|
| 333 |
+
inputs=[agent_selector],
|
| 334 |
+
outputs=[chatbot, floor_status, agent_table]
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
revoke_btn.click(
|
| 338 |
+
fn=revoke_floor_action,
|
| 339 |
+
outputs=[chatbot, floor_status, agent_table]
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
if __name__ == "__main__":
|
| 343 |
+
print("π Starting OFP Floor Manager Test Interface...")
|
| 344 |
+
print("π± Open your browser to test the interface")
|
| 345 |
+
print("=" * 50)
|
| 346 |
+
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
|
test_backend.sh
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "π§ͺ Testing OFP Floor Manager Backend"
|
| 4 |
+
echo "======================================"
|
| 5 |
+
echo ""
|
| 6 |
+
|
| 7 |
+
# Start the server in background
|
| 8 |
+
echo "π‘ Starting Gradio server..."
|
| 9 |
+
python3 test_app.py > /tmp/gradio_test.log 2>&1 &
|
| 10 |
+
SERVER_PID=$!
|
| 11 |
+
|
| 12 |
+
# Wait for server to start
|
| 13 |
+
echo "β³ Waiting for server to start..."
|
| 14 |
+
sleep 5
|
| 15 |
+
|
| 16 |
+
# Check if server is running
|
| 17 |
+
if ! ps -p $SERVER_PID > /dev/null; then
|
| 18 |
+
echo "β Server failed to start. Check logs:"
|
| 19 |
+
cat /tmp/gradio_test.log
|
| 20 |
+
exit 1
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
echo "β
Server started (PID: $SERVER_PID)"
|
| 24 |
+
echo ""
|
| 25 |
+
|
| 26 |
+
# Test 1: Create Session
|
| 27 |
+
echo "π§ͺ Test 1: Creating new session..."
|
| 28 |
+
RESPONSE=$(curl -s -X POST http://localhost:7860/api/predict \
|
| 29 |
+
-H "Content-Type: application/json" \
|
| 30 |
+
-d '{"fn_index": 0, "data": [], "session_hash": "test_session_1"}')
|
| 31 |
+
|
| 32 |
+
if echo "$RESPONSE" | grep -q "session_"; then
|
| 33 |
+
echo "β
Session created successfully"
|
| 34 |
+
SESSION_ID=$(echo "$RESPONSE" | grep -o 'session_[a-z0-9]*' | head -1)
|
| 35 |
+
echo " Session ID: $SESSION_ID"
|
| 36 |
+
else
|
| 37 |
+
echo "β Failed to create session"
|
| 38 |
+
echo " Response: $RESPONSE"
|
| 39 |
+
fi
|
| 40 |
+
|
| 41 |
+
echo ""
|
| 42 |
+
|
| 43 |
+
# Test 2: Add Agent
|
| 44 |
+
echo "π§ͺ Test 2: Adding agent 'Alice'..."
|
| 45 |
+
RESPONSE=$(curl -s -X POST http://localhost:7860/api/predict \
|
| 46 |
+
-H "Content-Type: application/json" \
|
| 47 |
+
-d '{"fn_index": 1, "data": ["Alice"], "session_hash": "test_session_1"}')
|
| 48 |
+
|
| 49 |
+
if echo "$RESPONSE" | grep -q "Alice"; then
|
| 50 |
+
echo "β
Agent added successfully"
|
| 51 |
+
else
|
| 52 |
+
echo "β Failed to add agent"
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
echo ""
|
| 56 |
+
|
| 57 |
+
# Test 3: Add Another Agent
|
| 58 |
+
echo "π§ͺ Test 3: Adding agent 'Bob'..."
|
| 59 |
+
RESPONSE=$(curl -s -X POST http://localhost:7860/api/predict \
|
| 60 |
+
-H "Content-Type: application/json" \
|
| 61 |
+
-d '{"fn_index": 1, "data": ["Bob"], "session_hash": "test_session_1"}')
|
| 62 |
+
|
| 63 |
+
if echo "$RESPONSE" | grep -q "Bob"; then
|
| 64 |
+
echo "β
Agent added successfully"
|
| 65 |
+
else
|
| 66 |
+
echo "β Failed to add agent"
|
| 67 |
+
fi
|
| 68 |
+
|
| 69 |
+
echo ""
|
| 70 |
+
|
| 71 |
+
# Test 4: Send Message
|
| 72 |
+
echo "π§ͺ Test 4: Sending message from Alice..."
|
| 73 |
+
RESPONSE=$(curl -s -X POST http://localhost:7860/api/predict \
|
| 74 |
+
-H "Content-Type: application/json" \
|
| 75 |
+
-d '{"fn_index": 2, "data": ["Alice", "Hello Bob!"], "session_hash": "test_session_1"}')
|
| 76 |
+
|
| 77 |
+
if echo "$RESPONSE" | grep -q "Hello Bob"; then
|
| 78 |
+
echo "β
Message sent successfully"
|
| 79 |
+
else
|
| 80 |
+
echo "β Failed to send message"
|
| 81 |
+
fi
|
| 82 |
+
|
| 83 |
+
echo ""
|
| 84 |
+
|
| 85 |
+
# Test 5: Grant Floor
|
| 86 |
+
echo "π§ͺ Test 5: Granting floor to Bob..."
|
| 87 |
+
RESPONSE=$(curl -s -X POST http://localhost:7860/api/predict \
|
| 88 |
+
-H "Content-Type: application/json" \
|
| 89 |
+
-d '{"fn_index": 3, "data": ["Bob"], "session_hash": "test_session_1"}')
|
| 90 |
+
|
| 91 |
+
if echo "$RESPONSE" | grep -q "granted"; then
|
| 92 |
+
echo "β
Floor granted successfully"
|
| 93 |
+
else
|
| 94 |
+
echo "β Failed to grant floor"
|
| 95 |
+
fi
|
| 96 |
+
|
| 97 |
+
echo ""
|
| 98 |
+
echo "======================================"
|
| 99 |
+
echo "π All tests completed!"
|
| 100 |
+
echo ""
|
| 101 |
+
|
| 102 |
+
# Show server logs
|
| 103 |
+
echo "π Server logs:"
|
| 104 |
+
tail -20 /tmp/gradio_test.log
|
| 105 |
+
|
| 106 |
+
echo ""
|
| 107 |
+
echo "π Stopping server (PID: $SERVER_PID)..."
|
| 108 |
+
kill $SERVER_PID
|
| 109 |
+
wait $SERVER_PID 2>/dev/null
|
| 110 |
+
|
| 111 |
+
echo "β
Server stopped"
|
| 112 |
+
echo ""
|
| 113 |
+
echo "π‘ To run the server interactively: python3 test_app.py"
|