fix(cost_calculator): prevent provider leakage and preserve custom_llm_provider for OpenAI-aliased completions (#48226)

This commit is contained in:
pfolex 2026-10-24 19:55:36 -04:00
parent 9b7f2c8dea
commit 577db03ef6
3 changed files with 112 additions and 6 deletions

View file

@ -1658,14 +1658,27 @@ def completion_cost(
explicit_pricing,
)
requested_model: Final = model
request_pricing_provider: Final = custom_llm_provider if isinstance(custom_llm_provider, str) else None
request_pricing_provider: Final = (
custom_llm_provider
if isinstance(custom_llm_provider, str)
else _get_provider_for_cost_calc(model)
)
raw_candidates: Final = (
(
selected_model,
base_model_provider if base_model_provider is not None else request_pricing_provider,
),
(_get_response_model(completion_response), request_pricing_provider),
*(((model, request_pricing_provider),) if model is not None else ()),
*(
(
(
model,
request_pricing_provider or _get_provider_for_cost_calc(model),
),
)
if model is not None
else ()
),
)
potential_model_names: Final = (
raw_candidates if custom_pricing is True else _priced_candidates_before_unpriced_rules(raw_candidates)
@ -1673,6 +1686,9 @@ def completion_cost(
for idx, (model, candidate_pricing_provider) in enumerate(potential_model_names):
try:
candidate_custom_llm_provider: str | None = (
candidate_pricing_provider or request_pricing_provider
)
if verbose_logger.isEnabledFor(logging.DEBUG):
verbose_logger.debug("selected model name for cost calculation: %s", model)
@ -1742,10 +1758,15 @@ def completion_cost(
hidden_params = getattr(completion_response, "_hidden_params", None)
if hidden_params is not None:
custom_llm_provider = pricing_provider or hidden_params.get(
"custom_llm_provider", custom_llm_provider or None
custom_llm_provider = (
candidate_pricing_provider
or pricing_provider
or hidden_params.get("custom_llm_provider")
or candidate_custom_llm_provider
)
region_name = hidden_params.get("region_name", region_name)
else:
custom_llm_provider = candidate_pricing_provider or candidate_custom_llm_provider
else:
if model is None:

View file

@ -122,8 +122,15 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
returned_response: Final = ModelResponse(**response_json)
set_provider_response_headers_in_hidden_params(returned_response, response.headers)
if hasattr(model_response, "_hidden_params") and model_response._hidden_params:
for k, v in model_response._hidden_params.items():
if k not in returned_response._hidden_params:
returned_response._hidden_params[k] = v
if custom_llm_provider is not None:
returned_response.model = custom_llm_provider + "/" + (returned_response.model or "")
returned_response._hidden_params["custom_llm_provider"] = custom_llm_provider
if returned_response.model and not returned_response.model.startswith(f"{custom_llm_provider}/"):
returned_response.model = custom_llm_provider + "/" + returned_response.model
if base_model is not None:
returned_response.hidden_params["model"] = base_model
@ -143,6 +150,15 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
api_key: str | None = None,
json_mode: bool | None = None,
) -> ModelResponse:
resolved_custom_llm_provider = (
litellm_params.get("custom_llm_provider")
if isinstance(litellm_params, dict)
else None
) or (
getattr(model_response, "_hidden_params", {}).get("custom_llm_provider")
if hasattr(model_response, "_hidden_params") and isinstance(getattr(model_response, "_hidden_params"), dict)
else None
)
return OpenAILikeChatConfig._transform_response(
model=model,
response=raw_response,
@ -156,7 +172,7 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
print_verbose=None,
encoding=None,
json_mode=json_mode,
custom_llm_provider=None,
custom_llm_provider=resolved_custom_llm_provider,
base_model=None,
)

View file

@ -54,3 +54,72 @@ def test_bills_visit_website_per_open():
def test_bills_searches_and_opens_together():
cost = cost_per_web_search_request(usage=_usage_with_actions(2, opens=3), model_info=PRICED_MODEL_INFO)
assert cost == pytest.approx(2 * 0.005 + 3 * 0.001)
def test_groq_openai_aliased_completion_cost():
"""Regression test for Issue #48226: completion_cost for Groq models prefixed with openai/."""
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import litellm
from litellm.types.utils import Choices, Message, ModelResponse
body = {
"id": "chatcmpl-x",
"object": "chat.completion",
"created": 1761100000,
"model": "openai/gpt-oss-120b",
"choices": [{"index": 0, "finish_reason": "stop", "message": {"role": "assistant", "content": "ok"}}],
"usage": {"prompt_tokens": 77, "completion_tokens": 19, "total_tokens": 96},
"service_tier": "on_demand",
"system_fingerprint": "fp_x",
"x_groq": {"id": "req_x"},
}
class MockHandler(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers.get("content-length", 0)))
data = json.dumps(body).encode()
self.send_response(200)
self.send_header("content-type", "application/json")
self.send_header("content-length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, *a):
pass
srv = HTTPServer(("127.0.0.1", 0), MockHandler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
resp = litellm.completion(
model="groq/openai/gpt-oss-120b",
messages=[{"role": "user", "content": "hi"}],
api_base=f"http://127.0.0.1:{srv.server_port}/openai/v1",
api_key="test",
)
assert resp._hidden_params.get("custom_llm_provider") == "groq"
assert resp.model == "groq/openai/gpt-oss-120b"
assert resp._hidden_params.get("response_cost") == pytest.approx(2.295e-05)
c1 = litellm.completion_cost(completion_response=resp)
assert c1 == pytest.approx(2.295e-05)
c2 = litellm.completion_cost(completion_response=resp, model="groq/openai/gpt-oss-120b")
assert c2 == pytest.approx(2.295e-05)
c3 = litellm.completion_cost(completion_response=resp, custom_llm_provider="groq")
assert c3 == pytest.approx(2.295e-05)
# Raw response without hidden params must also resolve when explicit model is passed
raw_resp = ModelResponse(
id="chatcmpl-x",
model="openai/gpt-oss-120b",
choices=[Choices(message=Message(content="ok", role="assistant"))],
usage=Usage(prompt_tokens=77, completion_tokens=19, total_tokens=96),
)
c_raw = litellm.completion_cost(completion_response=raw_resp, model="groq/openai/gpt-oss-120b")
assert c_raw == pytest.approx(2.295e-05)