Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -14,6 +14,9 @@ import queue
|
|
14 |
import uuid
|
15 |
from gradio_consilium_roundtable import consilium_roundtable
|
16 |
from research_tools.base_tool import BaseTool
|
|
|
|
|
|
|
17 |
from openfloor import *
|
18 |
from openfloor.manifest import *
|
19 |
from openfloor.envelope import *
|
@@ -43,601 +46,6 @@ avatar_images = {
|
|
43 |
"Wikipedia Research Agent": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png"
|
44 |
}
|
45 |
|
46 |
-
class OpenFloorResearchAgent:
|
47 |
-
"""Wrap research tools as independent OpenFloor agents"""
|
48 |
-
|
49 |
-
def __init__(self, tool: BaseTool, port: int = None):
|
50 |
-
self.tool = tool
|
51 |
-
self.port = port
|
52 |
-
self.manifest = self._create_manifest()
|
53 |
-
self.active_conversations = {}
|
54 |
-
|
55 |
-
def _create_manifest(self) -> Manifest:
|
56 |
-
"""Create OpenFloor manifest for this research agent"""
|
57 |
-
speaker_uri = f"tag:research.consilium,2025:{self.tool.name.lower().replace(' ', '-')}-agent"
|
58 |
-
|
59 |
-
# Tool-specific keyphrases and capabilities
|
60 |
-
tool_configs = {
|
61 |
-
'Web Search': {
|
62 |
-
'keyphrases': ['web', 'search', 'current', 'news', 'latest', 'recent'],
|
63 |
-
'synopsis': 'Real-time web search for current information and trends'
|
64 |
-
},
|
65 |
-
'Wikipedia': {
|
66 |
-
'keyphrases': ['facts', 'encyclopedia', 'history', 'knowledge', 'definition'],
|
67 |
-
'synopsis': 'Authoritative encyclopedia research and factual verification'
|
68 |
-
},
|
69 |
-
'arXiv': {
|
70 |
-
'keyphrases': ['academic', 'research', 'papers', 'science', 'study'],
|
71 |
-
'synopsis': 'Academic research papers and scientific literature analysis'
|
72 |
-
},
|
73 |
-
'GitHub': {
|
74 |
-
'keyphrases': ['technology', 'code', 'development', 'programming', 'trends'],
|
75 |
-
'synopsis': 'Technology adoption trends and software development analysis'
|
76 |
-
},
|
77 |
-
'SEC EDGAR': {
|
78 |
-
'keyphrases': ['financial', 'company', 'earnings', 'sec', 'filings'],
|
79 |
-
'synopsis': 'Corporate financial data and SEC regulatory filings research'
|
80 |
-
}
|
81 |
-
}
|
82 |
-
|
83 |
-
config = tool_configs.get(self.tool.name, {
|
84 |
-
'keyphrases': ['research', 'data'],
|
85 |
-
'synopsis': self.tool.description
|
86 |
-
})
|
87 |
-
|
88 |
-
return Manifest(
|
89 |
-
identification=Identification(
|
90 |
-
speakerUri=speaker_uri,
|
91 |
-
serviceUrl=f"http://localhost:{self.port}/openfloor" if self.port else None,
|
92 |
-
conversationalName=f"{self.tool.name} Research Agent",
|
93 |
-
organization="Consilium Research Division",
|
94 |
-
role="Research Specialist",
|
95 |
-
synopsis=config['synopsis']
|
96 |
-
),
|
97 |
-
capabilities=[
|
98 |
-
Capability(
|
99 |
-
keyphrases=config['keyphrases'],
|
100 |
-
descriptions=[self.tool.description],
|
101 |
-
languages=["en-us"]
|
102 |
-
)
|
103 |
-
]
|
104 |
-
)
|
105 |
-
|
106 |
-
def handle_utterance_event(self, envelope: Envelope) -> Envelope:
|
107 |
-
"""Handle research requests from AI experts"""
|
108 |
-
print(f"🔍 DEBUG: {self.tool.name} - Starting handle_utterance_event")
|
109 |
-
|
110 |
-
# Extract the query from the utterance
|
111 |
-
for event in envelope.events:
|
112 |
-
if hasattr(event, 'eventType') and event.eventType == 'utterance':
|
113 |
-
dialog_event = event.parameters.get('dialogEvent')
|
114 |
-
|
115 |
-
if dialog_event and isinstance(dialog_event, dict):
|
116 |
-
# dialog_event is a dict, not an object - use dict access
|
117 |
-
features = dialog_event.get('features')
|
118 |
-
print(f"🔍 DEBUG: features: {features}")
|
119 |
-
|
120 |
-
if features and 'text' in features:
|
121 |
-
text_feature = features['text']
|
122 |
-
print(f"🔍 DEBUG: text_feature: {text_feature}")
|
123 |
-
|
124 |
-
if 'tokens' in text_feature:
|
125 |
-
tokens = text_feature['tokens']
|
126 |
-
query_text = ' '.join([token.get('value', '') for token in tokens])
|
127 |
-
|
128 |
-
print(f"🔍 DEBUG: {self.tool.name} received query: '{query_text}'")
|
129 |
-
|
130 |
-
# Perform the research
|
131 |
-
import time
|
132 |
-
start_time = time.time()
|
133 |
-
research_result = self.tool.search(query_text)
|
134 |
-
end_time = time.time()
|
135 |
-
|
136 |
-
print(f"🔍 DEBUG: {self.tool.name} completed in {end_time - start_time:.2f}s")
|
137 |
-
print(f"🔍 DEBUG: Result length: {len(research_result)} chars")
|
138 |
-
print(f"🔍 DEBUG: Result preview: {research_result[:200]}...")
|
139 |
-
|
140 |
-
# Create response envelope
|
141 |
-
return self._create_response_envelope(envelope, research_result, query_text)
|
142 |
-
|
143 |
-
return self._create_error_response(envelope, "Could not extract query from request")
|
144 |
-
|
145 |
-
def _create_response_envelope(self, original_envelope: Envelope, research_result: str, query: str) -> Envelope:
|
146 |
-
"""Create OpenFloor response envelope with research results"""
|
147 |
-
|
148 |
-
# Create response dialog event
|
149 |
-
response_dialog = DialogEvent(
|
150 |
-
speakerUri=self.manifest.identification.speakerUri,
|
151 |
-
features={
|
152 |
-
"text": TextFeature(values=[research_result])
|
153 |
-
}
|
154 |
-
)
|
155 |
-
|
156 |
-
# Create context with research metadata
|
157 |
-
research_context = ContextEvent(
|
158 |
-
parameters={
|
159 |
-
"research_tool": self.tool.name,
|
160 |
-
"query": query,
|
161 |
-
"source": self.tool.name.lower().replace(' ', '_'),
|
162 |
-
"confidence": self._assess_result_confidence(research_result),
|
163 |
-
"timestamp": datetime.now().isoformat()
|
164 |
-
}
|
165 |
-
)
|
166 |
-
|
167 |
-
# Create response envelope
|
168 |
-
response_envelope = Envelope(
|
169 |
-
conversation=original_envelope.conversation,
|
170 |
-
sender=Sender(speakerUri=self.manifest.identification.speakerUri),
|
171 |
-
events=[
|
172 |
-
UtteranceEvent(dialogEvent=response_dialog),
|
173 |
-
research_context
|
174 |
-
]
|
175 |
-
)
|
176 |
-
|
177 |
-
return response_envelope
|
178 |
-
|
179 |
-
def _assess_result_confidence(self, result: str) -> float:
|
180 |
-
"""Assess confidence in research result quality"""
|
181 |
-
if not result or len(result) < 50:
|
182 |
-
return 0.3
|
183 |
-
|
184 |
-
quality_indicators = [
|
185 |
-
(len(result) > 500, 0.2), # Substantial content
|
186 |
-
(any(year in result for year in ['2024', '2025']), 0.2), # Recent data
|
187 |
-
(result.count('\n') > 5, 0.1), # Well-structured
|
188 |
-
('error' not in result.lower(), 0.3), # No errors
|
189 |
-
(any(indicator in result.lower() for indicator in ['data', 'study', 'research']), 0.2) # Authoritative
|
190 |
-
]
|
191 |
-
|
192 |
-
confidence = 0.5 # Base confidence
|
193 |
-
for condition, boost in quality_indicators:
|
194 |
-
if condition:
|
195 |
-
confidence += boost
|
196 |
-
|
197 |
-
return min(1.0, confidence)
|
198 |
-
|
199 |
-
def _create_error_response(self, original_envelope: Envelope, error_msg: str) -> Envelope:
|
200 |
-
"""Create error response envelope"""
|
201 |
-
error_dialog = DialogEvent(
|
202 |
-
speakerUri=self.manifest.identification.speakerUri,
|
203 |
-
features={
|
204 |
-
"text": TextFeature(values=[f"Research error: {error_msg}"])
|
205 |
-
}
|
206 |
-
)
|
207 |
-
|
208 |
-
return Envelope(
|
209 |
-
conversation=original_envelope.conversation,
|
210 |
-
sender=Sender(speakerUri=self.manifest.identification.speakerUri),
|
211 |
-
events=[UtteranceEvent(dialogEvent=error_dialog)]
|
212 |
-
)
|
213 |
-
|
214 |
-
def join_conversation(self, conversation_id: str) -> bool:
|
215 |
-
"""Join a conversation as an active research agent"""
|
216 |
-
self.active_conversations[conversation_id] = {
|
217 |
-
'joined_at': datetime.now(),
|
218 |
-
'status': 'active'
|
219 |
-
}
|
220 |
-
return True
|
221 |
-
|
222 |
-
def leave_conversation(self, conversation_id: str) -> bool:
|
223 |
-
"""Leave a conversation"""
|
224 |
-
if conversation_id in self.active_conversations:
|
225 |
-
del self.active_conversations[conversation_id]
|
226 |
-
return True
|
227 |
-
|
228 |
-
def get_manifest(self) -> Manifest:
|
229 |
-
"""Return the OpenFloor manifest for this research agent"""
|
230 |
-
return self.manifest
|
231 |
-
|
232 |
-
|
233 |
-
class OpenFloorAgentServer:
|
234 |
-
"""Run a research agent as an actual OpenFloor service"""
|
235 |
-
|
236 |
-
def __init__(self, research_agent: OpenFloorResearchAgent, port: int):
|
237 |
-
self.agent = research_agent
|
238 |
-
self.port = port
|
239 |
-
self.app = None
|
240 |
-
|
241 |
-
def start_server(self):
|
242 |
-
"""Start the OpenFloor agent server"""
|
243 |
-
from flask import Flask, request, jsonify
|
244 |
-
|
245 |
-
app = Flask(f"research-agent-{self.port}")
|
246 |
-
|
247 |
-
@app.route('/openfloor/conversation', methods=['POST'])
|
248 |
-
def handle_conversation():
|
249 |
-
try:
|
250 |
-
print(f"🔍 DEBUG: Flask route called for agent {self.agent.tool.name}")
|
251 |
-
|
252 |
-
# Parse incoming OpenFloor envelope
|
253 |
-
envelope_data = request.get_json()
|
254 |
-
print(f"🔍 DEBUG: Received envelope data: {str(envelope_data)[:200]}...")
|
255 |
-
|
256 |
-
envelope = Envelope.from_json(json.dumps(envelope_data))
|
257 |
-
print(f"🔍 DEBUG: Envelope parsed successfully")
|
258 |
-
|
259 |
-
# Process the request
|
260 |
-
print(f"🔍 DEBUG: Calling handle_utterance_event...")
|
261 |
-
response_envelope = self.agent.handle_utterance_event(envelope)
|
262 |
-
print(f"🔍 DEBUG: handle_utterance_event completed")
|
263 |
-
|
264 |
-
# Return OpenFloor response
|
265 |
-
response_json = json.loads(response_envelope.to_json())
|
266 |
-
print(f"🔍 DEBUG: Returning response: {str(response_json)[:200]}...")
|
267 |
-
return jsonify(response_json)
|
268 |
-
|
269 |
-
except Exception as e:
|
270 |
-
print(f"🔍 DEBUG: Exception in Flask route: {e}")
|
271 |
-
import traceback
|
272 |
-
traceback.print_exc()
|
273 |
-
|
274 |
-
error_response = self.agent._create_error_response(
|
275 |
-
envelope if 'envelope' in locals() else None,
|
276 |
-
str(e)
|
277 |
-
)
|
278 |
-
return jsonify(json.loads(error_response.to_json())), 500
|
279 |
-
|
280 |
-
@app.route('/openfloor/manifest', methods=['GET'])
|
281 |
-
def get_manifest():
|
282 |
-
"""Return agent manifest"""
|
283 |
-
return jsonify(json.loads(self.agent.manifest.to_json()))
|
284 |
-
|
285 |
-
# Start server in background thread
|
286 |
-
import threading
|
287 |
-
server_thread = threading.Thread(
|
288 |
-
target=lambda: app.run(host='localhost', port=self.port, debug=False)
|
289 |
-
)
|
290 |
-
server_thread.daemon = True
|
291 |
-
server_thread.start()
|
292 |
-
|
293 |
-
print(f"🚀 OpenFloor agent '{self.agent.manifest.identification.conversationalName}' started on port {self.port}")
|
294 |
-
return True
|
295 |
-
|
296 |
-
|
297 |
-
class OpenFloorManager:
|
298 |
-
"""Central floor manager for coordinating all OpenFloor agents - FIXED VERSION"""
|
299 |
-
|
300 |
-
def __init__(self, port: int = 7860):
|
301 |
-
self.port = port
|
302 |
-
self.agent_registry = {} # speakerUri -> agent info
|
303 |
-
self.active_conversations = {} # conversation_id -> conversation state
|
304 |
-
self.visual_callback = None
|
305 |
-
self.message_history = {} # conversation_id -> message list
|
306 |
-
|
307 |
-
def register_agent(self, manifest: Manifest, agent_url: str):
|
308 |
-
"""Register an agent with the floor manager"""
|
309 |
-
speaker_uri = manifest.identification.speakerUri
|
310 |
-
self.agent_registry[speaker_uri] = {
|
311 |
-
'manifest': manifest,
|
312 |
-
'url': agent_url,
|
313 |
-
'status': 'available',
|
314 |
-
'last_seen': datetime.now()
|
315 |
-
}
|
316 |
-
print(f"🏛️ Floor Manager: Registered agent {manifest.identification.conversationalName}")
|
317 |
-
|
318 |
-
def discover_agents(self) -> List[Manifest]:
|
319 |
-
"""Return manifests of all registered agents"""
|
320 |
-
return [info['manifest'] for info in self.agent_registry.values()]
|
321 |
-
|
322 |
-
def create_conversation(self, initial_participants: List[str] = None) -> str:
|
323 |
-
"""Create a new conversation with optional initial participants"""
|
324 |
-
conversation_id = f"conv:{uuid.uuid4()}"
|
325 |
-
|
326 |
-
# Create proper OpenFloor conversation structure
|
327 |
-
conversants = []
|
328 |
-
if initial_participants:
|
329 |
-
for participant_uri in initial_participants:
|
330 |
-
if participant_uri in self.agent_registry:
|
331 |
-
manifest = self.agent_registry[participant_uri]['manifest']
|
332 |
-
conversants.append(Conversant(
|
333 |
-
identification=manifest.identification
|
334 |
-
))
|
335 |
-
|
336 |
-
conversation = Conversation(
|
337 |
-
id=conversation_id,
|
338 |
-
conversants=conversants
|
339 |
-
)
|
340 |
-
|
341 |
-
self.active_conversations[conversation_id] = {
|
342 |
-
'conversation': conversation,
|
343 |
-
'participants': initial_participants or [],
|
344 |
-
'messages': [],
|
345 |
-
'created_at': datetime.now(),
|
346 |
-
'status': 'active'
|
347 |
-
}
|
348 |
-
|
349 |
-
self.message_history[conversation_id] = []
|
350 |
-
|
351 |
-
print(f"🏛️ Floor Manager: Created conversation {conversation_id}")
|
352 |
-
return conversation_id
|
353 |
-
|
354 |
-
def invite_agent_to_conversation(self, conversation_id: str, target_speaker_uri: str,
|
355 |
-
inviting_speaker_uri: str) -> bool:
|
356 |
-
"""Send proper OpenFloor InviteEvent to an agent"""
|
357 |
-
if conversation_id not in self.active_conversations:
|
358 |
-
print(f"🏛️ Floor Manager: Conversation {conversation_id} not found")
|
359 |
-
return False
|
360 |
-
|
361 |
-
if target_speaker_uri not in self.agent_registry:
|
362 |
-
print(f"🏛️ Floor Manager: Agent {target_speaker_uri} not registered")
|
363 |
-
return False
|
364 |
-
|
365 |
-
conversation_state = self.active_conversations[conversation_id]
|
366 |
-
conversation = conversation_state['conversation']
|
367 |
-
target_agent = self.agent_registry[target_speaker_uri]
|
368 |
-
|
369 |
-
# Create proper OpenFloor InviteEvent
|
370 |
-
invite_envelope = Envelope(
|
371 |
-
conversation=conversation,
|
372 |
-
sender=Sender(speakerUri=inviting_speaker_uri),
|
373 |
-
events=[
|
374 |
-
InviteEvent(
|
375 |
-
to=To(speakerUri=target_speaker_uri),
|
376 |
-
parameters={
|
377 |
-
'conversation_id': conversation_id,
|
378 |
-
'invited_by': inviting_speaker_uri,
|
379 |
-
'invitation_message': f"You are invited to join the expert analysis discussion"
|
380 |
-
}
|
381 |
-
)
|
382 |
-
]
|
383 |
-
)
|
384 |
-
|
385 |
-
# Send invitation to target agent
|
386 |
-
response = self._send_to_agent(target_agent['url'], invite_envelope)
|
387 |
-
|
388 |
-
if response:
|
389 |
-
# Add to conversation participants
|
390 |
-
if target_speaker_uri not in conversation_state['participants']:
|
391 |
-
conversation_state['participants'].append(target_speaker_uri)
|
392 |
-
|
393 |
-
# Add to conversation conversants
|
394 |
-
target_manifest = target_agent['manifest']
|
395 |
-
conversation.conversants.append(Conversant(
|
396 |
-
identification=target_manifest.identification
|
397 |
-
))
|
398 |
-
|
399 |
-
self._update_visual_state(conversation_id)
|
400 |
-
print(f"🏛️ Floor Manager: Successfully invited {target_speaker_uri} to {conversation_id}")
|
401 |
-
return True
|
402 |
-
|
403 |
-
print(f"🏛️ Floor Manager: Failed to invite {target_speaker_uri}")
|
404 |
-
return False
|
405 |
-
|
406 |
-
def route_message(self, envelope: Envelope) -> bool:
|
407 |
-
"""Route message to appropriate recipients with proper OpenFloor semantics"""
|
408 |
-
conversation_id = envelope.conversation.id
|
409 |
-
|
410 |
-
if conversation_id not in self.active_conversations:
|
411 |
-
print(f"🏛️ Floor Manager: Cannot route - conversation {conversation_id} not found")
|
412 |
-
return False
|
413 |
-
|
414 |
-
conversation_state = self.active_conversations[conversation_id]
|
415 |
-
sender_uri = envelope.sender.speakerUri
|
416 |
-
|
417 |
-
# Store message in conversation history
|
418 |
-
message_record = {
|
419 |
-
'envelope': envelope,
|
420 |
-
'timestamp': datetime.now(),
|
421 |
-
'sender': sender_uri
|
422 |
-
}
|
423 |
-
|
424 |
-
conversation_state['messages'].append(message_record)
|
425 |
-
self.message_history[conversation_id].append(message_record)
|
426 |
-
|
427 |
-
# Process each event in the envelope
|
428 |
-
routed_successfully = True
|
429 |
-
|
430 |
-
for event in envelope.events:
|
431 |
-
if hasattr(event, 'to') and event.to:
|
432 |
-
# Directed message - send to specific agent
|
433 |
-
target_uri = event.to.speakerUri
|
434 |
-
if target_uri in self.agent_registry and target_uri != sender_uri:
|
435 |
-
target_agent = self.agent_registry[target_uri]
|
436 |
-
success = self._send_to_agent(target_agent['url'], envelope)
|
437 |
-
if not success:
|
438 |
-
routed_successfully = False
|
439 |
-
print(f"🏛️ Floor Manager: Failed to route directed message to {target_uri}")
|
440 |
-
else:
|
441 |
-
# Broadcast to all conversation participants except sender
|
442 |
-
for participant_uri in conversation_state['participants']:
|
443 |
-
if participant_uri != sender_uri and participant_uri in self.agent_registry:
|
444 |
-
participant_agent = self.agent_registry[participant_uri]
|
445 |
-
success = self._send_to_agent(participant_agent['url'], envelope)
|
446 |
-
if not success:
|
447 |
-
routed_successfully = False
|
448 |
-
print(f"🏛️ Floor Manager: Failed to broadcast to {participant_uri}")
|
449 |
-
|
450 |
-
# Update visual state after routing
|
451 |
-
self._update_visual_state(conversation_id)
|
452 |
-
|
453 |
-
return routed_successfully
|
454 |
-
|
455 |
-
def _send_to_agent(self, agent_url: str, envelope: Envelope) -> bool:
|
456 |
-
"""Send envelope to specific agent via HTTP POST"""
|
457 |
-
if agent_url == "internal://ai-expert":
|
458 |
-
# Internal AI experts don't have HTTP endpoints
|
459 |
-
return True
|
460 |
-
|
461 |
-
try:
|
462 |
-
# Fix: Use proper OpenFloor endpoint
|
463 |
-
response = requests.post(
|
464 |
-
f"{agent_url}/openfloor/conversation", # ✅ Fixed endpoint
|
465 |
-
json=json.loads(envelope.to_json()),
|
466 |
-
headers={'Content-Type': 'application/json'},
|
467 |
-
timeout=30
|
468 |
-
)
|
469 |
-
success = response.status_code == 200
|
470 |
-
if success:
|
471 |
-
print(f"🏛️ Floor Manager: Successfully sent message to {agent_url}")
|
472 |
-
else:
|
473 |
-
print(f"🏛️ Floor Manager: HTTP error {response.status_code} sending to {agent_url}")
|
474 |
-
print(f"🏛️ Floor Manager: Response: {response.text}")
|
475 |
-
return success
|
476 |
-
except Exception as e:
|
477 |
-
print(f"🏛️ Floor Manager: Error sending to {agent_url}: {e}")
|
478 |
-
return False
|
479 |
-
|
480 |
-
def _update_visual_state(self, conversation_id: str):
|
481 |
-
"""Update visual interface based on conversation state"""
|
482 |
-
if not self.visual_callback or conversation_id not in self.active_conversations:
|
483 |
-
return
|
484 |
-
|
485 |
-
conversation_state = self.active_conversations[conversation_id]
|
486 |
-
|
487 |
-
# Convert to visual format
|
488 |
-
participants = []
|
489 |
-
messages = []
|
490 |
-
|
491 |
-
# Get participant names
|
492 |
-
for participant_uri in conversation_state['participants']:
|
493 |
-
if participant_uri in self.agent_registry:
|
494 |
-
agent_info = self.agent_registry[participant_uri]
|
495 |
-
participants.append(agent_info['manifest'].identification.conversationalName)
|
496 |
-
else:
|
497 |
-
# Handle AI experts or other internal participants
|
498 |
-
participants.append(participant_uri.split(':')[-1] if ':' in participant_uri else participant_uri)
|
499 |
-
|
500 |
-
# Convert messages
|
501 |
-
for msg_record in conversation_state['messages']:
|
502 |
-
envelope = msg_record['envelope']
|
503 |
-
sender_uri = envelope.sender.speakerUri
|
504 |
-
|
505 |
-
# Get sender name
|
506 |
-
if sender_uri in self.agent_registry:
|
507 |
-
sender_name = self.agent_registry[sender_uri]['manifest'].identification.conversationalName
|
508 |
-
else:
|
509 |
-
sender_name = sender_uri.split(':')[-1] if ':' in sender_uri else sender_uri
|
510 |
-
|
511 |
-
# Extract message content from events
|
512 |
-
for event in envelope.events:
|
513 |
-
if hasattr(event, 'eventType'):
|
514 |
-
if event.eventType == 'utterance':
|
515 |
-
# Extract text from utterance dialog event
|
516 |
-
dialog_event = event.parameters.get('dialogEvent')
|
517 |
-
if dialog_event:
|
518 |
-
text = self._extract_text_from_dialog_event(dialog_event)
|
519 |
-
if text:
|
520 |
-
messages.append({
|
521 |
-
'speaker': sender_name,
|
522 |
-
'text': text,
|
523 |
-
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
524 |
-
'type': 'utterance'
|
525 |
-
})
|
526 |
-
|
527 |
-
elif event.eventType == 'context':
|
528 |
-
# Handle context events (like research results)
|
529 |
-
context_params = event.parameters
|
530 |
-
if 'research_function' in context_params:
|
531 |
-
messages.append({
|
532 |
-
'speaker': sender_name,
|
533 |
-
'text': f"🔍 Research: {context_params.get('result', 'No result')}",
|
534 |
-
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
535 |
-
'type': 'research_result'
|
536 |
-
})
|
537 |
-
|
538 |
-
elif event.eventType == 'invite':
|
539 |
-
messages.append({
|
540 |
-
'speaker': sender_name,
|
541 |
-
'text': f"📨 Invited agent to join conversation",
|
542 |
-
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
543 |
-
'type': 'system'
|
544 |
-
})
|
545 |
-
|
546 |
-
elif event.eventType == 'bye':
|
547 |
-
messages.append({
|
548 |
-
'speaker': sender_name,
|
549 |
-
'text': f"👋 Left the conversation",
|
550 |
-
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
551 |
-
'type': 'system'
|
552 |
-
})
|
553 |
-
|
554 |
-
# Call visual callback
|
555 |
-
self.visual_callback({
|
556 |
-
'participants': participants,
|
557 |
-
'messages': messages,
|
558 |
-
'currentSpeaker': None,
|
559 |
-
'thinking': [],
|
560 |
-
'showBubbles': participants,
|
561 |
-
'avatarImages': avatar_images
|
562 |
-
})
|
563 |
-
|
564 |
-
def _extract_text_from_dialog_event(self, dialog_event) -> str:
|
565 |
-
"""Extract text from dialog event structure"""
|
566 |
-
try:
|
567 |
-
if isinstance(dialog_event, dict):
|
568 |
-
features = dialog_event.get('features', {})
|
569 |
-
text_feature = features.get('text', {})
|
570 |
-
tokens = text_feature.get('tokens', [])
|
571 |
-
return ' '.join([token.get('value', '') for token in tokens])
|
572 |
-
return ""
|
573 |
-
except Exception as e:
|
574 |
-
print(f"🏛️ Floor Manager: Error extracting text: {e}")
|
575 |
-
return ""
|
576 |
-
|
577 |
-
def set_visual_callback(self, callback):
|
578 |
-
"""Set callback for visual updates"""
|
579 |
-
self.visual_callback = callback
|
580 |
-
|
581 |
-
def start_floor_manager_service(self):
|
582 |
-
"""Start the floor manager HTTP service"""
|
583 |
-
from flask import Flask, request, jsonify
|
584 |
-
|
585 |
-
app = Flask("openfloor-manager")
|
586 |
-
|
587 |
-
@app.route('/openfloor/discover', methods=['GET'])
|
588 |
-
def discover_agents():
|
589 |
-
"""Return list of available agent manifests"""
|
590 |
-
manifests = [json.loads(manifest.to_json()) for manifest in self.discover_agents()]
|
591 |
-
return jsonify(manifests)
|
592 |
-
|
593 |
-
@app.route('/openfloor/conversation', methods=['POST'])
|
594 |
-
def handle_conversation():
|
595 |
-
"""Handle incoming conversation messages"""
|
596 |
-
try:
|
597 |
-
envelope_data = request.get_json()
|
598 |
-
envelope = Envelope.from_json(json.dumps(envelope_data))
|
599 |
-
|
600 |
-
success = self.route_message(envelope)
|
601 |
-
|
602 |
-
if success:
|
603 |
-
return jsonify({'status': 'routed'})
|
604 |
-
else:
|
605 |
-
return jsonify({'error': 'Failed to route message'}), 400
|
606 |
-
|
607 |
-
except Exception as e:
|
608 |
-
print(f"🏛️ Floor Manager: Error handling conversation: {e}")
|
609 |
-
return jsonify({'error': str(e)}), 500
|
610 |
-
|
611 |
-
@app.route('/openfloor/invite', methods=['POST'])
|
612 |
-
def invite_agent():
|
613 |
-
"""Handle agent invitation requests"""
|
614 |
-
try:
|
615 |
-
data = request.get_json()
|
616 |
-
conversation_id = data['conversation_id']
|
617 |
-
target_speaker_uri = data['target_speaker_uri']
|
618 |
-
inviting_speaker_uri = data['inviting_speaker_uri']
|
619 |
-
|
620 |
-
success = self.invite_agent_to_conversation(
|
621 |
-
conversation_id, target_speaker_uri, inviting_speaker_uri
|
622 |
-
)
|
623 |
-
|
624 |
-
return jsonify({'success': success})
|
625 |
-
|
626 |
-
except Exception as e:
|
627 |
-
print(f"🏛️ Floor Manager: Error inviting agent: {e}")
|
628 |
-
return jsonify({'error': str(e)}), 500
|
629 |
-
|
630 |
-
# Start server in background thread
|
631 |
-
import threading
|
632 |
-
server_thread = threading.Thread(
|
633 |
-
target=lambda: app.run(host='localhost', port=self.port + 100, debug=False)
|
634 |
-
)
|
635 |
-
server_thread.daemon = True
|
636 |
-
server_thread.start()
|
637 |
-
|
638 |
-
print(f"🏛️ OpenFloor Manager started on port {self.port + 100}")
|
639 |
-
return True
|
640 |
-
|
641 |
|
642 |
def get_session_id(request: gr.Request = None) -> str:
|
643 |
"""Generate or retrieve session ID"""
|
|
|
14 |
import uuid
|
15 |
from gradio_consilium_roundtable import consilium_roundtable
|
16 |
from research_tools.base_tool import BaseTool
|
17 |
+
from openfloor.OpenFloorResearchAgent import OpenFloorResearchAgent
|
18 |
+
from openfloor.OpenFloorAgentServer import OpenFloorAgentServer
|
19 |
+
from openfloor.OpenFloorManager import OpenFloorManager
|
20 |
from openfloor import *
|
21 |
from openfloor.manifest import *
|
22 |
from openfloor.envelope import *
|
|
|
46 |
"Wikipedia Research Agent": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png"
|
47 |
}
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
def get_session_id(request: gr.Request = None) -> str:
|
51 |
"""Generate or retrieve session ID"""
|