Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -295,13 +295,14 @@ class OpenFloorAgentServer:
|
|
295 |
|
296 |
|
297 |
class OpenFloorManager:
|
298 |
-
"""Central floor manager for coordinating all OpenFloor agents"""
|
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 |
|
306 |
def register_agent(self, manifest: Manifest, agent_url: str):
|
307 |
"""Register an agent with the floor manager"""
|
@@ -322,32 +323,52 @@ class OpenFloorManager:
|
|
322 |
"""Create a new conversation with optional initial participants"""
|
323 |
conversation_id = f"conv:{uuid.uuid4()}"
|
324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
self.active_conversations[conversation_id] = {
|
326 |
-
'
|
327 |
'participants': initial_participants or [],
|
328 |
'messages': [],
|
329 |
'created_at': datetime.now(),
|
330 |
'status': 'active'
|
331 |
}
|
332 |
|
|
|
|
|
333 |
print(f"ποΈ Floor Manager: Created conversation {conversation_id}")
|
334 |
return conversation_id
|
335 |
|
336 |
def invite_agent_to_conversation(self, conversation_id: str, target_speaker_uri: str,
|
337 |
inviting_speaker_uri: str) -> bool:
|
338 |
-
"""Send InviteEvent to an agent"""
|
339 |
if conversation_id not in self.active_conversations:
|
|
|
340 |
return False
|
341 |
|
342 |
if target_speaker_uri not in self.agent_registry:
|
|
|
343 |
return False
|
344 |
|
345 |
-
|
|
|
346 |
target_agent = self.agent_registry[target_speaker_uri]
|
347 |
|
348 |
-
# Create proper InviteEvent
|
349 |
invite_envelope = Envelope(
|
350 |
-
conversation=
|
351 |
sender=Sender(speakerUri=inviting_speaker_uri),
|
352 |
events=[
|
353 |
InviteEvent(
|
@@ -366,107 +387,190 @@ class OpenFloorManager:
|
|
366 |
|
367 |
if response:
|
368 |
# Add to conversation participants
|
369 |
-
if target_speaker_uri not in
|
370 |
-
|
371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
return True
|
373 |
|
|
|
374 |
return False
|
375 |
|
376 |
def route_message(self, envelope: Envelope) -> bool:
|
377 |
-
"""Route message to appropriate recipients"""
|
378 |
conversation_id = envelope.conversation.id
|
379 |
|
380 |
if conversation_id not in self.active_conversations:
|
|
|
381 |
return False
|
382 |
|
383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
384 |
|
385 |
# Process each event in the envelope
|
|
|
|
|
386 |
for event in envelope.events:
|
387 |
if hasattr(event, 'to') and event.to:
|
388 |
# Directed message - send to specific agent
|
389 |
target_uri = event.to.speakerUri
|
390 |
-
if target_uri in self.agent_registry:
|
391 |
target_agent = self.agent_registry[target_uri]
|
392 |
-
self._send_to_agent(target_agent['url'], envelope)
|
|
|
|
|
|
|
393 |
else:
|
394 |
-
# Broadcast to all conversation participants
|
395 |
-
for participant_uri in
|
396 |
-
if participant_uri !=
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
'timestamp': datetime.now()
|
405 |
-
})
|
406 |
-
|
407 |
self._update_visual_state(conversation_id)
|
408 |
-
|
|
|
409 |
|
410 |
def _send_to_agent(self, agent_url: str, envelope: Envelope) -> bool:
|
411 |
-
"""Send envelope to specific agent"""
|
|
|
|
|
|
|
|
|
412 |
try:
|
413 |
response = requests.post(
|
414 |
-
f"{agent_url}/
|
415 |
json=json.loads(envelope.to_json()),
|
416 |
headers={'Content-Type': 'application/json'},
|
417 |
timeout=30
|
418 |
)
|
419 |
-
|
|
|
|
|
|
|
|
|
|
|
420 |
except Exception as e:
|
421 |
print(f"ποΈ Floor Manager: Error sending to {agent_url}: {e}")
|
422 |
return False
|
423 |
|
424 |
def _update_visual_state(self, conversation_id: str):
|
425 |
"""Update visual interface based on conversation state"""
|
426 |
-
if self.visual_callback
|
427 |
-
|
428 |
|
429 |
-
|
430 |
-
|
431 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
432 |
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
|
|
437 |
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
if hasattr(event, 'eventType') and event.eventType == 'utterance':
|
448 |
-
# Extract text from dialog event
|
449 |
-
dialog_event = event.parameters.get('dialogEvent')
|
450 |
-
if dialog_event:
|
451 |
-
features = dialog_event.get('features', {})
|
452 |
-
text_feature = features.get('text', {})
|
453 |
-
tokens = text_feature.get('tokens', [])
|
454 |
-
text = ' '.join([token.get('value', '') for token in tokens])
|
455 |
-
|
456 |
messages.append({
|
457 |
'speaker': sender_name,
|
458 |
'text': text,
|
459 |
-
'timestamp':
|
|
|
460 |
})
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
470 |
|
471 |
def set_visual_callback(self, callback):
|
472 |
"""Set callback for visual updates"""
|
@@ -499,6 +603,7 @@ class OpenFloorManager:
|
|
499 |
return jsonify({'error': 'Failed to route message'}), 400
|
500 |
|
501 |
except Exception as e:
|
|
|
502 |
return jsonify({'error': str(e)}), 500
|
503 |
|
504 |
@app.route('/openfloor/invite', methods=['POST'])
|
@@ -517,6 +622,7 @@ class OpenFloorManager:
|
|
517 |
return jsonify({'success': success})
|
518 |
|
519 |
except Exception as e:
|
|
|
520 |
return jsonify({'error': str(e)}), 500
|
521 |
|
522 |
# Start server in background thread
|
@@ -590,7 +696,11 @@ class VisualConsensusEngine:
|
|
590 |
self.moderator_model = moderator_model or MODERATOR_MODEL
|
591 |
self.update_callback = update_callback
|
592 |
self.session_id = session_id
|
593 |
-
|
|
|
|
|
|
|
|
|
594 |
# Create OpenFloor research agents
|
595 |
from research_tools import WebSearchTool, WikipediaSearchTool, ArxivSearchTool, GitHubSearchTool, SECSearchTool
|
596 |
|
@@ -602,8 +712,12 @@ class VisualConsensusEngine:
|
|
602 |
'sec_edgar': OpenFloorResearchAgent(SECSearchTool(), port=8005)
|
603 |
}
|
604 |
|
|
|
605 |
self.start_openfloor_research_agents()
|
606 |
|
|
|
|
|
|
|
607 |
# Available research agents for discovery
|
608 |
self.available_research_agents = list(self.research_agents.keys())
|
609 |
|
@@ -643,6 +757,12 @@ class VisualConsensusEngine:
|
|
643 |
'sambanova': sambanova_key
|
644 |
}
|
645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
646 |
# PROFESSIONAL: Strong, expert role definitions matched to decision protocols
|
647 |
self.roles = {
|
648 |
'standard': "Provide expert analysis with clear reasoning and evidence.",
|
@@ -683,8 +803,7 @@ class VisualConsensusEngine:
|
|
683 |
}
|
684 |
|
685 |
def start_openfloor_research_agents(self):
|
686 |
-
"""Start
|
687 |
-
|
688 |
agent_ports = {
|
689 |
'web_search': 8001,
|
690 |
'wikipedia': 8002,
|
@@ -703,18 +822,178 @@ class VisualConsensusEngine:
|
|
703 |
self.agent_servers[agent_name] = {
|
704 |
'server': server,
|
705 |
'port': port,
|
706 |
-
'url': f"http://localhost:{port}
|
707 |
'manifest_url': f"http://localhost:{port}/openfloor/manifest"
|
708 |
}
|
|
|
|
|
|
|
|
|
709 |
|
710 |
# Small delay between starting servers
|
711 |
time.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
712 |
|
713 |
def update_visual_state(self, state_update: Dict[str, Any]):
|
714 |
"""Update the visual roundtable state for this session"""
|
715 |
if self.update_callback:
|
716 |
self.update_callback(state_update)
|
717 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
718 |
def handle_function_calls(self, completion, original_prompt: str, calling_model: str) -> str:
|
719 |
"""UNIFIED function call handler with enhanced research capabilities"""
|
720 |
|
@@ -870,131 +1149,34 @@ class VisualConsensusEngine:
|
|
870 |
print(f"Error in follow-up completion for {calling_model}: {str(e)}")
|
871 |
return message.content or "Analysis completed with research integration."
|
872 |
|
873 |
-
|
874 |
def _execute_research_function(self, function_name: str, arguments: dict, requesting_model_name: str = None) -> str:
|
875 |
-
"""Execute research function using proper OpenFloor
|
876 |
|
877 |
query_param = arguments.get("query") or arguments.get("topic") or arguments.get("technology") or arguments.get("company")
|
878 |
|
879 |
-
|
880 |
-
|
881 |
-
|
|
|
|
|
882 |
|
883 |
try:
|
884 |
-
#
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
"search_financial_data": "sec_edgar"
|
891 |
-
}
|
892 |
|
893 |
-
|
|
|
894 |
|
895 |
-
if function_name in function_to_agent:
|
896 |
-
agent_name = function_to_agent[function_name]
|
897 |
-
|
898 |
-
if agent_name not in self.agent_servers:
|
899 |
-
return f"Research agent '{agent_name}' not available"
|
900 |
-
|
901 |
-
agent_server = self.agent_servers[agent_name]
|
902 |
-
|
903 |
-
self.update_research_progress(f"Sending HTTP request to OpenFloor agent...")
|
904 |
-
|
905 |
-
# Create OpenFloor envelope
|
906 |
-
conversation = Conversation()
|
907 |
-
request_dialog = DialogEvent(
|
908 |
-
speakerUri=f"tag:consilium.ai,2025:{requesting_model_name or 'expert'}",
|
909 |
-
features={"text": TextFeature(values=[query_param])}
|
910 |
-
)
|
911 |
-
|
912 |
-
request_envelope = Envelope(
|
913 |
-
conversation=conversation,
|
914 |
-
sender=Sender(speakerUri=f"tag:consilium.ai,2025:{requesting_model_name or 'expert'}"),
|
915 |
-
events=[
|
916 |
-
UtteranceEvent(
|
917 |
-
dialogEvent=request_dialog,
|
918 |
-
to=To(speakerUri=self.research_agents[agent_name].manifest.identification.speakerUri)
|
919 |
-
)
|
920 |
-
]
|
921 |
-
)
|
922 |
-
|
923 |
-
# Send HTTP POST request to OpenFloor agent service
|
924 |
-
response = self._send_openfloor_request(agent_server['url'], request_envelope)
|
925 |
-
|
926 |
-
if response:
|
927 |
-
result = self._extract_research_result_from_envelope(response)
|
928 |
-
self.update_research_progress(f"OpenFloor HTTP response received - {len(result)} characters")
|
929 |
-
else:
|
930 |
-
result = f"Failed to get response from {agent_name} OpenFloor service"
|
931 |
-
|
932 |
-
else:
|
933 |
-
result = f"Unknown research function: {function_name}"
|
934 |
-
|
935 |
-
# Phase 3: Show research complete
|
936 |
-
if query_param:
|
937 |
-
self.show_research_complete(function_name, query_param, len(result), requesting_model_name)
|
938 |
-
|
939 |
return result
|
940 |
|
941 |
except Exception as e:
|
942 |
error_msg = str(e)
|
943 |
-
|
944 |
-
|
945 |
-
return f"OpenFloor HTTP research error: {error_msg}"
|
946 |
-
|
947 |
-
def _send_openfloor_request(self, agent_url: str, envelope: Envelope) -> Optional[Envelope]:
|
948 |
-
"""Send HTTP request to OpenFloor agent service"""
|
949 |
-
try:
|
950 |
-
import requests
|
951 |
-
|
952 |
-
print(f"π DEBUG: Sending request to {agent_url}")
|
953 |
-
|
954 |
-
# Serialize envelope to JSON
|
955 |
-
envelope_json = json.loads(envelope.to_json())
|
956 |
-
print(f"π DEBUG: Envelope serialized, size: {len(str(envelope_json))} chars")
|
957 |
-
|
958 |
-
# Send HTTP POST request
|
959 |
-
response = requests.post(
|
960 |
-
agent_url,
|
961 |
-
json=envelope_json,
|
962 |
-
headers={'Content-Type': 'application/json'},
|
963 |
-
timeout=30
|
964 |
-
)
|
965 |
-
|
966 |
-
print(f"π DEBUG: Response status: {response.status_code}")
|
967 |
-
print(f"π DEBUG: Response text: {response.text[:200]}...")
|
968 |
-
|
969 |
-
if response.status_code == 200:
|
970 |
-
# Parse response back to OpenFloor envelope
|
971 |
-
response_data = response.json()
|
972 |
-
return Envelope.from_json(json.dumps(response_data))
|
973 |
-
else:
|
974 |
-
print(f"OpenFloor HTTP error: {response.status_code} - {response.text}")
|
975 |
-
return None
|
976 |
-
|
977 |
-
except Exception as e:
|
978 |
-
print(f"π DEBUG: Exception in _send_openfloor_request: {e}")
|
979 |
-
import traceback
|
980 |
-
traceback.print_exc()
|
981 |
-
return None
|
982 |
-
|
983 |
-
def _extract_research_result_from_envelope(self, envelope: Envelope) -> str:
|
984 |
-
"""Extract research result from OpenFloor response envelope"""
|
985 |
-
try:
|
986 |
-
for event in envelope.events:
|
987 |
-
if hasattr(event, 'eventType') and event.eventType == 'utterance':
|
988 |
-
dialog_event = event.parameters.get('dialogEvent')
|
989 |
-
if dialog_event and hasattr(dialog_event, 'features'):
|
990 |
-
text_feature = dialog_event.features.get('text')
|
991 |
-
if text_feature and hasattr(text_feature, 'tokens'):
|
992 |
-
return ' '.join([token.get('value', '') for token in text_feature.tokens])
|
993 |
-
|
994 |
-
return "No research result found in OpenFloor response"
|
995 |
-
|
996 |
-
except Exception as e:
|
997 |
-
return f"Error extracting OpenFloor research result: {str(e)}"
|
998 |
|
999 |
def show_research_starting(self, function: str, query: str):
|
1000 |
"""Invite specific research agent to join conversation"""
|
@@ -1036,7 +1218,6 @@ class VisualConsensusEngine:
|
|
1036 |
"showBubbles": existing_bubbles
|
1037 |
})
|
1038 |
|
1039 |
-
|
1040 |
def show_research_complete(self, function: str, query: str, result_length: int, requesting_model_name: str = None):
|
1041 |
"""Show research complete and dismiss the specific agent"""
|
1042 |
function_to_agent = {
|
@@ -1077,17 +1258,6 @@ class VisualConsensusEngine:
|
|
1077 |
# Use the existing dismiss method
|
1078 |
self.dismiss_research_agent(agent_name, "current_conversation")
|
1079 |
|
1080 |
-
def estimate_research_time(self, function_name: str) -> str:
|
1081 |
-
"""Provide time estimates for different research functions"""
|
1082 |
-
time_estimates = {
|
1083 |
-
"search_web": "30-60 seconds",
|
1084 |
-
"search_wikipedia": "15-30 seconds",
|
1085 |
-
"search_academic": "2-5 minutes",
|
1086 |
-
"search_technology_trends": "1-2 minutes",
|
1087 |
-
"search_financial_data": "1-3 minutes"
|
1088 |
-
}
|
1089 |
-
return time_estimates.get(function_name, "1-3 minutes")
|
1090 |
-
|
1091 |
def show_research_error(self, function: str, query: str, error: str, requesting_model_name: str = None):
|
1092 |
"""Show research error from the specific agent and dismiss it"""
|
1093 |
function_to_agent = {
|
@@ -1128,55 +1298,6 @@ class VisualConsensusEngine:
|
|
1128 |
# Dismiss the research agent since research failed
|
1129 |
self.dismiss_research_agent(agent_name, "current_conversation")
|
1130 |
|
1131 |
-
def update_research_progress(self, progress_text: str, function_name: str = None):
|
1132 |
-
"""Update research progress from the specific active research agent"""
|
1133 |
-
|
1134 |
-
# Map function to agent to identify which agent is providing progress
|
1135 |
-
function_to_agent = {
|
1136 |
-
"search_web": "web_search",
|
1137 |
-
"search_wikipedia": "wikipedia",
|
1138 |
-
"search_academic": "arxiv",
|
1139 |
-
"search_technology_trends": "github",
|
1140 |
-
"search_financial_data": "sec_edgar"
|
1141 |
-
}
|
1142 |
-
|
1143 |
-
# Determine which research agent is active
|
1144 |
-
if function_name and function_name in function_to_agent:
|
1145 |
-
agent_name = function_to_agent[function_name]
|
1146 |
-
research_agent = self.research_agents[agent_name]
|
1147 |
-
agent_display_name = research_agent.manifest.identification.conversationalName
|
1148 |
-
else:
|
1149 |
-
# Fallback to generic research agent if function not specified
|
1150 |
-
agent_display_name = "Research Agent"
|
1151 |
-
|
1152 |
-
session = get_or_create_session_state(self.session_id)
|
1153 |
-
current_state = session["roundtable_state"]
|
1154 |
-
all_messages = list(current_state.get("messages", []))
|
1155 |
-
participants = current_state.get("participants", [])
|
1156 |
-
|
1157 |
-
# Ensure the specific research agent is visible
|
1158 |
-
existing_bubbles = list(current_state.get("showBubbles", []))
|
1159 |
-
if agent_display_name not in existing_bubbles:
|
1160 |
-
existing_bubbles.append(agent_display_name)
|
1161 |
-
|
1162 |
-
# Add progress message from the specific agent
|
1163 |
-
progress_message = {
|
1164 |
-
"speaker": agent_display_name,
|
1165 |
-
"text": f"π {progress_text}",
|
1166 |
-
"type": "research_progress"
|
1167 |
-
}
|
1168 |
-
all_messages.append(progress_message)
|
1169 |
-
|
1170 |
-
# Keep the agent active and visible during progress
|
1171 |
-
self.update_visual_state({
|
1172 |
-
"participants": participants,
|
1173 |
-
"messages": all_messages,
|
1174 |
-
"currentSpeaker": agent_display_name,
|
1175 |
-
"thinking": [],
|
1176 |
-
"showBubbles": existing_bubbles
|
1177 |
-
})
|
1178 |
-
time.sleep(0.3)
|
1179 |
-
|
1180 |
def invite_research_agent(self, agent_name: str, conversation_id: str, requesting_expert: str):
|
1181 |
"""Invite a research agent to join the conversation visually"""
|
1182 |
|
@@ -1483,6 +1604,32 @@ class VisualConsensusEngine:
|
|
1483 |
if not available_models:
|
1484 |
return "β No AI models available"
|
1485 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1486 |
model_roles = self.assign_roles(available_models, role_assignment)
|
1487 |
|
1488 |
visual_participant_names = [self.models[model]['name'] for model in available_models]
|
@@ -2364,6 +2511,38 @@ with gr.Blocks(title="π Consilium: Multi-AI Expert Consensus Platform - OFP (
|
|
2364 |
- Creates interesting chains of reasoning and idea evolution
|
2365 |
- Can lead to surprising consensus emergence
|
2366 |
- **Use when:** You want to see how ideas build and evolve
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2367 |
""")
|
2368 |
|
2369 |
# Launch configuration
|
|
|
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"""
|
|
|
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(
|
|
|
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 |
response = requests.post(
|
463 |
+
f"{agent_url}/conversation",
|
464 |
json=json.loads(envelope.to_json()),
|
465 |
headers={'Content-Type': 'application/json'},
|
466 |
timeout=30
|
467 |
)
|
468 |
+
success = response.status_code == 200
|
469 |
+
if success:
|
470 |
+
print(f"ποΈ Floor Manager: Successfully sent message to {agent_url}")
|
471 |
+
else:
|
472 |
+
print(f"ποΈ Floor Manager: HTTP error {response.status_code} sending to {agent_url}")
|
473 |
+
return success
|
474 |
except Exception as e:
|
475 |
print(f"ποΈ Floor Manager: Error sending to {agent_url}: {e}")
|
476 |
return False
|
477 |
|
478 |
def _update_visual_state(self, conversation_id: str):
|
479 |
"""Update visual interface based on conversation state"""
|
480 |
+
if not self.visual_callback or conversation_id not in self.active_conversations:
|
481 |
+
return
|
482 |
|
483 |
+
conversation_state = self.active_conversations[conversation_id]
|
484 |
+
|
485 |
+
# Convert to visual format
|
486 |
+
participants = []
|
487 |
+
messages = []
|
488 |
+
|
489 |
+
# Get participant names
|
490 |
+
for participant_uri in conversation_state['participants']:
|
491 |
+
if participant_uri in self.agent_registry:
|
492 |
+
agent_info = self.agent_registry[participant_uri]
|
493 |
+
participants.append(agent_info['manifest'].identification.conversationalName)
|
494 |
+
else:
|
495 |
+
# Handle AI experts or other internal participants
|
496 |
+
participants.append(participant_uri.split(':')[-1] if ':' in participant_uri else participant_uri)
|
497 |
+
|
498 |
+
# Convert messages
|
499 |
+
for msg_record in conversation_state['messages']:
|
500 |
+
envelope = msg_record['envelope']
|
501 |
+
sender_uri = envelope.sender.speakerUri
|
502 |
|
503 |
+
# Get sender name
|
504 |
+
if sender_uri in self.agent_registry:
|
505 |
+
sender_name = self.agent_registry[sender_uri]['manifest'].identification.conversationalName
|
506 |
+
else:
|
507 |
+
sender_name = sender_uri.split(':')[-1] if ':' in sender_uri else sender_uri
|
508 |
|
509 |
+
# Extract message content from events
|
510 |
+
for event in envelope.events:
|
511 |
+
if hasattr(event, 'eventType'):
|
512 |
+
if event.eventType == 'utterance':
|
513 |
+
# Extract text from utterance dialog event
|
514 |
+
dialog_event = event.parameters.get('dialogEvent')
|
515 |
+
if dialog_event:
|
516 |
+
text = self._extract_text_from_dialog_event(dialog_event)
|
517 |
+
if text:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
518 |
messages.append({
|
519 |
'speaker': sender_name,
|
520 |
'text': text,
|
521 |
+
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
522 |
+
'type': 'utterance'
|
523 |
})
|
524 |
+
|
525 |
+
elif event.eventType == 'context':
|
526 |
+
# Handle context events (like research results)
|
527 |
+
context_params = event.parameters
|
528 |
+
if 'research_function' in context_params:
|
529 |
+
messages.append({
|
530 |
+
'speaker': sender_name,
|
531 |
+
'text': f"π Research: {context_params.get('result', 'No result')}",
|
532 |
+
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
533 |
+
'type': 'research_result'
|
534 |
+
})
|
535 |
+
|
536 |
+
elif event.eventType == 'invite':
|
537 |
+
messages.append({
|
538 |
+
'speaker': sender_name,
|
539 |
+
'text': f"π¨ Invited agent to join conversation",
|
540 |
+
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
541 |
+
'type': 'system'
|
542 |
+
})
|
543 |
+
|
544 |
+
elif event.eventType == 'bye':
|
545 |
+
messages.append({
|
546 |
+
'speaker': sender_name,
|
547 |
+
'text': f"π Left the conversation",
|
548 |
+
'timestamp': msg_record['timestamp'].strftime('%H:%M:%S'),
|
549 |
+
'type': 'system'
|
550 |
+
})
|
551 |
+
|
552 |
+
# Call visual callback
|
553 |
+
self.visual_callback({
|
554 |
+
'participants': participants,
|
555 |
+
'messages': messages,
|
556 |
+
'currentSpeaker': None,
|
557 |
+
'thinking': [],
|
558 |
+
'showBubbles': participants,
|
559 |
+
'avatarImages': avatar_images
|
560 |
+
})
|
561 |
+
|
562 |
+
def _extract_text_from_dialog_event(self, dialog_event) -> str:
|
563 |
+
"""Extract text from dialog event structure"""
|
564 |
+
try:
|
565 |
+
if isinstance(dialog_event, dict):
|
566 |
+
features = dialog_event.get('features', {})
|
567 |
+
text_feature = features.get('text', {})
|
568 |
+
tokens = text_feature.get('tokens', [])
|
569 |
+
return ' '.join([token.get('value', '') for token in tokens])
|
570 |
+
return ""
|
571 |
+
except Exception as e:
|
572 |
+
print(f"ποΈ Floor Manager: Error extracting text: {e}")
|
573 |
+
return ""
|
574 |
|
575 |
def set_visual_callback(self, callback):
|
576 |
"""Set callback for visual updates"""
|
|
|
603 |
return jsonify({'error': 'Failed to route message'}), 400
|
604 |
|
605 |
except Exception as e:
|
606 |
+
print(f"ποΈ Floor Manager: Error handling conversation: {e}")
|
607 |
return jsonify({'error': str(e)}), 500
|
608 |
|
609 |
@app.route('/openfloor/invite', methods=['POST'])
|
|
|
622 |
return jsonify({'success': success})
|
623 |
|
624 |
except Exception as e:
|
625 |
+
print(f"ποΈ Floor Manager: Error inviting agent: {e}")
|
626 |
return jsonify({'error': str(e)}), 500
|
627 |
|
628 |
# Start server in background thread
|
|
|
696 |
self.moderator_model = moderator_model or MODERATOR_MODEL
|
697 |
self.update_callback = update_callback
|
698 |
self.session_id = session_id
|
699 |
+
|
700 |
+
# Initialize OpenFloor Manager FIRST
|
701 |
+
self.floor_manager = OpenFloorManager(port=7860)
|
702 |
+
self.floor_manager.set_visual_callback(self.update_visual_state)
|
703 |
+
|
704 |
# Create OpenFloor research agents
|
705 |
from research_tools import WebSearchTool, WikipediaSearchTool, ArxivSearchTool, GitHubSearchTool, SECSearchTool
|
706 |
|
|
|
712 |
'sec_edgar': OpenFloorResearchAgent(SECSearchTool(), port=8005)
|
713 |
}
|
714 |
|
715 |
+
# Start research agents and register with floor manager
|
716 |
self.start_openfloor_research_agents()
|
717 |
|
718 |
+
# Create a persistent conversation for this session
|
719 |
+
self.conversation_id = self.floor_manager.create_conversation([])
|
720 |
+
|
721 |
# Available research agents for discovery
|
722 |
self.available_research_agents = list(self.research_agents.keys())
|
723 |
|
|
|
757 |
'sambanova': sambanova_key
|
758 |
}
|
759 |
|
760 |
+
# Register AI experts with floor manager
|
761 |
+
self.register_ai_experts()
|
762 |
+
|
763 |
+
# Start floor manager service
|
764 |
+
self.floor_manager.start_floor_manager_service()
|
765 |
+
|
766 |
# PROFESSIONAL: Strong, expert role definitions matched to decision protocols
|
767 |
self.roles = {
|
768 |
'standard': "Provide expert analysis with clear reasoning and evidence.",
|
|
|
803 |
}
|
804 |
|
805 |
def start_openfloor_research_agents(self):
|
806 |
+
"""Start research agents and register them with the floor manager"""
|
|
|
807 |
agent_ports = {
|
808 |
'web_search': 8001,
|
809 |
'wikipedia': 8002,
|
|
|
822 |
self.agent_servers[agent_name] = {
|
823 |
'server': server,
|
824 |
'port': port,
|
825 |
+
'url': f"http://localhost:{port}",
|
826 |
'manifest_url': f"http://localhost:{port}/openfloor/manifest"
|
827 |
}
|
828 |
+
|
829 |
+
# Register with floor manager
|
830 |
+
manifest = agent.get_manifest()
|
831 |
+
self.floor_manager.register_agent(manifest, f"http://localhost:{port}")
|
832 |
|
833 |
# Small delay between starting servers
|
834 |
time.sleep(0.5)
|
835 |
+
|
836 |
+
def register_ai_experts(self):
|
837 |
+
"""Register AI expert models as OpenFloor agents"""
|
838 |
+
for model_key, model_info in self.models.items():
|
839 |
+
if model_info['available']:
|
840 |
+
# Create manifest for AI expert
|
841 |
+
expert_manifest = Manifest(
|
842 |
+
identification=Identification(
|
843 |
+
speakerUri=f"tag:consilium.ai,2025:{model_key}",
|
844 |
+
conversationalName=model_info['name'],
|
845 |
+
role="AI Expert",
|
846 |
+
organization="Consilium Expert Panel",
|
847 |
+
synopsis=f"Expert AI model: {model_info['name']}"
|
848 |
+
),
|
849 |
+
capabilities=[
|
850 |
+
Capability(
|
851 |
+
keyphrases=["analysis", "expertise", "reasoning", "decision"],
|
852 |
+
descriptions=[f"Expert analysis and reasoning by {model_info['name']}"],
|
853 |
+
languages=["en-us"]
|
854 |
+
)
|
855 |
+
]
|
856 |
+
)
|
857 |
+
|
858 |
+
# Register with floor manager (no URL since these are internal)
|
859 |
+
self.floor_manager.register_agent(expert_manifest, "internal://ai-expert")
|
860 |
|
861 |
def update_visual_state(self, state_update: Dict[str, Any]):
|
862 |
"""Update the visual roundtable state for this session"""
|
863 |
if self.update_callback:
|
864 |
self.update_callback(state_update)
|
865 |
+
|
866 |
+
def send_research_request_via_floor(self, function_name: str, query: str, requesting_expert: str) -> str:
|
867 |
+
"""Send research request through proper OpenFloor messaging"""
|
868 |
+
|
869 |
+
# Map function to research agent
|
870 |
+
function_to_agent = {
|
871 |
+
"search_web": "web_search",
|
872 |
+
"search_wikipedia": "wikipedia",
|
873 |
+
"search_academic": "arxiv",
|
874 |
+
"search_technology_trends": "github",
|
875 |
+
"search_financial_data": "sec_edgar"
|
876 |
+
}
|
877 |
+
|
878 |
+
if function_name not in function_to_agent:
|
879 |
+
return f"Unknown research function: {function_name}"
|
880 |
+
|
881 |
+
agent_name = function_to_agent[function_name]
|
882 |
+
research_agent = self.research_agents[agent_name]
|
883 |
+
target_speaker_uri = research_agent.manifest.identification.speakerUri
|
884 |
+
requesting_speaker_uri = f"tag:consilium.ai,2025:{requesting_expert}"
|
885 |
+
|
886 |
+
# Step 1: Invite research agent to conversation
|
887 |
+
success = self.floor_manager.invite_agent_to_conversation(
|
888 |
+
self.conversation_id,
|
889 |
+
target_speaker_uri,
|
890 |
+
requesting_speaker_uri
|
891 |
+
)
|
892 |
+
|
893 |
+
if not success:
|
894 |
+
return f"Failed to invite {agent_name} to conversation"
|
895 |
+
|
896 |
+
# Step 2: Send research request via floor manager
|
897 |
+
research_dialog = DialogEvent(
|
898 |
+
speakerUri=requesting_speaker_uri,
|
899 |
+
features={"text": TextFeature(values=[query])}
|
900 |
+
)
|
901 |
+
|
902 |
+
research_envelope = Envelope(
|
903 |
+
conversation=Conversation(id=self.conversation_id),
|
904 |
+
sender=Sender(speakerUri=requesting_speaker_uri),
|
905 |
+
events=[
|
906 |
+
UtteranceEvent(
|
907 |
+
dialogEvent=research_dialog,
|
908 |
+
to=To(speakerUri=target_speaker_uri)
|
909 |
+
)
|
910 |
+
]
|
911 |
+
)
|
912 |
+
|
913 |
+
# Route through floor manager
|
914 |
+
routing_success = self.floor_manager.route_message(research_envelope)
|
915 |
+
|
916 |
+
if not routing_success:
|
917 |
+
return f"Failed to route research request to {agent_name}"
|
918 |
+
|
919 |
+
# Step 3: Wait for response and collect result
|
920 |
+
# In a real implementation, this would be asynchronous
|
921 |
+
# For now, we'll use the direct research call as fallback
|
922 |
+
try:
|
923 |
+
result = research_agent.tool.search(query)
|
924 |
+
|
925 |
+
# Step 4: Send result back through floor as ContextEvent
|
926 |
+
result_envelope = Envelope(
|
927 |
+
conversation=Conversation(id=self.conversation_id),
|
928 |
+
sender=Sender(speakerUri=target_speaker_uri),
|
929 |
+
events=[
|
930 |
+
ContextEvent(
|
931 |
+
parameters={
|
932 |
+
"research_function": function_name,
|
933 |
+
"query": query,
|
934 |
+
"requesting_expert": requesting_expert,
|
935 |
+
"result": result
|
936 |
+
}
|
937 |
+
)
|
938 |
+
]
|
939 |
+
)
|
940 |
+
|
941 |
+
self.floor_manager.route_message(result_envelope)
|
942 |
+
|
943 |
+
# Step 5: Remove research agent from conversation
|
944 |
+
self.dismiss_research_agent_via_floor(agent_name, requesting_expert)
|
945 |
+
|
946 |
+
return result
|
947 |
+
|
948 |
+
except Exception as e:
|
949 |
+
error_msg = f"Research error: {str(e)}"
|
950 |
+
|
951 |
+
# Send error through floor
|
952 |
+
error_envelope = Envelope(
|
953 |
+
conversation=Conversation(id=self.conversation_id),
|
954 |
+
sender=Sender(speakerUri=target_speaker_uri),
|
955 |
+
events=[
|
956 |
+
ContextEvent(
|
957 |
+
parameters={
|
958 |
+
"research_error": error_msg,
|
959 |
+
"function": function_name,
|
960 |
+
"query": query
|
961 |
+
}
|
962 |
+
)
|
963 |
+
]
|
964 |
+
)
|
965 |
+
|
966 |
+
self.floor_manager.route_message(error_envelope)
|
967 |
+
self.dismiss_research_agent_via_floor(agent_name, requesting_expert)
|
968 |
+
|
969 |
+
return error_msg
|
970 |
+
|
971 |
+
def dismiss_research_agent_via_floor(self, agent_name: str, requesting_expert: str):
|
972 |
+
"""Properly dismiss research agent via OpenFloor messaging"""
|
973 |
+
research_agent = self.research_agents[agent_name]
|
974 |
+
target_speaker_uri = research_agent.manifest.identification.speakerUri
|
975 |
+
|
976 |
+
# Send bye event
|
977 |
+
bye_envelope = Envelope(
|
978 |
+
conversation=Conversation(id=self.conversation_id),
|
979 |
+
sender=Sender(speakerUri=target_speaker_uri),
|
980 |
+
events=[
|
981 |
+
ByeEvent(
|
982 |
+
parameters={
|
983 |
+
"message": "Research task completed. Leaving conversation."
|
984 |
+
}
|
985 |
+
)
|
986 |
+
]
|
987 |
+
)
|
988 |
+
|
989 |
+
self.floor_manager.route_message(bye_envelope)
|
990 |
+
|
991 |
+
# Remove from conversation participants
|
992 |
+
if self.conversation_id in self.floor_manager.active_conversations:
|
993 |
+
conversation_state = self.floor_manager.active_conversations[self.conversation_id]
|
994 |
+
if target_speaker_uri in conversation_state['participants']:
|
995 |
+
conversation_state['participants'].remove(target_speaker_uri)
|
996 |
+
|
997 |
def handle_function_calls(self, completion, original_prompt: str, calling_model: str) -> str:
|
998 |
"""UNIFIED function call handler with enhanced research capabilities"""
|
999 |
|
|
|
1149 |
print(f"Error in follow-up completion for {calling_model}: {str(e)}")
|
1150 |
return message.content or "Analysis completed with research integration."
|
1151 |
|
|
|
1152 |
def _execute_research_function(self, function_name: str, arguments: dict, requesting_model_name: str = None) -> str:
|
1153 |
+
"""Execute research function using proper OpenFloor messaging"""
|
1154 |
|
1155 |
query_param = arguments.get("query") or arguments.get("topic") or arguments.get("technology") or arguments.get("company")
|
1156 |
|
1157 |
+
if not query_param:
|
1158 |
+
return "No query parameter found in research request"
|
1159 |
+
|
1160 |
+
# Show research starting
|
1161 |
+
self.show_research_starting(function_name, query_param)
|
1162 |
|
1163 |
try:
|
1164 |
+
# Use OpenFloor messaging for research
|
1165 |
+
result = self.send_research_request_via_floor(
|
1166 |
+
function_name,
|
1167 |
+
query_param,
|
1168 |
+
requesting_model_name or 'unknown'
|
1169 |
+
)
|
|
|
|
|
1170 |
|
1171 |
+
# Show research complete
|
1172 |
+
self.show_research_complete(function_name, query_param, len(result), requesting_model_name)
|
1173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1174 |
return result
|
1175 |
|
1176 |
except Exception as e:
|
1177 |
error_msg = str(e)
|
1178 |
+
self.show_research_error(function_name, query_param, error_msg, requesting_model_name)
|
1179 |
+
return f"OpenFloor research error: {error_msg}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1180 |
|
1181 |
def show_research_starting(self, function: str, query: str):
|
1182 |
"""Invite specific research agent to join conversation"""
|
|
|
1218 |
"showBubbles": existing_bubbles
|
1219 |
})
|
1220 |
|
|
|
1221 |
def show_research_complete(self, function: str, query: str, result_length: int, requesting_model_name: str = None):
|
1222 |
"""Show research complete and dismiss the specific agent"""
|
1223 |
function_to_agent = {
|
|
|
1258 |
# Use the existing dismiss method
|
1259 |
self.dismiss_research_agent(agent_name, "current_conversation")
|
1260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1261 |
def show_research_error(self, function: str, query: str, error: str, requesting_model_name: str = None):
|
1262 |
"""Show research error from the specific agent and dismiss it"""
|
1263 |
function_to_agent = {
|
|
|
1298 |
# Dismiss the research agent since research failed
|
1299 |
self.dismiss_research_agent(agent_name, "current_conversation")
|
1300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1301 |
def invite_research_agent(self, agent_name: str, conversation_id: str, requesting_expert: str):
|
1302 |
"""Invite a research agent to join the conversation visually"""
|
1303 |
|
|
|
1604 |
if not available_models:
|
1605 |
return "β No AI models available"
|
1606 |
|
1607 |
+
# Add AI experts to the conversation
|
1608 |
+
for model in available_models:
|
1609 |
+
expert_speaker_uri = f"tag:consilium.ai,2025:{model}"
|
1610 |
+
if self.conversation_id in self.floor_manager.active_conversations:
|
1611 |
+
conversation_state = self.floor_manager.active_conversations[self.conversation_id]
|
1612 |
+
if expert_speaker_uri not in conversation_state['participants']:
|
1613 |
+
conversation_state['participants'].append(expert_speaker_uri)
|
1614 |
+
|
1615 |
+
# Send conversation start event through floor
|
1616 |
+
start_envelope = Envelope(
|
1617 |
+
conversation=Conversation(id=self.conversation_id),
|
1618 |
+
sender=Sender(speakerUri="tag:consilium.ai,2025:session-manager"),
|
1619 |
+
events=[
|
1620 |
+
ContextEvent(
|
1621 |
+
parameters={
|
1622 |
+
"session_start": True,
|
1623 |
+
"question": question,
|
1624 |
+
"protocol": decision_protocol,
|
1625 |
+
"participants": [self.models[model]['name'] for model in available_models]
|
1626 |
+
}
|
1627 |
+
)
|
1628 |
+
]
|
1629 |
+
)
|
1630 |
+
|
1631 |
+
self.floor_manager.route_message(start_envelope)
|
1632 |
+
|
1633 |
model_roles = self.assign_roles(available_models, role_assignment)
|
1634 |
|
1635 |
visual_participant_names = [self.models[model]['name'] for model in available_models]
|
|
|
2511 |
- Creates interesting chains of reasoning and idea evolution
|
2512 |
- Can lead to surprising consensus emergence
|
2513 |
- **Use when:** You want to see how ideas build and evolve
|
2514 |
+
|
2515 |
+
## ποΈ **OpenFloor Protocol Integration**
|
2516 |
+
|
2517 |
+
This implementation uses the [Open Floor Protocol (OFP)](https://github.com/open-voice-interoperability/openfloor-docs) for:
|
2518 |
+
|
2519 |
+
### π **Inter-Agent Communication**
|
2520 |
+
- **Envelope Structure**: Standard JSON format for agent messaging
|
2521 |
+
- **Event Types**: Utterance, Context, Invite, Bye events
|
2522 |
+
- **Conversation Management**: Persistent conversation threads
|
2523 |
+
- **Agent Discovery**: Manifest-based capability matching
|
2524 |
+
|
2525 |
+
### π **Research Agent Architecture**
|
2526 |
+
- **Dedicated Services**: Each research tool runs as an independent OpenFloor agent
|
2527 |
+
- **HTTP Endpoints**: `/openfloor/conversation` and `/openfloor/manifest`
|
2528 |
+
- **Proper Invitation Flow**: Agents are invited to join conversations for specific research tasks
|
2529 |
+
- **Context Events**: Research results are delivered via standardized context events
|
2530 |
+
|
2531 |
+
### ποΈ **Floor Manager**
|
2532 |
+
- **Central Coordination**: Routes messages between all agents
|
2533 |
+
- **Conversation State**: Maintains participant lists and message history
|
2534 |
+
- **Visual Integration**: Updates UI based on OpenFloor conversation events
|
2535 |
+
- **Session Isolation**: Each user session has its own conversation context
|
2536 |
+
|
2537 |
+
### π **Event Flow Example**
|
2538 |
+
1. **AI Expert** needs research β generates function call
|
2539 |
+
2. **Floor Manager** invites **Research Agent** to conversation
|
2540 |
+
3. **Research Agent** joins and receives query via UtteranceEvent
|
2541 |
+
4. **Research Agent** performs search and responds with ContextEvent
|
2542 |
+
5. **Floor Manager** routes result back to requesting expert
|
2543 |
+
6. **Research Agent** sends ByeEvent and leaves conversation
|
2544 |
+
|
2545 |
+
This architecture ensures true OpenFloor compliance while maintaining the visual roundtable experience.
|
2546 |
""")
|
2547 |
|
2548 |
# Launch configuration
|