mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-07-30 15:26:14 +00:00
Merge pull request #616 from Routstr/fix/ppq-upstream-inference-cost
fix: bill PPQ.AI BYOK upstream_inference_cost + BYOK fee
This commit is contained in:
@@ -264,7 +264,17 @@ def _coerce_usd(value: object) -> float:
|
||||
def _resolve_usd_cost(usage_data: dict, response_data: dict) -> float:
|
||||
"""Resolve USD cost with clear priority order.
|
||||
|
||||
Priority: cost_details.total_cost → total_cost → cost (in both usage and response).
|
||||
Priority:
|
||||
|
||||
1. ``cost_details.total_cost``
|
||||
2. ``cost_details.upstream_inference_cost`` (BYOK — see below)
|
||||
3. ``total_cost`` → ``cost`` (in both usage and response)
|
||||
|
||||
**BYOK path (PPQ.AI):** when ``is_byok`` is true the ``usage.cost`` field
|
||||
is only a small (~5 %) routing fee, not the inference cost. The real cost
|
||||
lives in ``cost_details.upstream_inference_cost`` and the provider's
|
||||
balance is debited by ``upstream_inference_cost + byok_fee``. Billing just
|
||||
the fee under-charges by ~20×.
|
||||
"""
|
||||
cost_details = usage_data.get("cost_details")
|
||||
if isinstance(cost_details, dict):
|
||||
@@ -272,6 +282,18 @@ def _resolve_usd_cost(usage_data: dict, response_data: dict) -> float:
|
||||
if cost > 0:
|
||||
return cost
|
||||
|
||||
# PPQ.AI BYOK: upstream_inference_cost is the real inference cost;
|
||||
# usage.cost is only a ~5 % BYOK routing fee. Bill the sum — what PPQ
|
||||
# actually deducts from the balance. For non-BYOK providers (e.g.
|
||||
# OpenRouter) usage.cost already equals upstream_inference_cost, so we
|
||||
# fall through to the normal ``cost`` lookup below.
|
||||
upstream_cost = _coerce_usd(
|
||||
cost_details.get("upstream_inference_cost")
|
||||
)
|
||||
if upstream_cost > 0 and usage_data.get("is_byok"):
|
||||
byok_fee = _coerce_usd(usage_data.get("cost"))
|
||||
return upstream_cost + byok_fee
|
||||
|
||||
for source in [usage_data, response_data]:
|
||||
if not isinstance(source, dict):
|
||||
continue
|
||||
|
||||
@@ -532,6 +532,72 @@ async def test_openrouter_upstream_inference_cost_components_are_used() -> None:
|
||||
assert result.input_msats + result.output_msats == result.total_msats == 4471
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PPQ.AI BYOK: upstream_inference_cost + BYOK fee billing
|
||||
#
|
||||
# PPQ.AI (bring-your-own-key) returns a small ~5 % BYOK routing fee in
|
||||
# ``usage.cost`` and the real inference cost in
|
||||
# ``cost_details.upstream_inference_cost``. The old code billed only the fee,
|
||||
# under-charging by ~20×. The fix bills ``upstream_inference_cost + byok_fee``
|
||||
# — what PPQ actually deducts from the balance.
|
||||
# Payload numbers are from a live ``glm-5.2-fast`` request (GitHub issue #615).
|
||||
# ============================================================================
|
||||
@pytest.mark.asyncio
|
||||
async def test_ppq_byok_bills_upstream_inference_cost_plus_fee() -> None:
|
||||
"""PPQ.AI BYOK must bill upstream_inference_cost + byok_fee, not just the
|
||||
fee. Mirrors the live request from GitHub issue #615."""
|
||||
response = {
|
||||
"model": "glm-5.2-fast",
|
||||
"usage": {
|
||||
"prompt_tokens": 164371, # includes 159301 cached
|
||||
"completion_tokens": 99,
|
||||
"cost": 0.002260057305, # ~5% BYOK routing fee
|
||||
"is_byok": True,
|
||||
"prompt_tokens_details": {"cached_tokens": 159301},
|
||||
"cost_details": {
|
||||
"upstream_inference_cost": 0.04475361,
|
||||
"upstream_inference_prompt_cost": 0.04410021,
|
||||
"upstream_inference_completions_cost": 0.0006534,
|
||||
},
|
||||
},
|
||||
}
|
||||
result = await calculate_cost(response, max_cost=100000)
|
||||
|
||||
assert isinstance(result, CostData)
|
||||
# The fix bills upstream_inference_cost + byok_fee (~0.047 USD → ~940k
|
||||
# msats), not the fee alone (~0.0023 USD → ~45k msats). ~20× correction.
|
||||
assert result.total_msats == 940274
|
||||
assert result.input_msats + result.output_msats == result.total_msats
|
||||
assert result.input_msats == 926546
|
||||
assert result.output_msats == 13728
|
||||
assert result.total_usd == pytest.approx(0.047013667305)
|
||||
# Token normalisation (OpenAI dialect: cached included in prompt_tokens)
|
||||
assert result.input_tokens == 5070 # 164371 - 159301
|
||||
assert result.cache_read_input_tokens == 159301
|
||||
assert result.output_tokens == 99
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ppq_byok_fee_only_would_undercharge() -> None:
|
||||
"""Sanity check: billing only usage.cost (the BYOK fee) under-charges by
|
||||
~20×. This documents the regression the fix prevents."""
|
||||
response = {
|
||||
"model": "glm-5.2-fast",
|
||||
"usage": {
|
||||
"prompt_tokens": 164371,
|
||||
"completion_tokens": 99,
|
||||
"cost": 0.002260057305, # BYOK fee only — no upstream_inference_cost
|
||||
"is_byok": True,
|
||||
"prompt_tokens_details": {"cached_tokens": 159301},
|
||||
},
|
||||
}
|
||||
result = await calculate_cost(response, max_cost=100000)
|
||||
|
||||
assert isinstance(result, CostData)
|
||||
# Without upstream_inference_cost, only the fee is billed — the old bug.
|
||||
assert result.total_msats == 45202
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Test 13: Missing Usage Block
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user