Skip to content

fix(guardrails): skip INPUT validation when no user messages with experimental_use_latest_role_message_only#25355

Open
michelligabriele wants to merge 1 commit intoBerriAI:mainfrom
michelligabriele:fix/bedrock-guardrail-role-filter-fallback
Open

fix(guardrails): skip INPUT validation when no user messages with experimental_use_latest_role_message_only#25355
michelligabriele wants to merge 1 commit intoBerriAI:mainfrom
michelligabriele:fix/bedrock-guardrail-role-filter-fallback

Conversation

@michelligabriele
Copy link
Copy Markdown
Contributor

Fixes #23476

Relevant issues

Fixes #23476

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🐛 Bug Fix

Changes

When experimental_use_latest_role_message_only is enabled and no user-role messages exist in the conversation, the post-call hooks (async_post_call_success_hook, async_post_call_streaming_iterator_hook) and apply_guardrail fall back to sending ALL messages — including tool and assistant role content — to the Bedrock guardrail API. This happens because _prepare_guardrail_messages_for_role returns None for payload_messages, and the or fallback (payload_messages or all_messages) evaluates to the full unfiltered list.

Fix: Replace the or fallback in 3 locations with explicit logic that skips INPUT validation entirely when no user messages are found (runs OUTPUT validation only). Added a defense-in-depth role guard in _create_bedrock_input_content_request that filters out non-user messages when the flag is enabled.

Tests: 4 new unit tests in tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py:

  • Role guard filters non-user messages when flag enabled
  • Role guard includes all messages when flag disabled
  • _prepare_guardrail_messages_for_role returns None when no user messages
  • async_post_call_success_hook skips INPUT and only runs OUTPUT when no user messages

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 8, 2026 4:16pm

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing michelligabriele:fix/bedrock-guardrail-role-filter-fallback (1ed3878) with main (62757ff)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR fixes a bug where async_post_call_success_hook, async_post_call_streaming_iterator_hook, and apply_guardrail would fall back to sending all messages (including tool and assistant roles) to Bedrock's INPUT guardrail when experimental_use_latest_role_message_only is enabled but no user-role messages exist in the conversation. The root cause was the or short-circuit fallback (payload_messages or all_messages) which evaluated to the full unfiltered list when payload_messages was None.

Key changes:

  • Replaces the or fallback in 3 locations with explicit logic: when the flag is enabled and no user messages are found, INPUT validation is skipped entirely and only OUTPUT validation runs.
  • Adds a defense-in-depth role filter inside _create_bedrock_input_content_request that skips non-user messages when the flag is active.
  • Adds 4 unit tests covering the role guard, the None return from _prepare_guardrail_messages_for_role, and the end-to-end INPUT-skip behavior in async_post_call_success_hook.
  • Minor: three input_messages = None / filtered_messages = None reassignments inside if … is None: guards are redundant no-ops and could be simplified.
  • The streaming iterator hook (async_post_call_streaming_iterator_hook) received the same fix but has no corresponding new test."

Confidence Score: 5/5

Safe to merge — the fix is correct and all remaining findings are minor style issues that do not affect correctness.

All findings are P2: redundant no-op assignments (cosmetic) and a missing streaming-path test (the logic is structurally identical to the tested success-hook path). The core bug fix is correct, existing tests pass, and no regressions are introduced.

No files require special attention.

Vulnerabilities

No security concerns identified. The change narrows rather than widens the content sent to Bedrock guardrails — non-user messages that were previously incorrectly forwarded to the INPUT validation endpoint are now correctly excluded when the flag is enabled.

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py Fix is correct: replaces the unsafe or fallback with explicit flag-aware logic in async_post_call_success_hook, async_post_call_streaming_iterator_hook, and apply_guardrail; adds a defense-in-depth role filter in _create_bedrock_input_content_request. Minor: three redundant = None no-op assignments.
tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py Four new unit tests covering the role-guard filter, flag-disabled baseline, None-return from _prepare_guardrail_messages_for_role, and INPUT-skip in async_post_call_success_hook. All mocked — no real network calls. Streaming iterator hook path is untested.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[async_post_call_success_hook] --> B{should_validate_input?}
    B -- No --> C[OUTPUT validation only]
    B -- Yes --> D[_prepare_guardrail_messages_for_role]
    D --> E{payload_messages is None?}
    E -- No --> F[Run INPUT + OUTPUT in parallel]
    E -- Yes --> G{experimental_use_latest_role_message_only?}
    G -- No --> H[Fallback: use new_messages - Run INPUT + OUTPUT in parallel]
    G -- Yes --> I[Skip INPUT entirely - Run OUTPUT only]
    F --> J[Apply masking / blocked response]
    H --> J
    C --> J
    I --> J
Loading

Reviews (1): Last reviewed commit: "fix(guardrails): skip INPUT validation w..." | Re-trigger Greptile

Comment on lines +979 to +983
if input_messages is None:
if self.experimental_use_latest_role_message_only:
input_messages = None # no user messages → skip INPUT validation
else:
input_messages = new_messages
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant None reassignment (appears in 3 places)

Inside the if input_messages is None: guard, input_messages is already None, so input_messages = None on line 981 is a no-op and just adds noise. The same pattern appears at line 1138 in async_post_call_streaming_iterator_hook and line 1448 in apply_guardrail. Consider simplifying to make the intent clearer:

Suggested change
if input_messages is None:
if self.experimental_use_latest_role_message_only:
input_messages = None # no user messages → skip INPUT validation
else:
input_messages = new_messages
input_messages = input_filter.payload_messages
if input_messages is None and not self.experimental_use_latest_role_message_only:
input_messages = new_messages

This removes the dead inner branch while keeping exactly the same runtime behavior.

Comment on lines +1255 to +1311
@pytest.mark.asyncio
async def test_post_call_success_hook_skips_input_when_no_user_messages_and_flag_enabled():
"""When experimental_use_latest_role_message_only is True and there are no
user messages, async_post_call_success_hook should skip INPUT validation
and only run OUTPUT validation."""
guardrail = BedrockGuardrail(
guardrailIdentifier="test-id",
guardrailVersion="DRAFT",
experimental_use_latest_role_message_only=True,
)

mock_user_api_key_dict = UserAPIKeyAuth()

mock_response = litellm.ModelResponse(
id="test-id",
choices=[
litellm.Choices(
index=0,
message=litellm.Message(role="assistant", content="safe response"),
finish_reason="stop",
)
],
created=1234567890,
model="gpt-4o",
object="chat.completion",
)

request_data = {
"model": "gpt-4o",
"messages": [
{"role": "assistant", "content": "previous response"},
{"role": "tool", "content": "tool result"},
],
}

call_sources = []

async def mock_make_bedrock_api_request(source=None, **kwargs):
call_sources.append(source)
mock_bedrock = MagicMock()
mock_bedrock.get.return_value = None
return mock_bedrock

with patch.object(
guardrail,
"make_bedrock_api_request",
side_effect=mock_make_bedrock_api_request,
):
await guardrail.async_post_call_success_hook(
data=request_data,
user_api_key_dict=mock_user_api_key_dict,
response=mock_response,
)

# Only OUTPUT should have been validated, not INPUT
assert "OUTPUT" in call_sources
assert "INPUT" not in call_sources
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing test coverage for streaming iterator hook

async_post_call_streaming_iterator_hook received the same INPUT-skip logic (around line 1132) as async_post_call_success_hook, but there is no corresponding test verifying that it also skips INPUT when there are no user messages and the flag is enabled. Given that the two paths are structurally identical, adding a parallel test for the streaming path would be a low-effort safeguard against a future regression in that branch.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: experimental_use_latest_role_message_only erroneously sending tool and assistant prompts to guardrail

1 participant