mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
Merge 00c63b6fc6 into 9ffb82e139
This commit is contained in:
commit
72f7d9af36
2 changed files with 62 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ The A2A SDK can point to LiteLLM's URL and invoke agents registered with LiteLLM
|
|||
|
||||
import json
|
||||
from collections.abc import AsyncGenerator, Callable, Coroutine, Mapping
|
||||
from json import JSONDecodeError
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, Final, Literal, Protocol
|
||||
from urllib.parse import urlparse
|
||||
|
|
@ -820,7 +821,16 @@ async def invoke_agent_a2a(
|
|||
body: dict[str, Any] = {}
|
||||
request_data: dict[str, object] = body
|
||||
try:
|
||||
body = await request.json()
|
||||
# JSON-RPC has its own error codes for a bad body, and callers (the a2a-sdk
|
||||
# client included) map them to typed errors. Letting json() raise would send
|
||||
# them an -32603 internal error, or nothing at all.
|
||||
try:
|
||||
body = await request.json()
|
||||
except (JSONDecodeError, UnicodeDecodeError):
|
||||
return _jsonrpc_error(None, -32700, "Parse error")
|
||||
if not isinstance(body, dict):
|
||||
return _jsonrpc_error(None, -32600, "Invalid Request: body must be a JSON object")
|
||||
|
||||
request_data = body
|
||||
|
||||
verbose_proxy_logger.debug("A2A request for agent '%s': %s", agent_id, body)
|
||||
|
|
|
|||
|
|
@ -3343,3 +3343,54 @@ def test_a2a_http_refusals_answer_as_jsonrpc_errors_with_a_null_id_for_unparseab
|
|||
assert body["error"]["code"] == -32000
|
||||
assert body["error"]["message"] == "Agent 'agent-1' is not allowed"
|
||||
assert body["error"]["data"]["code"] == "403"
|
||||
|
||||
|
||||
def _mock_request(raw_body: bytes) -> MagicMock:
|
||||
"""A request whose json() behaves like Starlette's: empty body is JSONDecodeError."""
|
||||
req: Final = MagicMock()
|
||||
req.headers = {}
|
||||
req.body = AsyncMock(return_value=raw_body)
|
||||
|
||||
async def _json() -> object:
|
||||
return json.loads(raw_body)
|
||||
|
||||
req.json = AsyncMock(side_effect=_json)
|
||||
return req
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("raw_body", "expected_code", "expected_message"),
|
||||
[
|
||||
(b"", -32700, "Parse error"),
|
||||
(b"{", -32700, "Parse error"),
|
||||
(b"[]", -32600, "Invalid Request: body must be a JSON object"),
|
||||
(b'"x"', -32600, "Invalid Request: body must be a JSON object"),
|
||||
],
|
||||
)
|
||||
async def test_unparseable_bodies_answer_as_jsonrpc_errors(
|
||||
raw_body: bytes, expected_code: int, expected_message: str
|
||||
) -> None:
|
||||
"""A2A clients map the JSON-RPC code to a typed error, so an unreadable body has to come back
|
||||
as -32700/-32600 with a null id -- not as -32603 internal error (and not as an AttributeError)."""
|
||||
from litellm.proxy.agent_endpoints.a2a_endpoints import invoke_agent_a2a
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.general_settings", {}),
|
||||
patch("litellm.proxy.proxy_server.proxy_config", MagicMock()),
|
||||
patch("litellm.proxy.proxy_server.version", "1.0.0"),
|
||||
patch("litellm.a2a_protocol.main.A2A_SDK_AVAILABLE", True),
|
||||
):
|
||||
response: Final = await invoke_agent_a2a(
|
||||
agent_id="agent-1",
|
||||
request=_mock_request(raw_body),
|
||||
fastapi_response=MagicMock(),
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test"),
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert json.loads(response.body) == {
|
||||
"jsonrpc": "2.0",
|
||||
"id": None,
|
||||
"error": {"code": expected_code, "message": expected_message},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue