mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-08 08:08:37 +00:00
Merge 5d0de99c2d into 0070ce23b3
This commit is contained in:
commit
d749f68807
3 changed files with 115 additions and 4 deletions
|
|
@ -1734,14 +1734,25 @@ 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)
|
||||
|
|
@ -1751,6 +1762,7 @@ def completion_cost(
|
|||
for idx, (candidate_model, candidate_pricing_provider) in enumerate(potential_model_names):
|
||||
model = candidate_model
|
||||
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)
|
||||
|
||||
|
|
@ -1820,8 +1832,13 @@ 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",
|
||||
candidate_custom_llm_provider or custom_llm_provider or None,
|
||||
)
|
||||
)
|
||||
region_name = hidden_params.get("region_name", region_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -122,8 +122,27 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
|
|||
returned_response: Final = ModelResponse(**response_json)
|
||||
set_provider_response_headers_in_hidden_params(returned_response, response.headers)
|
||||
|
||||
sh: Final = model_response.hidden_params # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] # raw
|
||||
dh: Final = returned_response.hidden_params # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] # raw
|
||||
for k, v in sh.items(): # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] # raw
|
||||
if k not in dh:
|
||||
dh[k] = v
|
||||
|
||||
resolved_provider: Final = custom_llm_provider or (
|
||||
sh["custom_llm_provider"]
|
||||
if "custom_llm_provider" in sh and isinstance(sh["custom_llm_provider"], str)
|
||||
else None
|
||||
)
|
||||
if custom_llm_provider is not None:
|
||||
returned_response.model = custom_llm_provider + "/" + (returned_response.model or "")
|
||||
elif resolved_provider is not None:
|
||||
dh["custom_llm_provider"] = resolved_provider
|
||||
if (
|
||||
returned_response.model
|
||||
and "/" in returned_response.model
|
||||
and not returned_response.model.startswith(f"{resolved_provider}/")
|
||||
):
|
||||
returned_response.model = f"{resolved_provider}/{returned_response.model}"
|
||||
|
||||
if base_model is not None:
|
||||
returned_response.hidden_params["model"] = base_model
|
||||
|
|
@ -143,6 +162,13 @@ class OpenAILikeChatConfig(OpenAIGPTConfig):
|
|||
api_key: str | None = None,
|
||||
json_mode: bool | None = None,
|
||||
) -> ModelResponse:
|
||||
if "custom_llm_provider" in litellm_params and isinstance(
|
||||
litellm_params["custom_llm_provider"], # pyright: ignore[reportUnknownArgumentType] # untyped
|
||||
str,
|
||||
):
|
||||
mh: Final = model_response.hidden_params # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] # raw
|
||||
if "custom_llm_provider" not in mh:
|
||||
mh["custom_llm_provider"] = litellm_params["custom_llm_provider"] # pyright: ignore[reportUnknownArgumentType] # untyped
|
||||
return OpenAILikeChatConfig._transform_response(
|
||||
model=model,
|
||||
response=raw_response,
|
||||
|
|
|
|||
|
|
@ -54,3 +54,71 @@ 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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue