mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
fix(vertex): leave a streamed generateContent response unpriced for the logger
The Vertex pass-through handler assembled a streamed generateContent response with stream_chunk_builder but no logging object, so the builder priced the assembled response by its model name alone and stamped that cost into its hidden params; the logger then reused the stamped cost as already calculated, overriding the deployment's custom pricing. Pass the logging object, as the Anthropic handler does, so the builder leaves the pricing to the logger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8319c2d035
commit
6ce2248bf3
2 changed files with 56 additions and 1 deletions
|
|
@ -659,7 +659,14 @@ class VertexPassthroughLoggingHandler:
|
|||
continue
|
||||
all_openai_chunks.append(parsed_chunk)
|
||||
|
||||
complete_streaming_response: Final = litellm.stream_chunk_builder(chunks=all_openai_chunks)
|
||||
# With the logging object the builder leaves pricing to the logger,
|
||||
# which honours the deployment's custom pricing; without it the
|
||||
# builder stamps a cost priced by model name alone, which the logger
|
||||
# then reuses as already calculated.
|
||||
complete_streaming_response: Final = litellm.stream_chunk_builder(
|
||||
chunks=all_openai_chunks,
|
||||
logging_obj=litellm_logging_obj,
|
||||
)
|
||||
|
||||
return complete_streaming_response
|
||||
|
||||
|
|
|
|||
|
|
@ -174,3 +174,51 @@ def test_generate_content_cost_uses_the_deployments_custom_pricing(monkeypatch:
|
|||
)
|
||||
|
||||
assert kwargs["response_cost"] == pytest.approx(1.5 + 7.5)
|
||||
|
||||
|
||||
def test_assembled_stream_is_priced_at_the_deployments_custom_pricing(monkeypatch: pytest.MonkeyPatch):
|
||||
"""The logger prices a streamed generateContent call from the response the
|
||||
handler assembles; that response must not arrive carrying a cost priced
|
||||
by model name alone, which the logger would reuse as already calculated."""
|
||||
import litellm
|
||||
|
||||
deployment_id: Final = "vertex-gemini-with-custom-price"
|
||||
custom_price: Final = {"input_cost_per_token": 1.5e-6, "output_cost_per_token": 7.5e-6}
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
deployment_id,
|
||||
{**custom_price, "litellm_provider": "vertex_ai-language-models", "mode": "chat"},
|
||||
)
|
||||
logging_obj: Final = Logging(
|
||||
model="gemini-2.5-flash",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=True,
|
||||
call_type="pass_through_endpoint",
|
||||
start_time=datetime(2026, 1, 1),
|
||||
litellm_call_id="call-1",
|
||||
function_id="fn-1",
|
||||
)
|
||||
logging_obj.update_environment_variables(
|
||||
litellm_params={"litellm_metadata": {"model_info": {"id": deployment_id, **custom_price}}},
|
||||
optional_params={},
|
||||
model="gemini-2.5-flash",
|
||||
custom_llm_provider="vertex_ai",
|
||||
)
|
||||
last_chunk: Final = {
|
||||
"candidates": [{"content": {"parts": [{"text": "Hi."}], "role": "model"}, "finishReason": "STOP", "index": 0}],
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 1_000_000,
|
||||
"candidatesTokenCount": 1_000_000,
|
||||
"totalTokenCount": 2_000_000,
|
||||
},
|
||||
"modelVersion": "gemini-2.5-flash",
|
||||
}
|
||||
|
||||
response: Final = VertexPassthroughLoggingHandler._build_complete_streaming_response(
|
||||
all_chunks=[f"data: {json.dumps(last_chunk)}"],
|
||||
litellm_logging_obj=logging_obj,
|
||||
model="gemini-2.5-flash",
|
||||
url_route="/v1/projects/p/locations/global/publishers/google/models/gemini-2.5-flash:streamGenerateContent",
|
||||
)
|
||||
|
||||
assert logging_obj.response_cost_calculator(result=response) == pytest.approx(1.5 + 7.5)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue