This commit is contained in:
devin-ai-integration[bot] 2026-10-04 23:06:36 +05:30 • committed by GitHub
commit 86478467f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1777 additions and 5 deletions

View file

@ -2912,6 +2912,7 @@ class Router:
cast(_HiddenParamsHost, fallback_item)._hidden_params = {
**item_hidden_params,
**fallback_hidden_params,
"response_cost": item_hidden_params.get("response_cost"),
"additional_headers": {**item_headers, **fallback_headers},
}

View file

@ -28,6 +28,7 @@ class Reply:
chunks: tuple[bytes, ...] | None = None
abort_after: int | None = None
gate_after_first: threading.Event | None = None
gate_timeout_seconds: float = 5.0
pause_between_chunks: float = 0
headers: Mapping[str, str] = MappingProxyType({})
drop_connection: bool = False
@ -109,7 +110,9 @@ def wire_server(
self.wfile.write(b"%x\r\n%s\r\n" % (len(chunk), chunk))
self.wfile.flush()
if index == 0 and reply.gate_after_first is not None:
assert reply.gate_after_first.wait(timeout=5), "Stream barrier was never released"
assert reply.gate_after_first.wait(timeout=reply.gate_timeout_seconds), (
"Stream barrier was never released"
)
if reply.pause_between_chunks and index + 1 < len(reply.chunks):
time.sleep(reply.pause_between_chunks)
else:

View file

@ -0,0 +1,44 @@
import json
import os
from collections.abc import AsyncGenerator
from itertools import count
from typing import Final
from litellm.integrations.custom_logger import CustomLogger
from litellm.proxy._types import UserAPIKeyAuth
from litellm.types.utils import ModelResponseStream
class FallbackCostRecorder(CustomLogger):
async def async_post_call_streaming_iterator_hook(
self,
user_api_key_dict: UserAPIKeyAuth,
response: AsyncGenerator[ModelResponseStream, None],
request_data: dict[str, object],
) -> AsyncGenerator[ModelResponseStream, None]:
log_path: Final = os.environ["LITELLM_FALLBACK_COST_LOG"]
chunk_indices: Final = count()
async for item in response:
hidden_params: Final = getattr(item, "_hidden_params", None)
usage: Final = getattr(item, "usage", None)
event_type: Final = getattr(item, "type", None)
record: Final = {
"request_model": request_data.get("model"),
"item_type": type(item).__name__,
"worker_pid": os.getpid(),
"response_cost": hidden_params.get("response_cost") if isinstance(hidden_params, dict) else None,
"usage_cost": getattr(
usage,
"cost",
getattr(getattr(getattr(item, "response", None), "usage", None), "cost", None),
),
"event_type": event_type,
"chunk_index": next(chunk_indices) if event_type is None else None,
"has_hidden_params": hasattr(item, "_hidden_params"),
}
with open(log_path, "a", encoding="utf-8") as log:
log.write(json.dumps(record, default=str) + "\n")
yield item
proxy_handler_instance = FallbackCostRecorder()

File diff suppressed because it is too large Load diff

View file

@ -3035,6 +3035,36 @@ def test_completion_streaming_iterator_preserves_response_headers():
assert result._hidden_params["additional_headers"]["llm_provider-x-request-id"] == "req-provider-sync"
@pytest.mark.parametrize("response_cost", (None, 0.00015))
def test_apply_fallback_hidden_params_to_item_keeps_the_chunks_own_response_cost(
response_cost: float | None,
) -> None:
chunk: Final = litellm.ModelResponseStream(choices=[{"index": 0, "delta": {"content": "chunk"}}])
chunk._hidden_params = {
"response_cost": response_cost,
"model_id": "failed-deployment",
"additional_headers": {"x-a": "1"},
}
Router._apply_fallback_hidden_params_to_item(
chunk,
(
{
"response_cost": 0.0,
"model_id": "fallback-deployment",
"additional_headers": {"x-b": "2"},
},
{"x-b": "2"},
),
)
assert chunk._hidden_params == {
"response_cost": response_cost,
"model_id": "fallback-deployment",
"additional_headers": {"x-a": "1", "x-b": "2"},
}
def test_adopt_fallback_response_headers_replaces_rather_than_merges():
"""LIT-6767: direct unit for FallbackAwareStreamWrapper.adopt_fallback_response_headers.