[BUG][AuctionSwarm/_extract_bid][Six copies of the tool-call unwrap accept different provider shapes, so every bid silently ties]
Summary
On a provider that returns tool-call objects rather than dicts, AuctionSwarm gives every agent the no-confidence sentinel (0.0, 1.0). All bids tie, the winner is whichever agent the tie-break happens to land on, and the auction silently stops auctioning. Nothing raises and nothing is logged.
The cause is that unwrapping a forced tool call into its arguments dict is hand-rolled in six places, and no two copies accept the same set of inputs. GroupChat handles object-shaped tool calls; AuctionSwarm, ten files away and parsing the identical structure, does not.
Reproduction
from pydantic import BaseModel
from swarms.structs.groupchat import _extract_args
from swarms.structs.auction_swarm import _extract_bid
class Fn(BaseModel):
name: str
arguments: str
class ToolCall(BaseModel):
function: Fn
gc = [ToolCall(function=Fn(name="respond", arguments='{"score": 0.9, "message": "hi"}'))]
au = [ToolCall(function=Fn(name="bid", arguments='{"confidence": 0.9, "estimated_cost": 5.0}'))]
print(_extract_args(gc)) # -> (0.9, 'hi') correct
print(_extract_bid(au)) # -> (0.0, 1.0) silently droppedpydantic tool call:
groupchat._extract_args -> (0.9, 'hi') (expected (0.9, 'hi'))
auction._extract_bid -> (0.0, 1.0) (expected (0.9, 5.0))
plain dict tool call:
groupchat._extract_args -> (0.9, 'hi')
auction._extract_bid -> (0.9, 5.0)Same input shape, two sibling parsers, one works.
The shape matrix
Six copies of the same "string → literal_eval, list → first element, pydantic → model_dump(), .get("function"), .get("arguments"), json.loads if str" chain, each supporting a different subset:
| site | pydantic object | plain dict | str(list) repr |
|---|---|---|---|
groupchat.py:110 _extract_args |
yes | yes | yes |
auction_swarm.py:118 _extract_bid |
no | yes | yes |
auto_agent_builder.py:90 _extract_agents |
yes | yes | no |
heavy_swarm.py:1237 _parse_tool_calls |
yes | no | no |
planner_worker_swarm.py:621 _parse_structured_output |
no | yes | no |
hierarchical_order_parser.py:68 _from_function |
no | yes | no |
Every gap fails silently into that copy's own "nothing" sentinel, except heavy_swarm, which is the mirror image of auction_swarm: it reaches straight through tool_call.function.arguments and catches only json.JSONDecodeError, so a dict-returning provider raises an uncaught AttributeError out of question generation.
groupchat._extract_args is the only copy that accepts all three shapes, and its own inline note records why each branch exists:
Providers return either a plain dict or a pydantic object whose function/arguments are attributes.
That knowledge never reached the other five.
Expected fix
One shared helper that turns raw tool-call output into an arguments dict, covering all three shapes. Each site keeps only its own key reads and its own defaults. Removes roughly 140 duplicated lines and, more to the point, makes every structure accept the same provider output.
swarms/utils/str_to_dict.py is the natural home: it is 27 lines and already owns the "provider output to dict" job for the string half.
Related
This is a concrete instance of the copy-paste families in #1850. The helper is the tool_call_arguments half; the mixin is separate.
Source: kyegomez/swarms