Spaces:
Running
Running
| from openfloor.manifest import * | |
| from openfloor.envelope import * | |
| # Model Images | |
| avatar_images = { | |
| "Qwen3-32B": "https://cdn-avatars.huggingface.co/v1/production/uploads/620760a26e3b7210c2ff1943/-s1gyJfvbE1RgO5iBeNOi.png", | |
| "DeepSeek-R1": "https://logosandtypes.com/wp-content/uploads/2025/02/deepseek.svg", | |
| "Mistral Large": "https://logosandtypes.com/wp-content/uploads/2025/02/mistral-ai.svg", | |
| "Meta-Llama-3.3-70B-Instruct": "https://registry.npmmirror.com/@lobehub/icons-static-png/1.46.0/files/dark/meta-color.png", | |
| "arXiv Research Agent": "https://public.boxcloud.com/api/2.0/internal_files/804104772302/versions/860288648702/representations/png_paged_2048x2048/content/1.png?access_token=1!r4Iuj5vkFMywOMAPQ4M6QIr3eqkJ6CjlMzh77DAkRcGdVRvzG-Xh6GFZz_JkzoJuO9yRR5cQ6cs5VvUolhHxNM6JdliJ2JOi9VWm-BbB5C63s0_7bpaQYLFAJmLnlG2RzhX74_bK4XS-csGP8CI-9tVa6LUcrCNTKJmc-yddIepopLMZLqJ34h0nu69Yt0Not4yDErBTk2jWaneTBdhdXErOhCs9cz4HK-itpCfdL3Lze9oAjf6o8EVWRn6R0YPw95trQl7IziLd1P78BFuVaDjborvhs_yWgcw0uxXNZz_8WZh5z5NOvDq6sMo0uYGWiJ_g1JWyiaDJpsWBlHRiRwwF5FZLsVSXRz6dXD1MtKyOPs8J6CBYkGisicIysuiPsT1Kcyrgm-3jH1-tanOVs66TCmnGNbSYH_o_-x9iOdkI8rEL7-l2i5iHn22i-q8apZTOd_eQp22UCsmUBJQig7att_AwVKasmqOegDZHO2h1b_vSjeZ8ISBcg8i7fnFdF9Ej35s6OFkV5IyZtMzbAKdRlwdt5lupsshO5FCByR0kau9PVIiwJilI0t7zYsJtSXzVxVQEyEPuLTAlJJI7827NoNA1OSojaPsfhFrW4jEfJIgMoxNl_vFfZvLBmAA7Pk1SeaN7J0ebDji-bDbwqlPadp7JOB3s2Six11fm4Ss.&shared_link=https%3A%2F%2Fcornell.app.box.com%2Fv%2Farxiv-logomark-small-png&box_client_name=box-content-preview&box_client_version=3.7.0", | |
| "GitHub Research Agent": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/GitHub_Invertocat_Logo.svg/250px-GitHub_Invertocat_Logo.svg.png", | |
| "SEC EDGAR Research Agent": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Seal_of_the_United_States_Securities_and_Exchange_Commission.svg/250px-Seal_of_the_United_States_Securities_and_Exchange_Commission.svg.png", | |
| "Web Search Research Agent": "https://duckduckgo.com/static-assets/favicons/DDG-iOS-icon_76x76.png", | |
| "Wikipedia Research Agent": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png" | |
| } | |
| class OpenFloorManager: | |
| """Central floor manager for coordinating all OpenFloor agents - FIXED VERSION""" | |
| def __init__(self, port: int = 7860): | |
| self.port = port | |
| self.agent_registry = {} # speakerUri -> agent info | |
| self.active_conversations = {} # conversation_id -> conversation state | |
| self.visual_callback = None | |
| self.message_history = {} # conversation_id -> message list | |
| def register_agent(self, manifest: Manifest, agent_url: str): | |
| """Register an agent with the floor manager""" | |
| speaker_uri = manifest.identification.speakerUri | |
| self.agent_registry[speaker_uri] = { | |
| 'manifest': manifest, | |
| 'url': agent_url, | |
| 'status': 'available', | |
| 'last_seen': datetime.now() | |
| } | |
| print(f"ποΈ Floor Manager: Registered agent {manifest.identification.conversationalName}") | |
| def discover_agents(self) -> List[Manifest]: | |
| """Return manifests of all registered agents""" | |
| return [info['manifest'] for info in self.agent_registry.values()] | |
| def create_conversation(self, initial_participants: List[str] = None) -> str: | |
| """Create a new conversation with optional initial participants""" | |
| conversation_id = f"conv:{uuid.uuid4()}" | |
| # Create proper OpenFloor conversation structure | |
| conversants = [] | |
| if initial_participants: | |
| for participant_uri in initial_participants: | |
| if participant_uri in self.agent_registry: | |
| manifest = self.agent_registry[participant_uri]['manifest'] | |
| conversants.append(Conversant( | |
| identification=manifest.identification | |
| )) | |
| conversation = Conversation( | |
| id=conversation_id, | |
| conversants=conversants | |
| ) | |
| self.active_conversations[conversation_id] = { | |
| 'conversation': conversation, | |
| 'participants': initial_participants or [], | |
| 'messages': [], | |
| 'created_at': datetime.now(), | |
| 'status': 'active' | |
| } | |
| self.message_history[conversation_id] = [] | |
| print(f"ποΈ Floor Manager: Created conversation {conversation_id}") | |
| return conversation_id | |
| def invite_agent_to_conversation(self, conversation_id: str, target_speaker_uri: str, | |
| inviting_speaker_uri: str) -> bool: | |
| """Send proper OpenFloor InviteEvent to an agent""" | |
| if conversation_id not in self.active_conversations: | |
| print(f"ποΈ Floor Manager: Conversation {conversation_id} not found") | |
| return False | |
| if target_speaker_uri not in self.agent_registry: | |
| print(f"ποΈ Floor Manager: Agent {target_speaker_uri} not registered") | |
| return False | |
| conversation_state = self.active_conversations[conversation_id] | |
| conversation = conversation_state['conversation'] | |
| target_agent = self.agent_registry[target_speaker_uri] | |
| # Create proper OpenFloor InviteEvent | |
| invite_envelope = Envelope( | |
| conversation=conversation, | |
| sender=Sender(speakerUri=inviting_speaker_uri), | |
| events=[ | |
| InviteEvent( | |
| to=To(speakerUri=target_speaker_uri), | |
| parameters={ | |
| 'conversation_id': conversation_id, | |
| 'invited_by': inviting_speaker_uri, | |
| 'invitation_message': f"You are invited to join the expert analysis discussion" | |
| } | |
| ) | |
| ] | |
| ) | |
| # Send invitation to target agent | |
| response = self._send_to_agent(target_agent['url'], invite_envelope) | |
| if response: | |
| # Add to conversation participants | |
| if target_speaker_uri not in conversation_state['participants']: | |
| conversation_state['participants'].append(target_speaker_uri) | |
| # Add to conversation conversants | |
| target_manifest = target_agent['manifest'] | |
| conversation.conversants.append(Conversant( | |
| identification=target_manifest.identification | |
| )) | |
| self._update_visual_state(conversation_id) | |
| print(f"ποΈ Floor Manager: Successfully invited {target_speaker_uri} to {conversation_id}") | |
| return True | |
| print(f"ποΈ Floor Manager: Failed to invite {target_speaker_uri}") | |
| return False | |
| def route_message(self, envelope: Envelope) -> bool: | |
| """Route message to appropriate recipients with proper OpenFloor semantics""" | |
| conversation_id = envelope.conversation.id | |
| if conversation_id not in self.active_conversations: | |
| print(f"ποΈ Floor Manager: Cannot route - conversation {conversation_id} not found") | |
| return False | |
| conversation_state = self.active_conversations[conversation_id] | |
| sender_uri = envelope.sender.speakerUri | |
| # Store message in conversation history | |
| message_record = { | |
| 'envelope': envelope, | |
| 'timestamp': datetime.now(), | |
| 'sender': sender_uri | |
| } | |
| conversation_state['messages'].append(message_record) | |
| self.message_history[conversation_id].append(message_record) | |
| # Process each event in the envelope | |
| routed_successfully = True | |
| for event in envelope.events: | |
| if hasattr(event, 'to') and event.to: | |
| # Directed message - send to specific agent | |
| target_uri = event.to.speakerUri | |
| if target_uri in self.agent_registry and target_uri != sender_uri: | |
| target_agent = self.agent_registry[target_uri] | |
| success = self._send_to_agent(target_agent['url'], envelope) | |
| if not success: | |
| routed_successfully = False | |
| print(f"ποΈ Floor Manager: Failed to route directed message to {target_uri}") | |
| else: | |
| # Broadcast to all conversation participants except sender | |
| for participant_uri in conversation_state['participants']: | |
| if participant_uri != sender_uri and participant_uri in self.agent_registry: | |
| participant_agent = self.agent_registry[participant_uri] | |
| success = self._send_to_agent(participant_agent['url'], envelope) | |
| if not success: | |
| routed_successfully = False | |
| print(f"ποΈ Floor Manager: Failed to broadcast to {participant_uri}") | |
| # Update visual state after routing | |
| self._update_visual_state(conversation_id) | |
| return routed_successfully | |
| def _send_to_agent(self, agent_url: str, envelope: Envelope) -> bool: | |
| """Send envelope to specific agent via HTTP POST""" | |
| if agent_url == "internal://ai-expert": | |
| # Internal AI experts don't have HTTP endpoints | |
| return True | |
| try: | |
| # Fix: Use proper OpenFloor endpoint | |
| response = requests.post( | |
| f"{agent_url}/openfloor/conversation", # β Fixed endpoint | |
| json=json.loads(envelope.to_json()), | |
| headers={'Content-Type': 'application/json'}, | |
| timeout=30 | |
| ) | |
| success = response.status_code == 200 | |
| if success: | |
| print(f"ποΈ Floor Manager: Successfully sent message to {agent_url}") | |
| else: | |
| print(f"ποΈ Floor Manager: HTTP error {response.status_code} sending to {agent_url}") | |
| print(f"ποΈ Floor Manager: Response: {response.text}") | |
| return success | |
| except Exception as e: | |
| print(f"ποΈ Floor Manager: Error sending to {agent_url}: {e}") | |
| return False | |
| def _update_visual_state(self, conversation_id: str): | |
| """Update visual interface based on conversation state""" | |
| if not self.visual_callback or conversation_id not in self.active_conversations: | |
| return | |
| conversation_state = self.active_conversations[conversation_id] | |
| # Convert to visual format | |
| participants = [] | |
| messages = [] | |
| # Get participant names | |
| for participant_uri in conversation_state['participants']: | |
| if participant_uri in self.agent_registry: | |
| agent_info = self.agent_registry[participant_uri] | |
| participants.append(agent_info['manifest'].identification.conversationalName) | |
| else: | |
| # Handle AI experts or other internal participants | |
| participants.append(participant_uri.split(':')[-1] if ':' in participant_uri else participant_uri) | |
| # Convert messages | |
| for msg_record in conversation_state['messages']: | |
| envelope = msg_record['envelope'] | |
| sender_uri = envelope.sender.speakerUri | |
| # Get sender name | |
| if sender_uri in self.agent_registry: | |
| sender_name = self.agent_registry[sender_uri]['manifest'].identification.conversationalName | |
| else: | |
| sender_name = sender_uri.split(':')[-1] if ':' in sender_uri else sender_uri | |
| # Extract message content from events | |
| for event in envelope.events: | |
| if hasattr(event, 'eventType'): | |
| if event.eventType == 'utterance': | |
| # Extract text from utterance dialog event | |
| dialog_event = event.parameters.get('dialogEvent') | |
| if dialog_event: | |
| text = self._extract_text_from_dialog_event(dialog_event) | |
| if text: | |
| messages.append({ | |
| 'speaker': sender_name, | |
| 'text': text, | |
| 'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'), | |
| 'type': 'utterance' | |
| }) | |
| elif event.eventType == 'context': | |
| # Handle context events (like research results) | |
| context_params = event.parameters | |
| if 'research_function' in context_params: | |
| messages.append({ | |
| 'speaker': sender_name, | |
| 'text': f"π Research: {context_params.get('result', 'No result')}", | |
| 'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'), | |
| 'type': 'research_result' | |
| }) | |
| elif event.eventType == 'invite': | |
| messages.append({ | |
| 'speaker': sender_name, | |
| 'text': f"π¨ Invited agent to join conversation", | |
| 'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'), | |
| 'type': 'system' | |
| }) | |
| elif event.eventType == 'bye': | |
| messages.append({ | |
| 'speaker': sender_name, | |
| 'text': f"π Left the conversation", | |
| 'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'), | |
| 'type': 'system' | |
| }) | |
| # Call visual callback | |
| self.visual_callback({ | |
| 'participants': participants, | |
| 'messages': messages, | |
| 'currentSpeaker': None, | |
| 'thinking': [], | |
| 'showBubbles': participants, | |
| 'avatarImages': avatar_images | |
| }) | |
| def _extract_text_from_dialog_event(self, dialog_event) -> str: | |
| """Extract text from dialog event structure""" | |
| try: | |
| if isinstance(dialog_event, dict): | |
| features = dialog_event.get('features', {}) | |
| text_feature = features.get('text', {}) | |
| tokens = text_feature.get('tokens', []) | |
| return ' '.join([token.get('value', '') for token in tokens]) | |
| return "" | |
| except Exception as e: | |
| print(f"ποΈ Floor Manager: Error extracting text: {e}") | |
| return "" | |
| def set_visual_callback(self, callback): | |
| """Set callback for visual updates""" | |
| self.visual_callback = callback | |
| def start_floor_manager_service(self): | |
| """Start the floor manager HTTP service""" | |
| from flask import Flask, request, jsonify | |
| app = Flask("openfloor-manager") | |
| def discover_agents(): | |
| """Return list of available agent manifests""" | |
| manifests = [json.loads(manifest.to_json()) for manifest in self.discover_agents()] | |
| return jsonify(manifests) | |
| def handle_conversation(): | |
| """Handle incoming conversation messages""" | |
| try: | |
| envelope_data = request.get_json() | |
| envelope = Envelope.from_json(json.dumps(envelope_data)) | |
| success = self.route_message(envelope) | |
| if success: | |
| return jsonify({'status': 'routed'}) | |
| else: | |
| return jsonify({'error': 'Failed to route message'}), 400 | |
| except Exception as e: | |
| print(f"ποΈ Floor Manager: Error handling conversation: {e}") | |
| return jsonify({'error': str(e)}), 500 | |
| def invite_agent(): | |
| """Handle agent invitation requests""" | |
| try: | |
| data = request.get_json() | |
| conversation_id = data['conversation_id'] | |
| target_speaker_uri = data['target_speaker_uri'] | |
| inviting_speaker_uri = data['inviting_speaker_uri'] | |
| success = self.invite_agent_to_conversation( | |
| conversation_id, target_speaker_uri, inviting_speaker_uri | |
| ) | |
| return jsonify({'success': success}) | |
| except Exception as e: | |
| print(f"ποΈ Floor Manager: Error inviting agent: {e}") | |
| return jsonify({'error': str(e)}), 500 | |
| # Start server in background thread | |
| import threading | |
| server_thread = threading.Thread( | |
| target=lambda: app.run(host='localhost', port=self.port + 100, debug=False) | |
| ) | |
| server_thread.daemon = True | |
| server_thread.start() | |
| print(f"ποΈ OpenFloor Manager started on port {self.port + 100}") | |
| return True |