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
4 changes: 3 additions & 1 deletion sdk/python/agentfield/multimodal_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,14 +629,16 @@ def _resolve_cost(response: Any, usage_obj: Any) -> tuple[Optional[float], Optio
Returns ``(None, None)`` when no cost can be determined.
"""
# 1. Provider-native cost (e.g. OpenRouter returns usage.cost in USD/credits).
# A reported 0 is a real figure here (free-tier models), unlike the LiteLLM
# branches below, where 0 means "absent from the pricing DB".
if usage_obj is not None:
native = getattr(usage_obj, "cost", None)
if native is None and isinstance(usage_obj, dict):
native = usage_obj.get("cost")
if native is not None:
try:
cost = float(native)
if cost > 0:
if cost >= 0:
return cost, "provider"
except (TypeError, ValueError):
pass
Expand Down
27 changes: 27 additions & 0 deletions sdk/python/tests/test_usage_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@ def test_openrouter_native_cost_wins(self):
assert cost == 0.0042
assert source == "provider"

def test_openrouter_native_zero_cost_is_known(self):
# Free-tier models (e.g. ":free") report usage.cost == 0. That figure is
# determined, so it must not fall through to "unknown".
usage = types.SimpleNamespace(prompt_tokens=10, completion_tokens=5, cost=0.0)
resp = types.SimpleNamespace(model="openrouter/openai/gpt-4o-mini:free")
mock_litellm = MagicMock()
mock_litellm.completion_cost.side_effect = Exception("no price")
with patch.dict("sys.modules", {"litellm": mock_litellm}):
cost, source = _resolve_cost(resp, usage)
assert cost == 0.0
assert source == "provider"

def test_hidden_params_response_cost(self):
usage = types.SimpleNamespace(prompt_tokens=10, completion_tokens=5)
resp = types.SimpleNamespace(
Expand Down Expand Up @@ -278,6 +290,21 @@ def test_openrouter_native_cost_read_through(self):
assert result.cost_usd == 0.0012
assert result.cost_source == "provider"

def test_openrouter_native_zero_cost_read_through(self):
# A known $0 must reach the caller as 0.0/"provider"; reporting it as
# unknown makes CostTracker.serialize() emit total_cost_usd: null for a
# run whose calls were all free.
usage = types.SimpleNamespace(prompt_tokens=10, completion_tokens=5, cost=0.0)
resp = _fake_response(usage, model="openrouter/openai/gpt-4o-mini:free")
mock_litellm = MagicMock()
mock_litellm.completion_cost.side_effect = Exception("no price")
with patch.dict("sys.modules", {"litellm": mock_litellm}):
result = detect_multimodal_response(resp)
assert result.cost_usd == 0.0
assert result.cost_source == "provider"
assert result.usage["prompt_tokens"] == 10
assert result.usage["completion_tokens"] == 5


# ---------------------------------------------------------------------------
# OpenRouter usage-accounting injection
Expand Down
Loading