BolyosCsaba commited on
Commit
148a4a7
Β·
1 Parent(s): 6d97770

initial commit

Browse files
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
- title: FloorManager
3
- emoji: 🐠
4
- colorFrom: pink
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 6.2.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: open floor protocol minimal floor manager
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"