Repository navigation
fix(panw_prisma_airs): honor experimental_use_latest_role_message_only on every request shape - #42447
Conversation
…y to every request shape Explicit true/false now applies to chat completions, Anthropic /v1/messages and /v1/responses alike; unset keeps latest-only for Anthropic and full history otherwise. Text indices are mapped back to their source message by value instead of by count, so Responses instructions, function_call_output and reasoning items no longer derail the alignment and silently rescan the whole history Co-authored-by: scthornton <scthornton@gmail.com> Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ssageValues Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…o agree A Responses function_call_output whose text equals the latest user turn could claim that turn's slot in a forward-only walk and demote the latest-only scan to an earlier message. Walk both directions and fall back to the full role-filter scan when they disagree Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
bugbot run |
|
…ole_message_all_shapes
…from aligned texts An image-only latest user turn no longer promotes an earlier user turn into the latest-only scan; it scans nothing on the request side, as the Anthropic path did before. A latest user/developer message whose text never reached texts (a trailing Responses reasoning item) falls back to the role-filter scan instead of narrowing. Types the test helpers, drops the narrating docstrings and adds regressions for both shapes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
bugbot run |
|
The bidirectional walk in One edge worth a look before this goes in. When the flag is on and the latest user turn carries no text (image-only content), The warning at L1786 doesn't cover it, since it's gated on I don't think the scan decision is wrong. Latest-only means earlier turns aren't rescanned, and there's nothing in the latest turn to scan. It's the silence I'd flag. When I ran the 8-turn before/after through a local proxy against the live AIRS endpoint, what made the behavior legible was every turn producing a scan_id I could reconcile in the activity log. A turn that produces none looks identical to a guardrail that isn't firing, which is painful to debug from the operator side. A debug line when an image-only latest turn yields an empty set would close that gap. Smaller and separate: Happy to re-run the live before/after against the real AIRS endpoint on this branch if that's useful. I still have the 8-turn harness and the e2e artifacts from the original measurement. |
…to scan An image-only latest user turn with experimental_use_latest_role_message_only=true intentionally yields zero scanner calls. Emit a debug line naming the call_id so operators can tell this apart from the guardrail not firing. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@scthornton good catch on the silent zero-scan: 03cff16 adds a debug line with the call_id. Messages reach the guardrail as dicts, the handler already calls .get() upstream |
|
bugbot run |
|
@greptileai please re-review the tip 03cff16, which adds a debug log line and the matching regression assertion |
…ole_message_all_shapes
|
bugbot run |
|
@greptileai review 25a6891 is a merge of current main with no PANW changes, re-review for a fresh verdict on the tip please |
|
Hi @scthornton , thanks for the review. Could you please rerun the live e2e tests against real AIRS endpoint to help validate? Thanks ahead |
…ole_message_all_shapes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # tests/integration/observability/test_guardrail_effects.py
|
bugbot run |
|
@greptileai please review 4b8cebc, a merge of current main that resolves a test file conflict and changes nothing in this diff |
…ole_message_all_shapes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # tests/integration/contracts.json
|
bugbot run |
|
@greptileai please review e8b444f, a merge of current main that drops the legacy covers marker after main removed contracts.json |
…urn selection The Responses translation handler gives reasoning input items the default user role, so a reasoning item with text content after the latest prompt was picked as the latest human turn and the real prompt went unscanned under experimental_use_latest_role_message_only. Map reasoning items back to their texts positions from the raw input and exclude them; fall back to the role-filter scan when the raw items do not account for every text Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8be0af1. Configure here.
|
Reran the live e2e against 8be0af1. Local proxy on the PR head, guardrail api_base pointed at a recording forwarder that relays every scan to the live AIRS Scan API, Gemini behind it. Same 8-turn conversation as before, replayed with the flag unset and then true, across three request shapes:
All 96 scans were live AIRS calls, HTTP 200 with real scan_ids. With the flag on, every call scanned exactly the current user turn, and no assistant or tool-output text was scanned in any run. Chat numbers match the original validation on #38977. test_panw_prisma_airs.py passes locally on the same head (226). One thing I noticed that's separate from this PR: on /v1/responses the instructions field isn't scanned at all, even with the flag unset, which is why the Responses baseline is 36 scans instead of 44. That looks like the Responses text extraction rather than anything here, so I'd treat it as its own issue. Happy to open one. LGTM from my side. |
|
@scthornton Thanks for the validation. We will keep track of the findings for instruction. |
…ole_message_all_shapes
TLDR
Problem this solves:
experimental_use_latest_role_message_only: trueon the PANW Prisma AIRS guardrail only took effect on Anthropic/v1/messages; on/v1/chat/completionsand/v1/responsesevery turn was still sent to AIRS/v1/responses,instructions,function_call_outputandreasoningitems break the alignment between the flattened texts and the normalized messages, so a naive fix falls back to scanning everything, or worse, scans the model's reasoning text instead of the user's promptHow it solves it:
trueorfalsenow wins on every endpoint, same semantics as the bedrock guardrail flag; unset keeps today's behavior (latest-only on Anthropic/v1/messages, full role-filter scan elsewhere)structured_messagesand maps each flattened text back to the message it came from, skipping Responses-only entries;reasoningitems are identified from the rawinputso model-authored text is never taken for the latest human turnSupersedes #38977 by @scthornton (co-authored on the first commit), whose base branch was deleted before it could merge
User Flow
Before: an admin enables latest-only scanning to cut AIRS spend on long agent conversations, but only Anthropic traffic benefits
experimental_use_latest_role_message_only: trueon thepanw_prisma_airsguardrail and restart the proxy/v1/chat/completionsor/v1/responseswith an 8-turn history: AIRS receives one scan per user and system text, 36 to 44 scans over the conversationPrompt blocked by PANW Prisma AI Security policy, even though the latest turn is cleanAfter: the same setting applies to every endpoint
instructions,function_call_outputorreasoningitems are presentRelevant issues
Supersedes #38977
Affected release
Linear ticket
Resolves LIT-7388
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Against the live Prisma AIRS API (by @scthornton, on this tip 8be0af1)
Local proxy on the PR head, guardrail
api_basepointed at a recording forwarder relaying every scan to the live AIRS Scan API, Gemini upstream. Same 8-turn conversation replayed with the flag unset and thentrue. All 96 scans were live AIRS calls, HTTP 200 with realscan_ids. Details in #42447 (comment)true/v1/chat/completions/v1/responses,instructions+ history/v1/responses,function_call+function_call_outputin historyWith the flag on, every call scanned exactly the current user turn; no assistant or tool-output text was scanned in any run
Base vs head on a local rig (this PR)
Two proxies, merge base b682278 and this tip 8be0af1, each
proxy_cli.py --num_workers 4, same config, Postgres and Redis. Real OpenAI (openai/gpt-5.4-mini), Anthropic (anthropic/claude-haiku-4-5) and OpenRouter (openrouter/openai/gpt-oss-120b, the one model here that returnsreasoningitems with textcontent) upstreams. The scanner is a local stand-in speaking the AIRS/v1/scan/sync/requestwire format that logs every scan; it blocks any text containingRIGBLOCKMEand masksEMAIL@RIG.TESTunder the mask profile. The scan log is the evidence for which texts PANW was asked to inspectHistory in every request:
system: You are terse.,user: first turn: remember the word pineapple,assistant: Noted., thenuser: latest turn: which word did I ask you to remember? answer with one word. Example request:Request-side texts sent to the scanner, flag
true:/v1/chat/completions/v1/responses, plain/v1/responses+instructionsNoted., latest turn/v1/responses+function_call_outputNoted., latest turn/v1/responses+reasoningitem (summary only, replayed from a realgpt-5.4-miniresponse)/v1/responsestool loop,reasoningitem with text content after the latest turn (realgpt-oss-120boutput)Noted., latest turn, reasoning text/v1/messagesOutcomes, flag
true,/v1/responseswithinstructions:RIGBLOCKMEonly in the first user turn, clean latest turnPrompt blocked by PANW Prisma AI Security policy (Category: malicious)"output_text": "Hi", scan log shows only the latest turnRIGBLOCKMEin the latest turnEMAIL@RIG.TESTin the latest turn, mask profile"output_text": "my email is [MASKED_EMAIL]", only the email turn scannedinput_imageonly,RIGBLOCKMEin the first turnPANW Prisma AIRS: latest user message has no text, so experimental_use_latest_role_message_only leaves nothing to scan for call_id=...reasoningitem after aRIGBLOCKMElatest turnFlag unset and flag
false: scans identical to Before on every endpoint and shape, with one exception,/v1/responseswithinstructionsorfunction_call_outputno longer scans the assistant turn (Noted.), see REVIEWER MUST KNOW. Anthropic unset stays latest-only, Anthropicfalsestays full scan. On the final re-run the Anthropic upstream answered 400Your credit balance is too low; the pre-call scan had already run with the same scope, and the 819a9e1 run completed the same cells end to endRegression coverage:
tests/integration/observability/test_guardrail_effects.py::test_panw_latest_role_message_only_scans_only_latest_turn_on_responses_inputboots a live proxy with a scripted upstream and recording scanner and fails on the merge base withplain: latest-only scanned ['first turn', 'latest turn ...'].tests/test_litellm/proxy/guardrails/guardrail_hooks/test_panw_prisma_airs.py(226 tests) covers plain,instructions,function_call_output, summary-only and text-contentreasoning, tool loops, developer role, image-only latest turn, duplicate text collisions, unalignable input, masking, and explicittrue,falseand unset. Eleven mutations of the selection logic (ignore the flag, pick the first human turn, drop the alignment or count guards, stop excludingreasoningitems, and so on) each turn at least one test redFull audit at 819a9e1 vs b682278: 138 cells on both proxies (three endpoints, unset/true/false, streaming and non-streaming, curl plus
openaiandanthropicSDKs sync and async, edge shapes, blocked and masked outcomes, bad model, bad key, scanner 500 and slow scanner withfallback_on_errorblock and allow,during_callandlogging_onlymodes, 30-request burst with scanner outage, workerkill -9). Every flag cell reproduces the tables above; unset and false are identical on both legs in every cell. Two findings identical on both legs and untouched here: a chatcontentof5returns 500 from the managed files hook, and alogging_onlyguardrail withdefault_on: falseruns for every request. Dashboard Playground before/after recording: https://berriaillm.slack.com/archives/D0BEC4CBJKT/p1790067473347679Not in this PR: on
/v1/responsestheinstructionsfield is never scanned, flag on or off (that is why the Responses baseline above is 36 scans, not 44). It lives in the Responses text extraction, predates this change, and @scthornton offered to open a separate issueType
🐛 Bug Fix
Caveats (if any)
Medium
scan_id. The config description says so and the proxy logs a debug line with the call idLow
function_call_outputwhose text equals the latest user text cannot be attributed unambiguously; the request falls back to scanning every extracted text, the same as Before, which still includes the latest turnFinal Attestation
ran /live-pr-risk and found no regressions/backward incompatible risks
REVIEWER MUST KNOW BEFORE APPROVING
experimental_use_latest_role_message_only: trueon/v1/chat/completionsand/v1/responsesnow sends only the latest user or developer turn to AIRS. Before: every user, system and developer text was scanned and a flagged old turn returned 400. After: one scan; flagged history only returns 200, a flagged latest turn still returns 400, and an image-only latest turn sends no request scan. This is the change LIT-7388 asks for, approved by @yucheng-berri in the Devin session that took fix(panw_prisma_airs): apply experimental_use_latest_role_message_only to every request shape #38977 over into this PRfalseon/v1/responseswithinstructionsor afunction_call_outputitem: the assistant turn is no longer scanned. Before: system, user and assistant texts were all scanned because the role filter could not align the texts and scanned everything. After: system, user and developer texts only, the documented role filter that chat completions already applied. Not requested by the ticket, fail direction is fewer scans of model-authored text; surfaced here for @yucheng-berri to accept at approvalLink to Devin session: https://app.devin.ai/sessions/9bc67ae5d93b4e59959263bdd9efedbf
Open in Devin Desktop: https://app.devin.ai/desktop/session/9bc67ae5d93b4e59959263bdd9efedbf?variant=devin