Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/pass_through_endpoints/llm_provider_handlers/base_passthrough_logging_handler.py: 38%
70 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1import json
2from datetime import datetime
3from typing import TYPE_CHECKING, Any, Final
5import httpx
7import litellm
8from litellm._logging import verbose_proxy_logger
9from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
10from litellm.litellm_core_utils.litellm_logging import (
11 get_standard_logging_object_payload,
12)
13from litellm.llms.base_llm.chat.transformation import BaseConfig
14from litellm.proxy._types import PassThroughEndpointLoggingTypedDict
15from litellm.proxy.auth.auth_utils import get_end_user_id_from_request_body
16from litellm.types.passthrough_endpoints.pass_through_endpoints import (
17 PassthroughStandardLoggingPayload,
18)
19from litellm.types.utils import LlmProviders, ModelResponse, TextCompletionResponse
21if TYPE_CHECKING: 21 ↛ 22line 21 didn't jump to line 22 because the condition on line 21 was never true
22 from ..success_handler import PassThroughEndpointLogging
23 from ..types import EndpointType
24else:
25 PassThroughEndpointLogging = Any
26 EndpointType = Any
28from abc import ABC, abstractmethod
31class BasePassthroughLoggingHandler(ABC):
32 @property
33 @abstractmethod
34 def llm_provider_name(self) -> LlmProviders:
35 pass
37 @abstractmethod
38 def get_provider_config(self, model: str) -> BaseConfig:
39 pass
41 def passthrough_chat_handler(
42 self,
43 httpx_response: httpx.Response,
44 response_body: dict,
45 logging_obj: LiteLLMLoggingObj,
46 url_route: str,
47 result: str,
48 start_time: datetime,
49 end_time: datetime,
50 cache_hit: bool,
51 request_body: dict,
52 **kwargs,
53 ) -> PassThroughEndpointLoggingTypedDict:
54 """
55 Transforms LLM response to OpenAI response, generates a standard logging object so downstream logging can be handled
56 """
57 model: Final = request_body.get("model", response_body.get("model", ""))
58 provider_config: Final = self.get_provider_config(model=model)
59 litellm_model_response: Final[ModelResponse] = provider_config.transform_response(
60 raw_response=httpx_response,
61 model_response=litellm.ModelResponse(),
62 model=model,
63 messages=[],
64 logging_obj=logging_obj,
65 optional_params={},
66 api_key="",
67 request_data={},
68 encoding=litellm.encoding,
69 json_mode=False,
70 litellm_params={},
71 )
73 kwargs = self._create_response_logging_payload(
74 litellm_model_response=litellm_model_response,
75 model=model,
76 kwargs=kwargs,
77 start_time=start_time,
78 end_time=end_time,
79 logging_obj=logging_obj,
80 )
82 return {
83 "result": litellm_model_response,
84 "kwargs": kwargs,
85 }
87 def _get_user_from_metadata(
88 self,
89 passthrough_logging_payload: PassthroughStandardLoggingPayload,
90 ) -> str | None:
91 request_body: Final = passthrough_logging_payload.get("request_body")
92 if request_body:
93 return get_end_user_id_from_request_body(request_body)
94 return None
96 def _create_response_logging_payload(
97 self,
98 litellm_model_response: ModelResponse | TextCompletionResponse,
99 model: str,
100 kwargs: dict,
101 start_time: datetime,
102 end_time: datetime,
103 logging_obj: LiteLLMLoggingObj,
104 ) -> dict:
105 """
106 Create the standard logging object for Generic LLM passthrough
108 handles streaming and non-streaming responses
109 """
111 try:
112 response_cost: Final = litellm.completion_cost(
113 completion_response=litellm_model_response,
114 model=model,
115 )
117 kwargs["response_cost"] = response_cost
118 kwargs["model"] = model
119 # the pass-through success path reads spend from
120 # model_call_details["response_cost"], not from kwargs
121 logging_obj.model_call_details["response_cost"] = response_cost
122 passthrough_logging_payload: Final[PassthroughStandardLoggingPayload | None] = kwargs.get(
123 "passthrough_logging_payload"
124 )
125 if passthrough_logging_payload:
126 user: Final = self._get_user_from_metadata(
127 passthrough_logging_payload=passthrough_logging_payload,
128 )
129 if user:
130 kwargs.setdefault("litellm_params", {})
131 kwargs["litellm_params"].update({"proxy_server_request": {"body": {"user": user}}})
133 # Make standard logging object for Anthropic
134 standard_logging_object: Final = get_standard_logging_object_payload(
135 kwargs=kwargs,
136 init_response_obj=litellm_model_response,
137 start_time=start_time,
138 end_time=end_time,
139 logging_obj=logging_obj,
140 status="success",
141 )
143 # pretty print standard logging object
144 verbose_proxy_logger.debug(
145 "standard_logging_object= %s",
146 json.dumps(standard_logging_object, indent=4),
147 )
148 kwargs["standard_logging_object"] = standard_logging_object
150 # set litellm_call_id to logging response object
151 litellm_model_response.id = logging_obj.litellm_call_id
152 litellm_model_response.model = model
153 logging_obj.model_call_details["model"] = model
154 return kwargs
155 except Exception as e:
156 verbose_proxy_logger.exception("Error creating LLM passthrough response logging payload: %s", e)
157 return kwargs
159 @abstractmethod
160 def _build_complete_streaming_response(
161 self,
162 all_chunks: list[str],
163 litellm_logging_obj: LiteLLMLoggingObj,
164 model: str,
165 ) -> ModelResponse | TextCompletionResponse | None:
166 """
167 Builds complete response from raw chunks
169 - Converts str chunks to generic chunks
170 - Converts generic chunks to litellm chunks (OpenAI format)
171 - Builds complete response from litellm chunks
172 """
174 def _handle_logging_llm_collected_chunks(
175 self,
176 litellm_logging_obj: LiteLLMLoggingObj,
177 passthrough_success_handler_obj: PassThroughEndpointLogging,
178 url_route: str,
179 request_body: dict,
180 endpoint_type: EndpointType,
181 start_time: datetime,
182 all_chunks: list[str],
183 end_time: datetime,
184 ) -> PassThroughEndpointLoggingTypedDict:
185 """
186 Takes raw chunks from Anthropic passthrough endpoint and logs them in litellm callbacks
188 - Builds complete response from chunks
189 - Creates standard logging object
190 - Logs in litellm callbacks
191 """
193 model: Final = request_body.get("model", "")
194 complete_streaming_response: Final = self._build_complete_streaming_response(
195 all_chunks=all_chunks,
196 litellm_logging_obj=litellm_logging_obj,
197 model=model,
198 )
199 if complete_streaming_response is None:
200 verbose_proxy_logger.error(
201 "Unable to build complete streaming response for Anthropic passthrough endpoint, not logging..."
202 )
203 return {
204 "result": None,
205 "kwargs": {},
206 }
207 kwargs: Final = self._create_response_logging_payload(
208 litellm_model_response=complete_streaming_response,
209 model=model,
210 kwargs={},
211 start_time=start_time,
212 end_time=end_time,
213 logging_obj=litellm_logging_obj,
214 )
216 return {
217 "result": complete_streaming_response,
218 "kwargs": kwargs,
219 }