Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions swarms/structs/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2024,17 +2024,24 @@ async def arun(
Exception: If an error occurs during the asynchronous operation.
"""
try:
# Forward positionally, in run()'s own parameter order. Passing
# task/img as keywords while also splatting *args made every
# extra positional collide with `task`:
# TypeError: run() got multiple values for argument 'task'
# so arun(task, img, extra) raised instead of running.
return await asyncio.to_thread(
self.run,
task=task,
img=img,
task,
img,
*args,
**kwargs,
)
except Exception as error:
await self._handle_run_error(
error
) # Ensure this is also async if needed
# _handle_run_error is sync and re-raises; the other seven call
# sites do not await it. The await was only harmless because the
# method always raises before returning — the day it stops, this
# becomes `await None`.
self._handle_run_error(error)

def __call__(
self,
Expand Down
66 changes: 66 additions & 0 deletions tests/structs/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2864,3 +2864,69 @@ def run_all_tests():
results = run_all_tests()

print(results)


# ============================================================================
# arun forwarding and error path (#1853 item 3)
# ============================================================================


class TestArunForwarding:
"""arun() must forward its arguments to run() and not await a sync method."""

def _bare_agent(self):
# No LLM/model setup — these tests only exercise arun's plumbing.
agent = Agent.__new__(Agent)
agent.autosave = False
agent.agent_name = "arun-test"
agent.to_dict = lambda: {}
return agent

def test_extra_positional_args_reach_run(self):
"""`task=`/`img=` as keywords alongside *args made every extra
positional collide with `task`, so arun(task, img, extra) raised
`TypeError: run() got multiple values for argument 'task'`.
"""
import asyncio

agent = self._bare_agent()
seen = {}

def fake_run(*args, **kwargs):
seen["args"] = args
seen["kwargs"] = kwargs
return "ok"

agent.run = fake_run

result = asyncio.run(Agent.arun(agent, "T", "I", "EXTRA"))

assert result == "ok"
assert seen["args"] == ("T", "I", "EXTRA")

def test_error_path_does_not_await_a_sync_handler(self):
"""`_handle_run_error` is sync and re-raises; seven other call sites
do not await it. The await was harmless only because the method
always raises before returning.
"""
import asyncio
import inspect

assert not inspect.iscoroutinefunction(
Agent._handle_run_error
)
assert (
"await self._handle_run_error"
not in inspect.getsource(Agent.arun)
)

agent = self._bare_agent()

def boom(*args, **kwargs):
raise ValueError("boom")

agent.run = boom

# The original error surfaces — not a TypeError from awaiting None.
with pytest.raises(ValueError, match="boom"):
asyncio.run(Agent.arun(agent, "T"))
Loading