From 8aca0c37fd7c2cd00830dbe3ddd5e6322bd1c122 Mon Sep 17 00:00:00 2001 From: shivam Date: Thu, 17 Sep 2026 23:31:18 +0000 Subject: [PATCH] fix(batches): keep deployment credentials out of callback-visible params Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/common_utils/check_batch_cost.py | 6 +++++- litellm/batches/batch_line_item_logging.py | 2 +- tests/test_litellm/batches/test_batch_line_item_logging.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py b/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py index 457bcf8c147..b41b3a6e1ad 100644 --- a/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py +++ b/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py @@ -856,7 +856,6 @@ class CheckBatchCost: } }, **({"api_base": mask_api_base_credentials(deployment_api_base)} if deployment_api_base else {}), - "_litellm_internal_model_credentials": MappingProxyType({**credentials}), "metadata": { **(await self._build_creator_attribution_metadata(job, batch_id)), # spend logs read the deployment identity off these metadata keys, so @@ -868,6 +867,11 @@ class CheckBatchCost: optional_params={}, custom_llm_provider=str(llm_provider) if llm_provider else None, ) + # deployment credentials stay off litellm_params because every callback sees those; the + # line-item logger reads them back off this private attribute to fetch the batch files + setattr( # noqa: B010 # Logging has no declared attribute for trusted credentials transport + logging_obj, "_litellm_internal_model_credentials", MappingProxyType(dict(credentials)) + ) if not await self._claim_job_for_costing(job): verbose_proxy_logger.info( diff --git a/litellm/batches/batch_line_item_logging.py b/litellm/batches/batch_line_item_logging.py index 65227c24c8e..8a4206d3606 100644 --- a/litellm/batches/batch_line_item_logging.py +++ b/litellm/batches/batch_line_item_logging.py @@ -299,7 +299,7 @@ async def log_batch_line_items( is logged and swallowed: aggregate accounting must be unaffected.""" emitted = 0 # rebind-ok: loop accumulator for emitted line count try: - internal_credentials: Final = ( + internal_credentials: Final = getattr(parent, "_litellm_internal_model_credentials", None) or ( litellm_params.get("_litellm_internal_model_credentials") if litellm_params else None ) internal_mapping: Final = _as_object_mapping(internal_credentials) diff --git a/tests/test_litellm/batches/test_batch_line_item_logging.py b/tests/test_litellm/batches/test_batch_line_item_logging.py index 32eeb326c60..fe03e348ce0 100644 --- a/tests/test_litellm/batches/test_batch_line_item_logging.py +++ b/tests/test_litellm/batches/test_batch_line_item_logging.py @@ -414,9 +414,9 @@ async def test_line_items_edge_shapes_and_edge_cases(recorder): parent: Final = _parent_logging_with_params( { "metadata": {"model_info": {"id": "dep-1"}, "model_group": "gpt-4o"}, - "_litellm_internal_model_credentials": {"api_key": "sk-line-items-marker"}, } ) + parent._litellm_internal_model_credentials = {"api_key": "sk-line-items-marker"} # test-quality-ok: private transport attribute, same channel the batch cost tracker uses with ( patch("litellm.files.main.afile_content", file_mock), # test-quality-ok: afile_content is the provider boundary; no injection seam for managed file fetch patch("litellm.cost_calculator.batch_cost_calculator", return_value=(0.01, 0.02)), # test-quality-ok: the pricing table boundary, same seam existing batch_utils tests patch @@ -446,6 +446,7 @@ async def test_line_items_edge_shapes_and_edge_cases(recorder): assert "no status here" in _payload(failure)["error_str"] assert "sk-line-items-marker" in str(file_mock.call_args_list) + assert "sk-line-items-marker" not in str(by_custom_id["e"]["litellm_params"]) file_ids_fetched = [call.kwargs.get("file_id") or call.args[0] for call in file_mock.call_args_list] assert "input-2" in file_ids_fetched and "output-2" in file_ids_fetched assert not any(file_id is None for file_id in file_ids_fetched)