Commit graph

75 commits

Author SHA1 Message Date
yucheng
10506ab904 fix(guardrails): judge the whole latest user turn, run every during_call guardrail, log combined modes as configured
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 01:05:53 +00:00
yucheng-berri
0b3e56448f
Merge pull request #41126 from BerriAI/litellm_custom_code_guardrail_identity
fix(guardrails): resolve caller identity from metadata buckets in custom code guardrail
2026-09-15 17:47:17 -07:00
yassin
46bd3d40d7 refactor(logging): bill an assembled stream on the failure log via a public Logging method
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 20:23:50 +00:00
yassin
7095373dd5 fix(proxy): only discard parked stream logging for errors the failure path logs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 20:10:19 +00:00
jesus
8b24d4c24f fix(proxy): log blocked streaming guardrail responses as failures, not success
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 20:10:18 +00:00
yucheng
87bf5730da fix(guardrails): carry MCP caller identity in synthetic metadata instead of trusting top-level fields
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-14 21:37:44 +00:00
devin-ai-integration[bot]
b1360efc2f
fix(proxy): attribute gate-rejected requests to their endpoint in cache analytics (#40824)
* fix(proxy): attribute gate-rejected requests to their endpoint in cache analytics

Requests rejected before dispatch (bad key, blocked key, budget, rate limit, malformed body) were spend-logged with an empty call_type because the synthesized logging object never reached the failure lifter. The caching dashboard rolled all of them, plus failed calls on info routes such as /model/info, into one Unknown group.

Resolve call_type from the matched route first, falling back to body shape, and keep the synthesized logging object on request_data so the lifter sees it. Log bare auth exceptions with the 401 ProxyException the client gets so error_code is never empty. Exclude info routes from the cache analytics groups and error breakdown. The dashboard explains the Unknown group when older rows still produce one.

Resolves LIT-5884

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): keep the raw auth exception for failure callbacks

Record the client-facing status in the spend log through a separate client_exception argument so custom failure callbacks still receive the exception auth raised.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): keep the route for multi-operation endpoints and exclude info routes from cache filter options

Routes such as /v1/files map to several operations (create, list) and the
method is not available in the failure hook, so a rejected request there is
filed under its route instead of the first mapped call type. The key alias and
model filter-option queries now apply the same info-route exclusion as the
groups and error breakdown, so every offered filter value returns data. The
info-route exclusion and Unknown grouping are now covered against a real
Postgres in tests/proxy_behavior/spend/test_cache_activity.py

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): drop client_exception, the spend log row never used it

The DB spend row for a gate rejection is written by _ProxyDBLogger from the
original exception, so the status-bearing copy only reached the in-memory
logging payload. Live runs at the tip still recorded bare auth exceptions as
Unknown/Exception, the same as the base branch. Removing the plumbing keeps
this PR to endpoint attribution and the info-route exclusion

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 16:03:38 -07:00
mateo-berri
5fdbb2a1c8 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_legacy_hook_streaming_pipeline_step 2026-09-09 15:52:48 -07:00
Mateo Wang
2b2e6cf283
Merge pull request #40271 from BerriAI/litellm_lit_7174_stream_tool_call_rewrites
feat(guardrails): deliver tool-call rewrites into buffered chat, Responses, and Messages streams
2026-09-09 15:36:13 -07:00
mateo-berri
c5e93aff13 fix(proxy): warn at submit when a body-selected post_call policy is deferred 2026-09-09 13:45:26 -07:00
mateo-berri
8d040d89e6 fix(policy_engine): keep legacy hooks off streams their route cannot assemble and off guardrails with their own iterator hook
The streaming pipeline step only takes a post-call hook on routes whose
translation assembles the streamed response (chat completions, Responses,
Messages). On /v1/completions, the Gemini streamGenerateContent route, and
A2A streams the pipeline is skipped with the merge-base warning and the hook
runs on its own afterwards, instead of getting a None response while the
header says the guardrail ran. A guardrail that overrides
async_post_call_streaming_iterator_hook next to its post-call hook keeps its
native per-chunk path rather than running buffered through the adapter
2026-09-08 20:39:56 -07:00
mateo-berri
94f9230d13 fix(proxy): type the new pipeline tests and keep tag values out of the deferral warning
Every test this PR adds now annotates its fixture and parametrize
parameters. The submit-time warning for a tag-matched deferred policy
names only the policies, since a wildcard attachment pattern would let
caller-provided tag text reach the log.
2026-09-08 19:59:09 -07:00
mateo-berri
ddedb4867b fix: discard a streamed rewrite that drops or adds a tool call
A guardrail that removes or adds a tool call on an ended stream used to be
silently ignored: every handler substitutes the original list on a count
mismatch and the executor skipped its observer once the translation could
deliver rewrites. The executor now tracks the count change on the observer
and releases the original chunks with the discard warning on every
translation, matching what the merge base did for any tool call rewrite
2026-09-08 19:09:44 -07:00
mateo-berri
0c58346ba9 fix(proxy): warn when a deferred background policy was matched through a request tag
Retrieval re-matches only the key, team, and model scopes, so a post_call
policy that reached a pending background response through a request-body
tag does not govern the completed response. Log that at submit, next to the
deferral, and cover the retrieval re-match with tag-scoped tests.
2026-09-08 17:07:37 -07:00
mateo-berri
9cb5d9b76c fix(proxy): gate streaming pipelines on a route-resolved guardrail translation
The per-chunk hook skipped pipeline-managed guardrails whenever the request
route was empty, while the gated stream could still fail to resolve a
translation and release the buffered stream ungoverned. The gate now needs a
translation resolved from the route, the iterator hook resolves it once and
hands it to the gated stream, and the ungoverned release branch is gone.
2026-09-08 16:55:19 -07:00
mateo-berri
3ea65e761e test(guardrails): cover Responses and Messages pipeline tool-call delivery 2026-09-08 16:30:06 -07:00
mateo-berri
49ab5fa7fb Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit_7174_stream_tool_call_rewrites 2026-09-08 15:21:13 -07:00
mateo-berri
f4b939113b Merge remote-tracking branch 'origin/litellm_fix_post_call_policy_pipeline' into litellm_legacy_hook_streaming_pipeline_step
# Conflicts:
#	litellm/proxy/utils.py
2026-09-08 15:10:19 -07:00
mateo-berri
5dc7774709 Merge remote-tracking branch 'origin/litellm_fix_post_call_policy_pipeline' into litellm_post_call_pipeline_background_responses 2026-09-08 15:06:14 -07:00
mateo-berri
345298f3c9 fix(policy_engine): warn when a poll cannot re-match the submitted model name and keep default_on pre_call claims 2026-09-08 15:04:19 -07:00
mateo-berri
6475443efb fix(policy_engine): run per-chunk hook guardrails whose post_call pipeline cannot stream
The per-chunk streaming hook skipped every guardrail stepped by a post_call pipeline, even when the pipeline is left out of the stream for lacking the unified apply_guardrail interface, so a default_on guardrail that only implements async_post_call_streaming_hook stopped governing streams it governed on the merge base. The skip set now comes from the pipelines that gate the stream, the same way the iterator hook already computes it
2026-09-08 15:00:25 -07:00
mateo-berri
91091fd93e fix: withdraw policy header claims while a background response is pending and log ungoverned retrievals 2026-09-08 12:59:58 -07:00
mateo-berri
c6f5763443 feat(guardrails): run legacy post-call hooks as streaming pipeline steps
A post_call pipeline step whose guardrail only implements the older
async_post_call_success_hook used to skip the stream entirely: PR #38721
fails that shape open with a warning. The streaming step now assembles the
buffered stream into the response the hook expects, runs the hook, ends the
stream with the hook's exception when it raises, and delivers the hook's
rewrite through the same event write-back the unified guardrails use on
chat, Responses, and Messages streams (Messages gets the Anthropic shape).
A stream a pipeline manages no longer runs the same hook again after the
stream ends. A guardrail with neither the unified interface nor a post-call
hook keeps the fail-open, as does a rewrite the buffer cannot be patched
with.
2026-09-08 12:04:52 -07:00
mateo-berri
3caa3b60d5 feat(guardrails): run post_call policy pipelines on background Responses retrieval
A POST /v1/responses with background: true returns a queued response, so the
post_call pipelines attached at submit time had nothing to inspect. They now
defer on queued and in_progress responses and run on GET /v1/responses/{id}
instead: the retrieval resolves the response id back to its deployment,
re-attaches the policies that governed the original model, and reports them
in the x-litellm-applied-* headers of the retrieval response.
2026-09-08 11:58:18 -07:00
mateo-berri
cbe340a31c feat(guardrails): deliver tool-call rewrites into buffered streams
A post_call pipeline guardrail that rewrites a streamed tool call (its
arguments or its name) now has that rewrite written back across the buffered
chunks on chat, Responses, and Messages streams, so the client receives the
rewritten tool call instead of the original. The chat handler rewrites the
first fragment of each tool-call index and blanks the rest, the Responses
handler syncs the function_call output items and their argument events, and
the Messages handler rewrites the tool_use content_block_start and
input_json_delta events in both dict and SSE-bytes chunks.

The delivers_ended_stream_text_rewrites flag becomes
delivers_ended_stream_rewrites, since the write-back now covers both text and
tool calls, and the executor only discards a tool-call rewrite on translations
without write-back or on a shape the translation refuses.
2026-09-08 11:38:18 -07:00
mateo-berri
69d2ac1edb fix(policy_engine): run iterator-hook guardrails whose post_call pipeline cannot stream
The streaming loop skipped every guardrail stepped by a post_call pipeline, even when the pipeline was dropped from the stream for lacking the unified apply_guardrail interface, so a default_on guardrail that only implements async_post_call_streaming_iterator_hook stopped governing streams it governed on the merge base. The skip set now comes from the pipelines that will gate the stream
2026-09-07 21:46:00 -07:00
mateo-berri
192ea9ec80 fix(policy_engine): fail open on streaming shapes post_call pipelines cannot govern yet
A post_call pipeline now releases the original stream instead of refusing the
request on every shape it has no handler for: a background request, a pipeline
guardrail without the unified apply_guardrail interface, a route with no
endpoint translation, a buffered stream no translation resolves, and a rewrite
the translation cannot write back (tool-call edits, text edits on translations
without write-back, n>1 chat, an unended Anthropic stream, a Responses dump
with no event envelope). Each case logs a warning naming the policy and
guardrail. Real blocks and writable text masks are unchanged.
2026-09-07 17:55:37 -07:00
mateo-berri
0d5ea553da Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_post_call_policy_pipeline 2026-09-07 17:24:10 -07:00
Mateo Wang
5a821b593c
Merge pull request #38440 from BerriAI/litellm_prompt_registry_env
fix(prompts): key the in-memory prompt registry by environment
2026-09-03 14:09:26 -07:00
mateo-berri
ec677e5e74 Merge remote-tracking branch 'origin/litellm_fix_post_call_policy_pipeline' into litellm_post_call_pipeline_stream_rewrite
# Conflicts:
#	litellm/llms/anthropic/chat/guardrail_translation/handler.py
#	litellm/llms/openai/chat/guardrail_translation/handler.py
2026-09-01 17:01:42 -07:00
mateo-berri
d4c3ee25e6 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_post_call_policy_pipeline
# Conflicts:
#	litellm/proxy/policy_engine/pipeline_executor.py
2026-09-01 11:14:22 -07:00
devin-ai-integration[bot]
f079e4061b
fix(proxy): deliver budget alerts on webhook-only alerting and accept ALERTING_WEBHOOK_URL (#38441)
* fix(proxy): deliver budget alerts on webhook-only alerting and accept ALERTING_WEBHOOK_URL

ProxyLogging.budget_alerts forwarded to the alerting pipeline only when
'slack' was in general_settings.alerting, so alerting: ['webhook'] plus
WEBHOOK_URL silently never delivered a budget alert (the config
/health/services?service=webhook exists to test). Forward when 'webhook'
is present too; SlackAlerting.send_alert already fans out per channel.

Also accept a provider-neutral ALERTING_WEBHOOK_URL env fallback for the
Slack-format channel (any Slack-compatible receiver works), mark it as a
sensitive var, and de-brand the admin UI alerting copy.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): format settings.tsx with prettier

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(ui): regenerate schema.d.ts for updated alerting description

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: retrigger checks after ALERTING_WEBHOOK_URL docs merged

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-31 09:22:36 -07:00
mateo-berri
4675acf02c Merge remote-tracking branch 'origin/litellm_fix_post_call_policy_pipeline' into litellm_post_call_pipeline_stream_rewrite
# Conflicts:
#	litellm/proxy/policy_engine/pipeline_executor.py
2026-08-30 12:28:54 -07:00
mateo-berri
673d1743a6 fix(policy_engine): apply post_call pipeline text rewrites on streams
Buffered streams governed by post_call policy pipelines now deliver text
rewrites back into the stream per surface (chat SSE, responses SSE,
anthropic messages SSE) instead of rejecting the request with a 400
upfront. Rewrites chain across pipeline steps; tool-call rewrites and
translations without stream write-back still withhold the stream.
2026-08-29 22:12:59 -07:00
mateo-berri
bcee01a7a7 fix(policy_engine): merge guardrail metadata writes back on block and modify_response so failure spend records keep guardrail cost and status 2026-08-29 21:52:52 -07:00
mateo-berri
45a6b1de23 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_post_call_policy_pipeline
# Conflicts:
#	litellm/proxy/policy_engine/pipeline_executor.py
2026-08-29 21:01:45 -07:00
mateo-berri
2247fbc66d fix(policy_engine): withhold streams when a pipeline guardrail rewrites output at runtime 2026-08-29 15:44:08 -07:00
mateo-berri
90c8031dd7 fix(policy_engine): fail closed on content filter category MASK steps for streaming pipelines 2026-08-29 14:32:35 -07:00
mateo-berri
0c1f33dff7 fix(policy_engine): fail closed on content filter MASK steps for streaming pipelines
A litellm_content_filter step with a MASK action masks chat streams through
its own iterator hook under guardrails.add, which pipeline-managed guardrails
skip, so the pipeline path released the stream unmasked. CustomGuardrail now
declares rewrites_streamed_output (mask_response_content by default, any MASK
action for the content filter) and the upfront streaming check names such
steps in the same 400 it gives mask_response_content and incremental_diff
2026-08-29 14:19:21 -07:00
mateo-berri
d51198fdeb test(policy_engine): cover streaming pipeline gate branches
Adds regression tests for the modify_response block on the Anthropic route,
the gate with no iterator overrides, and the per-chunk hook skipping
pipeline-managed guardrails. Corrects the gate docstring: an allow releases
the chunks as the endpoint translation left them, not verbatim
2026-08-29 14:07:34 -07:00
mateo-berri
fa5a10941e fix(policy_engine): fail closed on streaming for content-rewriting pipeline steps and untranslatable routes 2026-08-29 13:28:11 -07:00
mateo-berri
3088db3f0e fix(prompts): accept a string prompt_version and carry the viewed environment into code snippets 2026-08-29 12:55:34 -07:00
mateo-berri
c5bcf3a735 feat(policy_engine): execute post_call guardrail pipelines on streaming responses 2026-08-29 12:06:43 -07:00
mateo-berri
588f30950c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_prompt_registry_env 2026-08-29 12:05:06 -07:00
mateo-berri
c64318cfbd Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_post_call_policy_pipeline 2026-08-29 11:24:43 -07:00
Devin AI
9bfb332904 refactor: replace fresh getattr/setattr and test type-ignores with typed access
Same-day debt cleanup on code that landed in the last 24 hours. No behavior change.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-29 07:59:27 +00:00
mateo-berri
996019cd23 fix(policy_engine): keep post_call pipeline guardrail logging and reject background bypass
Post_call pipelines run step hooks against a copied request dict, so guardrail
writes into the metadata bucket (applied_guardrails for the response header,
standard_logging_guardrail_information for spend logs) were dropped when the
guardrail was the first writer. Merge those writes back onto the request on the
post_call allow path, keeping the request payload and the executor's per-step
guardrails activation flag out of it.

Background /v1/responses requests dodge the streaming 400: pre_call sees stream
unset, then the polling task forces stream=true with pre-call logic skipped and
the streaming branch returns before post_call_success_hook, silently bypassing
post_call pipelines. Reject background=true at pre_call the same way as
stream=true.

Also pin the run_in_parallel pipeline-managed exclusion in both hook loops with
regression tests.
2026-08-29 00:14:23 -07:00
mateo-berri
55569729b0 fix(policy_engine): scope pipeline-managed guardrail skips to the pipeline's mode 2026-08-28 18:16:35 -07:00
mateo-berri
aeac6a412c fix(policy_engine): skip pipeline-managed guardrails in the response-path guardrail loop 2026-08-28 17:59:15 -07:00
mateo-berri
e6edd62f5d fix(policy_engine): propagate post_call pipeline replacement responses to the client 2026-08-28 17:40:48 -07:00