refactor(a2a): use tool calling for delegation instead of structured output#5751
refactor(a2a): use tool calling for delegation instead of structured output#5751greysonlalonde wants to merge 3 commits intomainfrom
Conversation
…output Each remote A2A agent is now exposed to the local LLM as a BaseTool (delegate_to_<card_name>); the local agent's tool-call loop drives multi-turn delegation. The Literal-constrained AgentResponse model and the explicit per-turn re-prompting loop are gone. Closes #3897. The original failure mode — Pydantic literal_error when skill.id != endpoint URL, and Gemini flash-lite hallucinating out-of-enum values — is structurally impossible: provider-side tool-call validation enforces the tool name, and there's no competing identifier.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
|
|
||
| try: | ||
| from a2a.types import Message, Role | ||
| from a2a.types import TaskState # noqa: F401 |
Why
Closes #3897.
A2A delegation currently relies on a
Literal[endpoint_url, ...]-constrainedAgentResponsemodel to pick a remote agent. The prompt shows the LLM each agent's card (skill IDs, names, URLs), but the only valid value fora2a_idsis the well-known endpoint URL — which is never explicitly labeled as the identifier. Predictable failures:skills[0].id(e.g."Research") instead of the endpoint URL →pydantic ValidationError: literal_error.Literal/enum constraints in JSON Schema emit out-of-set values → same error.A fuzzy-match fallback would paper over the symptom; the structural fix is to make the identifier set itself unambiguous and provider-enforced.
What
Each remote A2A agent is now exposed to the local LLM as a
BaseTool(delegate_to_<sanitized_card_name>); the local agent's tool-call loop drives multi-turn delegation.AgentResponse(a2a_ids, message, is_a2a)and the explicit per-turn re-prompting loop are gone.crewai/a2a/tools.py:A2ADelegationTool+A2ADelegationState(per-task shared state with per-endpoint history, IDs, turn counts).crewai/a2a/wrapper.pycollapsed from 1772 → ~530 LOC. Deleted_delegate_to_a2a/_adelegate_to_a2a,_prepare_delegation_context,_parse_agent_response,_handle_agent_response_and_continue,_handle_max_turns_exceeded,_emit_delegation_failed,_process_response_result,_init_delegation_state,_get_turn_context,_handle_task_completion,DelegationContext,DelegationState. Each of the four entry points (sync/async × execute_task/kickoff) now augments the prompt with agent cards, builds A2A tools, merges them into the call'stoolslist (or temporarily extendsself.toolsfor kickoff), and callsoriginal_fn.PREVIOUS_A2A_CONVERSATION_TEMPLATE,CONVERSATION_TURN_INFO_TEMPLATE,REMOTE_AGENT_*_NOTICE.AVAILABLE_AGENTS_TEMPLATEnow describes the tool-call protocol.response_model.py:create_agent_response_model/get_a2a_agents_and_response_modelreplaced with a singleextract_a2a_client_configs().types.py:AgentResponseProtocolremoved.agent/core.py+lite_agent.pyupdated to drop theAgentResponseProtocolbranch and theagent_response_modelarg.The original failure is now structurally impossible: provider-side tool-call validation (OpenAI / Anthropic / Gemini) enforces the tool name; there's no competing identifier set for the model to confuse.
A2AConfig.max_turnswires through toBaseTool.max_usage_count, so the existing per-agent turn limit is preserved without an explicit Python-side loop.Notes
tools.py. Net ≈ −600 LOC.pip-audit); will retarget tomainonce that lands.mypyclean across 473 files.ruffclean.