From 8bd4d59ffca425b856ec456b98ef7a07a39e74e4 Mon Sep 17 00:00:00 2001 From: Bharadwaj Pendyala Date: Thu, 20 Aug 2026 22:15:18 -0500 Subject: [PATCH] fix(logging): treat an empty prompt_id as no prompt in manager dispatch should_run_prompt_management_hooks gates on `if prompt_id`, so an empty string reaches dispatch as a call that names no prompt. Dispatch gated on `prompt_id is None`, so the empty string skipped the guard, a registered prompt manager was selected, and the cache_control hook behind it in the chain never ran. 4829bb3a15 fixed the prompt-less case. This narrows the remaining gap so both sites read a blank prompt_id the same way. --- litellm/litellm_core_utils/litellm_logging.py | 2 +- .../test_litellm_logging.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index c0750bb94e7..5df4523afcf 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1042,7 +1042,7 @@ class Logging(LiteLLMLoggingBaseClass): ) for logger in prompt_management_loggers: - if prompt_id is None and not self._prompt_manager_runs_without_prompt_id( + if not prompt_id and not self._prompt_manager_runs_without_prompt_id( logger=logger, prompt_spec=prompt_spec, dynamic_callback_params=dynamic_callback_params, diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 0222e756ba1..ae898b17e3b 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -5973,3 +5973,34 @@ def test_failure_handler_helper_fn_builds_payload_once_per_exception(): other_exc = _raise_and_catch(_ClientError(status_code=429, message="rate limited")) obj._failure_handler_helper_fn(exception=other_exc, traceback_exception="") assert obj.model_call_details["standard_logging_object"] is not first_payload + + +def test_prompt_hooks_skip_prompt_managers_when_prompt_id_is_empty(logging_obj, tmp_path, monkeypatch): + """ + `should_run_prompt_management_hooks` gates on `if prompt_id`, so an empty string reaches + dispatch as a call that names no prompt. Dispatch gated on `prompt_id is None`, so it + handed the call to the dotprompt manager anyway and the cache_control hook never ran. + """ + from litellm.integrations.dotprompt.dotprompt_manager import DotpromptManager + + (tmp_path / "stem.prompt").write_text("---\nmodel: claude-opus-4-5\n---\nyou are a stem tutor\n") + dotprompt_manager = DotpromptManager(prompt_directory=str(tmp_path)) + monkeypatch.setattr(litellm, "callbacks", [dotprompt_manager]) + + cache_control_params = {"cache_control_injection_points": [{"role": "system", "location": "message"}]} + messages = [ + {"role": "system", "content": "you are a stem tutor"}, + {"role": "user", "content": "hi"}, + ] + + assert logging_obj.should_run_prompt_management_hooks(prompt_id="", non_default_params=cache_control_params) + + _, compiled_messages, _ = logging_obj.get_chat_completion_prompt( + model="claude-opus-4-5", + messages=messages, + non_default_params=cache_control_params, + prompt_variables=None, + prompt_id="", + ) + + assert compiled_messages[0]["cache_control"] == {"type": "ephemeral"}