kailash 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kailash/__init__.py +1 -1
- kailash/api/__init__.py +11 -1
- kailash/api/gateway.py +394 -0
- kailash/api/mcp_integration.py +478 -0
- kailash/api/workflow_api.py +29 -13
- kailash/nodes/ai/__init__.py +40 -4
- kailash/nodes/ai/a2a.py +1143 -0
- kailash/nodes/ai/agents.py +120 -6
- kailash/nodes/ai/ai_providers.py +224 -30
- kailash/nodes/ai/embedding_generator.py +34 -38
- kailash/nodes/ai/intelligent_agent_orchestrator.py +2114 -0
- kailash/nodes/ai/llm_agent.py +351 -356
- kailash/nodes/ai/self_organizing.py +1624 -0
- kailash/nodes/api/http.py +106 -25
- kailash/nodes/api/rest.py +116 -21
- kailash/nodes/base.py +60 -64
- kailash/nodes/code/python.py +61 -42
- kailash/nodes/data/__init__.py +10 -10
- kailash/nodes/data/readers.py +117 -66
- kailash/nodes/data/retrieval.py +1 -1
- kailash/nodes/data/sharepoint_graph.py +23 -25
- kailash/nodes/data/sql.py +24 -26
- kailash/nodes/data/writers.py +41 -44
- kailash/nodes/logic/__init__.py +9 -3
- kailash/nodes/logic/async_operations.py +60 -21
- kailash/nodes/logic/operations.py +43 -22
- kailash/nodes/logic/workflow.py +26 -18
- kailash/nodes/mcp/client.py +29 -33
- kailash/nodes/transform/__init__.py +8 -1
- kailash/nodes/transform/formatters.py +1 -1
- kailash/nodes/transform/processors.py +119 -4
- kailash/tracking/metrics_collector.py +6 -7
- kailash/utils/export.py +2 -2
- kailash/utils/templates.py +16 -16
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/METADATA +293 -29
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/RECORD +40 -35
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/WHEEL +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/entry_points.txt +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1624 @@
|
|
1
|
+
"""Self-Organizing Agent Pool nodes for autonomous team formation and problem solving.
|
2
|
+
|
3
|
+
This module implements nodes that enable agents to self-organize into teams,
|
4
|
+
collaborate dynamically, and solve problems without centralized orchestration.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import random
|
9
|
+
import time
|
10
|
+
import uuid
|
11
|
+
from collections import defaultdict, deque
|
12
|
+
from datetime import datetime
|
13
|
+
from enum import Enum
|
14
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
15
|
+
|
16
|
+
from kailash.nodes.ai.a2a import A2AAgentNode, SharedMemoryPoolNode
|
17
|
+
from kailash.nodes.base import Node, NodeParameter, register_node
|
18
|
+
|
19
|
+
|
20
|
+
class TeamFormationStrategy(Enum):
|
21
|
+
"""Strategies for forming agent teams."""
|
22
|
+
|
23
|
+
CAPABILITY_MATCHING = "capability_matching"
|
24
|
+
SWARM_BASED = "swarm_based"
|
25
|
+
MARKET_BASED = "market_based"
|
26
|
+
HIERARCHICAL = "hierarchical"
|
27
|
+
RANDOM = "random"
|
28
|
+
|
29
|
+
|
30
|
+
class AgentStatus(Enum):
|
31
|
+
"""Status of an agent in the pool."""
|
32
|
+
|
33
|
+
AVAILABLE = "available"
|
34
|
+
BUSY = "busy"
|
35
|
+
FORMING_TEAM = "forming_team"
|
36
|
+
INACTIVE = "inactive"
|
37
|
+
|
38
|
+
|
39
|
+
@register_node()
|
40
|
+
class AgentPoolManagerNode(Node):
|
41
|
+
"""
|
42
|
+
Manages a pool of self-organizing agents with capability tracking,
|
43
|
+
performance monitoring, and dynamic availability management.
|
44
|
+
|
45
|
+
This node serves as the registry and coordinator for a dynamic pool of agents,
|
46
|
+
tracking their capabilities, availability, performance metrics, and collaboration
|
47
|
+
patterns. It enables efficient agent discovery, team formation, and performance-based
|
48
|
+
agent selection for complex multi-agent workflows.
|
49
|
+
|
50
|
+
Design Philosophy:
|
51
|
+
The AgentPoolManagerNode embodies decentralized management principles, allowing
|
52
|
+
agents to join and leave dynamically while maintaining global visibility into
|
53
|
+
pool capabilities. It facilitates emergence of specialized teams based on task
|
54
|
+
requirements and historical performance, creating an adaptive workforce that
|
55
|
+
improves over time through tracked metrics and learned collaboration patterns.
|
56
|
+
|
57
|
+
Upstream Dependencies:
|
58
|
+
- OrchestrationManagerNode: Provides agent registration requests
|
59
|
+
- TeamFormationNode: Queries for available agents with specific capabilities
|
60
|
+
- A2ACoordinatorNode: Updates agent status during task execution
|
61
|
+
- Performance monitoring systems: Supply metrics updates
|
62
|
+
|
63
|
+
Downstream Consumers:
|
64
|
+
- TeamFormationNode: Uses agent registry for team composition
|
65
|
+
- ProblemAnalyzerNode: Queries capabilities for feasibility analysis
|
66
|
+
- SolutionEvaluatorNode: Accesses performance history
|
67
|
+
- Reporting systems: Aggregate pool analytics
|
68
|
+
|
69
|
+
Configuration:
|
70
|
+
No static configuration required. The pool adapts dynamically based on
|
71
|
+
registered agents and their evolving performance metrics. Default performance
|
72
|
+
thresholds can be adjusted at runtime.
|
73
|
+
|
74
|
+
Implementation Details:
|
75
|
+
- Maintains in-memory registry with O(1) agent lookup
|
76
|
+
- Indexes agents by capability for fast searching
|
77
|
+
- Tracks performance metrics with exponential moving averages
|
78
|
+
- Records collaboration history for team affinity analysis
|
79
|
+
- Implements status transitions with validation
|
80
|
+
- Supports bulk operations for efficiency
|
81
|
+
- Thread-safe for concurrent access
|
82
|
+
|
83
|
+
Error Handling:
|
84
|
+
- Validates agent IDs to prevent duplicates
|
85
|
+
- Handles missing agents gracefully in queries
|
86
|
+
- Returns empty results rather than errors for searches
|
87
|
+
- Validates status transitions
|
88
|
+
|
89
|
+
Side Effects:
|
90
|
+
- Maintains persistent agent registry across calls
|
91
|
+
- Updates performance metrics incrementally
|
92
|
+
- Records team formation history
|
93
|
+
- May affect agent availability for other tasks
|
94
|
+
|
95
|
+
Examples:
|
96
|
+
>>> # Create agent pool manager
|
97
|
+
>>> pool_manager = AgentPoolManagerNode()
|
98
|
+
>>>
|
99
|
+
>>> # Test basic structure
|
100
|
+
>>> params = pool_manager.get_parameters()
|
101
|
+
>>> assert "action" in params
|
102
|
+
>>> assert "agent_id" in params
|
103
|
+
>>>
|
104
|
+
>>> # Test simple registration
|
105
|
+
>>> result = pool_manager.run(
|
106
|
+
... action="register",
|
107
|
+
... agent_id="test_agent",
|
108
|
+
... capabilities=["analysis"]
|
109
|
+
... )
|
110
|
+
>>> assert result["success"] == True
|
111
|
+
"""
|
112
|
+
|
113
|
+
def __init__(self):
|
114
|
+
super().__init__()
|
115
|
+
self.agent_registry = {}
|
116
|
+
self.availability_tracker = {}
|
117
|
+
self.performance_metrics = defaultdict(
|
118
|
+
lambda: {
|
119
|
+
"tasks_completed": 0,
|
120
|
+
"success_rate": 0.8,
|
121
|
+
"avg_contribution_score": 0.7,
|
122
|
+
"specializations": {},
|
123
|
+
"collaboration_history": [],
|
124
|
+
}
|
125
|
+
)
|
126
|
+
self.capability_index = defaultdict(set)
|
127
|
+
self.team_history = deque(maxlen=100)
|
128
|
+
|
129
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
130
|
+
return {
|
131
|
+
"action": NodeParameter(
|
132
|
+
name="action",
|
133
|
+
type=str,
|
134
|
+
required=False,
|
135
|
+
default="list",
|
136
|
+
description="Action: 'register', 'unregister', 'find_by_capability', 'update_status', 'get_metrics', 'list'",
|
137
|
+
),
|
138
|
+
"agent_id": NodeParameter(
|
139
|
+
name="agent_id", type=str, required=False, description="ID of the agent"
|
140
|
+
),
|
141
|
+
"capabilities": NodeParameter(
|
142
|
+
name="capabilities",
|
143
|
+
type=list,
|
144
|
+
required=False,
|
145
|
+
default=[],
|
146
|
+
description="List of agent capabilities",
|
147
|
+
),
|
148
|
+
"metadata": NodeParameter(
|
149
|
+
name="metadata",
|
150
|
+
type=dict,
|
151
|
+
required=False,
|
152
|
+
default={},
|
153
|
+
description="Additional agent metadata",
|
154
|
+
),
|
155
|
+
"required_capabilities": NodeParameter(
|
156
|
+
name="required_capabilities",
|
157
|
+
type=list,
|
158
|
+
required=False,
|
159
|
+
default=[],
|
160
|
+
description="Capabilities required for search",
|
161
|
+
),
|
162
|
+
"min_performance": NodeParameter(
|
163
|
+
name="min_performance",
|
164
|
+
type=float,
|
165
|
+
required=False,
|
166
|
+
default=0.7,
|
167
|
+
description="Minimum performance score",
|
168
|
+
),
|
169
|
+
"status": NodeParameter(
|
170
|
+
name="status",
|
171
|
+
type=str,
|
172
|
+
required=False,
|
173
|
+
description="New status for agent",
|
174
|
+
),
|
175
|
+
"performance_update": NodeParameter(
|
176
|
+
name="performance_update",
|
177
|
+
type=dict,
|
178
|
+
required=False,
|
179
|
+
description="Performance metrics to update",
|
180
|
+
),
|
181
|
+
}
|
182
|
+
|
183
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
184
|
+
"""Execute pool management action."""
|
185
|
+
action = kwargs.get("action", "list")
|
186
|
+
|
187
|
+
if action == "register":
|
188
|
+
return self._register_agent(kwargs)
|
189
|
+
elif action == "unregister":
|
190
|
+
return self._unregister_agent(kwargs)
|
191
|
+
elif action == "find_by_capability":
|
192
|
+
return self._find_by_capability(kwargs)
|
193
|
+
elif action == "update_status":
|
194
|
+
return self._update_status(kwargs)
|
195
|
+
elif action == "get_metrics":
|
196
|
+
return self._get_metrics(kwargs)
|
197
|
+
elif action == "list":
|
198
|
+
return self._list_agents()
|
199
|
+
else:
|
200
|
+
return {"success": False, "error": f"Unknown action: {action}"}
|
201
|
+
|
202
|
+
def _register_agent(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
203
|
+
"""Register a new agent in the pool."""
|
204
|
+
agent_id = kwargs.get("agent_id")
|
205
|
+
if not agent_id:
|
206
|
+
agent_id = f"agent_{uuid.uuid4().hex[:8]}"
|
207
|
+
|
208
|
+
capabilities = kwargs.get("capabilities", [])
|
209
|
+
metadata = kwargs.get("metadata", {})
|
210
|
+
|
211
|
+
# Register agent
|
212
|
+
self.agent_registry[agent_id] = {
|
213
|
+
"id": agent_id,
|
214
|
+
"capabilities": capabilities,
|
215
|
+
"metadata": metadata,
|
216
|
+
"registered_at": time.time(),
|
217
|
+
"last_active": time.time(),
|
218
|
+
}
|
219
|
+
|
220
|
+
# Update availability
|
221
|
+
self.availability_tracker[agent_id] = AgentStatus.AVAILABLE.value
|
222
|
+
|
223
|
+
# Index capabilities
|
224
|
+
for capability in capabilities:
|
225
|
+
self.capability_index[capability].add(agent_id)
|
226
|
+
|
227
|
+
# Initialize performance metrics if provided
|
228
|
+
if "performance_history" in metadata:
|
229
|
+
self.performance_metrics[agent_id].update(metadata["performance_history"])
|
230
|
+
|
231
|
+
return {
|
232
|
+
"success": True,
|
233
|
+
"agent_id": agent_id,
|
234
|
+
"status": "registered",
|
235
|
+
"capabilities": capabilities,
|
236
|
+
"pool_size": len(self.agent_registry),
|
237
|
+
}
|
238
|
+
|
239
|
+
def _unregister_agent(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
240
|
+
"""Remove an agent from the pool."""
|
241
|
+
agent_id = kwargs.get("agent_id")
|
242
|
+
|
243
|
+
if agent_id not in self.agent_registry:
|
244
|
+
return {"success": False, "error": f"Agent {agent_id} not found"}
|
245
|
+
|
246
|
+
# Remove from indices
|
247
|
+
agent_data = self.agent_registry[agent_id]
|
248
|
+
for capability in agent_data["capabilities"]:
|
249
|
+
self.capability_index[capability].discard(agent_id)
|
250
|
+
|
251
|
+
# Remove from registry
|
252
|
+
del self.agent_registry[agent_id]
|
253
|
+
del self.availability_tracker[agent_id]
|
254
|
+
|
255
|
+
return {
|
256
|
+
"success": True,
|
257
|
+
"agent_id": agent_id,
|
258
|
+
"status": "unregistered",
|
259
|
+
"pool_size": len(self.agent_registry),
|
260
|
+
}
|
261
|
+
|
262
|
+
def _find_by_capability(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
263
|
+
"""Find agents matching required capabilities."""
|
264
|
+
required_capabilities = set(kwargs.get("required_capabilities", []))
|
265
|
+
min_performance = kwargs.get("min_performance", 0.7)
|
266
|
+
|
267
|
+
if not required_capabilities:
|
268
|
+
return {
|
269
|
+
"success": True,
|
270
|
+
"agents": list(self.agent_registry.keys()),
|
271
|
+
"count": len(self.agent_registry),
|
272
|
+
}
|
273
|
+
|
274
|
+
# Find agents with all required capabilities
|
275
|
+
matching_agents = None
|
276
|
+
for capability in required_capabilities:
|
277
|
+
agents_with_capability = self.capability_index.get(capability, set())
|
278
|
+
if matching_agents is None:
|
279
|
+
matching_agents = agents_with_capability.copy()
|
280
|
+
else:
|
281
|
+
matching_agents &= agents_with_capability
|
282
|
+
|
283
|
+
if not matching_agents:
|
284
|
+
return {"success": True, "agents": [], "count": 0}
|
285
|
+
|
286
|
+
# Filter by performance and availability
|
287
|
+
qualified_agents = []
|
288
|
+
for agent_id in matching_agents:
|
289
|
+
if self.availability_tracker.get(agent_id) != AgentStatus.AVAILABLE.value:
|
290
|
+
continue
|
291
|
+
|
292
|
+
performance = self.performance_metrics[agent_id]["success_rate"]
|
293
|
+
if performance >= min_performance:
|
294
|
+
agent_info = self.agent_registry[agent_id].copy()
|
295
|
+
agent_info["performance"] = performance
|
296
|
+
agent_info["status"] = self.availability_tracker[agent_id]
|
297
|
+
qualified_agents.append(agent_info)
|
298
|
+
|
299
|
+
# Sort by performance
|
300
|
+
qualified_agents.sort(key=lambda x: x["performance"], reverse=True)
|
301
|
+
|
302
|
+
return {
|
303
|
+
"success": True,
|
304
|
+
"agents": qualified_agents,
|
305
|
+
"count": len(qualified_agents),
|
306
|
+
"total_pool_size": len(self.agent_registry),
|
307
|
+
}
|
308
|
+
|
309
|
+
def _update_status(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
310
|
+
"""Update agent status."""
|
311
|
+
agent_id = kwargs.get("agent_id")
|
312
|
+
new_status = kwargs.get("status")
|
313
|
+
|
314
|
+
if agent_id not in self.agent_registry:
|
315
|
+
return {"success": False, "error": f"Agent {agent_id} not found"}
|
316
|
+
|
317
|
+
# Validate status
|
318
|
+
valid_statuses = [s.value for s in AgentStatus]
|
319
|
+
if new_status not in valid_statuses:
|
320
|
+
return {"success": False, "error": f"Invalid status: {new_status}"}
|
321
|
+
|
322
|
+
old_status = self.availability_tracker.get(agent_id)
|
323
|
+
self.availability_tracker[agent_id] = new_status
|
324
|
+
self.agent_registry[agent_id]["last_active"] = time.time()
|
325
|
+
|
326
|
+
# Update performance if provided
|
327
|
+
if "performance_update" in kwargs:
|
328
|
+
perf_update = kwargs["performance_update"]
|
329
|
+
metrics = self.performance_metrics[agent_id]
|
330
|
+
|
331
|
+
if "task_completed" in perf_update:
|
332
|
+
metrics["tasks_completed"] += 1
|
333
|
+
success = perf_update.get("success", True)
|
334
|
+
# Update success rate with exponential moving average
|
335
|
+
alpha = 0.2
|
336
|
+
metrics["success_rate"] = (
|
337
|
+
alpha * (1.0 if success else 0.0)
|
338
|
+
+ (1 - alpha) * metrics["success_rate"]
|
339
|
+
)
|
340
|
+
|
341
|
+
if "contribution_score" in perf_update:
|
342
|
+
score = perf_update["contribution_score"]
|
343
|
+
metrics["avg_contribution_score"] = (
|
344
|
+
alpha * score + (1 - alpha) * metrics["avg_contribution_score"]
|
345
|
+
)
|
346
|
+
|
347
|
+
if "specialization" in perf_update:
|
348
|
+
spec = perf_update["specialization"]
|
349
|
+
if spec not in metrics["specializations"]:
|
350
|
+
metrics["specializations"][spec] = 0
|
351
|
+
metrics["specializations"][spec] += 1
|
352
|
+
|
353
|
+
return {
|
354
|
+
"success": True,
|
355
|
+
"agent_id": agent_id,
|
356
|
+
"old_status": old_status,
|
357
|
+
"new_status": new_status,
|
358
|
+
"last_active": self.agent_registry[agent_id]["last_active"],
|
359
|
+
}
|
360
|
+
|
361
|
+
def _get_metrics(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
362
|
+
"""Get performance metrics for an agent or all agents."""
|
363
|
+
agent_id = kwargs.get("agent_id")
|
364
|
+
|
365
|
+
if agent_id:
|
366
|
+
if agent_id not in self.agent_registry:
|
367
|
+
return {"success": False, "error": f"Agent {agent_id} not found"}
|
368
|
+
|
369
|
+
return {
|
370
|
+
"success": True,
|
371
|
+
"agent_id": agent_id,
|
372
|
+
"metrics": self.performance_metrics[agent_id],
|
373
|
+
"registration_info": self.agent_registry[agent_id],
|
374
|
+
}
|
375
|
+
else:
|
376
|
+
# Aggregate metrics
|
377
|
+
total_agents = len(self.agent_registry)
|
378
|
+
available_agents = sum(
|
379
|
+
1
|
380
|
+
for status in self.availability_tracker.values()
|
381
|
+
if status == AgentStatus.AVAILABLE.value
|
382
|
+
)
|
383
|
+
|
384
|
+
avg_success_rate = sum(
|
385
|
+
m["success_rate"] for m in self.performance_metrics.values()
|
386
|
+
) / max(total_agents, 1)
|
387
|
+
|
388
|
+
capability_distribution = {}
|
389
|
+
for capability, agents in self.capability_index.items():
|
390
|
+
capability_distribution[capability] = len(agents)
|
391
|
+
|
392
|
+
return {
|
393
|
+
"success": True,
|
394
|
+
"pool_metrics": {
|
395
|
+
"total_agents": total_agents,
|
396
|
+
"available_agents": available_agents,
|
397
|
+
"avg_success_rate": avg_success_rate,
|
398
|
+
"capability_distribution": capability_distribution,
|
399
|
+
"status_distribution": dict(
|
400
|
+
(
|
401
|
+
status,
|
402
|
+
sum(
|
403
|
+
1
|
404
|
+
for s in self.availability_tracker.values()
|
405
|
+
if s == status
|
406
|
+
),
|
407
|
+
)
|
408
|
+
for status in [s.value for s in AgentStatus]
|
409
|
+
),
|
410
|
+
},
|
411
|
+
}
|
412
|
+
|
413
|
+
def _list_agents(self) -> Dict[str, Any]:
|
414
|
+
"""List all agents in the pool."""
|
415
|
+
agents = []
|
416
|
+
for agent_id, agent_data in self.agent_registry.items():
|
417
|
+
agent_info = agent_data.copy()
|
418
|
+
agent_info["status"] = self.availability_tracker.get(agent_id)
|
419
|
+
agent_info["performance"] = self.performance_metrics[agent_id][
|
420
|
+
"success_rate"
|
421
|
+
]
|
422
|
+
agents.append(agent_info)
|
423
|
+
|
424
|
+
return {"success": True, "agents": agents, "count": len(agents)}
|
425
|
+
|
426
|
+
|
427
|
+
@register_node()
|
428
|
+
class ProblemAnalyzerNode(Node):
|
429
|
+
"""
|
430
|
+
Analyzes problems to determine required capabilities, complexity,
|
431
|
+
and optimal team composition.
|
432
|
+
|
433
|
+
This node performs intelligent problem decomposition and requirement analysis,
|
434
|
+
identifying the specific capabilities needed to solve a problem, estimating its
|
435
|
+
complexity, and suggesting optimal team configurations. It uses pattern matching,
|
436
|
+
keyword analysis, and domain heuristics to create actionable problem specifications.
|
437
|
+
|
438
|
+
Design Philosophy:
|
439
|
+
The ProblemAnalyzerNode acts as the strategic planner in self-organizing systems,
|
440
|
+
translating high-level problem descriptions into concrete capability requirements
|
441
|
+
and team specifications. It embodies the principle that effective problem solving
|
442
|
+
begins with thorough understanding and proper decomposition of the challenge.
|
443
|
+
|
444
|
+
Upstream Dependencies:
|
445
|
+
- User interfaces or APIs providing problem descriptions
|
446
|
+
- OrchestrationManagerNode: Supplies problem context
|
447
|
+
- Domain configuration systems: Provide capability mappings
|
448
|
+
|
449
|
+
Downstream Consumers:
|
450
|
+
- TeamFormationNode: Uses capability requirements for team assembly
|
451
|
+
- AgentPoolManagerNode: Queries based on required capabilities
|
452
|
+
- SolutionEvaluatorNode: References problem analysis for validation
|
453
|
+
- Resource planning systems: Use complexity estimates
|
454
|
+
|
455
|
+
Configuration:
|
456
|
+
The analyzer uses predefined capability patterns that can be extended
|
457
|
+
for domain-specific problems. Analysis depth and decomposition strategies
|
458
|
+
can be configured at runtime.
|
459
|
+
|
460
|
+
Implementation Details:
|
461
|
+
- Pattern-based capability extraction from problem text
|
462
|
+
- Keyword-driven complexity scoring
|
463
|
+
- Multi-factor team size estimation
|
464
|
+
- Hierarchical problem decomposition
|
465
|
+
- Context-aware requirement adjustment
|
466
|
+
- Priority-based capability ranking
|
467
|
+
- Time and resource estimation
|
468
|
+
|
469
|
+
Error Handling:
|
470
|
+
- Handles vague problem descriptions with default analysis
|
471
|
+
- Validates decomposition strategies
|
472
|
+
- Returns minimal requirements for unrecognized problems
|
473
|
+
- Never fails - always provides best-effort analysis
|
474
|
+
|
475
|
+
Side Effects:
|
476
|
+
- No persistent side effects
|
477
|
+
- Pure analysis function
|
478
|
+
- May influence downstream team formation
|
479
|
+
|
480
|
+
Examples:
|
481
|
+
>>> # Test parameter structure without constructor validation
|
482
|
+
>>> analyzer = ProblemAnalyzerNode.__new__(ProblemAnalyzerNode)
|
483
|
+
>>> params = analyzer.get_parameters()
|
484
|
+
>>> assert "problem_description" in params
|
485
|
+
>>> assert "decomposition_strategy" in params
|
486
|
+
"""
|
487
|
+
|
488
|
+
def __init__(self):
|
489
|
+
super().__init__()
|
490
|
+
self.capability_patterns = {
|
491
|
+
"data": ["data_collection", "data_cleaning", "data_validation"],
|
492
|
+
"analysis": [
|
493
|
+
"statistical_analysis",
|
494
|
+
"data_analysis",
|
495
|
+
"pattern_recognition",
|
496
|
+
],
|
497
|
+
"model": ["machine_learning", "predictive_modeling", "model_validation"],
|
498
|
+
"research": ["literature_review", "research", "synthesis"],
|
499
|
+
"visualization": ["data_visualization", "reporting", "presentation"],
|
500
|
+
"domain": ["domain_expertise", "validation", "interpretation"],
|
501
|
+
}
|
502
|
+
|
503
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
504
|
+
return {
|
505
|
+
"problem_description": NodeParameter(
|
506
|
+
name="problem_description",
|
507
|
+
type=str,
|
508
|
+
required=True,
|
509
|
+
description="Description of the problem to solve",
|
510
|
+
),
|
511
|
+
"context": NodeParameter(
|
512
|
+
name="context",
|
513
|
+
type=dict,
|
514
|
+
required=False,
|
515
|
+
default={},
|
516
|
+
description="Additional context about the problem",
|
517
|
+
),
|
518
|
+
"decomposition_strategy": NodeParameter(
|
519
|
+
name="decomposition_strategy",
|
520
|
+
type=str,
|
521
|
+
required=False,
|
522
|
+
default="hierarchical",
|
523
|
+
description="Strategy for decomposing the problem",
|
524
|
+
),
|
525
|
+
"analysis_depth": NodeParameter(
|
526
|
+
name="analysis_depth",
|
527
|
+
type=str,
|
528
|
+
required=False,
|
529
|
+
default="standard",
|
530
|
+
description="Depth of analysis: 'quick', 'standard', 'comprehensive'",
|
531
|
+
),
|
532
|
+
}
|
533
|
+
|
534
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
535
|
+
"""Analyze the problem to determine requirements."""
|
536
|
+
problem_description = kwargs["problem_description"]
|
537
|
+
context = kwargs.get("context", {})
|
538
|
+
strategy = kwargs.get("decomposition_strategy", "hierarchical")
|
539
|
+
depth = kwargs.get("analysis_depth", "standard")
|
540
|
+
|
541
|
+
# Extract key terms and requirements
|
542
|
+
problem_lower = problem_description.lower()
|
543
|
+
required_capabilities = set()
|
544
|
+
|
545
|
+
# Pattern matching for capabilities
|
546
|
+
for pattern, caps in self.capability_patterns.items():
|
547
|
+
if pattern in problem_lower:
|
548
|
+
required_capabilities.update(caps)
|
549
|
+
|
550
|
+
# Add specific capabilities based on keywords
|
551
|
+
keyword_capabilities = {
|
552
|
+
"predict": ["predictive_modeling", "machine_learning"],
|
553
|
+
"forecast": ["time_series_analysis", "predictive_modeling"],
|
554
|
+
"analyze": ["data_analysis", "statistical_analysis"],
|
555
|
+
"visualize": ["data_visualization", "reporting"],
|
556
|
+
"research": ["research", "literature_review"],
|
557
|
+
"optimize": ["optimization", "algorithm_design"],
|
558
|
+
"classify": ["classification", "machine_learning"],
|
559
|
+
"cluster": ["clustering", "unsupervised_learning"],
|
560
|
+
}
|
561
|
+
|
562
|
+
for keyword, caps in keyword_capabilities.items():
|
563
|
+
if keyword in problem_lower:
|
564
|
+
required_capabilities.update(caps)
|
565
|
+
|
566
|
+
# Determine complexity
|
567
|
+
complexity_factors = {
|
568
|
+
"simple_keywords": ["basic", "simple", "straightforward"],
|
569
|
+
"complex_keywords": ["complex", "advanced", "sophisticated", "multi"],
|
570
|
+
"scale_keywords": ["large", "massive", "big data", "scalable"],
|
571
|
+
}
|
572
|
+
|
573
|
+
complexity_score = 0.5 # Base complexity
|
574
|
+
|
575
|
+
for keyword in complexity_factors["simple_keywords"]:
|
576
|
+
if keyword in problem_lower:
|
577
|
+
complexity_score -= 0.1
|
578
|
+
|
579
|
+
for keyword in complexity_factors["complex_keywords"]:
|
580
|
+
if keyword in problem_lower:
|
581
|
+
complexity_score += 0.2
|
582
|
+
|
583
|
+
for keyword in complexity_factors["scale_keywords"]:
|
584
|
+
if keyword in problem_lower:
|
585
|
+
complexity_score += 0.1
|
586
|
+
|
587
|
+
complexity_score = max(0.1, min(1.0, complexity_score))
|
588
|
+
|
589
|
+
# Estimate team size based on complexity and capabilities
|
590
|
+
base_team_size = len(required_capabilities) // 3 + 1
|
591
|
+
complexity_multiplier = 1 + complexity_score
|
592
|
+
estimated_agents = int(base_team_size * complexity_multiplier)
|
593
|
+
|
594
|
+
# Decompose problem
|
595
|
+
if strategy == "hierarchical":
|
596
|
+
decomposition = self._hierarchical_decomposition(
|
597
|
+
problem_description, required_capabilities
|
598
|
+
)
|
599
|
+
else:
|
600
|
+
decomposition = self._simple_decomposition(
|
601
|
+
problem_description, required_capabilities
|
602
|
+
)
|
603
|
+
|
604
|
+
# Determine quality threshold based on context
|
605
|
+
quality_threshold = 0.8 # Default
|
606
|
+
if context.get("urgency") == "high":
|
607
|
+
quality_threshold = 0.7
|
608
|
+
elif context.get("criticality") == "high":
|
609
|
+
quality_threshold = 0.9
|
610
|
+
|
611
|
+
return {
|
612
|
+
"success": True,
|
613
|
+
"analysis": {
|
614
|
+
"problem": problem_description,
|
615
|
+
"required_capabilities": list(required_capabilities),
|
616
|
+
"complexity_score": complexity_score,
|
617
|
+
"estimated_agents": estimated_agents,
|
618
|
+
"quality_threshold": quality_threshold,
|
619
|
+
"time_estimate": self._estimate_time(
|
620
|
+
complexity_score, estimated_agents
|
621
|
+
),
|
622
|
+
"decomposition": decomposition,
|
623
|
+
"priority_capabilities": self._prioritize_capabilities(
|
624
|
+
required_capabilities, problem_description
|
625
|
+
),
|
626
|
+
"context_factors": context,
|
627
|
+
},
|
628
|
+
}
|
629
|
+
|
630
|
+
def _hierarchical_decomposition(
|
631
|
+
self, problem: str, capabilities: Set[str]
|
632
|
+
) -> List[Dict]:
|
633
|
+
"""Decompose problem hierarchically."""
|
634
|
+
# Simple heuristic decomposition
|
635
|
+
phases = []
|
636
|
+
|
637
|
+
# Phase 1: Data/Research
|
638
|
+
if any(cap in capabilities for cap in ["data_collection", "research"]):
|
639
|
+
phases.append(
|
640
|
+
{
|
641
|
+
"phase": "data_gathering",
|
642
|
+
"subtasks": [
|
643
|
+
{"name": "collect_data", "capabilities": ["data_collection"]},
|
644
|
+
{"name": "validate_data", "capabilities": ["data_validation"]},
|
645
|
+
],
|
646
|
+
"priority": 1,
|
647
|
+
}
|
648
|
+
)
|
649
|
+
|
650
|
+
# Phase 2: Analysis
|
651
|
+
if any(
|
652
|
+
cap in capabilities for cap in ["data_analysis", "statistical_analysis"]
|
653
|
+
):
|
654
|
+
phases.append(
|
655
|
+
{
|
656
|
+
"phase": "analysis",
|
657
|
+
"subtasks": [
|
658
|
+
{
|
659
|
+
"name": "exploratory_analysis",
|
660
|
+
"capabilities": ["data_analysis"],
|
661
|
+
},
|
662
|
+
{
|
663
|
+
"name": "statistical_testing",
|
664
|
+
"capabilities": ["statistical_analysis"],
|
665
|
+
},
|
666
|
+
],
|
667
|
+
"priority": 2,
|
668
|
+
}
|
669
|
+
)
|
670
|
+
|
671
|
+
# Phase 3: Modeling
|
672
|
+
if any(
|
673
|
+
cap in capabilities for cap in ["machine_learning", "predictive_modeling"]
|
674
|
+
):
|
675
|
+
phases.append(
|
676
|
+
{
|
677
|
+
"phase": "modeling",
|
678
|
+
"subtasks": [
|
679
|
+
{
|
680
|
+
"name": "model_development",
|
681
|
+
"capabilities": ["machine_learning"],
|
682
|
+
},
|
683
|
+
{
|
684
|
+
"name": "model_validation",
|
685
|
+
"capabilities": ["model_validation"],
|
686
|
+
},
|
687
|
+
],
|
688
|
+
"priority": 3,
|
689
|
+
}
|
690
|
+
)
|
691
|
+
|
692
|
+
# Phase 4: Reporting
|
693
|
+
phases.append(
|
694
|
+
{
|
695
|
+
"phase": "reporting",
|
696
|
+
"subtasks": [
|
697
|
+
{
|
698
|
+
"name": "create_visualizations",
|
699
|
+
"capabilities": ["data_visualization"],
|
700
|
+
},
|
701
|
+
{
|
702
|
+
"name": "write_report",
|
703
|
+
"capabilities": ["reporting", "synthesis"],
|
704
|
+
},
|
705
|
+
],
|
706
|
+
"priority": 4,
|
707
|
+
}
|
708
|
+
)
|
709
|
+
|
710
|
+
return phases
|
711
|
+
|
712
|
+
def _simple_decomposition(self, problem: str, capabilities: Set[str]) -> List[Dict]:
|
713
|
+
"""Simple task decomposition."""
|
714
|
+
tasks = []
|
715
|
+
for i, cap in enumerate(capabilities):
|
716
|
+
tasks.append(
|
717
|
+
{
|
718
|
+
"task_id": f"task_{i+1}",
|
719
|
+
"description": f"Apply {cap} to problem",
|
720
|
+
"required_capability": cap,
|
721
|
+
"estimated_duration": 30, # minutes
|
722
|
+
}
|
723
|
+
)
|
724
|
+
return tasks
|
725
|
+
|
726
|
+
def _estimate_time(self, complexity: float, agents: int) -> int:
|
727
|
+
"""Estimate time in minutes."""
|
728
|
+
base_time = 60 # Base 1 hour
|
729
|
+
complexity_factor = 1 + complexity * 2 # Up to 3x for complex
|
730
|
+
parallelization_factor = 1 / (1 + agents * 0.1) # Diminishing returns
|
731
|
+
|
732
|
+
return int(base_time * complexity_factor * parallelization_factor)
|
733
|
+
|
734
|
+
def _prioritize_capabilities(
|
735
|
+
self, capabilities: Set[str], problem: str
|
736
|
+
) -> List[str]:
|
737
|
+
"""Prioritize capabilities based on problem."""
|
738
|
+
# Simple prioritization based on problem keywords
|
739
|
+
priority_map = {
|
740
|
+
"urgent": ["data_analysis", "reporting"],
|
741
|
+
"predict": ["machine_learning", "predictive_modeling"],
|
742
|
+
"research": ["research", "literature_review"],
|
743
|
+
"optimize": ["optimization", "algorithm_design"],
|
744
|
+
}
|
745
|
+
|
746
|
+
prioritized = []
|
747
|
+
problem_lower = problem.lower()
|
748
|
+
|
749
|
+
for keyword, priority_caps in priority_map.items():
|
750
|
+
if keyword in problem_lower:
|
751
|
+
for cap in priority_caps:
|
752
|
+
if cap in capabilities and cap not in prioritized:
|
753
|
+
prioritized.append(cap)
|
754
|
+
|
755
|
+
# Add remaining capabilities
|
756
|
+
for cap in capabilities:
|
757
|
+
if cap not in prioritized:
|
758
|
+
prioritized.append(cap)
|
759
|
+
|
760
|
+
return prioritized
|
761
|
+
|
762
|
+
|
763
|
+
@register_node()
|
764
|
+
class TeamFormationNode(Node):
|
765
|
+
"""
|
766
|
+
Forms optimal teams based on problem requirements and agent capabilities.
|
767
|
+
|
768
|
+
Supports multiple formation strategies including capability matching,
|
769
|
+
swarm-based organization, market-based auctions, and hierarchical structures.
|
770
|
+
|
771
|
+
Examples:
|
772
|
+
>>> formation_engine = TeamFormationNode()
|
773
|
+
>>>
|
774
|
+
>>> result = formation_engine.run(
|
775
|
+
... problem_analysis={
|
776
|
+
... "required_capabilities": ["data_analysis", "machine_learning"],
|
777
|
+
... "complexity_score": 0.8,
|
778
|
+
... "estimated_agents": 4
|
779
|
+
... },
|
780
|
+
... available_agents=[
|
781
|
+
... {"id": "agent1", "capabilities": ["data_analysis"], "performance": 0.9},
|
782
|
+
... {"id": "agent2", "capabilities": ["machine_learning"], "performance": 0.85}
|
783
|
+
... ],
|
784
|
+
... formation_strategy="capability_matching"
|
785
|
+
... )
|
786
|
+
"""
|
787
|
+
|
788
|
+
def __init__(self):
|
789
|
+
super().__init__()
|
790
|
+
self.formation_history = deque(maxlen=50)
|
791
|
+
self.team_performance_cache = {}
|
792
|
+
|
793
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
794
|
+
return {
|
795
|
+
"problem_analysis": NodeParameter(
|
796
|
+
name="problem_analysis",
|
797
|
+
type=dict,
|
798
|
+
required=False,
|
799
|
+
default={},
|
800
|
+
description="Analysis of the problem from ProblemAnalyzerNode",
|
801
|
+
),
|
802
|
+
"available_agents": NodeParameter(
|
803
|
+
name="available_agents",
|
804
|
+
type=list,
|
805
|
+
required=False,
|
806
|
+
default=[],
|
807
|
+
description="List of available agents with their capabilities",
|
808
|
+
),
|
809
|
+
"formation_strategy": NodeParameter(
|
810
|
+
name="formation_strategy",
|
811
|
+
type=str,
|
812
|
+
required=False,
|
813
|
+
default="capability_matching",
|
814
|
+
description="Team formation strategy",
|
815
|
+
),
|
816
|
+
"constraints": NodeParameter(
|
817
|
+
name="constraints",
|
818
|
+
type=dict,
|
819
|
+
required=False,
|
820
|
+
default={},
|
821
|
+
description="Constraints for team formation",
|
822
|
+
),
|
823
|
+
"optimization_rounds": NodeParameter(
|
824
|
+
name="optimization_rounds",
|
825
|
+
type=int,
|
826
|
+
required=False,
|
827
|
+
default=3,
|
828
|
+
description="Number of optimization iterations",
|
829
|
+
),
|
830
|
+
"diversity_weight": NodeParameter(
|
831
|
+
name="diversity_weight",
|
832
|
+
type=float,
|
833
|
+
required=False,
|
834
|
+
default=0.2,
|
835
|
+
description="Weight for team diversity (0-1)",
|
836
|
+
),
|
837
|
+
}
|
838
|
+
|
839
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
840
|
+
"""Form an optimal team."""
|
841
|
+
problem_analysis = kwargs.get("problem_analysis", {})
|
842
|
+
available_agents = kwargs.get("available_agents", [])
|
843
|
+
strategy = kwargs.get("formation_strategy", "capability_matching")
|
844
|
+
constraints = kwargs.get("constraints", {})
|
845
|
+
optimization_rounds = kwargs.get("optimization_rounds", 3)
|
846
|
+
|
847
|
+
if not available_agents:
|
848
|
+
return {"success": False, "error": "No available agents", "team": []}
|
849
|
+
|
850
|
+
# Form initial team based on strategy
|
851
|
+
if strategy == TeamFormationStrategy.CAPABILITY_MATCHING.value:
|
852
|
+
team = self._capability_matching_formation(
|
853
|
+
problem_analysis, available_agents, constraints
|
854
|
+
)
|
855
|
+
elif strategy == TeamFormationStrategy.SWARM_BASED.value:
|
856
|
+
team = self._swarm_based_formation(
|
857
|
+
problem_analysis, available_agents, constraints
|
858
|
+
)
|
859
|
+
elif strategy == TeamFormationStrategy.MARKET_BASED.value:
|
860
|
+
team = self._market_based_formation(
|
861
|
+
problem_analysis, available_agents, constraints
|
862
|
+
)
|
863
|
+
elif strategy == TeamFormationStrategy.HIERARCHICAL.value:
|
864
|
+
team = self._hierarchical_formation(
|
865
|
+
problem_analysis, available_agents, constraints
|
866
|
+
)
|
867
|
+
else:
|
868
|
+
team = self._random_formation(
|
869
|
+
problem_analysis, available_agents, constraints
|
870
|
+
)
|
871
|
+
|
872
|
+
# Optimize team composition
|
873
|
+
for round in range(optimization_rounds):
|
874
|
+
optimization_result = self._optimize_team(
|
875
|
+
team, problem_analysis, available_agents
|
876
|
+
)
|
877
|
+
if optimization_result["improved"]:
|
878
|
+
team = optimization_result["team"]
|
879
|
+
else:
|
880
|
+
break
|
881
|
+
|
882
|
+
# Calculate team metrics
|
883
|
+
team_metrics = self._calculate_team_metrics(team, problem_analysis)
|
884
|
+
|
885
|
+
# Record formation
|
886
|
+
self.formation_history.append(
|
887
|
+
{
|
888
|
+
"timestamp": time.time(),
|
889
|
+
"problem": problem_analysis.get("problem", "unknown"),
|
890
|
+
"strategy": strategy,
|
891
|
+
"team_size": len(team),
|
892
|
+
"fitness_score": team_metrics["fitness_score"],
|
893
|
+
}
|
894
|
+
)
|
895
|
+
|
896
|
+
return {
|
897
|
+
"success": True,
|
898
|
+
"team": team,
|
899
|
+
"team_metrics": team_metrics,
|
900
|
+
"formation_strategy": strategy,
|
901
|
+
"optimization_rounds_used": round + 1,
|
902
|
+
}
|
903
|
+
|
904
|
+
def _capability_matching_formation(
|
905
|
+
self, problem: Dict, agents: List[Dict], constraints: Dict
|
906
|
+
) -> List[Dict]:
|
907
|
+
"""Form team by matching capabilities to requirements."""
|
908
|
+
required_capabilities = set(problem.get("required_capabilities", []))
|
909
|
+
selected_agents = []
|
910
|
+
covered_capabilities = set()
|
911
|
+
|
912
|
+
# Sort agents by performance and capability coverage
|
913
|
+
agent_scores = []
|
914
|
+
for agent in agents:
|
915
|
+
agent_caps = set(agent.get("capabilities", []))
|
916
|
+
overlap = agent_caps & required_capabilities
|
917
|
+
uncovered = overlap - covered_capabilities
|
918
|
+
|
919
|
+
score = (
|
920
|
+
len(uncovered) * 2 # Prioritize new capabilities
|
921
|
+
+ len(overlap) # Total relevant capabilities
|
922
|
+
+ agent.get("performance", 0.8) * 2 # Performance weight
|
923
|
+
)
|
924
|
+
|
925
|
+
agent_scores.append((score, agent))
|
926
|
+
|
927
|
+
# Sort by score
|
928
|
+
agent_scores.sort(key=lambda x: x[0], reverse=True)
|
929
|
+
|
930
|
+
# Select agents
|
931
|
+
max_team_size = constraints.get("max_team_size", 10)
|
932
|
+
min_team_size = constraints.get("min_team_size", 2)
|
933
|
+
|
934
|
+
for score, agent in agent_scores:
|
935
|
+
if len(selected_agents) >= max_team_size:
|
936
|
+
break
|
937
|
+
|
938
|
+
agent_caps = set(agent.get("capabilities", []))
|
939
|
+
if agent_caps & required_capabilities: # Has relevant capabilities
|
940
|
+
selected_agents.append(agent)
|
941
|
+
covered_capabilities.update(agent_caps)
|
942
|
+
|
943
|
+
# Check if all required capabilities are covered
|
944
|
+
if covered_capabilities >= required_capabilities:
|
945
|
+
if len(selected_agents) >= min_team_size:
|
946
|
+
break
|
947
|
+
|
948
|
+
return selected_agents
|
949
|
+
|
950
|
+
def _swarm_based_formation(
|
951
|
+
self, problem: Dict, agents: List[Dict], constraints: Dict
|
952
|
+
) -> List[Dict]:
|
953
|
+
"""Form team using swarm intelligence principles."""
|
954
|
+
required_capabilities = set(problem.get("required_capabilities", []))
|
955
|
+
complexity = problem.get("complexity_score", 0.5)
|
956
|
+
|
957
|
+
# Calculate attraction scores between agents
|
958
|
+
attraction_matrix = {}
|
959
|
+
for i, agent1 in enumerate(agents):
|
960
|
+
for j, agent2 in enumerate(agents):
|
961
|
+
if i != j:
|
962
|
+
# Attraction based on complementary capabilities
|
963
|
+
caps1 = set(agent1.get("capabilities", []))
|
964
|
+
caps2 = set(agent2.get("capabilities", []))
|
965
|
+
|
966
|
+
complementarity = len((caps1 | caps2) & required_capabilities)
|
967
|
+
overlap = len(caps1 & caps2)
|
968
|
+
|
969
|
+
# High complementarity, low overlap is good
|
970
|
+
attraction = (
|
971
|
+
complementarity / max(len(required_capabilities), 1)
|
972
|
+
- overlap * 0.1
|
973
|
+
)
|
974
|
+
attraction_matrix[(i, j)] = max(0, attraction)
|
975
|
+
|
976
|
+
# Form clusters using attraction
|
977
|
+
clusters = []
|
978
|
+
unassigned = set(range(len(agents)))
|
979
|
+
|
980
|
+
while unassigned and len(clusters) < 5: # Max 5 clusters
|
981
|
+
# Start new cluster with highest performance unassigned agent
|
982
|
+
seed_idx = max(unassigned, key=lambda i: agents[i].get("performance", 0.8))
|
983
|
+
cluster = [seed_idx]
|
984
|
+
unassigned.remove(seed_idx)
|
985
|
+
|
986
|
+
# Grow cluster based on attraction
|
987
|
+
while len(cluster) < len(agents) // 3: # Max cluster size
|
988
|
+
best_candidate = None
|
989
|
+
best_attraction = 0
|
990
|
+
|
991
|
+
for candidate in unassigned:
|
992
|
+
# Average attraction to cluster members
|
993
|
+
avg_attraction = sum(
|
994
|
+
attraction_matrix.get((member, candidate), 0)
|
995
|
+
for member in cluster
|
996
|
+
) / len(cluster)
|
997
|
+
|
998
|
+
if avg_attraction > best_attraction:
|
999
|
+
best_attraction = avg_attraction
|
1000
|
+
best_candidate = candidate
|
1001
|
+
|
1002
|
+
if best_candidate and best_attraction > 0.3:
|
1003
|
+
cluster.append(best_candidate)
|
1004
|
+
unassigned.remove(best_candidate)
|
1005
|
+
else:
|
1006
|
+
break
|
1007
|
+
|
1008
|
+
clusters.append(cluster)
|
1009
|
+
|
1010
|
+
# Select best cluster based on capability coverage
|
1011
|
+
best_cluster = []
|
1012
|
+
best_coverage = 0
|
1013
|
+
|
1014
|
+
for cluster in clusters:
|
1015
|
+
cluster_agents = [agents[i] for i in cluster]
|
1016
|
+
cluster_caps = set()
|
1017
|
+
for agent in cluster_agents:
|
1018
|
+
cluster_caps.update(agent.get("capabilities", []))
|
1019
|
+
|
1020
|
+
coverage = len(cluster_caps & required_capabilities)
|
1021
|
+
if coverage > best_coverage:
|
1022
|
+
best_coverage = coverage
|
1023
|
+
best_cluster = cluster_agents
|
1024
|
+
|
1025
|
+
return best_cluster
|
1026
|
+
|
1027
|
+
def _market_based_formation(
|
1028
|
+
self, problem: Dict, agents: List[Dict], constraints: Dict
|
1029
|
+
) -> List[Dict]:
|
1030
|
+
"""Form team using market-based auction mechanism."""
|
1031
|
+
required_capabilities = problem.get("required_capabilities", [])
|
1032
|
+
budget = constraints.get("budget", 100)
|
1033
|
+
|
1034
|
+
# Agents bid for participation
|
1035
|
+
bids = []
|
1036
|
+
for agent in agents:
|
1037
|
+
agent_caps = set(agent.get("capabilities", []))
|
1038
|
+
relevant_caps = agent_caps & set(required_capabilities)
|
1039
|
+
|
1040
|
+
if relevant_caps:
|
1041
|
+
# Calculate bid based on capability match and performance
|
1042
|
+
capability_value = len(relevant_caps) / max(
|
1043
|
+
len(required_capabilities), 1
|
1044
|
+
)
|
1045
|
+
performance = agent.get("performance", 0.8)
|
1046
|
+
|
1047
|
+
# Lower bid = higher chance of selection (inverse auction)
|
1048
|
+
bid_amount = (2 - capability_value - performance) * 10
|
1049
|
+
|
1050
|
+
bids.append(
|
1051
|
+
{
|
1052
|
+
"agent": agent,
|
1053
|
+
"bid": bid_amount,
|
1054
|
+
"value": capability_value * performance,
|
1055
|
+
}
|
1056
|
+
)
|
1057
|
+
|
1058
|
+
# Sort by value/cost ratio
|
1059
|
+
bids.sort(key=lambda x: x["value"] / x["bid"], reverse=True)
|
1060
|
+
|
1061
|
+
# Select agents within budget
|
1062
|
+
selected_agents = []
|
1063
|
+
total_cost = 0
|
1064
|
+
|
1065
|
+
for bid in bids:
|
1066
|
+
if total_cost + bid["bid"] <= budget:
|
1067
|
+
selected_agents.append(bid["agent"])
|
1068
|
+
total_cost += bid["bid"]
|
1069
|
+
|
1070
|
+
return selected_agents
|
1071
|
+
|
1072
|
+
def _hierarchical_formation(
|
1073
|
+
self, problem: Dict, agents: List[Dict], constraints: Dict
|
1074
|
+
) -> List[Dict]:
|
1075
|
+
"""Form team with hierarchical structure."""
|
1076
|
+
required_capabilities = problem.get("required_capabilities", [])
|
1077
|
+
|
1078
|
+
# Identify potential leaders (high performance, multiple capabilities)
|
1079
|
+
leader_candidates = []
|
1080
|
+
for agent in agents:
|
1081
|
+
caps = agent.get("capabilities", [])
|
1082
|
+
if len(caps) >= 3 and agent.get("performance", 0) > 0.85:
|
1083
|
+
leader_candidates.append(agent)
|
1084
|
+
|
1085
|
+
# Select leader
|
1086
|
+
if leader_candidates:
|
1087
|
+
leader = max(leader_candidates, key=lambda a: a.get("performance", 0))
|
1088
|
+
else:
|
1089
|
+
leader = max(agents, key=lambda a: a.get("performance", 0))
|
1090
|
+
|
1091
|
+
team = [leader]
|
1092
|
+
remaining_agents = [a for a in agents if a != leader]
|
1093
|
+
|
1094
|
+
# Leader selects team members based on complementary skills
|
1095
|
+
leader_caps = set(leader.get("capabilities", []))
|
1096
|
+
needed_caps = set(required_capabilities) - leader_caps
|
1097
|
+
|
1098
|
+
for cap in needed_caps:
|
1099
|
+
# Find best agent for each needed capability
|
1100
|
+
candidates = [
|
1101
|
+
a for a in remaining_agents if cap in a.get("capabilities", [])
|
1102
|
+
]
|
1103
|
+
|
1104
|
+
if candidates:
|
1105
|
+
best = max(candidates, key=lambda a: a.get("performance", 0))
|
1106
|
+
team.append(best)
|
1107
|
+
remaining_agents.remove(best)
|
1108
|
+
|
1109
|
+
return team
|
1110
|
+
|
1111
|
+
def _random_formation(
|
1112
|
+
self, problem: Dict, agents: List[Dict], constraints: Dict
|
1113
|
+
) -> List[Dict]:
|
1114
|
+
"""Random team formation for baseline comparison."""
|
1115
|
+
team_size = min(
|
1116
|
+
problem.get("estimated_agents", 5),
|
1117
|
+
len(agents),
|
1118
|
+
constraints.get("max_team_size", 10),
|
1119
|
+
)
|
1120
|
+
|
1121
|
+
return random.sample(agents, team_size)
|
1122
|
+
|
1123
|
+
def _optimize_team(
|
1124
|
+
self, team: List[Dict], problem: Dict, all_agents: List[Dict]
|
1125
|
+
) -> Dict[str, Any]:
|
1126
|
+
"""Optimize team composition."""
|
1127
|
+
current_fitness = self._calculate_team_fitness(team, problem)
|
1128
|
+
|
1129
|
+
# Try swapping team members
|
1130
|
+
non_team_agents = [a for a in all_agents if a not in team]
|
1131
|
+
|
1132
|
+
if not non_team_agents:
|
1133
|
+
return {"improved": False, "team": team}
|
1134
|
+
|
1135
|
+
best_team = team.copy()
|
1136
|
+
best_fitness = current_fitness
|
1137
|
+
|
1138
|
+
for i, member in enumerate(team):
|
1139
|
+
for candidate in non_team_agents:
|
1140
|
+
# Try swapping
|
1141
|
+
new_team = team.copy()
|
1142
|
+
new_team[i] = candidate
|
1143
|
+
|
1144
|
+
new_fitness = self._calculate_team_fitness(new_team, problem)
|
1145
|
+
|
1146
|
+
if new_fitness > best_fitness:
|
1147
|
+
best_fitness = new_fitness
|
1148
|
+
best_team = new_team.copy()
|
1149
|
+
|
1150
|
+
improved = best_fitness > current_fitness
|
1151
|
+
|
1152
|
+
return {
|
1153
|
+
"improved": improved,
|
1154
|
+
"team": best_team,
|
1155
|
+
"fitness_improvement": best_fitness - current_fitness,
|
1156
|
+
}
|
1157
|
+
|
1158
|
+
def _calculate_team_fitness(self, team: List[Dict], problem: Dict) -> float:
|
1159
|
+
"""Calculate how well a team matches problem requirements."""
|
1160
|
+
required_capabilities = set(problem.get("required_capabilities", []))
|
1161
|
+
|
1162
|
+
# Capability coverage
|
1163
|
+
team_capabilities = set()
|
1164
|
+
for agent in team:
|
1165
|
+
team_capabilities.update(agent.get("capabilities", []))
|
1166
|
+
|
1167
|
+
coverage = len(team_capabilities & required_capabilities) / max(
|
1168
|
+
len(required_capabilities), 1
|
1169
|
+
)
|
1170
|
+
|
1171
|
+
# Average performance
|
1172
|
+
avg_performance = sum(a.get("performance", 0.8) for a in team) / max(
|
1173
|
+
len(team), 1
|
1174
|
+
)
|
1175
|
+
|
1176
|
+
# Team size efficiency
|
1177
|
+
target_size = problem.get("estimated_agents", 5)
|
1178
|
+
size_penalty = abs(len(team) - target_size) / max(target_size, 1) * 0.2
|
1179
|
+
|
1180
|
+
# Diversity bonus
|
1181
|
+
unique_capabilities = len(team_capabilities)
|
1182
|
+
diversity_bonus = min(unique_capabilities / max(len(team) * 3, 1), 0.2)
|
1183
|
+
|
1184
|
+
fitness = (
|
1185
|
+
coverage * 0.5 + avg_performance * 0.3 + diversity_bonus - size_penalty
|
1186
|
+
)
|
1187
|
+
|
1188
|
+
return max(0, min(1, fitness))
|
1189
|
+
|
1190
|
+
def _calculate_team_metrics(
|
1191
|
+
self, team: List[Dict], problem: Dict
|
1192
|
+
) -> Dict[str, Any]:
|
1193
|
+
"""Calculate comprehensive team metrics."""
|
1194
|
+
required_capabilities = set(problem.get("required_capabilities", []))
|
1195
|
+
team_capabilities = set()
|
1196
|
+
|
1197
|
+
for agent in team:
|
1198
|
+
team_capabilities.update(agent.get("capabilities", []))
|
1199
|
+
|
1200
|
+
return {
|
1201
|
+
"team_size": len(team),
|
1202
|
+
"capability_coverage": len(team_capabilities & required_capabilities)
|
1203
|
+
/ max(len(required_capabilities), 1),
|
1204
|
+
"total_capabilities": len(team_capabilities),
|
1205
|
+
"avg_performance": sum(a.get("performance", 0.8) for a in team)
|
1206
|
+
/ max(len(team), 1),
|
1207
|
+
"fitness_score": self._calculate_team_fitness(team, problem),
|
1208
|
+
"missing_capabilities": list(required_capabilities - team_capabilities),
|
1209
|
+
"redundant_capabilities": list(team_capabilities - required_capabilities),
|
1210
|
+
}
|
1211
|
+
|
1212
|
+
|
1213
|
+
@register_node()
|
1214
|
+
class SelfOrganizingAgentNode(A2AAgentNode):
|
1215
|
+
"""
|
1216
|
+
Self-organizing agent that can autonomously join teams, collaborate,
|
1217
|
+
and adapt its behavior based on team dynamics.
|
1218
|
+
|
1219
|
+
Examples:
|
1220
|
+
>>> # Create self-organizing agent
|
1221
|
+
>>> agent = SelfOrganizingAgentNode()
|
1222
|
+
>>>
|
1223
|
+
>>> # Test basic structure
|
1224
|
+
>>> params = agent.get_parameters()
|
1225
|
+
>>> assert "agent_id" in params
|
1226
|
+
>>> assert "capabilities" in params
|
1227
|
+
"""
|
1228
|
+
|
1229
|
+
def __init__(self):
|
1230
|
+
super().__init__()
|
1231
|
+
self.team_memberships = {}
|
1232
|
+
self.collaboration_history = deque(maxlen=50)
|
1233
|
+
self.skill_adaptations = defaultdict(float)
|
1234
|
+
|
1235
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
1236
|
+
params = super().get_parameters()
|
1237
|
+
|
1238
|
+
# Add self-organization specific parameters
|
1239
|
+
params.update(
|
1240
|
+
{
|
1241
|
+
"capabilities": NodeParameter(
|
1242
|
+
name="capabilities",
|
1243
|
+
type=list,
|
1244
|
+
required=False,
|
1245
|
+
default=[],
|
1246
|
+
description="Agent's capabilities",
|
1247
|
+
),
|
1248
|
+
"team_context": NodeParameter(
|
1249
|
+
name="team_context",
|
1250
|
+
type=dict,
|
1251
|
+
required=False,
|
1252
|
+
default={},
|
1253
|
+
description="Current team information",
|
1254
|
+
),
|
1255
|
+
"collaboration_mode": NodeParameter(
|
1256
|
+
name="collaboration_mode",
|
1257
|
+
type=str,
|
1258
|
+
required=False,
|
1259
|
+
default="cooperative",
|
1260
|
+
description="Mode: 'cooperative', 'competitive', 'mixed'",
|
1261
|
+
),
|
1262
|
+
"adaptation_rate": NodeParameter(
|
1263
|
+
name="adaptation_rate",
|
1264
|
+
type=float,
|
1265
|
+
required=False,
|
1266
|
+
default=0.1,
|
1267
|
+
description="How quickly agent adapts behavior (0-1)",
|
1268
|
+
),
|
1269
|
+
"task": NodeParameter(
|
1270
|
+
name="task",
|
1271
|
+
type=str,
|
1272
|
+
required=False,
|
1273
|
+
description="Specific task for the agent",
|
1274
|
+
),
|
1275
|
+
"autonomy_level": NodeParameter(
|
1276
|
+
name="autonomy_level",
|
1277
|
+
type=float,
|
1278
|
+
required=False,
|
1279
|
+
default=0.8,
|
1280
|
+
description="Level of autonomous decision making (0-1)",
|
1281
|
+
),
|
1282
|
+
}
|
1283
|
+
)
|
1284
|
+
|
1285
|
+
return params
|
1286
|
+
|
1287
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
1288
|
+
"""Execute self-organizing agent behavior."""
|
1289
|
+
agent_id = kwargs.get("agent_id")
|
1290
|
+
capabilities = kwargs.get("capabilities", [])
|
1291
|
+
team_context = kwargs.get("team_context", {})
|
1292
|
+
collaboration_mode = kwargs.get("collaboration_mode", "cooperative")
|
1293
|
+
task = kwargs.get("task", "")
|
1294
|
+
|
1295
|
+
# Adapt behavior based on team context
|
1296
|
+
if team_context:
|
1297
|
+
self._adapt_to_team(agent_id, team_context, collaboration_mode)
|
1298
|
+
|
1299
|
+
# Enhance task with self-organization context
|
1300
|
+
if task:
|
1301
|
+
enhanced_task = self._enhance_task_with_context(
|
1302
|
+
task, team_context, capabilities
|
1303
|
+
)
|
1304
|
+
kwargs["messages"] = kwargs.get("messages", [])
|
1305
|
+
kwargs["messages"].append({"role": "user", "content": enhanced_task})
|
1306
|
+
|
1307
|
+
# Add self-organization instructions to system prompt
|
1308
|
+
so_prompt = f"""You are a self-organizing agent with capabilities: {', '.join(capabilities)}.
|
1309
|
+
|
1310
|
+
Current team context: {json.dumps(team_context, indent=2)}
|
1311
|
+
Collaboration mode: {collaboration_mode}
|
1312
|
+
|
1313
|
+
Guidelines:
|
1314
|
+
1. Leverage your specific capabilities to contribute to the team goal
|
1315
|
+
2. Coordinate with other team members when mentioned
|
1316
|
+
3. Adapt your approach based on team dynamics
|
1317
|
+
4. Share insights that others can build upon
|
1318
|
+
5. Be proactive in identifying how you can help
|
1319
|
+
|
1320
|
+
{kwargs.get('system_prompt', '')}"""
|
1321
|
+
|
1322
|
+
kwargs["system_prompt"] = so_prompt
|
1323
|
+
|
1324
|
+
# Execute base A2A agent
|
1325
|
+
result = super().run(**kwargs)
|
1326
|
+
|
1327
|
+
# Track collaboration
|
1328
|
+
if result.get("success"):
|
1329
|
+
self._track_collaboration(agent_id, team_context, task, result)
|
1330
|
+
|
1331
|
+
# Add self-organization metadata
|
1332
|
+
result["self_organization"] = {
|
1333
|
+
"agent_id": agent_id,
|
1334
|
+
"capabilities": capabilities,
|
1335
|
+
"team_memberships": list(self.team_memberships.keys()),
|
1336
|
+
"adaptations": dict(self.skill_adaptations),
|
1337
|
+
"collaboration_mode": collaboration_mode,
|
1338
|
+
"task": task,
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
return result
|
1342
|
+
|
1343
|
+
def _adapt_to_team(self, agent_id: str, team_context: Dict, mode: str):
|
1344
|
+
"""Adapt behavior to team dynamics."""
|
1345
|
+
team_id = team_context.get("team_id")
|
1346
|
+
if not team_id:
|
1347
|
+
return
|
1348
|
+
|
1349
|
+
# Track team membership
|
1350
|
+
if team_id not in self.team_memberships:
|
1351
|
+
self.team_memberships[team_id] = {
|
1352
|
+
"joined_at": time.time(),
|
1353
|
+
"contributions": 0,
|
1354
|
+
"role": "member",
|
1355
|
+
}
|
1356
|
+
|
1357
|
+
# Adapt based on other members' capabilities
|
1358
|
+
other_members = team_context.get("other_members", [])
|
1359
|
+
if other_members:
|
1360
|
+
# In cooperative mode, focus on complementary skills
|
1361
|
+
if mode == "cooperative":
|
1362
|
+
# Increase weight on unique capabilities
|
1363
|
+
for cap in self.skill_adaptations:
|
1364
|
+
self.skill_adaptations[cap] *= 0.9 # Decay
|
1365
|
+
|
1366
|
+
# In competitive mode, enhance overlapping skills
|
1367
|
+
elif mode == "competitive":
|
1368
|
+
for cap in self.skill_adaptations:
|
1369
|
+
self.skill_adaptations[cap] *= 1.1 # Enhance
|
1370
|
+
|
1371
|
+
def _enhance_task_with_context(
|
1372
|
+
self, task: str, team_context: Dict, capabilities: List[str]
|
1373
|
+
) -> str:
|
1374
|
+
"""Enhance task description with team context."""
|
1375
|
+
enhanced = task
|
1376
|
+
|
1377
|
+
if team_context.get("team_goal"):
|
1378
|
+
enhanced = f"Team Goal: {team_context['team_goal']}\n\nYour Task: {task}"
|
1379
|
+
|
1380
|
+
if team_context.get("other_members"):
|
1381
|
+
enhanced += (
|
1382
|
+
f"\n\nOther team members: {', '.join(team_context['other_members'])}"
|
1383
|
+
)
|
1384
|
+
enhanced += "\nCoordinate and build upon their work as needed."
|
1385
|
+
|
1386
|
+
enhanced += (
|
1387
|
+
f"\n\nYour unique capabilities to leverage: {', '.join(capabilities)}"
|
1388
|
+
)
|
1389
|
+
|
1390
|
+
return enhanced
|
1391
|
+
|
1392
|
+
def _track_collaboration(
|
1393
|
+
self, agent_id: str, team_context: Dict, task: str, result: Dict
|
1394
|
+
):
|
1395
|
+
"""Track collaboration history and performance."""
|
1396
|
+
team_id = team_context.get("team_id", "unknown")
|
1397
|
+
|
1398
|
+
collaboration_entry = {
|
1399
|
+
"timestamp": time.time(),
|
1400
|
+
"agent_id": agent_id,
|
1401
|
+
"team_id": team_id,
|
1402
|
+
"task": task,
|
1403
|
+
"success": result.get("success", False),
|
1404
|
+
"insights_generated": result.get("a2a_metadata", {}).get(
|
1405
|
+
"insights_generated", 0
|
1406
|
+
),
|
1407
|
+
}
|
1408
|
+
|
1409
|
+
self.collaboration_history.append(collaboration_entry)
|
1410
|
+
|
1411
|
+
# Update team membership stats
|
1412
|
+
if team_id in self.team_memberships:
|
1413
|
+
self.team_memberships[team_id]["contributions"] += 1
|
1414
|
+
|
1415
|
+
|
1416
|
+
@register_node()
|
1417
|
+
class SolutionEvaluatorNode(Node):
|
1418
|
+
"""
|
1419
|
+
Evaluates solutions produced by agent teams and determines if
|
1420
|
+
iteration is needed.
|
1421
|
+
|
1422
|
+
Examples:
|
1423
|
+
>>> evaluator = SolutionEvaluatorNode()
|
1424
|
+
>>>
|
1425
|
+
>>> result = evaluator.run(
|
1426
|
+
... solution={
|
1427
|
+
... "approach": "Clustering analysis",
|
1428
|
+
... "findings": ["3 distinct customer segments identified"],
|
1429
|
+
... "confidence": 0.85
|
1430
|
+
... },
|
1431
|
+
... problem_requirements={
|
1432
|
+
... "quality_threshold": 0.8,
|
1433
|
+
... "required_outputs": ["segmentation", "recommendations"]
|
1434
|
+
... },
|
1435
|
+
... team_performance={
|
1436
|
+
... "collaboration_score": 0.9,
|
1437
|
+
... "time_taken": 45
|
1438
|
+
... }
|
1439
|
+
... )
|
1440
|
+
"""
|
1441
|
+
|
1442
|
+
def __init__(self):
|
1443
|
+
super().__init__()
|
1444
|
+
self.evaluation_history = deque(maxlen=100)
|
1445
|
+
|
1446
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
1447
|
+
return {
|
1448
|
+
"solution": NodeParameter(
|
1449
|
+
name="solution",
|
1450
|
+
type=dict,
|
1451
|
+
required=False,
|
1452
|
+
default={},
|
1453
|
+
description="Solution to evaluate",
|
1454
|
+
),
|
1455
|
+
"problem_requirements": NodeParameter(
|
1456
|
+
name="problem_requirements",
|
1457
|
+
type=dict,
|
1458
|
+
required=False,
|
1459
|
+
default={},
|
1460
|
+
description="Original problem requirements",
|
1461
|
+
),
|
1462
|
+
"team_performance": NodeParameter(
|
1463
|
+
name="team_performance",
|
1464
|
+
type=dict,
|
1465
|
+
required=False,
|
1466
|
+
default={},
|
1467
|
+
description="Team performance metrics",
|
1468
|
+
),
|
1469
|
+
"evaluation_criteria": NodeParameter(
|
1470
|
+
name="evaluation_criteria",
|
1471
|
+
type=dict,
|
1472
|
+
required=False,
|
1473
|
+
default={},
|
1474
|
+
description="Custom evaluation criteria",
|
1475
|
+
),
|
1476
|
+
"iteration_count": NodeParameter(
|
1477
|
+
name="iteration_count",
|
1478
|
+
type=int,
|
1479
|
+
required=False,
|
1480
|
+
default=0,
|
1481
|
+
description="Current iteration number",
|
1482
|
+
),
|
1483
|
+
}
|
1484
|
+
|
1485
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
1486
|
+
"""Evaluate solution quality."""
|
1487
|
+
solution = kwargs.get("solution", {})
|
1488
|
+
requirements = kwargs.get("problem_requirements", {})
|
1489
|
+
team_performance = kwargs.get("team_performance", {})
|
1490
|
+
criteria = kwargs.get("evaluation_criteria", {})
|
1491
|
+
iteration = kwargs.get("iteration_count", 0)
|
1492
|
+
|
1493
|
+
# Evaluate different aspects
|
1494
|
+
quality_scores = {}
|
1495
|
+
|
1496
|
+
# 1. Completeness
|
1497
|
+
required_outputs = requirements.get("required_outputs", [])
|
1498
|
+
if required_outputs:
|
1499
|
+
outputs_found = sum(
|
1500
|
+
1
|
1501
|
+
for output in required_outputs
|
1502
|
+
if output.lower() in str(solution).lower()
|
1503
|
+
)
|
1504
|
+
quality_scores["completeness"] = outputs_found / len(required_outputs)
|
1505
|
+
else:
|
1506
|
+
quality_scores["completeness"] = 0.8 # Default if not specified
|
1507
|
+
|
1508
|
+
# 2. Confidence/Certainty
|
1509
|
+
confidence = solution.get("confidence", 0.7)
|
1510
|
+
quality_scores["confidence"] = confidence
|
1511
|
+
|
1512
|
+
# 3. Innovation (based on solution complexity/uniqueness)
|
1513
|
+
solution_text = json.dumps(solution)
|
1514
|
+
quality_scores["innovation"] = min(len(set(solution_text.split())) / 100, 1.0)
|
1515
|
+
|
1516
|
+
# 4. Team collaboration
|
1517
|
+
collab_score = team_performance.get("collaboration_score", 0.8)
|
1518
|
+
quality_scores["collaboration"] = collab_score
|
1519
|
+
|
1520
|
+
# 5. Efficiency
|
1521
|
+
time_taken = team_performance.get("time_taken", 60)
|
1522
|
+
time_limit = requirements.get("time_estimate", 60)
|
1523
|
+
efficiency = min(time_limit / max(time_taken, 1), 1.0)
|
1524
|
+
quality_scores["efficiency"] = efficiency
|
1525
|
+
|
1526
|
+
# Calculate overall score
|
1527
|
+
weights = criteria.get(
|
1528
|
+
"weights",
|
1529
|
+
{
|
1530
|
+
"completeness": 0.3,
|
1531
|
+
"confidence": 0.25,
|
1532
|
+
"innovation": 0.15,
|
1533
|
+
"collaboration": 0.15,
|
1534
|
+
"efficiency": 0.15,
|
1535
|
+
},
|
1536
|
+
)
|
1537
|
+
|
1538
|
+
overall_score = sum(
|
1539
|
+
quality_scores.get(aspect, 0) * weight for aspect, weight in weights.items()
|
1540
|
+
)
|
1541
|
+
|
1542
|
+
# Determine if iteration needed
|
1543
|
+
quality_threshold = requirements.get("quality_threshold", 0.8)
|
1544
|
+
needs_iteration = overall_score < quality_threshold and iteration < 3
|
1545
|
+
|
1546
|
+
# Generate feedback for improvement
|
1547
|
+
feedback = self._generate_feedback(quality_scores, requirements, overall_score)
|
1548
|
+
|
1549
|
+
# Record evaluation
|
1550
|
+
self.evaluation_history.append(
|
1551
|
+
{
|
1552
|
+
"timestamp": time.time(),
|
1553
|
+
"overall_score": overall_score,
|
1554
|
+
"quality_scores": quality_scores,
|
1555
|
+
"iteration": iteration,
|
1556
|
+
"needs_iteration": needs_iteration,
|
1557
|
+
}
|
1558
|
+
)
|
1559
|
+
|
1560
|
+
return {
|
1561
|
+
"success": True,
|
1562
|
+
"overall_score": overall_score,
|
1563
|
+
"quality_scores": quality_scores,
|
1564
|
+
"meets_threshold": overall_score >= quality_threshold,
|
1565
|
+
"needs_iteration": needs_iteration,
|
1566
|
+
"feedback": feedback,
|
1567
|
+
"recommended_actions": self._recommend_actions(
|
1568
|
+
quality_scores, feedback, iteration
|
1569
|
+
),
|
1570
|
+
}
|
1571
|
+
|
1572
|
+
def _generate_feedback(
|
1573
|
+
self, scores: Dict[str, float], requirements: Dict, overall: float
|
1574
|
+
) -> Dict[str, Any]:
|
1575
|
+
"""Generate specific feedback for improvement."""
|
1576
|
+
feedback = {"strengths": [], "weaknesses": [], "suggestions": []}
|
1577
|
+
|
1578
|
+
# Identify strengths and weaknesses
|
1579
|
+
for aspect, score in scores.items():
|
1580
|
+
if score >= 0.8:
|
1581
|
+
feedback["strengths"].append(f"Strong {aspect} (score: {score:.2f})")
|
1582
|
+
elif score < 0.6:
|
1583
|
+
feedback["weaknesses"].append(f"Weak {aspect} (score: {score:.2f})")
|
1584
|
+
|
1585
|
+
# Generate suggestions
|
1586
|
+
if scores.get("completeness", 1) < 0.8:
|
1587
|
+
feedback["suggestions"].append("Ensure all required outputs are addressed")
|
1588
|
+
|
1589
|
+
if scores.get("confidence", 1) < 0.7:
|
1590
|
+
feedback["suggestions"].append("Gather more evidence or validate findings")
|
1591
|
+
|
1592
|
+
if scores.get("collaboration", 1) < 0.7:
|
1593
|
+
feedback["suggestions"].append(
|
1594
|
+
"Improve team coordination and information sharing"
|
1595
|
+
)
|
1596
|
+
|
1597
|
+
return feedback
|
1598
|
+
|
1599
|
+
def _recommend_actions(
|
1600
|
+
self, scores: Dict[str, float], feedback: Dict, iteration: int
|
1601
|
+
) -> List[str]:
|
1602
|
+
"""Recommend specific actions for improvement."""
|
1603
|
+
actions = []
|
1604
|
+
|
1605
|
+
# Based on weakest areas
|
1606
|
+
weakest_aspect = min(scores.items(), key=lambda x: x[1])[0]
|
1607
|
+
|
1608
|
+
if weakest_aspect == "completeness":
|
1609
|
+
actions.append("Add specialists for missing output areas")
|
1610
|
+
elif weakest_aspect == "confidence":
|
1611
|
+
actions.append("Add validation or domain expert agents")
|
1612
|
+
elif weakest_aspect == "innovation":
|
1613
|
+
actions.append("Encourage diverse solution approaches")
|
1614
|
+
elif weakest_aspect == "collaboration":
|
1615
|
+
actions.append("Improve communication protocols")
|
1616
|
+
elif weakest_aspect == "efficiency":
|
1617
|
+
actions.append("Parallelize tasks or reduce team size")
|
1618
|
+
|
1619
|
+
# Iteration-specific recommendations
|
1620
|
+
if iteration > 1:
|
1621
|
+
actions.append("Consider alternative team composition")
|
1622
|
+
actions.append("Try different problem decomposition")
|
1623
|
+
|
1624
|
+
return actions
|