Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion litellm/llms/a2a/chat/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
from litellm.types.llms.openai import AllMessageValues
from litellm.types.utils import Choices, Message, ModelResponse
from litellm.types.utils import Choices, Message, ModelResponse, Usage

from ..common_utils import (
A2AError,
Expand Down Expand Up @@ -312,6 +312,25 @@ def transform_response(
# Set ID from response
model_response.id = response_json.get("id", str(uuid.uuid4()))

# A2A agents don't return token usage; estimate it so per-token pricing
# produces real cost and callers don't receive usage of 0/0/0.
try:
from litellm.utils import token_counter

prompt_tokens = token_counter(model="gpt-3.5-turbo", messages=messages)
completion_tokens = token_counter(model="gpt-3.5-turbo", text=text, count_response_tokens=True)
setattr(
model_response,
"usage",
Usage(
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
),
)
except Exception: # noqa: BLE001 - best-effort estimate; a tokenizer hiccup must not break the response
pass
Comment on lines +331 to +332

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The bare except Exception: pass silently swallows any token-counting failure with no log output, making it impossible to diagnose why usage stays at zero for a user. Both langgraph/chat/transformation.py and vertex_ai/agent_engine/transformation.py use the same pattern but emit a verbose_logger.warning so the failure is at least visible in debug logs.

Suggested change
except Exception: # noqa: BLE001 - best-effort estimate; a tokenizer hiccup must not break the response
pass
except Exception as e: # noqa: BLE001 - best-effort estimate; a tokenizer hiccup must not break the response
verbose_logger.warning(f"A2A: failed to estimate token usage: {e}")


return model_response

def get_model_response_iterator(
Expand Down
42 changes: 42 additions & 0 deletions tests/test_litellm/llms/a2a/chat/test_a2a_chat_transformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Tests for litellm/llms/a2a/chat/transformation.py response transform."""

from unittest.mock import MagicMock

from litellm.llms.a2a.chat.transformation import A2AConfig
from litellm.types.utils import ModelResponse


def _raw_response(text: str) -> MagicMock:
raw = MagicMock()
raw.status_code = 200
raw.headers = {}
raw.json.return_value = {
"jsonrpc": "2.0",
"id": "resp-1",
"result": {
"kind": "message",
"parts": [{"kind": "text", "text": text}],
},
}
return raw


def test_transform_response_sets_usage():
"""Regression: A2AConfig.transform_response must populate usage so per-token
pricing computes real cost and callers don't get usage 0/0/0."""
result = A2AConfig().transform_response(
model="a2a/test-agent",
raw_response=_raw_response("hello from the agent"),
model_response=ModelResponse(),
logging_obj=MagicMock(),
request_data={},
messages=[{"role": "user", "content": "hi there agent"}],
optional_params={},
litellm_params={},
encoding=None,
)

assert result.usage is not None
assert result.usage.prompt_tokens > 0
assert result.usage.completion_tokens > 0
assert result.usage.total_tokens == (result.usage.prompt_tokens + result.usage.completion_tokens)
Loading