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

1from typing import Final 

2 

3from fastapi import APIRouter, Depends, Request, Response 

4from fastapi.responses import ORJSONResponse 

5 

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 

11 

12router: Final = APIRouter( 

13 tags=["google genai endpoints"], 

14) 

15 

16 

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 ) 

44 

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 

48 

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 ) 

76 

77 

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 ) 

105 

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 

113 

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 ) 

141 

142 

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 

171 

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 

176 

177 # Translate contents to openai format messages using the adapter 

178 messages = GoogleGenAIAdapter().translate_generate_content_to_completion(model_name, contents).get("messages", []) 

179 

180 token_request: Final = TokenCountRequest( 

181 model=model_name, 

182 contents=contents, 

183 messages=messages, # compatibility when use openai-like endpoint 

184 ) 

185 

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 ) 

204 

205 ######################################################### 

206 # Return the response in the well known format 

207 ######################################################### 

208 return TokenCountDetailsResponse( 

209 totalTokens=0, 

210 promptTokensDetails=[], 

211 ) 

212 

213 

214# ============================================================ 

215# Google Interactions API Endpoints 

216# Per OpenAPI spec: https://ai.google.dev/static/api/interactions.openapi.json 

217# ============================================================ 

218 

219 

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. 

239  

240 Per OpenAPI spec: POST /{api_version}/interactions 

241  

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") 

245  

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 ) 

270 

271 data: Final = await _read_request_body(request=request) 

272 

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" 

276 

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 ) 

304 

305 

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. 

326 

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 ) 

342 

343 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"} 

344 

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 ) 

372 

373 

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. 

394 

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 ) 

410 

411 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"} 

412 

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 ) 

440 

441 

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. 

462 

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 ) 

478 

479 data: Final = {"interaction_id": interaction_id, "custom_llm_provider": "gemini"} 

480 

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 )