dtka commited on
Commit
458da34
·
1 Parent(s): 8a95645

fix: resolve syntax errors and improve code formatting

Browse files

- Fix unterminated f-strings in analyze_intent_and_select_swarm
- Clean up string formatting in route_to_swarm_and_aggregate
- Improve code readability with better variable naming
- Fix newline handling in log output

Files changed (1) hide show
  1. app.py +352 -22
app.py CHANGED
@@ -9,6 +9,9 @@ import io
9
  import matplotlib.pyplot as plt
10
  import seaborn as sns
11
  from dotenv import load_dotenv
 
 
 
12
 
13
  load_dotenv()
14
 
@@ -26,6 +29,191 @@ AGENT_ICONS = {
26
  "NGO Matcher": "🤝"
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def fetch_registry():
30
  # Load from local file first, fall back to remote if not found
31
  local_registry = "agents_registry.json"
@@ -38,7 +226,7 @@ def fetch_registry():
38
  print(f"Error loading local registry: {e}")
39
 
40
  # Fall back to remote registry
41
- remote_url = "https://huggingface.co/spaces/dtka/collective-intelligence-orchestrator/resolve/main/agents_registry.json"
42
  print(f"Fetching agents from remote registry: {remote_url}")
43
  try:
44
  res = requests.get(remote_url, timeout=5)
@@ -192,13 +380,70 @@ def breed_hybrid_agent():
192
  "description": hybrid_prompt,
193
  "origin": [agent1, agent2],
194
  "status": "prototype",
195
- "generated_at": datetime.utcnow().isoformat()
 
 
 
 
196
  }
197
 
198
- with open(f"hybrids/{hybrid_name}.json", "w") as f:
 
 
 
199
  json.dump(hybrid_metadata, f, indent=2)
200
 
201
- return f"Hybrid agent '{hybrid_name}' scaffolded. Metadata saved to hybrids/{hybrid_name}.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
  # ---- Claude Orchestrator ----
204
 
@@ -247,16 +492,32 @@ def claude_conductor(message, history, tools=None, index=None):
247
  conversation.append({"role": "user", "content": message})
248
 
249
  # Create system prompt
250
- system_prompt = f"""You are a coordinator of autonomous AI agents solving real-world crises.
251
- Analyze the user's input and determine which tools to use to gather information.
252
- Here are the available agents for this task:
253
- {tools_description}
254
- Provide a clear, concise response based on the available information."""
 
 
 
 
 
 
255
 
 
 
 
 
 
 
 
 
 
 
256
  try:
257
  # Call Claude API
258
  response = anthropic_client.messages.create(
259
- model="claude-sonnet-4-20250514",
260
  max_tokens=1000,
261
  system=system_prompt,
262
  messages=conversation,
@@ -314,18 +575,19 @@ if __name__ == "__main__":
314
  with gr.Tab("Chat with Swarm"):
315
  with gr.Row():
316
  with gr.Column(scale=1):
317
- gr.Markdown("### 🧩 Available Agents")
318
- if not cards:
319
- gr.Markdown("⚠️ No agents discovered. Please check agents_registry.json or try again later.")
320
- for icon, name, desc, categories in cards:
321
- categories_html = f"<br><span style='font-size: 0.8em; color: #666;'><i>Categories: {', '.join(categories) if categories else 'General'}</i></span>" if categories else ""
322
- gr.Markdown(
323
- f"<b>{icon} {name}</b><br>"
324
- f"<span style='font-size: 0.9em;'>{desc}</span>"
325
- f"{categories_html}",
326
- render=True,
327
- elem_id="agent-card"
328
- )
 
329
 
330
  with gr.Column(scale=2):
331
  # Create the chat interface with explicit buttons
@@ -478,6 +740,74 @@ if __name__ == "__main__":
478
  - Use this to understand which agents work together most frequently
479
  """)
480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  # Add Documentation Tab
482
  with gr.Tab("📚 Documentation"):
483
  def load_readme():
 
9
  import matplotlib.pyplot as plt
10
  import seaborn as sns
11
  from dotenv import load_dotenv
12
+ from datetime import datetime
13
+ from itertools import combinations
14
+ from collections import defaultdict
15
 
16
  load_dotenv()
17
 
 
29
  "NGO Matcher": "🤝"
30
  }
31
 
32
+ CLAUDE_MODEL="claude-sonnet-4-20250514"
33
+
34
+ RETIREMENT_THRESHOLD = 1 # Agent appears in fewer than this many swarms
35
+
36
+ def should_spawn_hybrid(threshold=3, log_path="swarm_log.jsonl", registry_path="agents_registry.json"):
37
+ from collections import Counter
38
+ import itertools
39
+
40
+ try:
41
+ with open(log_path, "r", encoding="utf-8") as f:
42
+ lines = [json.loads(line) for line in f if line.strip()]
43
+ except:
44
+ return None, None
45
+
46
+ pair_counts = Counter()
47
+
48
+ for entry in lines:
49
+ agents = sorted(entry.get("agents", []))
50
+ for a, b in itertools.combinations(agents, 2):
51
+ pair_counts[(a, b)] += 1
52
+
53
+ try:
54
+ with open(registry_path, "r", encoding="utf-8") as f:
55
+ registry = json.load(f)
56
+ hybrid_origins = [tuple(sorted(agent.get("origin", []))) for agent in registry.get("agents", []) if agent.get("status") == "prototype"]
57
+ except:
58
+ hybrid_origins = []
59
+
60
+ for (a, b), count in pair_counts.items():
61
+ if count >= threshold and (a, b) not in hybrid_origins:
62
+ return a, b
63
+
64
+ return None, None
65
+
66
+ def spawn_hybrid_agent(agent_a, agent_b, registry_path="agents_registry.json"):
67
+ import uuid
68
+ hybrid_name = f"Hybrid_{uuid.uuid4().hex[:6]}"
69
+ hybrid_description = f"Hybrid of {agent_a} and {agent_b}, designed through observed co-usage."
70
+ hybrid_icon = "🧬"
71
+
72
+ new_agent = {
73
+ "name": hybrid_name,
74
+ "description": hybrid_description,
75
+ "status": "prototype",
76
+ "origin": [agent_a, agent_b],
77
+ "icon": hybrid_icon
78
+ }
79
+
80
+ try:
81
+ with open(registry_path, "r", encoding="utf-8") as f:
82
+ data = json.load(f)
83
+ except:
84
+ data = {"agents": []}
85
+
86
+ data["agents"].append(new_agent)
87
+
88
+ with open(registry_path, "w", encoding="utf-8") as f:
89
+ json.dump(data, f, indent=2)
90
+
91
+ return hybrid_name
92
+
93
+ pair_counts = Counter()
94
+
95
+ for entry in lines:
96
+ agents = sorted(entry.get("agents", []))
97
+ for a, b in itertools.combinations(agents, 2):
98
+ pair_counts[(a, b)] += 1
99
+
100
+ try:
101
+ with open(registry_path, "r", encoding="utf-8") as f:
102
+ registry = json.load(f)
103
+ hybrid_origins = [tuple(sorted(agent.get("origin", []))) for agent in registry.get("agents", []) if agent.get("status") == "prototype"]
104
+ except:
105
+ hybrid_origins = []
106
+
107
+ for (a, b), count in pair_counts.items():
108
+ if count >= threshold and (a, b) not in hybrid_origins:
109
+ return a, b
110
+
111
+ return None, None
112
+
113
+ # ---- Utility: Deprecate Stale Agents ----
114
+ def deprecate_low_usage_agents(log_path="swarm_log.jsonl", registry_path="agents_registry.json"):
115
+ usage_counter = defaultdict(int)
116
+ try:
117
+ with open(log_path, "r", encoding="utf-8") as f:
118
+ for line in f:
119
+ entry = json.loads(line)
120
+ for agent in entry.get("agents", []):
121
+ usage_counter[agent] += 1
122
+
123
+ if not os.path.exists(registry_path):
124
+ print("No registry found to update.")
125
+ return
126
+
127
+ with open(registry_path, 'r') as f:
128
+ registry = json.load(f)
129
+
130
+ modified = False
131
+ for agent in registry.get("agents", []):
132
+ if agent.get("status") == "active" and usage_counter[agent["name"]] < RETIREMENT_THRESHOLD:
133
+ agent["status"] = "deprecated"
134
+ modified = True
135
+
136
+ if modified:
137
+ with open(registry_path, 'w') as f:
138
+ json.dump(registry, f, indent=2)
139
+ print("Stale agents deprecated.")
140
+ else:
141
+ print("No agents met deprecation criteria.")
142
+
143
+ except Exception as e:
144
+ print(f"Error during agent deprecation: {e}")
145
+
146
+ # ---- Swarm Self-Assembly ----
147
+
148
+ def analyze_intent_and_select_swarm(user_input, registry_path="agents_registry.json"):
149
+ try:
150
+ with open(registry_path, 'r') as f:
151
+ registry = json.load(f)
152
+ except Exception as e:
153
+ return [], f"Failed to load registry: {str(e)}"
154
+
155
+ try:
156
+ available_agents = "\n".join([f"- {a['name']}: {a.get('description', '')}" for a in registry['agents'] if a.get('status') == 'active'])
157
+
158
+ messages = [
159
+ {"role": "system", "content": "You're a swarm selector. Given a task, you select a subset of agents best suited to it."},
160
+ {"role": "user", "content": f"Task: {user_input}\nAvailable agents:\n{available_agents}"},
161
+ ]
162
+
163
+ response = anthropic_client.messages.create(
164
+ model=CLAUDE_MODEL,
165
+ max_tokens=256,
166
+ messages=messages
167
+ )
168
+
169
+ selected_names = []
170
+ for agent in registry['agents']:
171
+ if agent['name'].lower() in response.content[0].text.lower():
172
+ selected_names.append(agent['name'])
173
+
174
+ return selected_names, None
175
+
176
+ except Exception as e:
177
+ return [], f"Error selecting swarm: {str(e)}"
178
+
179
+ # ---- Swarm Execution & Aggregation ----
180
+
181
+ def route_to_swarm_and_aggregate(user_input, selected_agents, registry_path="agents_registry.json", log_path="swarm_log.jsonl"):
182
+ try:
183
+ with open(registry_path, 'r') as f:
184
+ registry = json.load(f)
185
+ except Exception as e:
186
+ return f"Failed to load registry: {str(e)}"
187
+
188
+ responses = []
189
+ for agent in registry['agents']:
190
+ if agent['name'] in selected_agents:
191
+ try:
192
+ resp = requests.post(
193
+ agent['endpoint'],
194
+ json={"input": user_input},
195
+ timeout=agent.get('timeout_seconds', 30)
196
+ )
197
+ resp.raise_for_status()
198
+ out = resp.json().get("output", "No output.")
199
+ responses.append(f"[{agent['name']}]\n{out}\n")
200
+ except Exception as e:
201
+ responses.append(f"[{agent['name']}] Error: {str(e)}")
202
+
203
+ # Log swarm usage
204
+ try:
205
+ with open(log_path, "a", encoding="utf-8") as logf:
206
+ json.dump({
207
+ "timestamp": datetime.utcnow().isoformat(),
208
+ "input": user_input,
209
+ "agents": selected_agents
210
+ }, logf)
211
+ logf.write("\n")
212
+ except:
213
+ pass
214
+
215
+ return "\n---\n".join(responses)
216
+
217
  def fetch_registry():
218
  # Load from local file first, fall back to remote if not found
219
  local_registry = "agents_registry.json"
 
226
  print(f"Error loading local registry: {e}")
227
 
228
  # Fall back to remote registry
229
+ remote_url = "https://huggingface.co/spaces/Agents-MCP-Hackathon/collective-intelligence-orchestrator/resolve/main/agents_registry.json"
230
  print(f"Fetching agents from remote registry: {remote_url}")
231
  try:
232
  res = requests.get(remote_url, timeout=5)
 
380
  "description": hybrid_prompt,
381
  "origin": [agent1, agent2],
382
  "status": "prototype",
383
+ "generated_at": datetime.utcnow().isoformat(),
384
+ "endpoint": f"https://huggingface.co/spaces/Agents-MCP-Hackathon/collective-intelligence-orchestrator/resolve/main/hybrids/{hybrid_name}/serve",
385
+ "icon": "🧬",
386
+ "categories": ["hybrid"],
387
+ "capabilities": ["emergent-analysis"]
388
  }
389
 
390
+ os.makedirs(f"hybrids/{hybrid_name}", exist_ok=True)
391
+
392
+ # Save metadata
393
+ with open(f"hybrids/{hybrid_name}/{hybrid_name}.json", "w") as f:
394
  json.dump(hybrid_metadata, f, indent=2)
395
 
396
+ # Generate agent.yaml
397
+ agent_yaml = {
398
+ "name": hybrid_name,
399
+ "description": hybrid_prompt,
400
+ "author": "Orchestrator",
401
+ "tags": ["hybrid", "generated"],
402
+ "capabilities": ["emergent-analysis"],
403
+ "timeout_seconds": 30
404
+ }
405
+ with open(f"hybrids/{hybrid_name}/agent.yaml", "w") as f:
406
+ yaml.dump(agent_yaml, f)
407
+
408
+ # Generate app.py
409
+ app_code = f"""import gradio as gr\n\ndef respond(input):\n return \"[Hybrid Agent: {hybrid_name}]\nResponding with insight from merged origins: {agent1} + {agent2}\"\n\niface = gr.Interface(fn=respond, inputs=\"text\", outputs=\"text\", title=\"{hybrid_name}\")\n\niface.launch(mcp_server=True)"""
410
+ with open(f"hybrids/{hybrid_name}/app.py", "w") as f:
411
+ f.write(app_code)
412
+
413
+ # Generate README
414
+ with open(f"hybrids/{hybrid_name}/README.md", "w") as f:
415
+ f.write(f"""# {hybrid_name}
416
+
417
+ This hybrid agent was auto-generated by combining:
418
+ - `{agent1}`
419
+ - `{agent2}`
420
+
421
+ ## Description
422
+ {hybrid_prompt}
423
+
424
+ ## Status
425
+ Prototype
426
+
427
+ Generated at {datetime.utcnow().isoformat()}
428
+ """)
429
+
430
+ # Auto-update registry
431
+ registry_path = "agents_registry.json"
432
+ try:
433
+ if os.path.exists(registry_path):
434
+ with open(registry_path, 'r') as f:
435
+ registry = json.load(f)
436
+ else:
437
+ registry = {"agents": []}
438
+
439
+ registry["agents"].append(hybrid_metadata)
440
+
441
+ with open(registry_path, 'w') as f:
442
+ json.dump(registry, f, indent=2)
443
+ except Exception as e:
444
+ print(f"Failed to update registry: {e}")
445
+
446
+ return f"Hybrid agent '{hybrid_name}' scaffolded and registered."
447
 
448
  # ---- Claude Orchestrator ----
449
 
 
492
  conversation.append({"role": "user", "content": message})
493
 
494
  # Create system prompt
495
+ system_prompt = f"""You are the conductor of a Collective Intelligence Swarm—a coordinated network of AI agents including both foundational agents and auto-generated hybrid prototypes.
496
+
497
+ Each agent specializes in real-world crisis domains (climate, public health, media monitoring, etc.) and has capabilities such as forecasting, summarization, cross-domain linking, or anomaly detection.
498
+
499
+ You must analyze the user's problem and determine which agents, or combination of agents, are best suited for the task.
500
+ Give preference to agents whose capabilities align with the user's request.
501
+
502
+ You may also draw on emergent-hybrid agents created from frequent co-occurrence patterns.
503
+
504
+ Here are the currently active tools:
505
+ {tools_description}
506
 
507
+ Respond clearly and concisely with your synthesis of the swarm’s outputs."""
508
+
509
+ # Load top co-occurring agents for swarm awareness
510
+ top_swarm_pairs = get_top_swarm_pairs(top_n=3)
511
+ co_usage_info = "\n".join(
512
+ f"- {a} + {b}: used together {count} times"
513
+ for (a, b), count in top_swarm_pairs
514
+ )
515
+ if co_usage_info:
516
+ system_prompt += f"\n\nHistorical synergy data:\n{co_usage_info}"
517
  try:
518
  # Call Claude API
519
  response = anthropic_client.messages.create(
520
+ model=CLAUDE_MODEL,
521
  max_tokens=1000,
522
  system=system_prompt,
523
  messages=conversation,
 
575
  with gr.Tab("Chat with Swarm"):
576
  with gr.Row():
577
  with gr.Column(scale=1):
578
+ with gr.Accordion("Agents Details"):
579
+ gr.Markdown("### 🧩 Available Agents")
580
+ if not cards:
581
+ gr.Markdown("⚠️ No agents discovered. Please check agents_registry.json or try again later.")
582
+ for icon, name, desc, categories in cards:
583
+ categories_html = f"<br><span style='font-size: 0.8em; color: #666;'><i>Categories: {', '.join(categories) if categories else 'General'}</i></span>" if categories else ""
584
+ gr.Markdown(
585
+ f"<b>{icon} {name}</b><br>"
586
+ f"<span style='font-size: 0.9em;'>{desc}</span>"
587
+ f"{categories_html}",
588
+ render=True,
589
+ elem_id="agent-card"
590
+ )
591
 
592
  with gr.Column(scale=2):
593
  # Create the chat interface with explicit buttons
 
740
  - Use this to understand which agents work together most frequently
741
  """)
742
 
743
+ # Add Evolution Dashboard Tab
744
+ with gr.Tab("🧬 Agent Evolution Dashboard"):
745
+ dashboard_md = gr.Markdown("""### Agent Registry Summary
746
+
747
+ Click **Refresh** to see the latest agent status.
748
+ Click **Retire Stale Agents** to deprecate unused tools.
749
+ """)
750
+
751
+ evolution_display = gr.Markdown("Loading...", elem_id="evolution_status")
752
+ refresh_btn = gr.Button("🔄 Refresh Dashboard")
753
+ retire_btn = gr.Button("🛑 Retire Stale Agents")
754
+
755
+ def list_agents_by_status():
756
+ try:
757
+ with open("agents_registry.json", "r", encoding="utf-8") as f:
758
+ data = json.load(f)
759
+
760
+ active, prototype, deprecated = [], [], []
761
+
762
+ for agent in data.get("agents", []):
763
+ name = agent.get("name", "Unnamed")
764
+ status = agent.get("status", "unknown")
765
+ origin = ", ".join(agent.get("origin", [])) if "origin" in agent else "—"
766
+ icon = agent.get("icon", "")
767
+ card = f"- {icon} **{name}** (origin: {origin})"
768
+
769
+ if status == "active":
770
+ active.append(card)
771
+ elif status == "prototype":
772
+ prototype.append(card)
773
+ elif status == "deprecated":
774
+ deprecated.append(card)
775
+
776
+ def format_group(title, items):
777
+ header = f"### {title}\n"
778
+ content = "\n".join(items) if items else "_No agents found._"
779
+ return header + content
780
+
781
+ return (
782
+ format_group("🟢 Active Agents", active) + "\n\n"
783
+ + format_group("🧪 Prototypes (Hybrids)", prototype) + "\n\n"
784
+ + format_group("🛑 Deprecated Agents", deprecated)
785
+ )
786
+
787
+ except Exception as e:
788
+ return f"Error loading registry: {e}"
789
+ # Spawn Hybrid Agent Button
790
+ spawn_btn = gr.Button("🧬 Spawn Hybrid Agent")
791
+
792
+ def handle_spawn_hybrid():
793
+ a, b = should_spawn_hybrid()
794
+ if a and b:
795
+ name = spawn_hybrid_agent(a, b)
796
+ return f"✅ Spawned new hybrid: {name}\n\n" + list_agents_by_status()
797
+ else:
798
+ return "⚠️ No suitable agent pair found for hybridization.\n\n" + list_agents_by_status()
799
+
800
+ spawn_btn.click(fn=handle_spawn_hybrid, outputs=evolution_display)
801
+ # Refresh Button
802
+ refresh_btn.click(list_agents_by_status, outputs=evolution_display)
803
+ # Retire Button
804
+ retire_btn.click(
805
+ fn=lambda: (deprecate_low_usage_agents(), list_agents_by_status())[1],
806
+ outputs=evolution_display
807
+ )
808
+
809
+ list_agents_by_status()
810
+
811
  # Add Documentation Tab
812
  with gr.Tab("📚 Documentation"):
813
  def load_readme():