fix(guardrails): redact Responses PromptObject variables

A Responses request can send `prompt` as a PromptObject rather than a string.
Its `variables` are substituted into the stored prompt on the provider side, so
they are caller text, and the dict shape was falling through untouched.

`id` and `version` pick which stored prompt to run and are left unchanged.
This commit is contained in:
Ninad Phalak 2026-09-04 02:35:32 -05:00
parent ce921f49e4
commit 119ec62952
No known key found for this signature in database
GPG key ID: 59119ED515433744
2 changed files with 26 additions and 0 deletions

View file

@ -110,6 +110,15 @@ def _collect_prompt(data: MutableRequest, slots: _SlotSink) -> None:
if isinstance(prompt, str):
_collect(data, "prompt", slots)
return
if isinstance(prompt, dict):
# A Responses API PromptObject. `variables` are substituted into the stored
# prompt on the provider side, so they are caller text. `id` and `version`
# identify which prompt to use and must arrive unchanged.
variables: Final = prompt.get("variables")
if isinstance(variables, dict):
for name in tuple(variables):
_collect(variables, name, slots)
return
if not isinstance(prompt, list):
return
for index in range(len(prompt)):

View file

@ -401,6 +401,23 @@ class TestRequestCoverage:
await guardrail.async_pre_call_hook(user_api_key_dict=None, cache=None, data=data, call_type="completion")
@pytest.mark.asyncio
async def test_responses_prompt_object_variables_are_redacted(self):
"""A PromptObject's variables are substituted into the prompt provider side.
The id and version pick which stored prompt to run and have to arrive
unchanged; the variables are caller text.
"""
guardrail = _guardrail()
_mock_post(guardrail, {"texts": ["[EMAIL_1]"]})
data = {"prompt": {"id": "pmpt_123", "version": "2", "variables": {"customer": "jane.doe@example.com"}}}
await guardrail.async_pre_call_hook(user_api_key_dict=None, cache=None, data=data, call_type="aresponses")
assert data["prompt"]["variables"]["customer"] == "[EMAIL_1]"
assert data["prompt"]["id"] == "pmpt_123"
assert data["prompt"]["version"] == "2"
@pytest.mark.asyncio
async def test_completions_suffix_is_redacted(self):
"""LiteLLM forwards the legacy `suffix` to providers that support it."""