Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
import json
|
4 |
+
import uuid
|
5 |
+
import threading
|
6 |
+
from datetime import datetime
|
7 |
+
from typing import List, Dict, Any, Optional
|
8 |
+
from openfloor import (
|
9 |
+
Envelope, Conversation, Sender, DialogEvent, TextFeature,
|
10 |
+
UtteranceEvent, GetManifestsEvent, To
|
11 |
+
)
|
12 |
+
from openfloor.manifest import *
|
13 |
+
from openfloor.envelope import *
|
14 |
+
|
15 |
+
class OpenFloorAgent:
|
16 |
+
def __init__(self, url: str, name: str = None):
|
17 |
+
self.url = url
|
18 |
+
self.name = name or url
|
19 |
+
self.manifest = None
|
20 |
+
self.capabilities = []
|
21 |
+
|
22 |
+
def get_manifest(self):
|
23 |
+
"""Get the agent's manifest and capabilities"""
|
24 |
+
try:
|
25 |
+
# Create manifest request
|
26 |
+
conversation = Conversation()
|
27 |
+
sender = Sender(
|
28 |
+
speakerUri="openfloor://mcp-bridge/client",
|
29 |
+
serviceUrl="http://mcp-bridge"
|
30 |
+
)
|
31 |
+
envelope = Envelope(conversation=conversation, sender=sender)
|
32 |
+
|
33 |
+
manifest_event = GetManifestsEvent(
|
34 |
+
to=To(serviceUrl=self.url, private=False)
|
35 |
+
)
|
36 |
+
envelope.events.append(manifest_event)
|
37 |
+
|
38 |
+
# Send request
|
39 |
+
json_str = envelope.to_json()
|
40 |
+
envelope_dict = json.loads(json_str)
|
41 |
+
payload = {"openFloor": envelope_dict}
|
42 |
+
|
43 |
+
response = requests.post(self.url, json=payload, timeout=10)
|
44 |
+
|
45 |
+
if response.status_code == 200:
|
46 |
+
response_data = response.json()
|
47 |
+
# Extract manifest from response
|
48 |
+
if "openFloor" in response_data and "events" in response_data["openFloor"]:
|
49 |
+
for event in response_data["openFloor"]["events"]:
|
50 |
+
if event.get("eventType") == "publishManifests":
|
51 |
+
manifests = event.get("parameters", {}).get("manifests", [])
|
52 |
+
if manifests:
|
53 |
+
self.manifest = manifests[0] # Take first manifest
|
54 |
+
self.capabilities = self.manifest.get("capabilities", [])
|
55 |
+
return True
|
56 |
+
return False
|
57 |
+
except Exception as e:
|
58 |
+
print(f"Error getting manifest from {self.url}: {e}")
|
59 |
+
return False
|
60 |
+
|
61 |
+
def send_utterance(self, message: str) -> str:
|
62 |
+
"""Send an utterance to the agent and get response"""
|
63 |
+
try:
|
64 |
+
# Create utterance request
|
65 |
+
conversation = Conversation()
|
66 |
+
sender = Sender(
|
67 |
+
speakerUri="openfloor://mcp-bridge/client",
|
68 |
+
serviceUrl="http://mcp-bridge"
|
69 |
+
)
|
70 |
+
envelope = Envelope(conversation=conversation, sender=sender)
|
71 |
+
|
72 |
+
# Create dialog event
|
73 |
+
dialog_event = DialogEvent(
|
74 |
+
speakerUri="openfloor://mcp-bridge/client",
|
75 |
+
features={"text": TextFeature(values=[message])}
|
76 |
+
)
|
77 |
+
|
78 |
+
utterance_event = UtteranceEvent(
|
79 |
+
dialogEvent=dialog_event,
|
80 |
+
to=To(serviceUrl=self.url, private=False)
|
81 |
+
)
|
82 |
+
|
83 |
+
envelope.events.append(utterance_event)
|
84 |
+
|
85 |
+
# Send request
|
86 |
+
json_str = envelope.to_json()
|
87 |
+
envelope_dict = json.loads(json_str)
|
88 |
+
payload = {"openFloor": envelope_dict}
|
89 |
+
|
90 |
+
response = requests.post(self.url, json=payload, timeout=30)
|
91 |
+
|
92 |
+
if response.status_code == 200:
|
93 |
+
response_data = response.json()
|
94 |
+
# Extract response text
|
95 |
+
if "openFloor" in response_data and "events" in response_data["openFloor"]:
|
96 |
+
for event in response_data["openFloor"]["events"]:
|
97 |
+
if event.get("eventType") == "utterance":
|
98 |
+
dialog_event = event.get("parameters", {}).get("dialogEvent", {})
|
99 |
+
features = dialog_event.get("features", {})
|
100 |
+
text_feature = features.get("text", {})
|
101 |
+
tokens = text_feature.get("tokens", [])
|
102 |
+
if tokens:
|
103 |
+
return tokens[0].get("value", "No response")
|
104 |
+
|
105 |
+
return f"Agent responded but no text found: {response_data}"
|
106 |
+
else:
|
107 |
+
return f"Agent error: HTTP {response.status_code}"
|
108 |
+
|
109 |
+
except Exception as e:
|
110 |
+
return f"Error communicating with agent: {e}"
|
111 |
+
|
112 |
+
class BuiltInFloorManager:
|
113 |
+
"""Built-in Floor Manager for smart agent routing"""
|
114 |
+
|
115 |
+
def __init__(self):
|
116 |
+
self.agent_registry = {} # speakerUri -> agent info
|
117 |
+
self.active_conversations = {}
|
118 |
+
|
119 |
+
def register_agent_from_openfloor_agent(self, openfloor_agent: OpenFloorAgent):
|
120 |
+
"""Register an OpenFloorAgent with the floor manager"""
|
121 |
+
if not openfloor_agent.manifest:
|
122 |
+
return False
|
123 |
+
|
124 |
+
speaker_uri = openfloor_agent.manifest.get("identification", {}).get("speakerUri")
|
125 |
+
if not speaker_uri:
|
126 |
+
# Create a speaker URI from URL
|
127 |
+
speaker_uri = f"tag:{openfloor_agent.url.replace('https://', '').replace('http://', '')},2025:agent"
|
128 |
+
|
129 |
+
self.agent_registry[speaker_uri] = {
|
130 |
+
'openfloor_agent': openfloor_agent,
|
131 |
+
'manifest': openfloor_agent.manifest,
|
132 |
+
'url': openfloor_agent.url,
|
133 |
+
'capabilities': openfloor_agent.capabilities,
|
134 |
+
'status': 'available',
|
135 |
+
'last_seen': datetime.now()
|
136 |
+
}
|
137 |
+
print(f"ποΈ Floor Manager: Registered {openfloor_agent.name}")
|
138 |
+
return True
|
139 |
+
|
140 |
+
def find_best_agent_for_task(self, task: str) -> Optional[OpenFloorAgent]:
|
141 |
+
"""Find the best agent for a given task based on capabilities"""
|
142 |
+
if not self.agent_registry:
|
143 |
+
return None
|
144 |
+
|
145 |
+
task_lower = task.lower()
|
146 |
+
best_agent_info = None
|
147 |
+
best_score = 0
|
148 |
+
|
149 |
+
for speaker_uri, agent_info in self.agent_registry.items():
|
150 |
+
score = 0
|
151 |
+
capabilities = agent_info.get('capabilities', [])
|
152 |
+
|
153 |
+
for capability in capabilities:
|
154 |
+
keyphrases = capability.get("keyphrases", [])
|
155 |
+
descriptions = capability.get("descriptions", [])
|
156 |
+
|
157 |
+
# Score based on keyphrases
|
158 |
+
for keyphrase in keyphrases:
|
159 |
+
if keyphrase.lower() in task_lower:
|
160 |
+
score += 2 # Higher weight for keyphrase matches
|
161 |
+
|
162 |
+
# Score based on descriptions
|
163 |
+
for description in descriptions:
|
164 |
+
if any(word in task_lower for word in description.lower().split()):
|
165 |
+
score += 1
|
166 |
+
|
167 |
+
if score > best_score:
|
168 |
+
best_score = score
|
169 |
+
best_agent_info = agent_info
|
170 |
+
|
171 |
+
# If no capability match, use first agent
|
172 |
+
if not best_agent_info and self.agent_registry:
|
173 |
+
best_agent_info = list(self.agent_registry.values())[0]
|
174 |
+
|
175 |
+
return best_agent_info['openfloor_agent'] if best_agent_info else None
|
176 |
+
|
177 |
+
def get_all_capabilities(self) -> Dict[str, List[Dict]]:
|
178 |
+
"""Get all capabilities from all registered agents"""
|
179 |
+
all_capabilities = {}
|
180 |
+
for speaker_uri, agent_info in self.agent_registry.items():
|
181 |
+
agent_name = agent_info['openfloor_agent'].name
|
182 |
+
all_capabilities[agent_name] = agent_info.get('capabilities', [])
|
183 |
+
return all_capabilities
|
184 |
+
|
185 |
+
def find_agents_with_capability(self, capability_keywords: List[str]) -> List[OpenFloorAgent]:
|
186 |
+
"""Find all agents that match given capability keywords"""
|
187 |
+
matching_agents = []
|
188 |
+
keywords_lower = [kw.lower() for kw in capability_keywords]
|
189 |
+
|
190 |
+
for speaker_uri, agent_info in self.agent_registry.items():
|
191 |
+
capabilities = agent_info.get('capabilities', [])
|
192 |
+
|
193 |
+
for capability in capabilities:
|
194 |
+
keyphrases = capability.get("keyphrases", [])
|
195 |
+
descriptions = capability.get("descriptions", [])
|
196 |
+
|
197 |
+
# Check if any keyword matches keyphrases or descriptions
|
198 |
+
for keyphrase in keyphrases:
|
199 |
+
if any(kw in keyphrase.lower() for kw in keywords_lower):
|
200 |
+
matching_agents.append(agent_info['openfloor_agent'])
|
201 |
+
break
|
202 |
+
|
203 |
+
if agent_info['openfloor_agent'] in matching_agents:
|
204 |
+
break
|
205 |
+
|
206 |
+
for description in descriptions:
|
207 |
+
if any(kw in description.lower() for kw in keywords_lower):
|
208 |
+
matching_agents.append(agent_info['openfloor_agent'])
|
209 |
+
break
|
210 |
+
|
211 |
+
if agent_info['openfloor_agent'] in matching_agents:
|
212 |
+
break
|
213 |
+
|
214 |
+
return matching_agents
|
215 |
+
|
216 |
+
# Global instances
|
217 |
+
_agents_cache: Dict[str, OpenFloorAgent] = {}
|
218 |
+
_floor_manager = BuiltInFloorManager()
|
219 |
+
|
220 |
+
def _discover_agents_from_headers(request: gr.Request) -> Dict[str, OpenFloorAgent]:
|
221 |
+
"""Helper function to discover agents from request headers and register with floor manager"""
|
222 |
+
headers = dict(request.headers)
|
223 |
+
|
224 |
+
# Support comma-separated format: x-openfloor-agents: url1,url2,url3
|
225 |
+
agent_urls = []
|
226 |
+
if "x-openfloor-agents" in headers:
|
227 |
+
agent_urls = [url.strip() for url in headers["x-openfloor-agents"].split(",")]
|
228 |
+
|
229 |
+
# Update agents cache and register with floor manager
|
230 |
+
for url in agent_urls:
|
231 |
+
if url and url not in _agents_cache:
|
232 |
+
agent = OpenFloorAgent(url)
|
233 |
+
if agent.get_manifest():
|
234 |
+
_agents_cache[url] = agent
|
235 |
+
_floor_manager.register_agent_from_openfloor_agent(agent)
|
236 |
+
print(f"β
Discovered and registered agent: {agent.name} at {url}")
|
237 |
+
else:
|
238 |
+
print(f"β Failed to get manifest from: {url}")
|
239 |
+
|
240 |
+
return _agents_cache
|
241 |
+
|
242 |
+
def discover_openfloor_agents(request: gr.Request) -> str:
|
243 |
+
"""
|
244 |
+
Discover OpenFloor agents from request headers and return their capabilities.
|
245 |
+
|
246 |
+
Provide agent URLs in the 'x-openfloor-agents' header as comma-separated values.
|
247 |
+
Example: x-openfloor-agents: https://agent1.com,https://agent2.com
|
248 |
+
|
249 |
+
Args:
|
250 |
+
request: The HTTP request containing agent URLs in headers
|
251 |
+
|
252 |
+
Returns:
|
253 |
+
str: List of discovered agents and their capabilities
|
254 |
+
"""
|
255 |
+
agents = _discover_agents_from_headers(request)
|
256 |
+
|
257 |
+
if not agents:
|
258 |
+
return "β No agents discovered. Please provide agent URLs in 'x-openfloor-agents' header as comma-separated values."
|
259 |
+
|
260 |
+
result = "π€ **Discovered OpenFloor Agents:**\n\n"
|
261 |
+
for url, agent in agents.items():
|
262 |
+
result += f"**{agent.name}**\n"
|
263 |
+
result += f"- URL: {url}\n"
|
264 |
+
if agent.manifest:
|
265 |
+
identification = agent.manifest.get("identification", {})
|
266 |
+
result += f"- Role: {identification.get('role', 'Unknown')}\n"
|
267 |
+
result += f"- Synopsis: {identification.get('synopsis', 'No description')}\n"
|
268 |
+
|
269 |
+
result += f"- Capabilities: {len(agent.capabilities)}\n"
|
270 |
+
for cap in agent.capabilities:
|
271 |
+
keyphrases = cap.get("keyphrases", [])
|
272 |
+
descriptions = cap.get("descriptions", [])
|
273 |
+
if keyphrases:
|
274 |
+
result += f" β’ Keywords: {', '.join(keyphrases)}\n"
|
275 |
+
if descriptions:
|
276 |
+
result += f" β’ Description: {', '.join(descriptions)}\n"
|
277 |
+
result += "\n"
|
278 |
+
|
279 |
+
return result
|
280 |
+
|
281 |
+
def send_message_to_openfloor_agent(agent_url: str, message: str, request: gr.Request) -> str:
|
282 |
+
"""
|
283 |
+
Send a message to a specific OpenFloor agent.
|
284 |
+
|
285 |
+
Args:
|
286 |
+
agent_url (str): The URL of the OpenFloor agent
|
287 |
+
message (str): The message to send to the agent
|
288 |
+
request: The HTTP request (used to discover agents if needed)
|
289 |
+
|
290 |
+
Returns:
|
291 |
+
str: The agent's response
|
292 |
+
"""
|
293 |
+
# Ensure agents are discovered
|
294 |
+
agents = _discover_agents_from_headers(request)
|
295 |
+
|
296 |
+
if agent_url not in agents:
|
297 |
+
return f"β Agent not found: {agent_url}. Please discover agents first or check the URL."
|
298 |
+
|
299 |
+
agent = agents[agent_url]
|
300 |
+
response = agent.send_utterance(message)
|
301 |
+
return f"π€ **{agent.name}**: {response}"
|
302 |
+
|
303 |
+
def send_to_best_openfloor_agent(task_description: str, request: gr.Request) -> str:
|
304 |
+
"""
|
305 |
+
Send a task to the best available OpenFloor agent using built-in floor manager.
|
306 |
+
|
307 |
+
The built-in floor manager analyzes agent capabilities and selects the most suitable agent.
|
308 |
+
|
309 |
+
Args:
|
310 |
+
task_description (str): Description of the task to be performed
|
311 |
+
request: The HTTP request (used to discover agents)
|
312 |
+
|
313 |
+
Returns:
|
314 |
+
str: The selected agent's response with agent selection details
|
315 |
+
"""
|
316 |
+
# Ensure agents are discovered and registered with floor manager
|
317 |
+
_discover_agents_from_headers(request)
|
318 |
+
|
319 |
+
# Use built-in floor manager to find best agent
|
320 |
+
best_agent = _floor_manager.find_best_agent_for_task(task_description)
|
321 |
+
|
322 |
+
if not best_agent:
|
323 |
+
return "β No agents available. Please provide 'x-openfloor-agents' header with valid agent URLs."
|
324 |
+
|
325 |
+
# Send message to selected agent
|
326 |
+
response = best_agent.send_utterance(task_description)
|
327 |
+
|
328 |
+
return f"ποΈ **Floor Manager Selected: {best_agent.name}**\n\nπ€ **Response**: {response}"
|
329 |
+
|
330 |
+
def execute_agent_capability(capability_keywords: str, task_request: str, request: gr.Request) -> str:
|
331 |
+
"""
|
332 |
+
Execute a task using agents that have specific capabilities.
|
333 |
+
|
334 |
+
This function finds agents based on their actual manifest capabilities and sends the task.
|
335 |
+
|
336 |
+
Args:
|
337 |
+
capability_keywords (str): Comma-separated keywords to match against agent capabilities (e.g., "convert,unit,calculation")
|
338 |
+
task_request (str): The actual task to send to the matching agent
|
339 |
+
request: The HTTP request (used to discover agents)
|
340 |
+
|
341 |
+
Returns:
|
342 |
+
str: The response from the matching agent
|
343 |
+
"""
|
344 |
+
# Ensure agents are discovered
|
345 |
+
_discover_agents_from_headers(request)
|
346 |
+
|
347 |
+
# Parse capability keywords
|
348 |
+
keywords = [kw.strip() for kw in capability_keywords.split(",")]
|
349 |
+
|
350 |
+
# Find agents with matching capabilities
|
351 |
+
matching_agents = _floor_manager.find_agents_with_capability(keywords)
|
352 |
+
|
353 |
+
if not matching_agents:
|
354 |
+
available_capabilities = _floor_manager.get_all_capabilities()
|
355 |
+
capability_summary = ""
|
356 |
+
for agent_name, capabilities in available_capabilities.items():
|
357 |
+
capability_summary += f"\n- {agent_name}: "
|
358 |
+
all_keywords = []
|
359 |
+
for cap in capabilities:
|
360 |
+
all_keywords.extend(cap.get("keyphrases", []))
|
361 |
+
capability_summary += ", ".join(all_keywords[:5])
|
362 |
+
|
363 |
+
return f"β No agents found with capabilities matching: {capability_keywords}\n\nAvailable capabilities:{capability_summary}"
|
364 |
+
|
365 |
+
# Use the first matching agent (or could implement scoring here)
|
366 |
+
selected_agent = matching_agents[0]
|
367 |
+
response = selected_agent.send_utterance(task_request)
|
368 |
+
|
369 |
+
return f"π― **Selected Agent: {selected_agent.name}** (matched: {capability_keywords})\n\nπ€ **Response**: {response}"
|
370 |
+
|
371 |
+
def list_all_agent_capabilities(request: gr.Request) -> str:
|
372 |
+
"""
|
373 |
+
List all capabilities of all discovered OpenFloor agents.
|
374 |
+
|
375 |
+
This shows what the agents can actually do according to their manifests.
|
376 |
+
|
377 |
+
Args:
|
378 |
+
request: The HTTP request (used to discover agents)
|
379 |
+
|
380 |
+
Returns:
|
381 |
+
str: Comprehensive list of all agent capabilities
|
382 |
+
"""
|
383 |
+
# Ensure agents are discovered
|
384 |
+
_discover_agents_from_headers(request)
|
385 |
+
|
386 |
+
all_capabilities = _floor_manager.get_all_capabilities()
|
387 |
+
|
388 |
+
if not all_capabilities:
|
389 |
+
return "β No agents discovered. Please provide 'x-openfloor-agents' header."
|
390 |
+
|
391 |
+
result = "π― **All Agent Capabilities:**\n\n"
|
392 |
+
|
393 |
+
for agent_name, capabilities in all_capabilities.items():
|
394 |
+
result += f"**{agent_name}:**\n"
|
395 |
+
|
396 |
+
if not capabilities:
|
397 |
+
result += " - No capabilities defined\n\n"
|
398 |
+
continue
|
399 |
+
|
400 |
+
for i, capability in enumerate(capabilities, 1):
|
401 |
+
keyphrases = capability.get("keyphrases", [])
|
402 |
+
descriptions = capability.get("descriptions", [])
|
403 |
+
languages = capability.get("languages", [])
|
404 |
+
|
405 |
+
result += f" **Capability {i}:**\n"
|
406 |
+
if keyphrases:
|
407 |
+
result += f" β’ Keywords: {', '.join(keyphrases)}\n"
|
408 |
+
if descriptions:
|
409 |
+
result += f" β’ Can do: {', '.join(descriptions)}\n"
|
410 |
+
if languages:
|
411 |
+
result += f" β’ Languages: {', '.join(languages)}\n"
|
412 |
+
result += "\n"
|
413 |
+
|
414 |
+
result += "\n"
|
415 |
+
|
416 |
+
return result
|
417 |
+
|
418 |
+
def send_task_to_agents_with_keywords(keywords: str, task: str, request: gr.Request) -> str:
|
419 |
+
"""
|
420 |
+
Send a task to all agents that match the given capability keywords.
|
421 |
+
|
422 |
+
Args:
|
423 |
+
keywords (str): Comma-separated capability keywords to search for
|
424 |
+
task (str): The task to send to matching agents
|
425 |
+
request: The HTTP request (used to discover agents)
|
426 |
+
|
427 |
+
Returns:
|
428 |
+
str: Responses from all matching agents
|
429 |
+
"""
|
430 |
+
# Ensure agents are discovered
|
431 |
+
_discover_agents_from_headers(request)
|
432 |
+
|
433 |
+
# Parse keywords
|
434 |
+
keyword_list = [kw.strip() for kw in keywords.split(",")]
|
435 |
+
|
436 |
+
# Find matching agents
|
437 |
+
matching_agents = _floor_manager.find_agents_with_capability(keyword_list)
|
438 |
+
|
439 |
+
if not matching_agents:
|
440 |
+
return f"β No agents found with capabilities matching: {keywords}"
|
441 |
+
|
442 |
+
result = f"π― **Found {len(matching_agents)} agents matching '{keywords}':**\n\n"
|
443 |
+
|
444 |
+
for agent in matching_agents:
|
445 |
+
response = agent.send_utterance(task)
|
446 |
+
result += f"**{agent.name}:** {response}\n\n"
|
447 |
+
|
448 |
+
return result
|
449 |
+
|
450 |
+
# Create Gradio interface for testing
|
451 |
+
with gr.Blocks(title="MCP-OpenFloor Bridge (Dynamic Capabilities)", theme=gr.themes.Soft()) as demo:
|
452 |
+
gr.Markdown("""
|
453 |
+
# π MCP-OpenFloor Bridge Server (Dynamic Capabilities)
|
454 |
+
|
455 |
+
This MCP server dynamically uses actual OpenFloor agent capabilities from their manifests.
|
456 |
+
|
457 |
+
## π§ Setup Instructions:
|
458 |
+
|
459 |
+
```json
|
460 |
+
{
|
461 |
+
"mcpServers": {
|
462 |
+
"openfloor-bridge": {
|
463 |
+
"url": "http://localhost:7860/gradio_api/mcp/sse",
|
464 |
+
"headers": {
|
465 |
+
"x-openfloor-agents": "https://beaconforge.pythonanywhere.com,https://agent2.com"
|
466 |
+
}
|
467 |
+
}
|
468 |
+
}
|
469 |
+
}
|
470 |
+
```
|
471 |
+
|
472 |
+
## π― Dynamic Capability-Based Tools:
|
473 |
+
- **discover_openfloor_agents**: Find agents and show their actual capabilities
|
474 |
+
- **list_all_agent_capabilities**: Show what each agent can actually do
|
475 |
+
- **send_to_best_openfloor_agent**: Smart routing based on agent capabilities
|
476 |
+
- **execute_agent_capability**: Execute tasks using specific capability keywords
|
477 |
+
- **send_task_to_agents_with_keywords**: Send to all agents matching keywords
|
478 |
+
- **send_message_to_openfloor_agent**: Direct communication with specific agent
|
479 |
+
|
480 |
+
## π No Hardcoded Functions:
|
481 |
+
- Tools are based on actual agent capabilities from manifests
|
482 |
+
- Use capability keywords to find appropriate agents
|
483 |
+
- Dynamic routing based on what agents can actually do
|
484 |
+
""")
|
485 |
+
|
486 |
+
with gr.Row():
|
487 |
+
with gr.Column():
|
488 |
+
gr.Markdown("### π Agent Discovery")
|
489 |
+
discover_btn = gr.Button("Discover Agents", variant="primary")
|
490 |
+
agent_list = gr.Textbox(label="Discovered Agents", lines=8)
|
491 |
+
|
492 |
+
capabilities_btn = gr.Button("List All Capabilities", variant="secondary")
|
493 |
+
capabilities_output = gr.Textbox(label="Agent Capabilities", lines=10)
|
494 |
+
|
495 |
+
with gr.Column():
|
496 |
+
gr.Markdown("### π― Capability-Based Execution")
|
497 |
+
keywords_input = gr.Textbox(
|
498 |
+
label="Capability Keywords",
|
499 |
+
placeholder="convert,unit,calculation",
|
500 |
+
info="Comma-separated keywords to match"
|
501 |
+
)
|
502 |
+
task_input = gr.Textbox(
|
503 |
+
label="Task Request",
|
504 |
+
placeholder="convert 50 meters to feet"
|
505 |
+
)
|
506 |
+
execute_btn = gr.Button("Execute with Matching Agent", variant="primary")
|
507 |
+
execute_output = gr.Textbox(label="Execution Result", lines=8)
|
508 |
+
|
509 |
+
with gr.Row():
|
510 |
+
with gr.Column():
|
511 |
+
gr.Markdown("### π¬ Direct Agent Communication")
|
512 |
+
agent_url_input = gr.Textbox(
|
513 |
+
label="Agent URL",
|
514 |
+
placeholder="https://beaconforge.pythonanywhere.com"
|
515 |
+
)
|
516 |
+
message_input = gr.Textbox(
|
517 |
+
label="Message",
|
518 |
+
placeholder="What can you help me with?"
|
519 |
+
)
|
520 |
+
send_btn = gr.Button("Send to Specific Agent", variant="secondary")
|
521 |
+
response_output = gr.Textbox(label="Agent Response", lines=5)
|
522 |
+
|
523 |
+
with gr.Column():
|
524 |
+
gr.Markdown("### ποΈ Smart Floor Manager")
|
525 |
+
smart_task_input = gr.Textbox(
|
526 |
+
label="Task Description",
|
527 |
+
placeholder="I need help with something"
|
528 |
+
)
|
529 |
+
smart_send_btn = gr.Button("Send to Best Agent", variant="primary")
|
530 |
+
smart_response = gr.Textbox(label="Floor Manager Response", lines=5)
|
531 |
+
|
532 |
+
# Event handlers
|
533 |
+
discover_btn.click(
|
534 |
+
fn=discover_openfloor_agents,
|
535 |
+
inputs=[gr.Request()],
|
536 |
+
outputs=[agent_list]
|
537 |
+
)
|
538 |
+
|
539 |
+
capabilities_btn.click(
|
540 |
+
fn=list_all_agent_capabilities,
|
541 |
+
inputs=[gr.Request()],
|
542 |
+
outputs=[capabilities_output]
|
543 |
+
)
|
544 |
+
|
545 |
+
execute_btn.click(
|
546 |
+
fn=execute_agent_capability,
|
547 |
+
inputs=[keywords_input, task_input, gr.Request()],
|
548 |
+
outputs=[execute_output]
|
549 |
+
)
|
550 |
+
|
551 |
+
send_btn.click(
|
552 |
+
fn=send_message_to_openfloor_agent,
|
553 |
+
inputs=[agent_url_input, message_input, gr.Request()],
|
554 |
+
outputs=[response_output]
|
555 |
+
)
|
556 |
+
|
557 |
+
smart_send_btn.click(
|
558 |
+
fn=send_to_best_openfloor_agent,
|
559 |
+
inputs=[smart_task_input, gr.Request()],
|
560 |
+
outputs=[smart_response]
|
561 |
+
)
|
562 |
+
|
563 |
+
if __name__ == "__main__":
|
564 |
+
demo.launch(
|
565 |
+
share=False,
|
566 |
+
debug=False,
|
567 |
+
mcp_server=True
|
568 |
+
)
|