Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/google_endpoints/endpoints.py: 93%
96 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
1from typing import Final
3from fastapi import APIRouter, Depends, Request, Response
4from fastapi.responses import ORJSONResponse
6from litellm.proxy._types import *
7from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth
8from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
9from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
10from litellm.types.llms.vertex_ai import TokenCountDetailsResponse
12router: Final = APIRouter(
13 tags=["google genai endpoints"],
14)
17@router.post(
18 "/v1beta/models/{model_name:path}:generateContent",
19 dependencies=[Depends(user_api_key_auth)],
20)
21@router.post(
22 "/models/{model_name:path}:generateContent",
23 dependencies=[Depends(user_api_key_auth)],
24)
25async def google_generate_content(
26 request: Request,
27 model_name: str,
28 fastapi_response: Response,
29 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
30):
31 from litellm.proxy.proxy_server import (
32 general_settings,
33 llm_router,
34 proxy_config,
35 proxy_logging_obj,
36 select_data_generator,
37 user_api_base,
38 user_max_tokens,
39 user_model,
40 user_request_timeout,
41 user_temperature,
42 version,
43 )
45 data: Final = await _read_request_body(request=request)
46 if "model" not in data: 46 ↛ 49line 46 didn't jump to line 49 because the condition on line 46 was always true
47 data["model"] = model_name
49 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
50 try:
51 return await processor.base_process_llm_request(
52 request=request,
53 fastapi_response=fastapi_response,
54 user_api_key_dict=user_api_key_dict,
55 route_type="agenerate_content",
56 proxy_logging_obj=proxy_logging_obj,
57 llm_router=llm_router,
58 general_settings=general_settings,
59 proxy_config=proxy_config,
60 select_data_generator=select_data_generator,
61 model=model_name,
62 user_model=user_model,
63 user_temperature=user_temperature,
64 user_request_timeout=user_request_timeout,
65 user_max_tokens=user_max_tokens,
66 user_api_base=user_api_base,
67 version=version,
68 )
69 except Exception as e:
70 raise await processor._handle_llm_api_exception(
71 e=e,
72 user_api_key_dict=user_api_key_dict,
73 proxy_logging_obj=proxy_logging_obj,
74 version=version,
75 )
78@router.post(
79 "/v1beta/models/{model_name:path}:streamGenerateContent",
80 dependencies=[Depends(user_api_key_auth)],
81)
82@router.post(
83 "/models/{model_name:path}:streamGenerateContent",
84 dependencies=[Depends(user_api_key_auth)],
85)
86async def google_stream_generate_content(
87 request: Request,
88 model_name: str,
89 fastapi_response: Response,
90 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
91):
92 from litellm.proxy.proxy_server import (
93 general_settings,
94 llm_router,
95 proxy_config,
96 proxy_logging_obj,
97 select_data_generator,
98 user_api_base,
99 user_max_tokens,
100 user_model,
101 user_request_timeout,
102 user_temperature,
103 version,
104 )
106 data: Final = await _read_request_body(request=request)
107 if "model" not in data: 107 ↛ 109line 107 didn't jump to line 109 because the condition on line 107 was always true
108 data["model"] = model_name
109 data["stream"] = True
110 # google-genai SDK (?alt=sse) must not receive OpenAI's data: [DONE] terminator.
111 data["_litellm_skip_openai_stream_done"] = True
112 data["_litellm_raw_sse_stream"] = True
114 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
115 try:
116 return await processor.base_process_llm_request(
117 request=request,
118 fastapi_response=fastapi_response,
119 user_api_key_dict=user_api_key_dict,
120 route_type="agenerate_content_stream",
121 proxy_logging_obj=proxy_logging_obj,
122 llm_router=llm_router,
123 general_settings=general_settings,
124 proxy_config=proxy_config,
125 select_data_generator=select_data_generator,
126 model=model_name,
127 user_model=user_model,
128 user_temperature=user_temperature,
129 user_request_timeout=user_request_timeout,
130 user_max_tokens=user_max_tokens,
131 user_api_base=user_api_base,
132 version=version,
133 )
134 except Exception as e:
135 raise await processor._handle_llm_api_exception(
136 e=e,
137 user_api_key_dict=user_api_key_dict,
138 proxy_logging_obj=proxy_logging_obj,
139 version=version,
140 )
143@router.post(
144 "/v1beta/models/{model_name:path}:countTokens",
145 dependencies=[Depends(user_api_key_auth)],
146 response_model=TokenCountDetailsResponse,
147)
148@router.post(
149 "/models/{model_name:path}:countTokens",
150 dependencies=[Depends(user_api_key_auth)],
151 response_model=TokenCountDetailsResponse,
152)
153async def google_count_tokens(request: Request, model_name: str):
154 """
155 ```json
156 return {
157 "totalTokens": 31,
158 "totalBillableCharacters": 96,
159 "promptTokensDetails": [
160 {
161 "modality": "TEXT",
162 "tokenCount": 31
163 }
164 ]
165 }
166 ```
167 """
168 from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
169 from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
170 from litellm.proxy.proxy_server import token_counter as internal_token_counter
172 data: Final = await _read_request_body(request=request)
173 contents: Final = data.get("contents", [])
174 # Create TokenCountRequest for the internal endpoint
175 from litellm.proxy._types import TokenCountRequest
177 # Translate contents to openai format messages using the adapter
178 messages = GoogleGenAIAdapter().translate_generate_content_to_completion(model_name, contents).get("messages", [])
180 token_request: Final = TokenCountRequest(
181 model=model_name,
182 contents=contents,
183 messages=messages, # compatibility when use openai-like endpoint
184 )
186 # Call the internal token counter function with direct request flag set to False
187 token_response: Final = await internal_token_counter(
188 request=token_request,
189 call_endpoint=True,
190 )
191 if token_response is not None: 191 ↛ 208line 191 didn't jump to line 208 because the condition on line 191 was always true
192 # cast the response to the well known format
193 original_response: Final[dict] = token_response.original_response or {}
194 if original_response: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 return TokenCountDetailsResponse(
196 totalTokens=original_response.get("totalTokens", 0),
197 promptTokensDetails=original_response.get("promptTokensDetails", []),
198 )
199 else:
200 return TokenCountDetailsResponse(
201 totalTokens=token_response.total_tokens or 0,
202 promptTokensDetails=[],
203 )
205 #########################################################
206 # Return the response in the well known format
207 #########################################################
208 return TokenCountDetailsResponse(
209 totalTokens=0,
210 promptTokensDetails=[],
211 )
214# ============================================================
215# Google Interactions API Endpoints
216# Per OpenAPI spec: https://ai.google.dev/static/api/interactions.openapi.json
217# ============================================================
220@router.post(
221 "/v1beta/interactions",
222 dependencies=[Depends(user_api_key_auth)],
223 response_class=ORJSONResponse,
224 tags=["interactions"],
225)
226@router.post(
227 "/interactions",
228 dependencies=[Depends(user_api_key_auth)],
229 response_class=ORJSONResponse,
230 tags=["interactions"],
231)
232async def create_interaction(
233 request: Request,
234 fastapi_response: Response,
235 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
236):
237 """
238 Create a new interaction using Google's Interactions API.
240 Per OpenAPI spec: POST /{api_version}/interactions
242 Supports both model interactions and agent interactions:
243 - Model: Provide `model` parameter (e.g., "gemini-2.5-flash")
244 - Agent: Provide `agent` parameter (e.g., "deep-research-pro-preview-12-2025")
246 Example:
247 ```bash
248 curl -X POST "http://localhost:4000/v1beta/interactions" \
249 -H "Authorization: Bearer sk-1234" \
250 -H "Content-Type: application/json" \
251 -d '{
252 "model": "gemini/gemini-2.5-flash",
253 "input": "Hello, how are you?"
254 }'
255 ```
256 """
257 from litellm.proxy.proxy_server import (
258 general_settings,
259 llm_router,
260 proxy_config,
261 proxy_logging_obj,
262 select_data_generator,
263 user_api_base,
264 user_max_tokens,
265 user_model,
266 user_request_timeout,
267 user_temperature,
268 version,
269 )
271 data: Final = await _read_request_body(request=request)
273 # Default to gemini provider for interactions
274 if "custom_llm_provider" not in data: 274 ↛ 277line 274 didn't jump to line 277 because the condition on line 274 was always true
275 data["custom_llm_provider"] = "gemini"
277 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
278 try:
279 return await processor.base_process_llm_request(
280 request=request,
281 fastapi_response=fastapi_response,
282 user_api_key_dict=user_api_key_dict,
283 route_type="acreate_interaction",
284 proxy_logging_obj=proxy_logging_obj,
285 llm_router=llm_router,
286 general_settings=general_settings,
287 proxy_config=proxy_config,
288 select_data_generator=select_data_generator,
289 model=data.get("model"),
290 user_model=user_model,
291 user_temperature=user_temperature,
292 user_request_timeout=user_request_timeout,
293 user_max_tokens=user_max_tokens,
294 user_api_base=user_api_base,
295 version=version,
296 )
297 except Exception as e:
298 raise await processor._handle_llm_api_exception(
299 e=e,
300 user_api_key_dict=user_api_key_dict,
301 proxy_logging_obj=proxy_logging_obj,
302 version=version,
303 )
306@router.get(
307 "/v1beta/interactions/{interaction_id}",
308 dependencies=[Depends(user_api_key_auth)],
309 response_class=ORJSONResponse,
310 tags=["interactions"],
311)
312@router.get(
313 "/interactions/{interaction_id}",
314 dependencies=[Depends(user_api_key_auth)],
315 response_class=ORJSONResponse,
316 tags=["interactions"],
317)
318async def get_interaction(
319 request: Request,
320 interaction_id: str,
321 fastapi_response: Response,
322 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
323):
324 """
325 Get an interaction by ID.
327 Per OpenAPI spec: GET /{api_version}/interactions/{interaction_id}
328 """
329 from litellm.proxy.proxy_server import (
330 general_settings,
331 llm_router,
332 proxy_config,
333 proxy_logging_obj,
334 select_data_generator,
335 user_api_base,
336 user_max_tokens,
337 user_model,
338 user_request_timeout,
339 user_temperature,
340 version,
341 )
343 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"}
345 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
346 try:
347 return await processor.base_process_llm_request(
348 request=request,
349 fastapi_response=fastapi_response,
350 user_api_key_dict=user_api_key_dict,
351 route_type="aget_interaction",
352 proxy_logging_obj=proxy_logging_obj,
353 llm_router=llm_router,
354 general_settings=general_settings,
355 proxy_config=proxy_config,
356 select_data_generator=select_data_generator,
357 model=None,
358 user_model=user_model,
359 user_temperature=user_temperature,
360 user_request_timeout=user_request_timeout,
361 user_max_tokens=user_max_tokens,
362 user_api_base=user_api_base,
363 version=version,
364 )
365 except Exception as e:
366 raise await processor._handle_llm_api_exception(
367 e=e,
368 user_api_key_dict=user_api_key_dict,
369 proxy_logging_obj=proxy_logging_obj,
370 version=version,
371 )
374@router.delete(
375 "/v1beta/interactions/{interaction_id}",
376 dependencies=[Depends(user_api_key_auth)],
377 response_class=ORJSONResponse,
378 tags=["interactions"],
379)
380@router.delete(
381 "/interactions/{interaction_id}",
382 dependencies=[Depends(user_api_key_auth)],
383 response_class=ORJSONResponse,
384 tags=["interactions"],
385)
386async def delete_interaction(
387 request: Request,
388 interaction_id: str,
389 fastapi_response: Response,
390 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
391):
392 """
393 Delete an interaction by ID.
395 Per OpenAPI spec: DELETE /{api_version}/interactions/{interaction_id}
396 """
397 from litellm.proxy.proxy_server import (
398 general_settings,
399 llm_router,
400 proxy_config,
401 proxy_logging_obj,
402 select_data_generator,
403 user_api_base,
404 user_max_tokens,
405 user_model,
406 user_request_timeout,
407 user_temperature,
408 version,
409 )
411 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"}
413 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
414 try:
415 return await processor.base_process_llm_request(
416 request=request,
417 fastapi_response=fastapi_response,
418 user_api_key_dict=user_api_key_dict,
419 route_type="adelete_interaction",
420 proxy_logging_obj=proxy_logging_obj,
421 llm_router=llm_router,
422 general_settings=general_settings,
423 proxy_config=proxy_config,
424 select_data_generator=select_data_generator,
425 model=None,
426 user_model=user_model,
427 user_temperature=user_temperature,
428 user_request_timeout=user_request_timeout,
429 user_max_tokens=user_max_tokens,
430 user_api_base=user_api_base,
431 version=version,
432 )
433 except Exception as e:
434 raise await processor._handle_llm_api_exception(
435 e=e,
436 user_api_key_dict=user_api_key_dict,
437 proxy_logging_obj=proxy_logging_obj,
438 version=version,
439 )
442@router.post(
443 "/v1beta/interactions/{interaction_id}/cancel",
444 dependencies=[Depends(user_api_key_auth)],
445 response_class=ORJSONResponse,
446 tags=["interactions"],
447)
448@router.post(
449 "/interactions/{interaction_id}/cancel",
450 dependencies=[Depends(user_api_key_auth)],
451 response_class=ORJSONResponse,
452 tags=["interactions"],
453)
454async def cancel_interaction(
455 request: Request,
456 interaction_id: str,
457 fastapi_response: Response,
458 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
459):
460 """
461 Cancel an interaction by ID.
463 Per OpenAPI spec: POST /{api_version}/interactions/{interaction_id}:cancel
464 """
465 from litellm.proxy.proxy_server import (
466 general_settings,
467 llm_router,
468 proxy_config,
469 proxy_logging_obj,
470 select_data_generator,
471 user_api_base,
472 user_max_tokens,
473 user_model,
474 user_request_timeout,
475 user_temperature,
476 version,
477 )
479 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"}
481 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
482 try:
483 return await processor.base_process_llm_request(
484 request=request,
485 fastapi_response=fastapi_response,
486 user_api_key_dict=user_api_key_dict,
487 route_type="acancel_interaction",
488 proxy_logging_obj=proxy_logging_obj,
489 llm_router=llm_router,
490 general_settings=general_settings,
491 proxy_config=proxy_config,
492 select_data_generator=select_data_generator,
493 model=None,
494 user_model=user_model,
495 user_temperature=user_temperature,
496 user_request_timeout=user_request_timeout,
497 user_max_tokens=user_max_tokens,
498 user_api_base=user_api_base,
499 version=version,
500 )
501 except Exception as e:
502 raise await processor._handle_llm_api_exception(
503 e=e,
504 user_api_key_dict=user_api_key_dict,
505 proxy_logging_obj=proxy_logging_obj,
506 version=version,
507 )