fix(a2a): normalize agent card protocolBinding casing before transport match - #37917
Conversation
…t match Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Greptile SummaryThe PR normalizes known mis-cased A2A protocol bindings on an independent agent-card copy and selects the legacy compatibility dialect for affected interfaces.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/a2a_protocol/card_resolver.py | Adds copy-based interface normalization, preserving the original resolved card while canonicalizing transport metadata on the client-facing copy. |
| litellm/a2a_protocol/main.py | Resolves and normalizes the agent card before passing it to the SDK client factory while preserving per-caller header isolation. |
| tests/test_litellm/a2a_protocol/test_card_resolver.py | Verifies canonicalization, selective version downgrade, and independence of the original protobuf card. |
| tests/test_litellm/a2a_protocol/test_main.py | Adds end-to-end mock-transport coverage for lowercase bindings and the compatibility response dialect. |
| tests/test_litellm/proxy/agent_endpoints/test_agent_header_isolation.py | Updates client-construction mocks for explicit card resolution while retaining shared-client and timeout assertions. |
Reviews (3): Last reviewed commit: "fix(a2a): speak the 0.3 dialect to serve..." | Re-trigger Greptile
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…resolved card Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai the normalization helper now returns a fresh card copy instead of mutating the resolved SDK card |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit de0d8ce. Configure here.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fe567bd. Configure here.
TLDR
Problem this solves:
protocolBinding(e.g. LangGraph Platform) never matchHow it solves it:
User Flow
Before: a gateway user calling a LangGraph Platform agent through the proxy gets a hard JSON-RPC error
agents:in config.yaml and restarts the proxymessage/sendJSON-RPC body{"jsonrpc":"2.0","id":"req-1","error":{"code":-32603,"message":"Internal error: no compatible transports found."}}After: the same request reaches the agent and returns its completed task
agents:in config.yaml and restarts the proxymessage/sendJSON-RPC body{"id":"req-1","jsonrpc":"2.0","result":{"task":{"id":"...","status":{"state":"TASK_STATE_COMPLETED",...},"artifacts":[{"name":"Assistant Response","parts":[{"text":"..."}]}],...}}}Relevant issues
Reported by a customer (Pylon ticket, their internal ref TPEDP-13005): after upgrading from v1.91 to v1.97, calls to agents behind LangGraph Platform via A2A fail. Two things broke together in that upgrade's a2a-sdk bump (0.3.24 -> 1.1.0). First, langgraph-api serves
"protocolBinding": "jsonrpc"while a2a-sdk'sClientFactory.create()matches transports case-sensitively against uppercaseTransportProtocolconstants, so client creation fails outright; v1.91's legacyA2AClientnever validated the binding. Second, the same servers declareprotocolVersion: "1.0"yet still answer in the A2A 0.3 JSON dialect ("kind"-discriminated tasks and messages), which the SDK's strict v1 transport rejects once the casing is fixed. The mis-cased binding fingerprints such a server, so normalization also downgrades that interface's declared version to "0.3", routing the SDK's own factory onto its v0.3 compat transport, which speaks that dialect in both directionsLinear ticket
Resolves LIT-6031
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup: a real LangGraph dev server (langgraph-api 0.13.0,
langgraph dev) serving an echo graph "agent" as assistantfe096781-5601-53d2-b2f6-0d3403f7e9ca. Its agent card declares"protocolBinding": "jsonrpc"(lowercase) with"protocolVersion": "1.0"and its replies use the A2A 0.3 "kind" dialect, exactly like LangGraph Platform. Each leg boots the proxy with 2 uvicorn workers (--num_workers 2) and registers the agent (the Before leg ran langgraph on port 43117 and the proxy on 43118, the After leg on 44229/44230):No LLM is involved by design: the failure is in agent card transport negotiation, before any model call, and the graph echoes its input
Before (7481649)
message/send
message/stream (served on the bare /a2a/{agent_id} JSON-RPC route)
After (fe567bd)
message/send
message/stream (served on the bare /a2a/{agent_id} JSON-RPC route)
Run observations:
Type
🐛 Bug Fix
Caveats (if any)
Final Attestation
Link to Devin session: https://app.devin.ai/sessions/e97eb796504147258f46ce84ea43d373
Note
Medium Risk
Changes the shared A2A client bootstrap and transport/version selection for every agent; mis-normalization could affect correctly declared v1 agents, though only miscased known bindings are altered.
Overview
Fixes A2A proxy calls to agents like LangGraph Platform that advertise lowercase
protocolBindingvalues and still speak the A2A 0.3"kind"JSON dialect while declaringprotocolVersion1.0.Adds
normalize_agent_card_interfaces, which copies the resolved card and rewrites known miscased bindings (jsonrpc→JSONRPC, etc.). When a binding is corrected, that interface’sprotocol_versionis set to0.3so a2a-sdk selects its compat transport instead of failing transport match or strict v1 parsing.create_a2a_clientnow fetches the agent card with LiteLLM’s resolver (including auth headers on the card GET), normalizes interfaces, then passes theagent_cardintocreate_clientrather than abase_urland internal resolver kwargs. The normalized card is always stored on_litellm_agent_card.Tests cover normalization edge cases, an end-to-end lowercase-binding + LangGraph-style task reply, and updated mocks for the new client-creation flow.
Reviewed by Cursor Bugbot for commit fe567bd. Bugbot is set up for automated code reviews on this repo. Configure here.