Skip to content

Commit f5b9544

Browse files
authored
Merge pull request #1147 from aparekh02/mvstream
[FEAT] [TEST] [DOCS] Streaming Callback for Majority Voting
2 parents 1729112 + e839dc5 commit f5b9544

3 files changed

Lines changed: 153 additions & 17 deletions

File tree

docs/swarms/structs/majorityvoting.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,3 +854,66 @@ for i, result in enumerate(concurrent_results, 1):
854854
else:
855855
print(f"Result: {result}")
856856
```
857+
### Example 4: Majorty Voting with Custom Streaming Features
858+
859+
This example demonstrates streaming callback with a custom streaming function.
860+
861+
```python
862+
from swarms import Agent
863+
from swarms.prompts.finance_agent_sys_prompt import (
864+
FINANCIAL_AGENT_SYS_PROMPT,
865+
)
866+
from swarms.structs.majority_voting import MajorityVoting
867+
from dotenv import load_dotenv
868+
869+
def streaming_callback(agent_name: str, chunk: str, is_final: bool):
870+
# Chunk buffer static per call (reset each session)
871+
if not hasattr(streaming_callback, "_buffer"):
872+
streaming_callback._buffer = ""
873+
streaming_callback._buffer_size = 0
874+
875+
min_chunk_size = 512 # or any large chunk size you want
876+
877+
if chunk:
878+
streaming_callback._buffer += chunk
879+
streaming_callback._buffer_size += len(chunk)
880+
if streaming_callback._buffer_size >= min_chunk_size or is_final:
881+
if streaming_callback._buffer:
882+
print(streaming_callback._buffer, end="", flush=True)
883+
streaming_callback._buffer = ""
884+
streaming_callback._buffer_size = 0
885+
if is_final:
886+
print()
887+
888+
load_dotenv()
889+
890+
891+
# Initialize the agent
892+
agent = Agent(
893+
agent_name="Financial-Analysis-Agent",
894+
agent_description="Personal finance advisor agent",
895+
system_prompt=FINANCIAL_AGENT_SYS_PROMPT,
896+
max_loops=1,
897+
model_name="gpt-4.1",
898+
dynamic_temperature_enabled=True,
899+
user_name="swarms_corp",
900+
retry_attempts=3,
901+
context_length=8192,
902+
return_step_meta=False,
903+
output_type="str", # "json", "dict", "csv" OR "string" "yaml" and
904+
auto_generate_prompt=False, # Auto generate prompt for the agent based on name, description, and system prompt, task
905+
max_tokens=4000, # max output tokens
906+
saved_state_path="agent_00.json",
907+
interactive=False,
908+
streaming_on=True, #if concurrent agents want to be streamed
909+
)
910+
911+
swarm = MajorityVoting(agents=[agent, agent, agent])
912+
913+
swarm.run(
914+
"Create a table of super high growth opportunities for AI. I have $40k to invest in ETFs, index funds, and more. Please create a table in markdown.",
915+
streaming_callback=streaming_callback,
916+
917+
)
918+
919+
```

swarms/structs/majority_voting.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)
1414
from swarms.utils.loguru_logger import initialize_logger
1515
from swarms.utils.output_types import OutputType
16+
from typing import Callable, Optional
1617

1718
logger = initialize_logger(log_folder="majority_voting")
1819

@@ -65,9 +66,16 @@ def default_consensus_agent(
6566
system_prompt: str = None,
6667
description: str = "An agent that uses consensus to generate a final answer.",
6768
model_name: str = "gpt-4.1",
69+
streaming_callback: Optional[Callable[[str], None]] = None,
6870
*args,
6971
**kwargs,
7072
):
73+
# If streaming_on is not None, force it to True; else, set to False
74+
if streaming_callback is not None:
75+
streaming_on_value = True
76+
else:
77+
streaming_on_value = False
78+
7179
return Agent(
7280
agent_name=name,
7381
agent_description=description,
@@ -76,6 +84,7 @@ def default_consensus_agent(
7684
system_prompt=system_prompt,
7785
dynamic_context_window=True,
7886
dynamic_temperature_enabled=True,
87+
streaming_on=streaming_on_value,
7988
*args,
8089
**kwargs,
8190
)
@@ -159,7 +168,7 @@ def reliability_check(self):
159168
title="Majority Voting",
160169
)
161170

162-
def run(self, task: str, *args, **kwargs) -> List[Any]:
171+
def run(self, task: str, streaming_callback: Optional[Callable[[str, str, bool], None]] = None, *args, **kwargs) -> List[Any]:
163172
"""
164173
Runs the majority voting system with multi-loop functionality and returns the majority vote.
165174
@@ -179,6 +188,7 @@ def run(self, task: str, *args, **kwargs) -> List[Any]:
179188
)
180189

181190
for i in range(self.max_loops):
191+
182192
output = run_agents_concurrently(
183193
agents=self.agents,
184194
task=self.conversation.get_str(),
@@ -190,10 +200,31 @@ def run(self, task: str, *args, **kwargs) -> List[Any]:
190200
role=agent.agent_name,
191201
content=output,
192202
)
193-
194-
# Now run the consensus agent
203+
204+
# Set streaming_on for the consensus agent based on the provided streaming_callback
205+
self.consensus_agent.streaming_on = streaming_callback is not None
206+
207+
# Instead of a simple passthrough wrapper, match the callback invocation pattern from the provided reference for the consensus agent:
208+
consensus_agent_name = self.consensus_agent.agent_name
209+
210+
if streaming_callback is not None:
211+
def consensus_streaming_callback(chunk: str):
212+
"""Wrapper for consensus agent streaming callback."""
213+
try:
214+
if chunk is not None and chunk.strip():
215+
streaming_callback(consensus_agent_name, chunk, False)
216+
except Exception as callback_error:
217+
if self.verbose:
218+
logger.warning(
219+
f"[STREAMING] Callback failed for {consensus_agent_name}: {str(callback_error)}"
220+
)
221+
else:
222+
consensus_streaming_callback = None
223+
224+
# Run the consensus agent with the streaming callback, if any
195225
consensus_output = self.consensus_agent.run(
196226
task=(f"History: {self.conversation.get_str()}"),
227+
streaming_callback=consensus_streaming_callback,
197228
)
198229

199230
self.conversation.add(

tests/structs/test_majority_voting.py

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -199,21 +199,63 @@ def test_majority_voting_different_output_types():
199199
max_loops=1,
200200
)
201201

202-
# Test different output types
203-
for output_type in ["dict", "string", "list"]:
204-
mv = MajorityVoting(
205-
name=f"Output-Type-Test-{output_type}",
206-
description=f"Testing output type: {output_type}",
207-
agents=[
208-
security_expert,
209-
compliance_officer,
210-
privacy_advocate,
211-
],
202+
# Assert majority vote is correct
203+
assert majority_vote is not None
204+
205+
def test_streaming_majority_voting():
206+
"""
207+
Test the streaming_majority_voting with logging/try-except and assertion.
208+
"""
209+
logs = []
210+
def streaming_callback(agent_name: str, chunk: str, is_final: bool):
211+
# Chunk buffer static per call (reset each session)
212+
if not hasattr(streaming_callback, "_buffer"):
213+
streaming_callback._buffer = ""
214+
streaming_callback._buffer_size = 0
215+
216+
min_chunk_size = 512 # or any large chunk size you want
217+
218+
if chunk:
219+
streaming_callback._buffer += chunk
220+
streaming_callback._buffer_size += len(chunk)
221+
if streaming_callback._buffer_size >= min_chunk_size or is_final:
222+
if streaming_callback._buffer:
223+
print(streaming_callback._buffer, end="", flush=True)
224+
logs.append(streaming_callback._buffer)
225+
streaming_callback._buffer = ""
226+
streaming_callback._buffer_size = 0
227+
if is_final:
228+
print()
229+
230+
try:
231+
# Initialize the agent
232+
agent = Agent(
233+
agent_name="Financial-Analysis-Agent",
234+
agent_description="Personal finance advisor agent",
235+
system_prompt="You are a financial analysis agent.", # replaced missing const
212236
max_loops=1,
213-
output_type=output_type,
237+
model_name="gpt-4.1",
238+
dynamic_temperature_enabled=True,
239+
user_name="swarms_corp",
240+
retry_attempts=3,
241+
context_length=8192,
242+
return_step_meta=False,
243+
output_type="str", # "json", "dict", "csv" OR "string" "yaml" and
244+
auto_generate_prompt=False, # Auto generate prompt for the agent based on name, description, and system prompt, task
245+
max_tokens=4000, # max output tokens
246+
saved_state_path="agent_00.json",
247+
interactive=False,
248+
streaming_on=True, #if concurrent agents want to be streamed
214249
)
215-
216-
result = mv.run(
217-
"What are the key considerations for implementing GDPR compliance in our data processing systems?"
250+
251+
swarm = MajorityVoting(agents=[agent, agent, agent])
252+
253+
result = swarm.run(
254+
"Create a table of super high growth opportunities for AI. I have $40k to invest in ETFs, index funds, and more. Please create a table in markdown.",
255+
streaming_callback=streaming_callback,
218256
)
219257
assert result is not None
258+
except Exception as e:
259+
print("Error in test_streaming_majority_voting:", e)
260+
print("Logs so far:", logs)
261+
raise

0 commit comments

Comments
 (0)