swarms 7.9.3__py3-none-any.whl → 7.9.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.
- swarms/agents/reasoning_agents.py +82 -87
- swarms/structs/agent.py +202 -17
- swarms/structs/concurrent_workflow.py +173 -2
- swarms/structs/interactive_groupchat.py +174 -155
- swarms/structs/ma_utils.py +66 -1
- swarms/utils/formatter.py +228 -51
- {swarms-7.9.3.dist-info → swarms-7.9.5.dist-info}/METADATA +1 -1
- {swarms-7.9.3.dist-info → swarms-7.9.5.dist-info}/RECORD +11 -11
- {swarms-7.9.3.dist-info → swarms-7.9.5.dist-info}/LICENSE +0 -0
- {swarms-7.9.3.dist-info → swarms-7.9.5.dist-info}/WHEEL +0 -0
- {swarms-7.9.3.dist-info → swarms-7.9.5.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ from swarms.utils.history_output_formatter import (
|
|
9
9
|
history_output_formatter,
|
10
10
|
)
|
11
11
|
from swarms.utils.loguru_logger import initialize_logger
|
12
|
+
from swarms.utils.formatter import formatter
|
12
13
|
|
13
14
|
logger = initialize_logger(log_folder="concurrent_workflow")
|
14
15
|
|
@@ -32,7 +33,7 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
32
33
|
return_str_on (bool): Flag indicating whether to return the output as a string. Defaults to False.
|
33
34
|
auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents. Defaults to False.
|
34
35
|
return_entire_history (bool): Flag indicating whether to return the entire conversation history. Defaults to False.
|
35
|
-
|
36
|
+
show_dashboard (bool): Flag indicating whether to show a real-time dashboard. Defaults to True.
|
36
37
|
|
37
38
|
Raises:
|
38
39
|
ValueError: If the list of agents is empty or if the description is empty.
|
@@ -46,6 +47,8 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
46
47
|
output_type (str): The type of output format.
|
47
48
|
max_loops (int): The maximum number of loops for each agent.
|
48
49
|
auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents.
|
50
|
+
show_dashboard (bool): Flag indicating whether to show a real-time dashboard.
|
51
|
+
agent_statuses (dict): Dictionary to track agent statuses.
|
49
52
|
"""
|
50
53
|
|
51
54
|
def __init__(
|
@@ -58,6 +61,7 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
58
61
|
output_type: str = "dict-all-except-first",
|
59
62
|
max_loops: int = 1,
|
60
63
|
auto_generate_prompts: bool = False,
|
64
|
+
show_dashboard: bool = True,
|
61
65
|
*args,
|
62
66
|
**kwargs,
|
63
67
|
):
|
@@ -76,10 +80,24 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
76
80
|
self.max_loops = max_loops
|
77
81
|
self.auto_generate_prompts = auto_generate_prompts
|
78
82
|
self.output_type = output_type
|
83
|
+
self.show_dashboard = show_dashboard
|
84
|
+
self.agent_statuses = {
|
85
|
+
agent.agent_name: {"status": "pending", "output": ""}
|
86
|
+
for agent in agents
|
87
|
+
}
|
79
88
|
|
80
89
|
self.reliability_check()
|
81
90
|
self.conversation = Conversation()
|
82
91
|
|
92
|
+
if self.show_dashboard is True:
|
93
|
+
self.agents = self.fix_agents()
|
94
|
+
|
95
|
+
def fix_agents(self):
|
96
|
+
if self.show_dashboard is True:
|
97
|
+
for agent in self.agents:
|
98
|
+
agent.print_on = False
|
99
|
+
return self.agents
|
100
|
+
|
83
101
|
def reliability_check(self):
|
84
102
|
try:
|
85
103
|
if self.agents is None:
|
@@ -115,7 +133,146 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
115
133
|
for agent in self.agents:
|
116
134
|
agent.auto_generate_prompt = True
|
117
135
|
|
118
|
-
def
|
136
|
+
def display_agent_dashboard(
|
137
|
+
self,
|
138
|
+
title: str = "🤖 Agent Dashboard",
|
139
|
+
is_final: bool = False,
|
140
|
+
) -> None:
|
141
|
+
"""
|
142
|
+
Displays the current status of all agents in a beautiful dashboard format.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
title (str): The title of the dashboard.
|
146
|
+
is_final (bool): Flag indicating whether this is the final dashboard.
|
147
|
+
"""
|
148
|
+
agents_data = [
|
149
|
+
{
|
150
|
+
"name": agent.agent_name,
|
151
|
+
"status": self.agent_statuses[agent.agent_name][
|
152
|
+
"status"
|
153
|
+
],
|
154
|
+
"output": self.agent_statuses[agent.agent_name][
|
155
|
+
"output"
|
156
|
+
],
|
157
|
+
}
|
158
|
+
for agent in self.agents
|
159
|
+
]
|
160
|
+
formatter.print_agent_dashboard(agents_data, title, is_final)
|
161
|
+
|
162
|
+
def run_with_dashboard(
|
163
|
+
self,
|
164
|
+
task: str,
|
165
|
+
img: Optional[str] = None,
|
166
|
+
imgs: Optional[List[str]] = None,
|
167
|
+
):
|
168
|
+
"""
|
169
|
+
Executes all agents in the workflow concurrently on the given task.
|
170
|
+
Now includes real-time dashboard updates.
|
171
|
+
"""
|
172
|
+
try:
|
173
|
+
self.conversation.add(role="User", content=task)
|
174
|
+
|
175
|
+
# Reset agent statuses
|
176
|
+
for agent in self.agents:
|
177
|
+
self.agent_statuses[agent.agent_name] = {
|
178
|
+
"status": "pending",
|
179
|
+
"output": "",
|
180
|
+
}
|
181
|
+
|
182
|
+
# Display initial dashboard if enabled
|
183
|
+
if self.show_dashboard:
|
184
|
+
self.display_agent_dashboard()
|
185
|
+
|
186
|
+
# Use 95% of available CPU cores for optimal performance
|
187
|
+
max_workers = int(os.cpu_count() * 0.95)
|
188
|
+
|
189
|
+
# Create a list to store all futures and their results
|
190
|
+
futures = []
|
191
|
+
results = []
|
192
|
+
|
193
|
+
def run_agent_with_status(agent, task, img, imgs):
|
194
|
+
try:
|
195
|
+
# Update status to running
|
196
|
+
self.agent_statuses[agent.agent_name][
|
197
|
+
"status"
|
198
|
+
] = "running"
|
199
|
+
if self.show_dashboard:
|
200
|
+
self.display_agent_dashboard()
|
201
|
+
|
202
|
+
# Run the agent
|
203
|
+
output = agent.run(task=task, img=img, imgs=imgs)
|
204
|
+
|
205
|
+
# Update status to completed
|
206
|
+
self.agent_statuses[agent.agent_name][
|
207
|
+
"status"
|
208
|
+
] = "completed"
|
209
|
+
self.agent_statuses[agent.agent_name][
|
210
|
+
"output"
|
211
|
+
] = output
|
212
|
+
if self.show_dashboard:
|
213
|
+
self.display_agent_dashboard()
|
214
|
+
|
215
|
+
return output
|
216
|
+
except Exception as e:
|
217
|
+
# Update status to error
|
218
|
+
self.agent_statuses[agent.agent_name][
|
219
|
+
"status"
|
220
|
+
] = "error"
|
221
|
+
self.agent_statuses[agent.agent_name][
|
222
|
+
"output"
|
223
|
+
] = f"Error: {str(e)}"
|
224
|
+
if self.show_dashboard:
|
225
|
+
self.display_agent_dashboard()
|
226
|
+
raise
|
227
|
+
|
228
|
+
# Run agents concurrently using ThreadPoolExecutor
|
229
|
+
with concurrent.futures.ThreadPoolExecutor(
|
230
|
+
max_workers=max_workers
|
231
|
+
) as executor:
|
232
|
+
# Submit all agent tasks
|
233
|
+
futures = [
|
234
|
+
executor.submit(
|
235
|
+
run_agent_with_status, agent, task, img, imgs
|
236
|
+
)
|
237
|
+
for agent in self.agents
|
238
|
+
]
|
239
|
+
|
240
|
+
# Wait for all futures to complete
|
241
|
+
concurrent.futures.wait(futures)
|
242
|
+
|
243
|
+
# Process results in order of completion
|
244
|
+
for future, agent in zip(futures, self.agents):
|
245
|
+
try:
|
246
|
+
output = future.result()
|
247
|
+
results.append((agent.agent_name, output))
|
248
|
+
except Exception as e:
|
249
|
+
logger.error(
|
250
|
+
f"Agent {agent.agent_name} failed: {str(e)}"
|
251
|
+
)
|
252
|
+
results.append(
|
253
|
+
(agent.agent_name, f"Error: {str(e)}")
|
254
|
+
)
|
255
|
+
|
256
|
+
# Add all results to conversation
|
257
|
+
for agent_name, output in results:
|
258
|
+
self.conversation.add(role=agent_name, content=output)
|
259
|
+
|
260
|
+
# Display final dashboard if enabled
|
261
|
+
if self.show_dashboard:
|
262
|
+
self.display_agent_dashboard(
|
263
|
+
"🎉 Final Agent Dashboard", is_final=True
|
264
|
+
)
|
265
|
+
|
266
|
+
return history_output_formatter(
|
267
|
+
conversation=self.conversation,
|
268
|
+
type=self.output_type,
|
269
|
+
)
|
270
|
+
finally:
|
271
|
+
# Always clean up the dashboard display
|
272
|
+
if self.show_dashboard:
|
273
|
+
formatter.stop_dashboard()
|
274
|
+
|
275
|
+
def _run(
|
119
276
|
self,
|
120
277
|
task: str,
|
121
278
|
img: Optional[str] = None,
|
@@ -167,6 +324,20 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
167
324
|
type=self.output_type,
|
168
325
|
)
|
169
326
|
|
327
|
+
def run(
|
328
|
+
self,
|
329
|
+
task: str,
|
330
|
+
img: Optional[str] = None,
|
331
|
+
imgs: Optional[List[str]] = None,
|
332
|
+
):
|
333
|
+
"""
|
334
|
+
Executes all agents in the workflow concurrently on the given task.
|
335
|
+
"""
|
336
|
+
if self.show_dashboard:
|
337
|
+
return self.run_with_dashboard(task, img, imgs)
|
338
|
+
else:
|
339
|
+
return self._run(task, img, imgs)
|
340
|
+
|
170
341
|
def batch_run(
|
171
342
|
self,
|
172
343
|
tasks: List[str],
|
@@ -6,6 +6,7 @@ from loguru import logger
|
|
6
6
|
|
7
7
|
from swarms.structs.agent import Agent
|
8
8
|
from swarms.structs.conversation import Conversation
|
9
|
+
from swarms.structs.ma_utils import create_agent_map
|
9
10
|
from swarms.utils.generate_keys import generate_api_key
|
10
11
|
from swarms.utils.history_output_formatter import (
|
11
12
|
history_output_formatter,
|
@@ -24,12 +25,6 @@ class AgentNotFoundError(InteractiveGroupChatError):
|
|
24
25
|
pass
|
25
26
|
|
26
27
|
|
27
|
-
class NoMentionedAgentsError(InteractiveGroupChatError):
|
28
|
-
"""Raised when no agents are mentioned in the task"""
|
29
|
-
|
30
|
-
pass
|
31
|
-
|
32
|
-
|
33
28
|
class InvalidTaskFormatError(InteractiveGroupChatError):
|
34
29
|
"""Raised when the task format is invalid"""
|
35
30
|
|
@@ -294,14 +289,7 @@ class InteractiveGroupChat:
|
|
294
289
|
# Initialize conversation history
|
295
290
|
self.conversation = Conversation(time_enabled=True)
|
296
291
|
|
297
|
-
|
298
|
-
self.agent_map = {}
|
299
|
-
for agent in agents:
|
300
|
-
if isinstance(agent, Agent):
|
301
|
-
self.agent_map[agent.agent_name] = agent
|
302
|
-
elif callable(agent):
|
303
|
-
# For callable functions, use the function name as the agent name
|
304
|
-
self.agent_map[agent.__name__] = agent
|
292
|
+
self.agent_map = create_agent_map(self.agents)
|
305
293
|
|
306
294
|
self._validate_initialization()
|
307
295
|
self._setup_conversation_context()
|
@@ -398,7 +386,7 @@ class InteractiveGroupChat:
|
|
398
386
|
Start an interactive terminal session for chatting with agents.
|
399
387
|
|
400
388
|
This method creates a REPL (Read-Eval-Print Loop) that allows users to:
|
401
|
-
- Chat with agents using @mentions
|
389
|
+
- Chat with agents using @mentions (optional)
|
402
390
|
- See available agents and their descriptions
|
403
391
|
- Exit the session using 'exit' or 'quit'
|
404
392
|
- Get help using 'help' or '?'
|
@@ -426,7 +414,9 @@ class InteractiveGroupChat:
|
|
426
414
|
print("- Type 'help' or '?' for help")
|
427
415
|
print("- Type 'exit' or 'quit' to end the session")
|
428
416
|
print("- Type 'speaker' to change speaker function")
|
429
|
-
print(
|
417
|
+
print(
|
418
|
+
"- Use @agent_name to mention specific agents (optional)"
|
419
|
+
)
|
430
420
|
print("\nStart chatting:")
|
431
421
|
|
432
422
|
while True:
|
@@ -441,9 +431,11 @@ class InteractiveGroupChat:
|
|
441
431
|
|
442
432
|
if user_input.lower() in ["help", "?"]:
|
443
433
|
print("\nHelp:")
|
444
|
-
print("1. Mention agents using @agent_name")
|
445
434
|
print(
|
446
|
-
"
|
435
|
+
"1. You can mention specific agents using @agent_name (optional)"
|
436
|
+
)
|
437
|
+
print(
|
438
|
+
"2. If no agents are mentioned, they will be selected automatically"
|
447
439
|
)
|
448
440
|
print("3. Available agents:")
|
449
441
|
for name in self.agent_map:
|
@@ -513,10 +505,6 @@ class InteractiveGroupChat:
|
|
513
505
|
print("\nChat:")
|
514
506
|
# print(response)
|
515
507
|
|
516
|
-
except NoMentionedAgentsError:
|
517
|
-
print(
|
518
|
-
"\nError: Please mention at least one agent using @agent_name"
|
519
|
-
)
|
520
508
|
except AgentNotFoundError as e:
|
521
509
|
print(f"\nError: {str(e)}")
|
522
510
|
except Exception as e:
|
@@ -699,13 +687,13 @@ Remember: You are part of a team. Your response should reflect that you've read,
|
|
699
687
|
|
700
688
|
def _extract_mentions(self, task: str) -> List[str]:
|
701
689
|
"""
|
702
|
-
Extracts @mentions from the task.
|
690
|
+
Extracts @mentions from the task. If no mentions are found, returns all available agents.
|
703
691
|
|
704
692
|
Args:
|
705
693
|
task (str): The input task
|
706
694
|
|
707
695
|
Returns:
|
708
|
-
List[str]: List of mentioned agent names
|
696
|
+
List[str]: List of mentioned agent names or all agent names if no mentions
|
709
697
|
|
710
698
|
Raises:
|
711
699
|
InvalidtaskFormatError: If the task format is invalid
|
@@ -713,11 +701,17 @@ Remember: You are part of a team. Your response should reflect that you've read,
|
|
713
701
|
try:
|
714
702
|
# Find all @mentions using regex
|
715
703
|
mentions = re.findall(r"@(\w+)", task)
|
716
|
-
|
704
|
+
valid_mentions = [
|
717
705
|
mention
|
718
706
|
for mention in mentions
|
719
707
|
if mention in self.agent_map
|
720
708
|
]
|
709
|
+
|
710
|
+
# If no valid mentions found, return all available agents
|
711
|
+
if not valid_mentions:
|
712
|
+
return list(self.agent_map.keys())
|
713
|
+
|
714
|
+
return valid_mentions
|
721
715
|
except Exception as e:
|
722
716
|
logger.error(f"Error extracting mentions: {e}")
|
723
717
|
raise InvalidTaskFormatError(f"Invalid task format: {e}")
|
@@ -810,6 +804,149 @@ Remember: You are part of a team. Your response should reflect that you've read,
|
|
810
804
|
# Fallback to original order
|
811
805
|
return mentioned_agents
|
812
806
|
|
807
|
+
def _process_dynamic_speakers(
|
808
|
+
self,
|
809
|
+
mentioned_agents: List[str],
|
810
|
+
img: Optional[str],
|
811
|
+
imgs: Optional[List[str]],
|
812
|
+
) -> None:
|
813
|
+
"""
|
814
|
+
Process responses using the dynamic speaker function.
|
815
|
+
"""
|
816
|
+
# Get strategy from speaker state (default to sequential)
|
817
|
+
strategy = self.speaker_state.get("strategy", "sequential")
|
818
|
+
|
819
|
+
# Track which agents have spoken to ensure all get a chance
|
820
|
+
spoken_agents = set()
|
821
|
+
last_response = ""
|
822
|
+
max_iterations = (
|
823
|
+
len(mentioned_agents) * 3
|
824
|
+
) # Allow more iterations for parallel
|
825
|
+
iteration = 0
|
826
|
+
|
827
|
+
while iteration < max_iterations and len(spoken_agents) < len(
|
828
|
+
mentioned_agents
|
829
|
+
):
|
830
|
+
# Determine next speaker(s) using dynamic function
|
831
|
+
next_speakers = self.speaker_function(
|
832
|
+
mentioned_agents,
|
833
|
+
last_response,
|
834
|
+
strategy=strategy,
|
835
|
+
**self.speaker_state,
|
836
|
+
)
|
837
|
+
|
838
|
+
# Handle both single agent and multiple agents
|
839
|
+
if isinstance(next_speakers, str):
|
840
|
+
next_speakers = [next_speakers]
|
841
|
+
|
842
|
+
# Filter out invalid agents
|
843
|
+
valid_next_speakers = [
|
844
|
+
agent
|
845
|
+
for agent in next_speakers
|
846
|
+
if agent in mentioned_agents
|
847
|
+
]
|
848
|
+
|
849
|
+
if not valid_next_speakers:
|
850
|
+
# If no valid mentions found, randomly select from unspoken agents
|
851
|
+
unspoken_agents = [
|
852
|
+
agent
|
853
|
+
for agent in mentioned_agents
|
854
|
+
if agent not in spoken_agents
|
855
|
+
]
|
856
|
+
if unspoken_agents:
|
857
|
+
valid_next_speakers = [
|
858
|
+
random.choice(unspoken_agents)
|
859
|
+
]
|
860
|
+
else:
|
861
|
+
# All agents have spoken, break the loop
|
862
|
+
break
|
863
|
+
|
864
|
+
# Process agents based on strategy
|
865
|
+
if strategy == "sequential":
|
866
|
+
self._process_sequential_speakers(
|
867
|
+
valid_next_speakers, spoken_agents, img, imgs
|
868
|
+
)
|
869
|
+
elif strategy == "parallel":
|
870
|
+
self._process_parallel_speakers(
|
871
|
+
valid_next_speakers, spoken_agents, img, imgs
|
872
|
+
)
|
873
|
+
|
874
|
+
iteration += 1
|
875
|
+
|
876
|
+
def _process_sequential_speakers(
|
877
|
+
self,
|
878
|
+
speakers: List[str],
|
879
|
+
spoken_agents: set,
|
880
|
+
img: Optional[str],
|
881
|
+
imgs: Optional[List[str]],
|
882
|
+
) -> None:
|
883
|
+
"""
|
884
|
+
Process speakers sequentially.
|
885
|
+
"""
|
886
|
+
for next_speaker in speakers:
|
887
|
+
if next_speaker in spoken_agents:
|
888
|
+
continue # Skip if already spoken
|
889
|
+
|
890
|
+
response = self._get_agent_response(
|
891
|
+
next_speaker, img, imgs
|
892
|
+
)
|
893
|
+
if response:
|
894
|
+
spoken_agents.add(next_speaker)
|
895
|
+
break # Only process one agent in sequential mode
|
896
|
+
|
897
|
+
def _process_parallel_speakers(
|
898
|
+
self,
|
899
|
+
speakers: List[str],
|
900
|
+
spoken_agents: set,
|
901
|
+
img: Optional[str],
|
902
|
+
imgs: Optional[List[str]],
|
903
|
+
) -> None:
|
904
|
+
"""
|
905
|
+
Process speakers in parallel.
|
906
|
+
"""
|
907
|
+
import concurrent.futures
|
908
|
+
|
909
|
+
# Get responses from all valid agents
|
910
|
+
responses = []
|
911
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
912
|
+
future_to_agent = {
|
913
|
+
executor.submit(
|
914
|
+
self._get_agent_response, agent, img, imgs
|
915
|
+
): agent
|
916
|
+
for agent in speakers
|
917
|
+
if agent not in spoken_agents
|
918
|
+
}
|
919
|
+
|
920
|
+
for future in concurrent.futures.as_completed(
|
921
|
+
future_to_agent
|
922
|
+
):
|
923
|
+
agent = future_to_agent[future]
|
924
|
+
try:
|
925
|
+
response = future.result()
|
926
|
+
if response:
|
927
|
+
responses.append(response)
|
928
|
+
spoken_agents.add(agent)
|
929
|
+
except Exception as e:
|
930
|
+
logger.error(
|
931
|
+
f"Error getting response from {agent}: {e}"
|
932
|
+
)
|
933
|
+
|
934
|
+
def _process_static_speakers(
|
935
|
+
self,
|
936
|
+
mentioned_agents: List[str],
|
937
|
+
img: Optional[str],
|
938
|
+
imgs: Optional[List[str]],
|
939
|
+
) -> None:
|
940
|
+
"""
|
941
|
+
Process responses using a static speaker function.
|
942
|
+
"""
|
943
|
+
speaking_order = self._get_speaking_order(mentioned_agents)
|
944
|
+
logger.info(f"Speaking order determined: {speaking_order}")
|
945
|
+
|
946
|
+
# Get responses from mentioned agents in the determined order
|
947
|
+
for agent_name in speaking_order:
|
948
|
+
self._get_agent_response(agent_name, img, imgs)
|
949
|
+
|
813
950
|
def run(
|
814
951
|
self,
|
815
952
|
task: str,
|
@@ -817,151 +954,33 @@ Remember: You are part of a team. Your response should reflect that you've read,
|
|
817
954
|
imgs: Optional[List[str]] = None,
|
818
955
|
) -> str:
|
819
956
|
"""
|
820
|
-
Process a task and get responses from
|
821
|
-
|
822
|
-
Otherwise, it can be called directly for single task processing.
|
957
|
+
Process a task and get responses from agents. If no agents are mentioned,
|
958
|
+
randomly selects agents to participate.
|
823
959
|
"""
|
824
960
|
try:
|
825
|
-
# Extract mentioned agents
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
"No valid agents mentioned in the task"
|
831
|
-
)
|
961
|
+
# Extract mentioned agents (or all agents if none mentioned)
|
962
|
+
if "@" in task:
|
963
|
+
mentioned_agents = self._extract_mentions(task)
|
964
|
+
else:
|
965
|
+
mentioned_agents = list(self.agent_map.keys())
|
832
966
|
|
833
967
|
# Add user task to conversation
|
834
968
|
self.conversation.add(role="User", content=task)
|
835
969
|
|
836
|
-
#
|
970
|
+
# Process responses based on speaker function type
|
837
971
|
if self.speaker_function == random_dynamic_speaker:
|
838
|
-
|
839
|
-
|
840
|
-
"strategy", "sequential"
|
972
|
+
self._process_dynamic_speakers(
|
973
|
+
mentioned_agents, img, imgs
|
841
974
|
)
|
842
|
-
|
843
|
-
# For dynamic speaker, we'll determine the next speaker after each response
|
844
|
-
# Track which agents have spoken to ensure all get a chance
|
845
|
-
spoken_agents = set()
|
846
|
-
last_response = ""
|
847
|
-
max_iterations = (
|
848
|
-
len(mentioned_agents) * 3
|
849
|
-
) # Allow more iterations for parallel
|
850
|
-
iteration = 0
|
851
|
-
|
852
|
-
while iteration < max_iterations and len(
|
853
|
-
spoken_agents
|
854
|
-
) < len(mentioned_agents):
|
855
|
-
# Determine next speaker(s) using dynamic function
|
856
|
-
next_speakers = self.speaker_function(
|
857
|
-
mentioned_agents, # Use all mentioned agents, not remaining_agents
|
858
|
-
last_response,
|
859
|
-
strategy=strategy,
|
860
|
-
**self.speaker_state,
|
861
|
-
)
|
862
|
-
|
863
|
-
# Handle both single agent and multiple agents
|
864
|
-
if isinstance(next_speakers, str):
|
865
|
-
next_speakers = [next_speakers]
|
866
|
-
|
867
|
-
# Filter out invalid agents
|
868
|
-
valid_next_speakers = [
|
869
|
-
agent
|
870
|
-
for agent in next_speakers
|
871
|
-
if agent in mentioned_agents
|
872
|
-
]
|
873
|
-
|
874
|
-
if not valid_next_speakers:
|
875
|
-
# If no valid mentions found, randomly select from unspoken agents
|
876
|
-
unspoken_agents = [
|
877
|
-
agent
|
878
|
-
for agent in mentioned_agents
|
879
|
-
if agent not in spoken_agents
|
880
|
-
]
|
881
|
-
if unspoken_agents:
|
882
|
-
valid_next_speakers = [
|
883
|
-
random.choice(unspoken_agents)
|
884
|
-
]
|
885
|
-
else:
|
886
|
-
# All agents have spoken, break the loop
|
887
|
-
break
|
888
|
-
|
889
|
-
# Process agents based on strategy
|
890
|
-
if strategy == "sequential":
|
891
|
-
# Process one agent at a time
|
892
|
-
for next_speaker in valid_next_speakers:
|
893
|
-
if next_speaker in spoken_agents:
|
894
|
-
continue # Skip if already spoken
|
895
|
-
|
896
|
-
response = self._get_agent_response(
|
897
|
-
next_speaker, img, imgs
|
898
|
-
)
|
899
|
-
if response:
|
900
|
-
last_response = response
|
901
|
-
spoken_agents.add(next_speaker)
|
902
|
-
break # Only process one agent in sequential mode
|
903
|
-
|
904
|
-
elif strategy == "parallel":
|
905
|
-
# Process all mentioned agents in parallel
|
906
|
-
import concurrent.futures
|
907
|
-
|
908
|
-
# Get responses from all valid agents
|
909
|
-
responses = []
|
910
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
911
|
-
future_to_agent = {
|
912
|
-
executor.submit(
|
913
|
-
self._get_agent_response,
|
914
|
-
agent,
|
915
|
-
img,
|
916
|
-
imgs,
|
917
|
-
): agent
|
918
|
-
for agent in valid_next_speakers
|
919
|
-
if agent not in spoken_agents
|
920
|
-
}
|
921
|
-
|
922
|
-
for (
|
923
|
-
future
|
924
|
-
) in concurrent.futures.as_completed(
|
925
|
-
future_to_agent
|
926
|
-
):
|
927
|
-
agent = future_to_agent[future]
|
928
|
-
try:
|
929
|
-
response = future.result()
|
930
|
-
if response:
|
931
|
-
responses.append(response)
|
932
|
-
spoken_agents.add(agent)
|
933
|
-
except Exception as e:
|
934
|
-
logger.error(
|
935
|
-
f"Error getting response from {agent}: {e}"
|
936
|
-
)
|
937
|
-
|
938
|
-
# Combine responses for next iteration
|
939
|
-
if responses:
|
940
|
-
last_response = "\n\n".join(responses)
|
941
|
-
|
942
|
-
iteration += 1
|
943
975
|
else:
|
944
|
-
|
945
|
-
|
946
|
-
mentioned_agents
|
976
|
+
self._process_static_speakers(
|
977
|
+
mentioned_agents, img, imgs
|
947
978
|
)
|
948
|
-
logger.info(
|
949
|
-
f"Speaking order determined: {speaking_order}"
|
950
|
-
)
|
951
|
-
|
952
|
-
# Get responses from mentioned agents in the determined order
|
953
|
-
for agent_name in speaking_order:
|
954
|
-
response = self._get_agent_response(
|
955
|
-
agent_name, img, imgs
|
956
|
-
)
|
957
979
|
|
958
980
|
return history_output_formatter(
|
959
981
|
self.conversation, self.output_type
|
960
982
|
)
|
961
983
|
|
962
|
-
except InteractiveGroupChatError as e:
|
963
|
-
logger.error(f"GroupChat error: {e}")
|
964
|
-
raise
|
965
984
|
except Exception as e:
|
966
985
|
logger.error(f"Unexpected error: {e}")
|
967
986
|
raise InteractiveGroupChatError(
|