Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/fine_tuning_endpoints/endpoints.py: 25%

206 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1######################################################################### 

2 

3# /v1/fine_tuning Endpoints 

4 

5# Equivalent of https://platform.openai.com/docs/api-reference/fine-tuning 

6########################################################################## 

7 

8import asyncio 

9from typing import Final, cast 

10 

11from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response 

12 

13import litellm 

14from litellm._logging import verbose_proxy_logger 

15from litellm.proxy._types import * 

16from litellm.proxy.auth.user_api_key_auth import user_api_key_auth 

17from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing 

18from litellm.proxy.openai_files_endpoints.common_utils import ( 

19 _is_base64_encoded_unified_file_id, 

20 validate_managed_id_requirement, 

21) 

22from litellm.proxy.utils import handle_exception_on_proxy 

23from litellm.types.utils import LiteLLMFineTuningJob 

24 

25router: Final = APIRouter() 

26 

27from litellm.types.llms.openai import LiteLLMFineTuningJobCreate 

28 

29fine_tuning_config = None 

30 

31 

32def set_fine_tuning_config(config): 

33 if config is None: 33 ↛ 37line 33 didn't jump to line 37 because the condition on line 33 was always true

34 return 

35 

36 global fine_tuning_config 

37 if not isinstance(config, list): 

38 raise ValueError("invalid fine_tuning config, expected a list is not a list") 

39 

40 for element in config: 

41 if isinstance(element, dict): 

42 for key, value in element.items(): 

43 if isinstance(value, str) and value.startswith("os.environ/"): 

44 element[key] = litellm.get_secret(value) 

45 

46 fine_tuning_config = config 

47 

48 

49# Function to search for specific custom_llm_provider and return its configuration 

50def get_fine_tuning_provider_config( 

51 custom_llm_provider: str, 

52): 

53 global fine_tuning_config 

54 if fine_tuning_config is None: 

55 raise ValueError("fine_tuning_config is not set, set it on your config.yaml file.") 

56 for setting in fine_tuning_config: 

57 if setting.get("custom_llm_provider") == custom_llm_provider: 

58 return setting 

59 return None 

60 

61 

62@router.post( 

63 "/v1/fine_tuning/jobs", 

64 dependencies=[Depends(user_api_key_auth)], 

65 tags=["fine-tuning"], 

66 summary="✨ (Enterprise) Create Fine-Tuning Job", 

67) 

68@router.post( 

69 "/fine_tuning/jobs", 

70 dependencies=[Depends(user_api_key_auth)], 

71 tags=["fine-tuning"], 

72 summary="✨ (Enterprise) Create Fine-Tuning Job", 

73) 

74async def create_fine_tuning_job( 

75 request: Request, 

76 fastapi_response: Response, 

77 fine_tuning_request: LiteLLMFineTuningJobCreate, 

78 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

79): 

80 """ 

81 Creates a fine-tuning job which begins the process of creating a new model from a given dataset. 

82 This is the equivalent of POST https://api.openai.com/v1/fine_tuning/jobs 

83 

84 Supports Identical Params as: https://platform.openai.com/docs/api-reference/fine-tuning/create 

85 

86 Example Curl: 

87 ``` 

88 curl http://localhost:4000/v1/fine_tuning/jobs \ 

89 -H "Content-Type: application/json" \ 

90 -H "Authorization: Bearer sk-1234" \ 

91 -d '{ 

92 "model": "gpt-3.5-turbo", 

93 "training_file": "file-abc123", 

94 "hyperparameters": { 

95 "n_epochs": 4 

96 } 

97 }' 

98 ``` 

99 """ 

100 from litellm.proxy.proxy_server import ( 

101 general_settings, 

102 llm_router, 

103 premium_user, 

104 proxy_config, 

105 proxy_logging_obj, 

106 version, 

107 ) 

108 

109 data = fine_tuning_request.model_dump(exclude_none=True) 

110 try: 

111 if premium_user is not True: 111 ↛ 115line 111 didn't jump to line 115 because the condition on line 111 was always true

112 raise ValueError(f"Only premium users can use this endpoint + {CommonProxyErrors.not_premium_user.value}") 

113 # Convert Pydantic model to dict 

114 

115 verbose_proxy_logger.debug( 

116 "Request received by LiteLLM:\n%s", 

117 json.dumps(data, indent=4), 

118 ) 

119 

120 # Include original request and headers in the data 

121 base_llm_response_processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

122 ( 

123 data, 

124 litellm_logging_obj, 

125 ) = await base_llm_response_processor.common_processing_pre_call_logic( 

126 request=request, 

127 general_settings=general_settings, 

128 user_api_key_dict=user_api_key_dict, 

129 version=version, 

130 proxy_logging_obj=proxy_logging_obj, 

131 proxy_config=proxy_config, 

132 route_type="acreate_fine_tuning_job", 

133 ) 

134 

135 ## CHECK IF MANAGED FILE ID 

136 unified_file_id: str | Literal[False] = False 

137 training_file: Final = fine_tuning_request.training_file 

138 await validate_managed_id_requirement( 

139 resource_id=training_file, 

140 resource_kind="file", 

141 user_api_key_dict=user_api_key_dict, 

142 managed_files_obj=proxy_logging_obj.get_proxy_hook("managed_files"), 

143 ) 

144 await validate_managed_id_requirement( 

145 resource_id=fine_tuning_request.validation_file, 

146 resource_kind="file", 

147 user_api_key_dict=user_api_key_dict, 

148 managed_files_obj=proxy_logging_obj.get_proxy_hook("managed_files"), 

149 ) 

150 response: LiteLLMFineTuningJob | None = None 

151 if training_file: 

152 unified_file_id = _is_base64_encoded_unified_file_id(training_file) 

153 ## IF SO, Route based on that 

154 if unified_file_id: 

155 """ """ 

156 if llm_router is None: 

157 raise HTTPException( 

158 status_code=500, 

159 detail={"error": "LLM Router not initialized. Ensure models added to proxy."}, 

160 ) 

161 

162 response = cast(LiteLLMFineTuningJob, await llm_router.acreate_fine_tuning_job(**data)) 

163 response.training_file = unified_file_id 

164 response._hidden_params["unified_file_id"] = unified_file_id 

165 ## ELSE, Route based on custom_llm_provider 

166 elif fine_tuning_request.custom_llm_provider: 

167 # get configs for custom_llm_provider 

168 llm_provider_config: Final = get_fine_tuning_provider_config( 

169 custom_llm_provider=fine_tuning_request.custom_llm_provider, 

170 ) 

171 # add llm_provider_config to data 

172 if llm_provider_config is not None: 

173 data.update(llm_provider_config) 

174 

175 response = await litellm.acreate_fine_tuning_job(**data) 

176 

177 if response is None: 

178 raise ValueError("Invalid request, No litellm managed file id or custom_llm_provider provided.") 

179 

180 ### CALL HOOKS ### - modify outgoing data 

181 _response: Final = await proxy_logging_obj.post_call_success_hook( 

182 data=data, 

183 user_api_key_dict=user_api_key_dict, 

184 response=response, 

185 ) 

186 if _response is not None and isinstance(_response, LiteLLMFineTuningJob): 

187 response = _response 

188 

189 ### ALERTING ### 

190 asyncio.create_task( 

191 proxy_logging_obj.update_request_status(litellm_call_id=data.get("litellm_call_id", ""), status="success") 

192 ) 

193 

194 ### RESPONSE HEADERS ### 

195 hidden_params: Final = getattr(response, "_hidden_params", {}) or {} 

196 model_id: Final = hidden_params.get("model_id", None) or "" 

197 cache_key: Final = hidden_params.get("cache_key", None) or "" 

198 api_base: Final = hidden_params.get("api_base", None) or "" 

199 

200 fastapi_response.headers.update( 

201 ProxyBaseLLMRequestProcessing.get_custom_headers( 

202 user_api_key_dict=user_api_key_dict, 

203 model_id=model_id, 

204 cache_key=cache_key, 

205 api_base=api_base, 

206 version=version, 

207 model_region=getattr(user_api_key_dict, "allowed_model_region", ""), 

208 ) 

209 ) 

210 

211 return response 

212 except Exception as e: 

213 await proxy_logging_obj.post_call_failure_hook( 

214 user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data 

215 ) 

216 verbose_proxy_logger.exception( 

217 "litellm.proxy.proxy_server.create_fine_tuning_job(): Exception occurred - %s", e 

218 ) 

219 raise handle_exception_on_proxy(e) 

220 

221 

222@router.get( 

223 "/v1/fine_tuning/jobs/{fine_tuning_job_id:path}", 

224 dependencies=[Depends(user_api_key_auth)], 

225 tags=["fine-tuning"], 

226 summary="✨ (Enterprise) Retrieve Fine-Tuning Job", 

227) 

228@router.get( 

229 "/fine_tuning/jobs/{fine_tuning_job_id:path}", 

230 dependencies=[Depends(user_api_key_auth)], 

231 tags=["fine-tuning"], 

232 summary="✨ (Enterprise) Retrieve Fine-Tuning Job", 

233) 

234async def retrieve_fine_tuning_job( 

235 request: Request, 

236 fastapi_response: Response, 

237 fine_tuning_job_id: str, 

238 custom_llm_provider: Literal["openai", "azure"] | None = None, 

239 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

240): 

241 """ 

242 Retrieves a fine-tuning job. 

243 This is the equivalent of GET https://api.openai.com/v1/fine_tuning/jobs/{fine_tuning_job_id} 

244 

245 Supported Query Params: 

246 - `custom_llm_provider`: Name of the LiteLLM provider 

247 - `fine_tuning_job_id`: The ID of the fine-tuning job to retrieve. 

248 """ 

249 from litellm.proxy.proxy_server import ( 

250 general_settings, 

251 llm_router, 

252 premium_user, 

253 proxy_config, 

254 proxy_logging_obj, 

255 version, 

256 ) 

257 

258 data: dict = {"fine_tuning_job_id": fine_tuning_job_id} 

259 try: 

260 if premium_user is not True: 260 ↛ 262line 260 didn't jump to line 262 because the condition on line 260 was always true

261 raise ValueError(f"Only premium users can use this endpoint + {CommonProxyErrors.not_premium_user.value}") 

262 await validate_managed_id_requirement( 

263 resource_id=fine_tuning_job_id, 

264 resource_kind="fine-tuning job", 

265 user_api_key_dict=user_api_key_dict, 

266 managed_files_obj=proxy_logging_obj.get_proxy_hook("managed_files"), 

267 ) 

268 # Include original request and headers in the data 

269 base_llm_response_processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

270 ( 

271 data, 

272 litellm_logging_obj, 

273 ) = await base_llm_response_processor.common_processing_pre_call_logic( 

274 request=request, 

275 general_settings=general_settings, 

276 user_api_key_dict=user_api_key_dict, 

277 version=version, 

278 proxy_logging_obj=proxy_logging_obj, 

279 proxy_config=proxy_config, 

280 route_type=CallTypes.aretrieve_fine_tuning_job.value, 

281 ) 

282 

283 try: 

284 request_body = await request.json() 

285 except Exception: 

286 request_body = {} 

287 

288 custom_llm_provider = request_body.get("custom_llm_provider", None) or custom_llm_provider 

289 

290 ## CHECK IF MANAGED FILE ID 

291 unified_finetuning_job_id: str | Literal[False] = False 

292 response: LiteLLMFineTuningJob | None = None 

293 if fine_tuning_job_id: 

294 unified_finetuning_job_id = _is_base64_encoded_unified_file_id(fine_tuning_job_id) 

295 if unified_finetuning_job_id: 

296 if llm_router is None: 

297 raise HTTPException( 

298 status_code=500, 

299 detail={"error": "LLM Router not initialized. Ensure models added to proxy."}, 

300 ) 

301 response = cast( 

302 LiteLLMFineTuningJob, 

303 await llm_router.aretrieve_fine_tuning_job( 

304 **data, 

305 ), 

306 ) 

307 response._hidden_params["unified_finetuning_job_id"] = unified_finetuning_job_id 

308 elif custom_llm_provider: 

309 # get configs for custom_llm_provider 

310 llm_provider_config: Final = get_fine_tuning_provider_config(custom_llm_provider=custom_llm_provider) 

311 

312 if llm_provider_config is not None: 

313 data.update(llm_provider_config) 

314 

315 response = await litellm.aretrieve_fine_tuning_job( 

316 **data, 

317 ) 

318 

319 if response is None: 

320 raise HTTPException( 

321 status_code=400, 

322 detail="Invalid request, No litellm managed file id or custom_llm_provider provided.", 

323 ) 

324 

325 ### CALL HOOKS ### - modify outgoing data 

326 _response: Final = await proxy_logging_obj.post_call_success_hook( 

327 data=data, 

328 user_api_key_dict=user_api_key_dict, 

329 response=response, 

330 ) 

331 if _response is not None and isinstance(_response, LiteLLMFineTuningJob): 

332 response = _response 

333 

334 ### ALERTING ### 

335 asyncio.create_task( 

336 proxy_logging_obj.update_request_status(litellm_call_id=data.get("litellm_call_id", ""), status="success") 

337 ) 

338 

339 ### RESPONSE HEADERS ### 

340 hidden_params: Final = getattr(response, "_hidden_params", {}) or {} 

341 model_id: Final = hidden_params.get("model_id", None) or "" 

342 cache_key: Final = hidden_params.get("cache_key", None) or "" 

343 api_base: Final = hidden_params.get("api_base", None) or "" 

344 

345 fastapi_response.headers.update( 

346 ProxyBaseLLMRequestProcessing.get_custom_headers( 

347 user_api_key_dict=user_api_key_dict, 

348 model_id=model_id, 

349 cache_key=cache_key, 

350 api_base=api_base, 

351 version=version, 

352 model_region=getattr(user_api_key_dict, "allowed_model_region", ""), 

353 ) 

354 ) 

355 

356 return response 

357 

358 except Exception as e: 

359 await proxy_logging_obj.post_call_failure_hook( 

360 user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data 

361 ) 

362 verbose_proxy_logger.exception( 

363 "litellm.proxy.proxy_server.retrieve_fine_tuning_job(): Exception occurred - %s", e 

364 ) 

365 raise handle_exception_on_proxy(e) 

366 

367 

368@router.get( 

369 "/v1/fine_tuning/jobs", 

370 dependencies=[Depends(user_api_key_auth)], 

371 tags=["fine-tuning"], 

372 summary="✨ (Enterprise) List Fine-Tuning Jobs", 

373) 

374@router.get( 

375 "/fine_tuning/jobs", 

376 dependencies=[Depends(user_api_key_auth)], 

377 tags=["fine-tuning"], 

378 summary="✨ (Enterprise) List Fine-Tuning Jobs", 

379) 

380async def list_fine_tuning_jobs( 

381 request: Request, 

382 fastapi_response: Response, 

383 custom_llm_provider: Literal["openai", "azure"] | None = None, 

384 target_model_names: str | None = Query( 

385 default=None, 

386 description="Comma separated list of model names to filter by. Example: 'gpt-4o,gpt-4o-mini'", 

387 ), 

388 after: str | None = None, 

389 limit: int | None = None, 

390 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

391): 

392 """ 

393 Lists fine-tuning jobs for the organization. 

394 This is the equivalent of GET https://api.openai.com/v1/fine_tuning/jobs 

395 

396 Supported Query Params: 

397 - `custom_llm_provider`: Name of the LiteLLM provider 

398 - `after`: Identifier for the last job from the previous pagination request. 

399 - `limit`: Number of fine-tuning jobs to retrieve (default is 20). 

400 """ 

401 from litellm.proxy.proxy_server import ( 

402 general_settings, 

403 llm_router, 

404 premium_user, 

405 proxy_config, 

406 proxy_logging_obj, 

407 version, 

408 ) 

409 

410 data: dict = {} 

411 try: 

412 if premium_user is not True: 412 ↛ 415line 412 didn't jump to line 415 because the condition on line 412 was always true

413 raise ValueError(f"Only premium users can use this endpoint + {CommonProxyErrors.not_premium_user.value}") 

414 # Include original request and headers in the data 

415 base_llm_response_processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

416 ( 

417 data, 

418 litellm_logging_obj, 

419 ) = await base_llm_response_processor.common_processing_pre_call_logic( 

420 request=request, 

421 general_settings=general_settings, 

422 user_api_key_dict=user_api_key_dict, 

423 version=version, 

424 proxy_logging_obj=proxy_logging_obj, 

425 proxy_config=proxy_config, 

426 route_type=CallTypes.alist_fine_tuning_jobs.value, 

427 ) 

428 

429 response: Any | None = None 

430 if target_model_names and isinstance(target_model_names, str): 

431 target_model_names_list: Final = target_model_names.split(",") 

432 if len(target_model_names_list) != 1: 

433 raise HTTPException( 

434 status_code=400, 

435 detail="target_model_names on list fine-tuning jobs must be a list of one model name. Example: ['gpt-4o']", 

436 ) 

437 ## Use router to list fine-tuning jobs for that model 

438 if llm_router is None: 

439 raise HTTPException( 

440 status_code=500, 

441 detail="LLM Router not initialized. Ensure models added to proxy.", 

442 ) 

443 data["model"] = target_model_names_list[0] 

444 response = await llm_router.alist_fine_tuning_jobs( 

445 **data, 

446 after=after, 

447 limit=limit, 

448 ) 

449 return response 

450 elif custom_llm_provider: 

451 # get configs for custom_llm_provider 

452 llm_provider_config: Final = get_fine_tuning_provider_config(custom_llm_provider=custom_llm_provider) 

453 

454 if llm_provider_config is not None: 

455 data.update(llm_provider_config) 

456 

457 response = await litellm.alist_fine_tuning_jobs( 

458 **data, 

459 after=after, 

460 limit=limit, 

461 ) 

462 if response is None: 

463 raise HTTPException( 

464 status_code=400, 

465 detail="Invalid request, No litellm managed file id or custom_llm_provider provided.", 

466 ) 

467 

468 ### RESPONSE HEADERS ### 

469 hidden_params: Final = getattr(response, "_hidden_params", {}) or {} 

470 model_id: Final = hidden_params.get("model_id", None) or "" 

471 cache_key: Final = hidden_params.get("cache_key", None) or "" 

472 api_base: Final = hidden_params.get("api_base", None) or "" 

473 

474 fastapi_response.headers.update( 

475 ProxyBaseLLMRequestProcessing.get_custom_headers( 

476 user_api_key_dict=user_api_key_dict, 

477 model_id=model_id, 

478 cache_key=cache_key, 

479 api_base=api_base, 

480 version=version, 

481 model_region=getattr(user_api_key_dict, "allowed_model_region", ""), 

482 ) 

483 ) 

484 

485 return response 

486 

487 except Exception as e: 

488 await proxy_logging_obj.post_call_failure_hook( 

489 user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data 

490 ) 

491 verbose_proxy_logger.exception("litellm.proxy.proxy_server.list_fine_tuning_jobs(): Exception occurred - %s", e) 

492 raise handle_exception_on_proxy(e) 

493 

494 

495@router.post( 

496 "/v1/fine_tuning/jobs/{fine_tuning_job_id:path}/cancel", 

497 dependencies=[Depends(user_api_key_auth)], 

498 tags=["fine-tuning"], 

499 summary="✨ (Enterprise) Cancel Fine-Tuning Jobs", 

500) 

501@router.post( 

502 "/fine_tuning/jobs/{fine_tuning_job_id:path}/cancel", 

503 dependencies=[Depends(user_api_key_auth)], 

504 tags=["fine-tuning"], 

505 summary="✨ (Enterprise) Cancel Fine-Tuning Jobs", 

506) 

507async def cancel_fine_tuning_job( 

508 request: Request, 

509 fastapi_response: Response, 

510 fine_tuning_job_id: str, 

511 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

512): 

513 """ 

514 Cancel a fine-tuning job. 

515 

516 This is the equivalent of POST https://api.openai.com/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel 

517 

518 Supported Query Params: 

519 - `custom_llm_provider`: Name of the LiteLLM provider 

520 - `fine_tuning_job_id`: The ID of the fine-tuning job to cancel. 

521 """ 

522 from litellm.proxy.proxy_server import ( 

523 general_settings, 

524 llm_router, 

525 premium_user, 

526 proxy_config, 

527 proxy_logging_obj, 

528 version, 

529 ) 

530 

531 data: dict = {"fine_tuning_job_id": fine_tuning_job_id} 

532 try: 

533 if premium_user is not True: 533 ↛ 535line 533 didn't jump to line 535 because the condition on line 533 was always true

534 raise ValueError(f"Only premium users can use this endpoint + {CommonProxyErrors.not_premium_user.value}") 

535 await validate_managed_id_requirement( 

536 resource_id=fine_tuning_job_id, 

537 resource_kind="fine-tuning job", 

538 user_api_key_dict=user_api_key_dict, 

539 managed_files_obj=proxy_logging_obj.get_proxy_hook("managed_files"), 

540 ) 

541 # Include original request and headers in the data 

542 base_llm_response_processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

543 ( 

544 data, 

545 litellm_logging_obj, 

546 ) = await base_llm_response_processor.common_processing_pre_call_logic( 

547 request=request, 

548 general_settings=general_settings, 

549 user_api_key_dict=user_api_key_dict, 

550 version=version, 

551 proxy_logging_obj=proxy_logging_obj, 

552 proxy_config=proxy_config, 

553 route_type=CallTypes.acancel_fine_tuning_job.value, 

554 ) 

555 

556 try: 

557 request_body = await request.json() 

558 except Exception: 

559 request_body = {} 

560 

561 custom_llm_provider: Final = request_body.get("custom_llm_provider", None) 

562 

563 ## CHECK IF MANAGED FILE ID 

564 unified_finetuning_job_id: str | Literal[False] = False 

565 response: LiteLLMFineTuningJob | None = None 

566 if fine_tuning_job_id: 

567 unified_finetuning_job_id = _is_base64_encoded_unified_file_id(fine_tuning_job_id) 

568 if unified_finetuning_job_id: 

569 if llm_router is None: 

570 raise HTTPException( 

571 status_code=500, 

572 detail={"error": "LLM Router not initialized. Ensure models added to proxy."}, 

573 ) 

574 response = cast( 

575 LiteLLMFineTuningJob, 

576 await llm_router.acancel_fine_tuning_job( 

577 **data, 

578 ), 

579 ) 

580 response._hidden_params["unified_finetuning_job_id"] = unified_finetuning_job_id 

581 else: 

582 # get configs for custom_llm_provider 

583 llm_provider_config: Final = get_fine_tuning_provider_config(custom_llm_provider=custom_llm_provider) 

584 

585 if llm_provider_config is not None: 

586 data.update(llm_provider_config) 

587 

588 response = await litellm.acancel_fine_tuning_job( 

589 **data, 

590 ) 

591 

592 if response is None: 

593 raise HTTPException( 

594 status_code=400, 

595 detail="Invalid request, No litellm managed file id or custom_llm_provider provided.", 

596 ) 

597 

598 ### CALL HOOKS ### - modify outgoing data 

599 _response: Final = await proxy_logging_obj.post_call_success_hook( 

600 data=data, 

601 user_api_key_dict=user_api_key_dict, 

602 response=response, 

603 ) 

604 if _response is not None and isinstance(_response, LiteLLMFineTuningJob): 

605 response = _response 

606 

607 ### ALERTING ### 

608 asyncio.create_task( 

609 proxy_logging_obj.update_request_status(litellm_call_id=data.get("litellm_call_id", ""), status="success") 

610 ) 

611 

612 ### RESPONSE HEADERS ### 

613 hidden_params: Final = getattr(response, "_hidden_params", {}) or {} 

614 model_id: Final = hidden_params.get("model_id", None) or "" 

615 cache_key: Final = hidden_params.get("cache_key", None) or "" 

616 api_base: Final = hidden_params.get("api_base", None) or "" 

617 

618 fastapi_response.headers.update( 

619 ProxyBaseLLMRequestProcessing.get_custom_headers( 

620 user_api_key_dict=user_api_key_dict, 

621 model_id=model_id, 

622 cache_key=cache_key, 

623 api_base=api_base, 

624 version=version, 

625 model_region=getattr(user_api_key_dict, "allowed_model_region", ""), 

626 ) 

627 ) 

628 

629 return response 

630 

631 except Exception as e: 

632 await proxy_logging_obj.post_call_failure_hook( 

633 user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data 

634 ) 

635 verbose_proxy_logger.exception( 

636 "litellm.proxy.proxy_server.cancel_fine_tuning_job(): Exception occurred - %s", e 

637 ) 

638 raise handle_exception_on_proxy(e)