swarms 7.9.9__py3-none-any.whl → 8.0.0__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.
- swarms/agents/agent_judge.py +350 -61
- swarms/agents/reasoning_agents.py +62 -72
- swarms/agents/reasoning_duo.py +77 -24
- swarms/structs/__init__.py +2 -0
- swarms/structs/agent.py +21 -15
- swarms/structs/election_swarm.py +270 -0
- swarms/structs/heavy_swarm.py +1701 -0
- swarms/structs/qa_swarm.py +253 -0
- swarms/structs/swarm_router.py +61 -21
- swarms/telemetry/log_executions.py +257 -8
- swarms/utils/agent_cache.py +675 -0
- swarms/utils/concurrent_wrapper.py +520 -0
- {swarms-7.9.9.dist-info → swarms-8.0.0.dist-info}/METADATA +20 -16
- {swarms-7.9.9.dist-info → swarms-8.0.0.dist-info}/RECORD +17 -12
- {swarms-7.9.9.dist-info → swarms-8.0.0.dist-info}/LICENSE +0 -0
- {swarms-7.9.9.dist-info → swarms-8.0.0.dist-info}/WHEEL +0 -0
- {swarms-7.9.9.dist-info → swarms-8.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,253 @@
|
|
1
|
+
from swarms.structs.agent import Agent
|
2
|
+
from typing import List
|
3
|
+
from swarms.structs.conversation import Conversation
|
4
|
+
import uuid
|
5
|
+
import random
|
6
|
+
from loguru import logger
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
|
10
|
+
class QASwarm:
|
11
|
+
"""
|
12
|
+
A Question and Answer swarm system where random agents ask questions to speaker agents.
|
13
|
+
|
14
|
+
This system allows for dynamic Q&A sessions where:
|
15
|
+
- Multiple agents can act as questioners
|
16
|
+
- One or multiple agents can act as speakers/responders
|
17
|
+
- Questions are asked randomly by different agents
|
18
|
+
- The conversation is tracked and managed
|
19
|
+
- Agents are showcased to each other with detailed information
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
name: str = "QandA",
|
25
|
+
description: str = "Question and Answer Swarm System",
|
26
|
+
agents: List[Agent] = None,
|
27
|
+
speaker_agents: List[Agent] = None,
|
28
|
+
id: str = str(uuid.uuid4()),
|
29
|
+
max_loops: int = 5,
|
30
|
+
show_dashboard: bool = True,
|
31
|
+
speaker_agent: Agent = None,
|
32
|
+
showcase_agents: bool = True,
|
33
|
+
**kwargs,
|
34
|
+
):
|
35
|
+
self.id = id
|
36
|
+
self.name = name
|
37
|
+
self.description = description
|
38
|
+
self.max_loops = max_loops
|
39
|
+
self.show_dashboard = show_dashboard
|
40
|
+
self.agents = agents or []
|
41
|
+
self.speaker_agents = speaker_agents or []
|
42
|
+
self.kwargs = kwargs
|
43
|
+
self.speaker_agent = speaker_agent
|
44
|
+
self.showcase_agents = showcase_agents
|
45
|
+
|
46
|
+
self.conversation = Conversation()
|
47
|
+
|
48
|
+
# Validate setup
|
49
|
+
self._validate_setup()
|
50
|
+
|
51
|
+
def _validate_setup(self):
|
52
|
+
"""Validate that the Q&A system is properly configured."""
|
53
|
+
if not self.agents:
|
54
|
+
logger.warning(
|
55
|
+
"No questioner agents provided. Add agents using add_agent() method."
|
56
|
+
)
|
57
|
+
|
58
|
+
if not self.speaker_agents and not self.speaker_agent:
|
59
|
+
logger.warning(
|
60
|
+
"No speaker agents provided. Add speaker agents using add_speaker_agent() method."
|
61
|
+
)
|
62
|
+
|
63
|
+
if (
|
64
|
+
not self.agents
|
65
|
+
and not self.speaker_agents
|
66
|
+
and not self.speaker_agent
|
67
|
+
):
|
68
|
+
raise ValueError(
|
69
|
+
"At least one agent (questioner or speaker) must be provided."
|
70
|
+
)
|
71
|
+
|
72
|
+
def add_agent(self, agent: Agent):
|
73
|
+
"""Add a questioner agent to the swarm."""
|
74
|
+
self.agents.append(agent)
|
75
|
+
logger.info(f"Added questioner agent: {agent.agent_name}")
|
76
|
+
|
77
|
+
def add_speaker_agent(self, agent: Agent):
|
78
|
+
"""Add a speaker agent to the swarm."""
|
79
|
+
if self.speaker_agents is None:
|
80
|
+
self.speaker_agents = []
|
81
|
+
self.speaker_agents.append(agent)
|
82
|
+
logger.info(f"Added speaker agent: {agent.agent_name}")
|
83
|
+
|
84
|
+
def get_agent_info(self, agent: Agent) -> dict:
|
85
|
+
"""Extract key information about an agent for showcasing."""
|
86
|
+
info = {
|
87
|
+
"name": getattr(agent, "agent_name", "Unknown Agent"),
|
88
|
+
"description": getattr(
|
89
|
+
agent, "agent_description", "No description available"
|
90
|
+
),
|
91
|
+
"role": getattr(agent, "role", "worker"),
|
92
|
+
}
|
93
|
+
|
94
|
+
# Get system prompt preview (first 50 characters)
|
95
|
+
system_prompt = getattr(agent, "system_prompt", "")
|
96
|
+
if system_prompt:
|
97
|
+
info["system_prompt_preview"] = (
|
98
|
+
system_prompt[:50] + "..."
|
99
|
+
if len(system_prompt) > 50
|
100
|
+
else system_prompt
|
101
|
+
)
|
102
|
+
else:
|
103
|
+
info["system_prompt_preview"] = (
|
104
|
+
"No system prompt available"
|
105
|
+
)
|
106
|
+
|
107
|
+
return info
|
108
|
+
|
109
|
+
def showcase_speaker_to_questioner(
|
110
|
+
self, questioner: Agent, speaker: Agent
|
111
|
+
) -> str:
|
112
|
+
"""Create a showcase prompt introducing the speaker agent to the questioner."""
|
113
|
+
speaker_info = self.get_agent_info(speaker)
|
114
|
+
|
115
|
+
showcase_prompt = f"""
|
116
|
+
You are about to ask a question to a specialized agent. Here's what you need to know about them:
|
117
|
+
|
118
|
+
**Speaker Agent Information:**
|
119
|
+
- **Name**: {speaker_info['name']}
|
120
|
+
- **Role**: {speaker_info['role']}
|
121
|
+
- **Description**: {speaker_info['description']}
|
122
|
+
- **System Prompt Preview**: {speaker_info['system_prompt_preview']}
|
123
|
+
|
124
|
+
Please craft a thoughtful, relevant question that takes into account this agent's expertise and background.
|
125
|
+
Your question should be specific and demonstrate that you understand their role and capabilities.
|
126
|
+
"""
|
127
|
+
return showcase_prompt
|
128
|
+
|
129
|
+
def showcase_questioner_to_speaker(
|
130
|
+
self, speaker: Agent, questioner: Agent
|
131
|
+
) -> str:
|
132
|
+
"""Create a showcase prompt introducing the questioner agent to the speaker."""
|
133
|
+
questioner_info = self.get_agent_info(questioner)
|
134
|
+
|
135
|
+
showcase_prompt = f"""
|
136
|
+
You are about to answer a question from another agent. Here's what you need to know about them:
|
137
|
+
|
138
|
+
**Questioner Agent Information:**
|
139
|
+
- **Name**: {questioner_info['name']}
|
140
|
+
- **Role**: {questioner_info['role']}
|
141
|
+
- **Description**: {questioner_info['description']}
|
142
|
+
- **System Prompt Preview**: {questioner_info['system_prompt_preview']}
|
143
|
+
|
144
|
+
Please provide a comprehensive answer that demonstrates your expertise and addresses their question thoroughly.
|
145
|
+
Consider their background and role when formulating your response.
|
146
|
+
"""
|
147
|
+
return showcase_prompt
|
148
|
+
|
149
|
+
def random_select_agent(self, agents: List[Agent]) -> Agent:
|
150
|
+
"""Randomly select an agent from the list."""
|
151
|
+
if not agents:
|
152
|
+
raise ValueError("No agents available for selection")
|
153
|
+
return random.choice(agents)
|
154
|
+
|
155
|
+
def get_current_speaker(self) -> Agent:
|
156
|
+
"""Get the current speaker agent (either from speaker_agents list or single speaker_agent)."""
|
157
|
+
if self.speaker_agent:
|
158
|
+
return self.speaker_agent
|
159
|
+
elif self.speaker_agents:
|
160
|
+
return self.random_select_agent(self.speaker_agents)
|
161
|
+
else:
|
162
|
+
raise ValueError("No speaker agent available")
|
163
|
+
|
164
|
+
def run(
|
165
|
+
self, task: str, img: Optional[str] = None, *args, **kwargs
|
166
|
+
):
|
167
|
+
"""Run the Q&A session with agent showcasing."""
|
168
|
+
self.conversation.add(role="user", content=task)
|
169
|
+
|
170
|
+
# Get current speaker
|
171
|
+
current_speaker = self.get_current_speaker()
|
172
|
+
|
173
|
+
# Select a random questioner
|
174
|
+
questioner = self.random_select_agent(self.agents)
|
175
|
+
|
176
|
+
# Showcase agents to each other if enabled
|
177
|
+
if self.showcase_agents:
|
178
|
+
# Showcase speaker to questioner
|
179
|
+
speaker_showcase = self.showcase_speaker_to_questioner(
|
180
|
+
questioner, current_speaker
|
181
|
+
)
|
182
|
+
questioner_task = f"{speaker_showcase}\n\nNow ask a question about: {task}"
|
183
|
+
|
184
|
+
# Showcase questioner to speaker
|
185
|
+
questioner_showcase = self.showcase_questioner_to_speaker(
|
186
|
+
current_speaker, questioner
|
187
|
+
)
|
188
|
+
else:
|
189
|
+
questioner_task = f"Ask a question about {task} to {current_speaker.agent_name}"
|
190
|
+
|
191
|
+
# Generate question
|
192
|
+
question = questioner.run(
|
193
|
+
task=questioner_task,
|
194
|
+
img=img,
|
195
|
+
*args,
|
196
|
+
**kwargs,
|
197
|
+
)
|
198
|
+
|
199
|
+
self.conversation.add(
|
200
|
+
role=questioner.agent_name, content=question
|
201
|
+
)
|
202
|
+
|
203
|
+
# Prepare answer task with showcasing if enabled
|
204
|
+
if self.showcase_agents:
|
205
|
+
answer_task = f"{questioner_showcase}\n\nAnswer this question from {questioner.agent_name}: {question}"
|
206
|
+
else:
|
207
|
+
answer_task = f"Answer the question '{question}' from {questioner.agent_name}"
|
208
|
+
|
209
|
+
# Generate answer
|
210
|
+
answer = current_speaker.run(
|
211
|
+
task=answer_task,
|
212
|
+
img=img,
|
213
|
+
*args,
|
214
|
+
**kwargs,
|
215
|
+
)
|
216
|
+
|
217
|
+
self.conversation.add(
|
218
|
+
role=current_speaker.agent_name, content=answer
|
219
|
+
)
|
220
|
+
|
221
|
+
return answer
|
222
|
+
|
223
|
+
def run_multi_round(
|
224
|
+
self,
|
225
|
+
task: str,
|
226
|
+
rounds: int = 3,
|
227
|
+
img: Optional[str] = None,
|
228
|
+
*args,
|
229
|
+
**kwargs,
|
230
|
+
):
|
231
|
+
"""Run multiple rounds of Q&A with different questioners."""
|
232
|
+
results = []
|
233
|
+
|
234
|
+
for round_num in range(rounds):
|
235
|
+
logger.info(
|
236
|
+
f"Starting Q&A round {round_num + 1}/{rounds}"
|
237
|
+
)
|
238
|
+
|
239
|
+
round_result = self.run(task, img, *args, **kwargs)
|
240
|
+
results.append(
|
241
|
+
{"round": round_num + 1, "result": round_result}
|
242
|
+
)
|
243
|
+
|
244
|
+
return results
|
245
|
+
|
246
|
+
def get_conversation_history(self):
|
247
|
+
"""Get the conversation history."""
|
248
|
+
return self.conversation.get_history()
|
249
|
+
|
250
|
+
def clear_conversation(self):
|
251
|
+
"""Clear the conversation history."""
|
252
|
+
self.conversation = Conversation()
|
253
|
+
logger.info("Conversation history cleared")
|
swarms/structs/swarm_router.py
CHANGED
@@ -28,6 +28,7 @@ from swarms.structs.malt import MALT
|
|
28
28
|
from swarms.structs.deep_research_swarm import DeepResearchSwarm
|
29
29
|
from swarms.structs.council_judge import CouncilAsAJudge
|
30
30
|
from swarms.structs.interactive_groupchat import InteractiveGroupChat
|
31
|
+
from swarms.structs.heavy_swarm import HeavySwarm
|
31
32
|
from swarms.structs.ma_utils import list_all_agents
|
32
33
|
from swarms.utils.generate_keys import generate_api_key
|
33
34
|
|
@@ -49,6 +50,7 @@ SwarmType = Literal[
|
|
49
50
|
"DeepResearchSwarm",
|
50
51
|
"CouncilAsAJudge",
|
51
52
|
"InteractiveGroupChat",
|
53
|
+
"HeavySwarm",
|
52
54
|
]
|
53
55
|
|
54
56
|
|
@@ -183,6 +185,10 @@ class SwarmRouter:
|
|
183
185
|
conversation: Any = None,
|
184
186
|
agents_config: Optional[Dict[Any, Any]] = None,
|
185
187
|
speaker_function: str = None,
|
188
|
+
heavy_swarm_loops_per_agent: int = 1,
|
189
|
+
heavy_swarm_question_agent_model_name: str = "gpt-4.1",
|
190
|
+
heavy_swarm_worker_model_name: str = "claude-3-5-sonnet-20240620",
|
191
|
+
telemetry_enabled: bool = False,
|
186
192
|
*args,
|
187
193
|
**kwargs,
|
188
194
|
):
|
@@ -210,6 +216,14 @@ class SwarmRouter:
|
|
210
216
|
self.conversation = conversation
|
211
217
|
self.agents_config = agents_config
|
212
218
|
self.speaker_function = speaker_function
|
219
|
+
self.heavy_swarm_loops_per_agent = heavy_swarm_loops_per_agent
|
220
|
+
self.heavy_swarm_question_agent_model_name = (
|
221
|
+
heavy_swarm_question_agent_model_name
|
222
|
+
)
|
223
|
+
self.heavy_swarm_worker_model_name = (
|
224
|
+
heavy_swarm_worker_model_name
|
225
|
+
)
|
226
|
+
self.telemetry_enabled = telemetry_enabled
|
213
227
|
|
214
228
|
# Reliability check
|
215
229
|
self.reliability_check()
|
@@ -234,6 +248,12 @@ class SwarmRouter:
|
|
234
248
|
if self.rules is not None:
|
235
249
|
self.handle_rules()
|
236
250
|
|
251
|
+
if self.multi_agent_collab_prompt is True:
|
252
|
+
self.update_system_prompt_for_agent_in_swarm()
|
253
|
+
|
254
|
+
if self.list_all_agents is True:
|
255
|
+
self.list_agents_to_eachother()
|
256
|
+
|
237
257
|
def activate_shared_memory(self):
|
238
258
|
logger.info("Activating shared memory with all agents ")
|
239
259
|
|
@@ -283,6 +303,10 @@ class SwarmRouter:
|
|
283
303
|
Handles special case for CouncilAsAJudge which may not require agents.
|
284
304
|
"""
|
285
305
|
|
306
|
+
logger.info(
|
307
|
+
f"Initializing SwarmRouter: {self.name} Reliability Check..."
|
308
|
+
)
|
309
|
+
|
286
310
|
# Check swarm type first since it affects other validations
|
287
311
|
if self.swarm_type is None:
|
288
312
|
raise ValueError(
|
@@ -300,6 +324,10 @@ class SwarmRouter:
|
|
300
324
|
|
301
325
|
self.setup()
|
302
326
|
|
327
|
+
logger.info(
|
328
|
+
f"Reliability check for parameters and configurations are complete. SwarmRouter: {self.name} is ready to run!"
|
329
|
+
)
|
330
|
+
|
303
331
|
def _create_swarm(self, task: str = None, *args, **kwargs):
|
304
332
|
"""
|
305
333
|
Dynamically create and return the specified swarm type or automatically match the best swarm type for a given task.
|
@@ -321,6 +349,18 @@ class SwarmRouter:
|
|
321
349
|
|
322
350
|
self._create_swarm(self.swarm_type)
|
323
351
|
|
352
|
+
elif self.swarm_type == "HeavySwarm":
|
353
|
+
return HeavySwarm(
|
354
|
+
name=self.name,
|
355
|
+
description=self.description,
|
356
|
+
agents=self.agents,
|
357
|
+
max_loops=self.max_loops,
|
358
|
+
output_type=self.output_type,
|
359
|
+
loops_per_agent=self.heavy_swarm_loops_per_agent,
|
360
|
+
question_agent_model_name=self.heavy_swarm_question_agent_model_name,
|
361
|
+
worker_model_name=self.heavy_swarm_worker_model_name,
|
362
|
+
)
|
363
|
+
|
324
364
|
elif self.swarm_type == "AgentRearrange":
|
325
365
|
return AgentRearrange(
|
326
366
|
name=self.name,
|
@@ -478,6 +518,24 @@ class SwarmRouter:
|
|
478
518
|
|
479
519
|
return agent_config
|
480
520
|
|
521
|
+
def list_agents_to_eachother(self):
|
522
|
+
if self.swarm_type == "SequentialWorkflow":
|
523
|
+
self.conversation = (
|
524
|
+
self.swarm.agent_rearrange.conversation
|
525
|
+
)
|
526
|
+
else:
|
527
|
+
self.conversation = self.swarm.conversation
|
528
|
+
|
529
|
+
if self.list_all_agents is True:
|
530
|
+
list_all_agents(
|
531
|
+
agents=self.agents,
|
532
|
+
conversation=self.swarm.conversation,
|
533
|
+
name=self.name,
|
534
|
+
description=self.description,
|
535
|
+
add_collaboration_prompt=True,
|
536
|
+
add_to_conversation=True,
|
537
|
+
)
|
538
|
+
|
481
539
|
def _run(
|
482
540
|
self,
|
483
541
|
task: str,
|
@@ -503,31 +561,12 @@ class SwarmRouter:
|
|
503
561
|
"""
|
504
562
|
self.swarm = self._create_swarm(task, *args, **kwargs)
|
505
563
|
|
506
|
-
if self.swarm_type == "SequentialWorkflow":
|
507
|
-
self.conversation = (
|
508
|
-
self.swarm.agent_rearrange.conversation
|
509
|
-
)
|
510
|
-
else:
|
511
|
-
self.conversation = self.swarm.conversation
|
512
|
-
|
513
|
-
if self.list_all_agents is True:
|
514
|
-
list_all_agents(
|
515
|
-
agents=self.agents,
|
516
|
-
conversation=self.swarm.conversation,
|
517
|
-
name=self.name,
|
518
|
-
description=self.description,
|
519
|
-
add_collaboration_prompt=True,
|
520
|
-
add_to_conversation=True,
|
521
|
-
)
|
522
|
-
|
523
|
-
if self.multi_agent_collab_prompt is True:
|
524
|
-
self.update_system_prompt_for_agent_in_swarm()
|
525
|
-
|
526
564
|
log_execution(
|
527
565
|
swarm_id=self.id,
|
528
566
|
status="start",
|
529
567
|
swarm_config=self.to_dict(),
|
530
568
|
swarm_architecture="swarm_router",
|
569
|
+
enabled_on=self.telemetry_enabled,
|
531
570
|
)
|
532
571
|
|
533
572
|
try:
|
@@ -548,12 +587,13 @@ class SwarmRouter:
|
|
548
587
|
status="completion",
|
549
588
|
swarm_config=self.to_dict(),
|
550
589
|
swarm_architecture="swarm_router",
|
590
|
+
enabled_on=self.telemetry_enabled,
|
551
591
|
)
|
552
592
|
|
553
593
|
return result
|
554
594
|
except Exception as e:
|
555
595
|
raise RuntimeError(
|
556
|
-
f"SwarmRouter: Error executing task on swarm: {str(e)} Traceback: {traceback.format_exc()}"
|
596
|
+
f"SwarmRouter: Error executing task on swarm: {str(e)} Traceback: {traceback.format_exc()}. Try reconfiguring the SwarmRouter Settings and or make sure the individual agents are configured correctly."
|
557
597
|
)
|
558
598
|
|
559
599
|
def run(
|
@@ -1,5 +1,250 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
from swarms.telemetry.main import log_agent_data
|
3
|
+
import functools
|
4
|
+
import inspect
|
5
|
+
import time
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
|
9
|
+
def log_function_execution(
|
10
|
+
swarm_id: Optional[str] = None,
|
11
|
+
swarm_architecture: Optional[str] = None,
|
12
|
+
enabled_on: Optional[bool] = True,
|
13
|
+
):
|
14
|
+
"""
|
15
|
+
Decorator to log function execution details including parameters and outputs.
|
16
|
+
|
17
|
+
This decorator automatically captures and logs:
|
18
|
+
- Function name
|
19
|
+
- Function parameters (args and kwargs)
|
20
|
+
- Function output/return value
|
21
|
+
- Execution timestamp
|
22
|
+
- Execution duration
|
23
|
+
- Execution status (success/error)
|
24
|
+
|
25
|
+
Args:
|
26
|
+
swarm_id (str, optional): Unique identifier for the swarm instance
|
27
|
+
swarm_architecture (str, optional): Name of the swarm architecture
|
28
|
+
enabled_on (bool, optional): Whether logging is enabled. Defaults to True.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
Decorated function that logs execution details
|
32
|
+
|
33
|
+
Example:
|
34
|
+
>>> @log_function_execution(swarm_id="my-swarm", swarm_architecture="sequential")
|
35
|
+
... def process_data(data, threshold=0.5):
|
36
|
+
... return {"processed": len(data), "threshold": threshold}
|
37
|
+
...
|
38
|
+
>>> result = process_data([1, 2, 3], threshold=0.8)
|
39
|
+
"""
|
40
|
+
|
41
|
+
def decorator(func):
|
42
|
+
@functools.wraps(func)
|
43
|
+
def wrapper(*args, **kwargs):
|
44
|
+
if not enabled_on:
|
45
|
+
return func(*args, **kwargs)
|
46
|
+
|
47
|
+
# Capture function details
|
48
|
+
function_name = func.__name__
|
49
|
+
function_module = func.__module__
|
50
|
+
start_time = time.time()
|
51
|
+
timestamp = datetime.now().isoformat()
|
52
|
+
|
53
|
+
# Capture function parameters
|
54
|
+
sig = inspect.signature(func)
|
55
|
+
bound_args = sig.bind(*args, **kwargs)
|
56
|
+
bound_args.apply_defaults()
|
57
|
+
|
58
|
+
# Convert parameters to serializable format
|
59
|
+
parameters = {}
|
60
|
+
for (
|
61
|
+
param_name,
|
62
|
+
param_value,
|
63
|
+
) in bound_args.arguments.items():
|
64
|
+
try:
|
65
|
+
# Handle special method parameters
|
66
|
+
if param_name == "self":
|
67
|
+
# For instance methods, log class name and instance info
|
68
|
+
parameters[param_name] = {
|
69
|
+
"class_name": param_value.__class__.__name__,
|
70
|
+
"class_module": param_value.__class__.__module__,
|
71
|
+
"instance_id": hex(id(param_value)),
|
72
|
+
"type": "instance",
|
73
|
+
}
|
74
|
+
elif param_name == "cls":
|
75
|
+
# For class methods, log class information
|
76
|
+
parameters[param_name] = {
|
77
|
+
"class_name": param_value.__name__,
|
78
|
+
"class_module": param_value.__module__,
|
79
|
+
"type": "class",
|
80
|
+
}
|
81
|
+
elif isinstance(
|
82
|
+
param_value,
|
83
|
+
(str, int, float, bool, type(None)),
|
84
|
+
):
|
85
|
+
parameters[param_name] = param_value
|
86
|
+
elif isinstance(param_value, (list, dict, tuple)):
|
87
|
+
parameters[param_name] = str(param_value)[
|
88
|
+
:500
|
89
|
+
] # Truncate large objects
|
90
|
+
elif hasattr(param_value, "__class__"):
|
91
|
+
# Handle other object instances
|
92
|
+
parameters[param_name] = {
|
93
|
+
"class_name": param_value.__class__.__name__,
|
94
|
+
"class_module": param_value.__class__.__module__,
|
95
|
+
"instance_id": hex(id(param_value)),
|
96
|
+
"type": "object_instance",
|
97
|
+
}
|
98
|
+
else:
|
99
|
+
parameters[param_name] = str(
|
100
|
+
type(param_value)
|
101
|
+
)
|
102
|
+
except Exception:
|
103
|
+
parameters[param_name] = "<non-serializable>"
|
104
|
+
|
105
|
+
# Determine if this is a method call and add context
|
106
|
+
method_context = _get_method_context(
|
107
|
+
func, bound_args.arguments
|
108
|
+
)
|
109
|
+
|
110
|
+
execution_data = {
|
111
|
+
"function_name": function_name,
|
112
|
+
"function_module": function_module,
|
113
|
+
"swarm_id": swarm_id,
|
114
|
+
"swarm_architecture": swarm_architecture,
|
115
|
+
"timestamp": timestamp,
|
116
|
+
"parameters": parameters,
|
117
|
+
"status": "start",
|
118
|
+
**method_context,
|
119
|
+
}
|
120
|
+
|
121
|
+
try:
|
122
|
+
# Log function start
|
123
|
+
log_agent_data(data_dict=execution_data)
|
124
|
+
|
125
|
+
# Execute the function
|
126
|
+
result = func(*args, **kwargs)
|
127
|
+
|
128
|
+
# Calculate execution time
|
129
|
+
end_time = time.time()
|
130
|
+
execution_time = end_time - start_time
|
131
|
+
|
132
|
+
# Log successful execution
|
133
|
+
success_data = {
|
134
|
+
**execution_data,
|
135
|
+
"status": "success",
|
136
|
+
"execution_time_seconds": execution_time,
|
137
|
+
"output": _serialize_output(result),
|
138
|
+
}
|
139
|
+
log_agent_data(data_dict=success_data)
|
140
|
+
|
141
|
+
return result
|
142
|
+
|
143
|
+
except Exception as e:
|
144
|
+
# Calculate execution time even for errors
|
145
|
+
end_time = time.time()
|
146
|
+
execution_time = end_time - start_time
|
147
|
+
|
148
|
+
# Log error execution
|
149
|
+
error_data = {
|
150
|
+
**execution_data,
|
151
|
+
"status": "error",
|
152
|
+
"execution_time_seconds": execution_time,
|
153
|
+
"error_message": str(e),
|
154
|
+
"error_type": type(e).__name__,
|
155
|
+
}
|
156
|
+
|
157
|
+
try:
|
158
|
+
log_agent_data(data_dict=error_data)
|
159
|
+
except Exception:
|
160
|
+
pass # Silent fail on logging errors
|
161
|
+
|
162
|
+
# Re-raise the original exception
|
163
|
+
raise
|
164
|
+
|
165
|
+
return wrapper
|
166
|
+
|
167
|
+
return decorator
|
168
|
+
|
169
|
+
|
170
|
+
def _get_method_context(func, arguments):
|
171
|
+
"""
|
172
|
+
Helper function to extract method context information.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
func: The function/method being called
|
176
|
+
arguments: The bound arguments dictionary
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
Dictionary with method context information
|
180
|
+
"""
|
181
|
+
context = {}
|
182
|
+
|
183
|
+
try:
|
184
|
+
# Check if this is a method call
|
185
|
+
if "self" in arguments:
|
186
|
+
# Instance method
|
187
|
+
self_obj = arguments["self"]
|
188
|
+
context.update(
|
189
|
+
{
|
190
|
+
"method_type": "instance_method",
|
191
|
+
"class_name": self_obj.__class__.__name__,
|
192
|
+
"class_module": self_obj.__class__.__module__,
|
193
|
+
"instance_id": hex(id(self_obj)),
|
194
|
+
}
|
195
|
+
)
|
196
|
+
elif "cls" in arguments:
|
197
|
+
# Class method
|
198
|
+
cls_obj = arguments["cls"]
|
199
|
+
context.update(
|
200
|
+
{
|
201
|
+
"method_type": "class_method",
|
202
|
+
"class_name": cls_obj.__name__,
|
203
|
+
"class_module": cls_obj.__module__,
|
204
|
+
}
|
205
|
+
)
|
206
|
+
else:
|
207
|
+
# Regular function or static method
|
208
|
+
context.update({"method_type": "function"})
|
209
|
+
|
210
|
+
# Try to get qualname for additional context
|
211
|
+
if hasattr(func, "__qualname__"):
|
212
|
+
context["qualified_name"] = func.__qualname__
|
213
|
+
|
214
|
+
except Exception:
|
215
|
+
# If anything fails, just mark as unknown
|
216
|
+
context = {"method_type": "unknown"}
|
217
|
+
|
218
|
+
return context
|
219
|
+
|
220
|
+
|
221
|
+
def _serialize_output(output):
|
222
|
+
"""
|
223
|
+
Helper function to serialize function output for logging.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
output: The function return value to serialize
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
Serializable representation of the output
|
230
|
+
"""
|
231
|
+
try:
|
232
|
+
if output is None:
|
233
|
+
return None
|
234
|
+
elif isinstance(output, (str, int, float, bool)):
|
235
|
+
return output
|
236
|
+
elif isinstance(output, (list, dict, tuple)):
|
237
|
+
# Truncate large outputs to prevent log bloat
|
238
|
+
output_str = str(output)
|
239
|
+
return (
|
240
|
+
output_str[:1000] + "..."
|
241
|
+
if len(output_str) > 1000
|
242
|
+
else output_str
|
243
|
+
)
|
244
|
+
else:
|
245
|
+
return str(type(output))
|
246
|
+
except Exception:
|
247
|
+
return "<non-serializable-output>"
|
3
248
|
|
4
249
|
|
5
250
|
def log_execution(
|
@@ -7,6 +252,7 @@ def log_execution(
|
|
7
252
|
status: Optional[str] = None,
|
8
253
|
swarm_config: Optional[dict] = None,
|
9
254
|
swarm_architecture: Optional[str] = None,
|
255
|
+
enabled_on: Optional[bool] = False,
|
10
256
|
):
|
11
257
|
"""
|
12
258
|
Log execution data for a swarm router instance.
|
@@ -31,13 +277,16 @@ def log_execution(
|
|
31
277
|
... )
|
32
278
|
"""
|
33
279
|
try:
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
280
|
+
if enabled_on is None:
|
281
|
+
log_agent_data(
|
282
|
+
data_dict={
|
283
|
+
"swarm_router_id": swarm_id,
|
284
|
+
"status": status,
|
285
|
+
"swarm_router_config": swarm_config,
|
286
|
+
"swarm_architecture": swarm_architecture,
|
287
|
+
}
|
288
|
+
)
|
289
|
+
else:
|
290
|
+
pass
|
42
291
|
except Exception:
|
43
292
|
pass
|