mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-18 05:28:02 +00:00
fix(a2a): fix legacy streaming chunk, agent card test, and metadata merge
- providers/litellm_completion: move 'final' out of the message object into the result envelope per the A2A spec (matches the bridge fix). - agent endpoints test: the runtime invocation path now preserves the top-level 'url' on the stored card, so update the assertion to match. - completion bridge metadata: when forwarding A2A metadata via extra_body.metadata, merge into any existing extra_body.metadata instead of replacing it, so an agent-configured metadata block is preserved (forward metadata still wins on key conflicts). Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
@@ -100,7 +100,15 @@ class A2ACompletionBridgeTransformation:
|
||||
extra_body = completion_params.get("extra_body")
|
||||
if not isinstance(extra_body, dict):
|
||||
extra_body = {}
|
||||
extra_body = {**extra_body, "metadata": forward_metadata}
|
||||
# Merge into any existing ``extra_body.metadata`` so an
|
||||
# agent-configured ``extra_body: {metadata: {...}}`` is preserved;
|
||||
# forwarded A2A metadata takes precedence on key conflicts.
|
||||
existing_metadata = extra_body.get("metadata")
|
||||
merged_metadata: Dict[str, Any] = (
|
||||
{**existing_metadata} if isinstance(existing_metadata, dict) else {}
|
||||
)
|
||||
merged_metadata.update(forward_metadata)
|
||||
extra_body = {**extra_body, "metadata": merged_metadata}
|
||||
completion_params["extra_body"] = extra_body
|
||||
|
||||
verbose_logger.debug(
|
||||
|
||||
@@ -266,15 +266,19 @@ class A2ACompletionBridgeTransformation:
|
||||
if not content and not is_final:
|
||||
return None
|
||||
|
||||
# Build A2A streaming chunk (legacy format)
|
||||
# Build A2A streaming chunk (legacy format). ``final`` is an
|
||||
# envelope-level streaming property per the A2A spec and must live
|
||||
# alongside ``message`` in ``result``, not inside the message object.
|
||||
a2a_chunk = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": request_id,
|
||||
"result": {
|
||||
"kind": "message",
|
||||
"role": "agent",
|
||||
"parts": [{"kind": "text", "text": content}],
|
||||
"messageId": uuid4().hex,
|
||||
"message": {
|
||||
"kind": "message",
|
||||
"role": "agent",
|
||||
"parts": [{"kind": "text", "text": content}],
|
||||
"messageId": uuid4().hex,
|
||||
},
|
||||
"final": is_final,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -414,8 +414,10 @@ class TestAgentRBACProxyAdmin:
|
||||
stored_card = call_kwargs["agent"]["agent_card_params"]
|
||||
new_agent_id = call_kwargs["agent_id"]
|
||||
|
||||
# Top-level url is dropped; supportedInterfaces points at the proxy.
|
||||
assert "url" not in stored_card
|
||||
# Top-level url is retained for runtime A2A invocation (the public
|
||||
# well-known endpoint rewrites it before exposing to clients);
|
||||
# supportedInterfaces points at the proxy.
|
||||
assert stored_card["url"] == "http://localhost"
|
||||
assert stored_card["supportedInterfaces"][0]["protocolBinding"] == "JSONRPC"
|
||||
assert stored_card["supportedInterfaces"][0]["url"].endswith(
|
||||
f"/a2a/{new_agent_id}"
|
||||
|
||||
Reference in New Issue
Block a user