mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix(a2a): read a stored card's capabilities the way the spec does and keep one Authorization line
This commit is contained in:
parent
75eec8712c
commit
dee5724c21
4 changed files with 51 additions and 8 deletions
|
|
@ -38,7 +38,7 @@ _REGISTRY_PARAMS_KEPT_OUT_OF_OPTIONAL_PARAMS: Final = (
|
|||
|
||||
def _card_declares_no_streaming(agent_card_params: Mapping[str, object]) -> bool:
|
||||
capabilities: Final = agent_card_params.get("capabilities")
|
||||
return isinstance(capabilities, Mapping) and capabilities.get("streaming") is False
|
||||
return isinstance(capabilities, Mapping) and not capabilities.get("streaming")
|
||||
|
||||
|
||||
def _registry_api_key(agent_litellm_params: dict[str, object]) -> str | None:
|
||||
|
|
|
|||
|
|
@ -176,14 +176,15 @@ def _forwarding_headers(
|
|||
agent_extra_headers: Mapping[str, str] | None,
|
||||
backend_auth_header: Mapping[str, str] | None,
|
||||
) -> dict[str, str] | None:
|
||||
backend_auth: Final = tuple(backend_auth_header.items()) if backend_auth_header else ()
|
||||
minted_names: Final = frozenset(name.lower() for name, _ in backend_auth)
|
||||
passthrough: Final = tuple(
|
||||
(name, value)
|
||||
for name, value in (agent_extra_headers.items() if agent_extra_headers else ())
|
||||
if not name.lower().startswith("x-litellm-")
|
||||
if not name.lower().startswith("x-litellm-") and name.lower() not in minted_names
|
||||
)
|
||||
trace_id: Final = request_data.get("litellm_trace_id")
|
||||
trace: Final = (("X-LiteLLM-Trace-Id", str(trace_id)),) if trace_id else ()
|
||||
backend_auth: Final = backend_auth_header.items() if backend_auth_header else ()
|
||||
merged: Final = dict((*passthrough, *caller_identity.items(), *trace, *backend_auth))
|
||||
return merged or None
|
||||
|
||||
|
|
|
|||
|
|
@ -2642,3 +2642,18 @@ async def test_handle_stream_message_is_untouched_while_keepalives_are_unconfigu
|
|||
|
||||
assert not any(chunk.startswith(":") for chunk in chunks)
|
||||
assert json.loads(chunks[-1].removeprefix("data: "))["result"]["kind"] == "task"
|
||||
|
||||
|
||||
def test_forwarding_headers_minted_bearer_replaces_a_forwarded_authorization_of_any_case():
|
||||
"""A client header the admin chose to forward keeps the casing the config named it with, so a forwarded
|
||||
`authorization` must not travel next to the minted `Authorization` as a second header line."""
|
||||
from litellm.proxy.agent_endpoints.a2a_endpoints import _forwarding_headers
|
||||
|
||||
merged = _forwarding_headers(
|
||||
caller_identity={},
|
||||
request_data={},
|
||||
agent_extra_headers={"authorization": "Bearer client-token", "X-Custom": "kept"},
|
||||
backend_auth_header={"Authorization": "Bearer minted-token"},
|
||||
)
|
||||
|
||||
assert merged == {"X-Custom": "kept", "Authorization": "Bearer minted-token"}
|
||||
|
|
|
|||
|
|
@ -75,10 +75,29 @@ def test_a2a_registry_integration():
|
|||
assert post.call_args.kwargs["headers"]["X-Agent"] == "static"
|
||||
|
||||
|
||||
def test_streaming_chat_to_an_agent_whose_card_declines_streaming_uses_a_blocking_send():
|
||||
def _foundry_card_stored_through_the_agents_api() -> dict:
|
||||
from litellm.proxy.a2a.agent_card import merge_agent_card
|
||||
|
||||
return merge_agent_card(
|
||||
{"name": "Foundry", "url": "https://foundry.example.com/a2a", "capabilities": {"streaming": False}},
|
||||
proxy_url="http://localhost:4000/a2a/foundry-agent",
|
||||
proxy_base_url="http://localhost:4000",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"agent_card_params",
|
||||
[
|
||||
{"url": "https://foundry.example.com/a2a", "capabilities": {"streaming": False}},
|
||||
_foundry_card_stored_through_the_agents_api(),
|
||||
],
|
||||
ids=["card registered verbatim from config.yaml", "card stored through POST /v1/agents"],
|
||||
)
|
||||
def test_streaming_chat_to_an_agent_whose_card_declines_streaming_uses_a_blocking_send(agent_card_params: dict):
|
||||
"""Microsoft Foundry agents publish `capabilities.streaming: false` and answer message/stream with a
|
||||
JSON-RPC error. A streaming chat call to such an agent must post a blocking message/send and hand the
|
||||
caller the answer as a stream, and an agent whose card is silent about streaming keeps message/stream."""
|
||||
caller the answer as a stream, whether the card was registered verbatim from config.yaml or stored
|
||||
through POST /v1/agents, which keeps only truthy capabilities and so drops the `false` itself."""
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry
|
||||
from litellm.types.agents import AgentResponse
|
||||
|
|
@ -86,7 +105,7 @@ def test_streaming_chat_to_an_agent_whose_card_declines_streaming_uses_a_blockin
|
|||
foundry_agent = AgentResponse(
|
||||
agent_id="foundry-id",
|
||||
agent_name="foundry-agent",
|
||||
agent_card_params={"url": "https://foundry.example.com/a2a", "capabilities": {"streaming": False}},
|
||||
agent_card_params=agent_card_params,
|
||||
litellm_params={"api_key": "registry-key"},
|
||||
)
|
||||
client = HTTPHandler()
|
||||
|
|
@ -126,14 +145,22 @@ def test_streaming_chat_to_an_agent_whose_card_declines_streaming_uses_a_blockin
|
|||
assert chunks[-1].choices[0].finish_reason == "stop"
|
||||
|
||||
|
||||
def test_registry_lookup_leaves_streaming_alone_when_the_card_does_not_decline_it():
|
||||
@pytest.mark.parametrize(
|
||||
"agent_card_params",
|
||||
[
|
||||
{"url": "https://agent.example.com/a2a"},
|
||||
{"url": "https://agent.example.com/a2a", "capabilities": {"streaming": True}},
|
||||
],
|
||||
ids=["card without a capabilities block", "card says streaming true"],
|
||||
)
|
||||
def test_registry_lookup_leaves_streaming_alone_when_the_card_does_not_decline_it(agent_card_params: dict):
|
||||
from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry
|
||||
from litellm.types.agents import AgentResponse
|
||||
|
||||
silent_agent = AgentResponse(
|
||||
agent_id="silent-id",
|
||||
agent_name="silent-agent",
|
||||
agent_card_params={"url": "https://agent.example.com/a2a"},
|
||||
agent_card_params=agent_card_params,
|
||||
litellm_params={"api_key": "registry-key"},
|
||||
)
|
||||
original_agents = global_agent_registry.agent_list.copy()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue