fix(a2a): allow LiteLLMSendMessageResponse.id to be str | int | None (LIT-2818) - #29196
Conversation
…(LIT-2818) Per JSON-RPC 2.0 the response id may be a String, Number, or Null. The upstream a2a SDK (a2a-sdk==0.3.24) reflects this on both SendMessageSuccessResponse and JSONRPCErrorResponse with id: str | int | None = None. Pinning id: str (required) on LiteLLMSendMessageResponse made the proxy raise a Pydantic ValidationError on legitimate JSON-RPC error responses with numeric or null id, surfacing as a 500 on the error-response path.
|
|
Greptile SummaryThis PR fixes a Pydantic validation crash on the A2A proxy error-response path by relaxing
Confidence Score: 5/5Safe to merge — the change only widens an accepted type from The fix is a minimal, well-scoped relaxation of a type constraint that was too strict relative to the upstream SDK and the JSON-RPC 2.0 spec. The No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/types/agents.py | Single-field change relaxing LiteLLMSendMessageResponse.id from required str to Optional[Union[str, int]] = None, aligning with JSON-RPC 2.0 spec and the upstream a2a SDK; well-commented and backward-compatible. |
| tests/test_litellm/a2a_protocol/test_send_message_response_id_lit2818.py | New regression test file with 14 cases covering all JSON-RPC id shapes (str/int/None/omitted) via constructor, from_dict, and from_a2a_response; also guards against invalid types (float, list, dict). No real network calls. |
Reviews (1): Last reviewed commit: "fix(a2a): allow LiteLLMSendMessageRespon..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
Summary
Relax
LiteLLMSendMessageResponse.idfromstr(required) toOptional[Union[str, int]] = Noneto match the JSON-RPC 2.0 spec and theupstream a2a SDK (
a2a-sdk==0.3.24).Linear
LIT-2818 — "Investigate LiteLLM error-response validation failure for A2A".
Root cause
LiteLLMSendMessageResponseinlitellm/types/agents.py:301wraps theupstream a2a SDK’s
SendMessageResponse. Per JSON-RPC 2.0 §4 / §5, theresponse
idis aString | Number | Nullvalue, and error responses thatcould not be correlated to a request MUST use
null. The upstream a2aSDK (
a2a-sdk==0.3.24) reflects this:Pre-fix,
LiteLLMSendMessageResponse.id: str(required) rejected threeshapes the SDK accepts:
idis a number (per spec,idMAY be a Number).idis null (per spec, error responses that could not be correlatedto a request MUST use
null).idis missing — produced by amodel_dump(exclude_none=True)roundtrip on a
null-id response (this is exactly the pathlitellm.a2a_protocol.main.asend_messagetakes:a2a_response.model_dump(mode="json", exclude_none=True)→LiteLLMSendMessageResponse.from_a2a_response).The success path (
result.kind="task"/"message") passes becauseproduction agents echo the client’s string
id. The error-response path(e.g. upstream JSON parse error or generic
Internal error) fails Pydanticvalidation, which the proxy surfaces as a 500 — matching the report on
LiteLLM
v1.82.0.Fix
Single-field change in
litellm/types/agents.py:LiteLLMSendMessageResponse:Matches
SendMessageSuccessResponse.idandJSONRPCErrorResponse.idon thepinned SDK version (
a2a-sdk==0.3.24). No callers rely onidbeing anon-None string — grep across
litellm/shows it is only assigned/round-trippedthrough to the response object.
Evidence
Direct reproduction against the two production code paths
(
LiteLLMSendMessageResponse.from_dictused by_send_message_via_completion_bridge, andLiteLLMSendMessageResponse.from_a2a_responseused byasend_message) onthe same checkout (
litellm_oss_agent_shin_daily_branchHEAD1fe911d) —first without the patch, then with the patch.
Before (clean
litellm_oss_agent_shin_daily_branch)After (this PR)
All five previously-rejected JSON-RPC error-response shapes
(numeric id, null id, omitted id across both code paths) are now accepted.
The success path (string id) is unchanged.
Tests
Added
tests/test_litellm/a2a_protocol/test_send_message_response_id_lit2818.pywith 14 cases covering:
idis string, int, None, or missing.from_dict(...): all three JSON-RPCidshapes + omitted-id roundtrip.from_a2a_response(...): reala2a.types.SendMessageResponse(root= JSONRPCErrorResponse(...))payloads for eachidshape — exercises thesame
model_dump(exclude_none=True)roundtrip the proxy takes.float,list,dictids are still rejected (we onlywidened the allowed set, did not loosen type-checking).
Full
tests/test_litellm/a2a_protocol/suite: 41 passed.Notes
Pushed via Git Data API (blobs → tree → commit → ref) against the upstream
base tree, so the merge-base diff should be exactly the two files above.
Verification (ship-pr)
litellm_oss_agent_shin_daily_branchlitellm_oss_agent_shin_daily_branch)tests/test_litellm/a2a_protocol/test_send_message_response_id_lit2818.py, full a2a_protocol suite 41 passed)mergeable_statecleanlitellm/types/agents.py+10/-1, new regression test file)