Commit graph

10241 commits

Author SHA1 Message Date
yuneng-jiang
431daa1479
Merge pull request #27812 from BerriAI/litellm_lazyFeatureRootPath
Some checks are pending
Unit Tests: Caching (Redis) / caching-redis (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / schema-migration (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests: Security / security (push) Waiting to run
[Fix] Lazy feature loading under SERVER_ROOT_PATH returns 404
2026-05-12 21:35:26 -07:00
yuneng-jiang
aee58db880
test: replace dall-e-3 with gpt-image-1 in health check and router tests (#27813)
OpenAI returns 'The model dall-e-3 does not exist' for the test account,
breaking test_openai_img_gen_health_check and test_image_generation.
Switch to gpt-image-1, matching the existing TestOpenAIGPTImage1 pattern.
2026-05-12 21:23:52 -07:00
Yuneng Jiang
83f26d17c1
Strip SERVER_ROOT_PATH before lazy-feature prefix match
LazyFeatureMiddleware compared the raw scope path against registered
prefixes (e.g. /policies), so requests under a server root path like
/api/v1/policies/... never matched, the feature never loaded, and the
endpoint returned 404. Strip the configured root path before matching,
normalizing trailing slashes and enforcing a component boundary so
/api does not falsely match /apiv2.
2026-05-12 20:43:08 -07:00
user
14a3083d45
fix(proxy): thread config_file_path into pass-through and MCP-tool YAML loaders
The previous commit's gate broke two legitimate startup paths for
operators using s3://gcs:// remote module loading from their config.yaml:

- general_settings.pass_through_endpoints[].custom_handler
- mcp_tools[].handler

Both call sites called get_instance_fn without a config_file_path, so
the new gate rejected them at startup. Thread config_file_path through:

- create_pass_through_route accepts config_file_path and forwards it to
  get_instance_fn. add_exact_path_route, add_subpath_route,
  _register_pass_through_endpoint, and initialize_pass_through_endpoints
  accept and propagate it.
- The YAML-load call site in proxy_server.load_config now passes
  config_file_path; the DB-overlay call site in _update_general_settings
  leaves it as the default None so the gate still fires on admin-written
  s3:// values.
- MCPToolRegistry.load_tools_from_config accepts config_file_path and
  threads it into get_instance_fn; _init_non_llm_configs forwards it
  from load_config.

Adds two regression tests verifying that the YAML-source callers thread
the path through to get_instance_fn.
2026-05-13 03:15:59 +00:00
user
08f3c1aae8
fix(types_utils): drop opt-in env from remote-module runtime gate
The runtime gate on s3://gcs:// loading in get_instance_fn previously
allowed an opt-in via LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API. That
env var is admin-flippable at runtime (DB-overlay environment_variables
flow into os.environ), which defeats the gate's purpose, and it isn't
needed for the documented operator flow: config.yaml callbacks always
pass config_file_path through to the loader.

Remove the helper, raise unconditionally when config_file_path is None,
and drop the corresponding test for the opt-in branch.
2026-05-13 02:52:14 +00:00
Cursor Agent
a82c240ab0
test(vcr): tighten OVERFLOW classification and switch respx detection to AST
Address two greptile P2 review concerns on PR #27795:

1. MISS:OVERFLOW was firing whenever total > MAX_EPISODES_PER_CASSETTE
   regardless of cassette state. A cassette that grew past the cap
   historically but this run only *replayed* (dirty=False) is
   healthy — the persister never tries to save, so the cache state is
   stable and the next run will replay too. Only flag OVERFLOW when
   dirty=True (new episodes were recorded that the persister would
   refuse to save). Add a regression test covering the
   dirty=False + large-total case.

2. _module_uses_respx did substring matching on the module source,
   which false-positives on comments / docstrings / string literals.
   A comment like # Previously tried respx.mock but switched to
   vcrpy would keep a file pinned on the opt-out list, defeating the
   dead-import pruning goal of this PR. Replace the substring scan
   with an ast.NodeVisitor (_RespxUsageVisitor) that only
   counts:

     - @pytest.mark.respx / @respx.mock decorators
     - with respx.mock(): ... (sync + async) context managers
     - respx.mock(...) calls outside a with/decorator
     - function parameters / fixture names equal to respx_mock

   Add tests for the comment / docstring / string-literal cases plus
   each real-usage pattern.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 01:46:25 +00:00
yuneng-jiang
a232cd2d19
Merge pull request #27798 from BerriAI/litellm_yj_may12
[Infra] Merge dev branch
2026-05-12 18:37:55 -07:00
yuneng-jiang
924e8b243f
Merge pull request #27573 from BerriAI/litellm_tag_budget_header_enforcement
fix: enforce tag budgets on x-litellm-tags header requests
2026-05-12 18:34:19 -07:00
Yassin Kortam
1c4e4d4a60
Fix 3 OpenTelemetry tracing bugs in proxy integration (#27757)
1. Missing litellm_request child span when proxy parent in metadata:
   _get_span_context now returns (ctx, None) for the metadata-injected
   proxy parent so the primary span is always emitted as a child of ctx.
   Proxy span lifecycle managed by new _end_proxy_span_from_kwargs.

2. open_telemetry_logger overwrite by later handlers:
   _init_otel_logger_on_litellm_proxy now uses first-registered-wins —
   only assigns proxy_server.open_telemetry_logger when currently None.

3. Duplicate litellm_request success spans in streaming paths:
   Added _mark_success_span_once with per-handler dedupe key stored in
   kwargs metadata, suppressing the second span when both sync and async
   success callbacks fire for the same request.

Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:32:05 -07:00
Cursor Agent
3390fc1972
test(vcr): mark Bedrock prompt-caching cross-call tests VCR-incompatible
The pass_through prompt-caching tests
(test_prompt_caching_returns_cache_read_tokens_on_second_call,
test_prompt_caching_streaming_second_call_returns_cache_read) make a
warm-up call and then assert the *second* call sees a non-zero
cache_read_input_tokens count from the upstream's prompt-cache. VCR
replay can't model cross-call provider state — both calls match the
same cassette episode, so the second call returns the first call's
pre-warmup response and the assertion fails:

    AssertionError: Expected cache_read_input_tokens > 0 on second call,
    but got 0. Full usage: {'input_tokens': 4986,
    'cache_creation_input_tokens': 4974, 'cache_read_input_tokens': 0}

This started biting after the AWS SigV4 fingerprint stabilization
(b637d9f64a): Bedrock requests now produce a stable per-access-key
fingerprint instead of a per-request signature, so cassettes
successfully replay where they previously always missed and re-recorded
live. Opt these tests out via skip_nodeid_suffixes so they run live and
match the existing pattern in tests/llm_translation/conftest.py
(::test_prompt_caching).

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 01:19:03 +00:00
user
d853d3dcd4
chore(tests): thread config_file_path through s3/gcs custom-logger tests
The pre-existing s3:// / gcs:// custom-logger tests called
``get_instance_fn`` without ``config_file_path``, which means the
new runtime gate (refuse remote URLs unless invoked from a
config-file load) now raises ``ValueError`` before reaching the
mocked download paths. Each test was exercising the documented
startup config-file load scenario; pass ``config_file_path="/any/path"``
to make that intent explicit and route past the gate.

Affected: test_s3_download_success, test_gcs_download_success,
test_invalid_url_format, test_download_failure_handling,
test_file_cleanup.
2026-05-13 01:13:52 +00:00
shivam
a5944140e9
fix(proxy): parse string metadata before pre-auth tag merge
`apply_client_tag_policy_pre_auth` overwrote string-typed metadata
with `{}` before merging header tags, dropping any tags inside. A
caller could send `metadata='{"tags":["over-budget"]}'` plus
`x-litellm-tags: within-budget` and bypass `_tag_max_budget_check`
on the body tag. Parse the string via `safe_json_loads` first so
existing tags survive the merge.

Also drop the empty `tests/test_litellm/proxy/credential_endpoints/`
directory — the cascade-rename tests it held imported a function
that was never implemented (out of scope for this PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:06:12 -07:00
user
dd6eb499e6
chore(proxy): refuse remote-URL instance-fn loads outside config-file path
``get_instance_fn`` previously routed any ``s3://`` / ``gcs://``
value into ``_load_instance_from_remote_storage`` regardless of how
the value got there. The function ultimately calls
``spec.loader.exec_module(module)`` — Python in the proxy process. On
admin-callable endpoints that accept a ``target`` / ``custom_handler``
field from the request body (e.g. ``/config/pass_through_endpoint``,
custom-callback registration), that is a one-step admin-to-RCE
primitive: any future privilege-escalation bug becomes immediate
code execution.

The documented operator flow for remote-module loading is
``litellm_settings.callbacks: ["s3://bucket/module.instance"]`` in
``config.yaml``. That path always carries the YAML's
``config_file_path`` through to ``get_instance_fn``. Use the presence
of ``config_file_path`` as the discriminator: refuse remote URLs
when it is absent (the request-body path) unless the operator
explicitly opts back in via
``LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API=true``.

The three success/failure/audit-log callback-loop call sites in
``proxy_server.py:load_config`` were already running inside the
startup config-file load but had stopped threading
``config_file_path`` through. Pass it through so the documented
``s3://`` callback flow continues to work unchanged.

Tests cover: remote URL without ``config_file_path`` raises;
remote URL with the opt-in env reaches the loader; remote URL
with ``config_file_path`` passes (documented startup flow); local
dotted-name imports unaffected.
2026-05-13 01:05:03 +00:00
Cursor Agent
b13ae8de50
fix(image_edits): drop _RewindableImage to prevent infinite multipart upload
The _RewindableImage(BytesIO) wrapper auto-rewound on every read after
EOF, which made the OpenAI SDK's multipart upload writer read the same
bytes forever instead of seeing EOF. Workers OOM'd / SIGKILL'd:

    [gw0] node down: Not properly terminated
    replacing crashed worker gw0
    ...
    worker 'gw1' crashed while running
        'tests/image_gen_tests/test_image_edits.py::TestOpenAIImageEditGPTImage1::test_openai_image_edit_litellm_sdk[False]'

The auto-rewind was added defensively for parametrized + flaky-retried
tests, but BaseLLMImageEditTest::test_openai_image_edit_litellm_sdk
already calls get_base_image_edit_call_args() once per invocation and
that helper now constructs fresh streams via _make_test_images(), so
rewinding inside the stream is unnecessary. Replace with plain BytesIO
seeded with the cached image bytes.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 01:04:31 +00:00
yuneng-jiang
4c3ff78742
Merge pull request #27793 from stuxf/chore/key-regenerate-ownership-rebind-guard
chore(proxy): close /key/regenerate ownership-rebind + premium-gate bypass
2026-05-12 17:58:08 -07:00
Cursor Agent
ba0f4c1566
fix(vcr): cover full RFC1918 172.16.0.0/12 range in local prefixes 2026-05-13 00:47:44 +00:00
user
8f3056c9c9
chore(proxy): block explicit-null user_id in ownership rebind guard
``model_dump(exclude_unset=True)`` in ``prepare_key_update_data``
includes any field the caller explicitly set, even when the value is
``None``. The previous guard short-circuited on ``getattr(data,
'user_id', None) is None``, which conflated "field omitted" (safe)
with "field explicitly set to null" (writes NULL to the token row,
detaching the key from its user and bypassing user-row role
checks).

Switch the omitted-vs-set distinction to ``data.model_fields_set``;
treat explicit-null and explicit-empty-string identically as a
removal attempt, both 403-rejected for non-admin callers.

Parametrized regression adds ``explicit_null_blocked`` alongside the
existing ``rebind_blocked`` / ``empty_blocked`` / ``same_user_id_allowed``
cases.
2026-05-13 00:46:28 +00:00
shivam
eb142b900e
test(proxy): drop allow_client_tags opt-in gate and add credential rename cascade tests
Removes the allow_client_tags metadata check from apply_client_tag_policy_pre_auth so
x-litellm-tags headers are always merged into request metadata, matching the post-auth
behavior in add_litellm_data_to_request. Updates pre-call tests accordingly and adds a
new test suite covering cascading credential renames into model rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:44:05 -07:00
Cursor Agent
dd89b9304f
fix(vcr): match real Bedrock hostnames in live-call probe
The suffix '.bedrock-runtime.amazonaws.com' never matched real Bedrock
endpoints, which use the format 'bedrock-runtime[-fips].{region}.amazonaws.com'
(region between 'bedrock-runtime' and 'amazonaws.com'). Add an explicit
host check for that pattern so Bedrock live calls are visible to the
probe, and update the unit test accordingly. Also drop the unused
'_LIVE_CALL_PROBE_INSTALLED' module variable.
2026-05-13 00:41:49 +00:00
user
d5d73f1d6a
chore(proxy): clarify ownership-rebind error message (actor vs target)
Previous wording read "User=<new_owner> is not allowed to update the
key to belong to user=<current_owner>" — easy to misread as "caller
wants to keep the key on its current owner". Reframe as
"Non-admin caller is not allowed to rebind the key from
user=<existing> to user=<incoming>" so the direction of the failed
operation is unambiguous.

Same shape preserved (HTTPException 403); only the ``detail`` string
changes. Regression test substring updated.
2026-05-13 00:41:08 +00:00
Cursor Agent
656377bb3d
test(image_edits): regenerate fixtures per call instead of holding open module-level file handles
Module-level

    TEST_IMAGES = [
        open(os.path.join(pwd, 'ishaan_github.png'), 'rb'),
        open(os.path.join(pwd, 'litellm_site.png'), 'rb'),
    ]
    SINGLE_TEST_IMAGE = open(...)

opens the file once at import. After the first multipart upload, the
file pointer is at EOF, so every subsequent test in the same xdist
worker sends an empty multipart body. That non-determinism (a) blows
the recorded cassette past MAX_EPISODES_PER_CASSETTE (50) so
_RedisPersister.save_cassette refuses to save it, and (b) re-bills the
live image edit endpoint on every CI run.

Recent CI runs confirm the leak: tests/image_gen_tests/test_image_edits.py
shows six tests parking at 51-52 cassette entries
(TestOpenAIImageEditGPTImage1::test_openai_image_edit_litellm_sdk[False],
TestOpenAIImageEditDallE2::..., test_openai_image_edit_with_bytesio,
test_openai_image_edit_litellm_router, test_multiple_vs_single_image_edit[False],
test_multiple_image_edit_with_different_formats).

Replace the module-level file handles with _make_test_images() /
_make_single_test_image() factories that return fresh _RewindableImage
(BytesIO subclass) objects whose pointer always starts at 0. The image
bytes are read once at import into module-level constants
(_ISHAAN_GITHUB_BYTES, _LITELLM_SITE_BYTES), so disk I/O cost is
unchanged.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 00:32:23 +00:00
Cursor Agent
93300019bd
test(vcr): drop dead 'from respx import MockRouter' imports
These seven test files were on _RESPX_CONFLICTING_FILES, which made the
auto-marker skip them entirely. Inspecting the source shows the only
respx artifact is a top-level 'from respx import MockRouter' that no
test ever uses - no @pytest.mark.respx, no respx_mock fixture, no
respx.mock context manager. The import is dead code left over from a
previous mocking pattern.

Now that apply_vcr_auto_marker_to_items detects respx per-item via the
marker / fixture chain (b637d9f64a), the file-level skip is no longer
needed for these files - they were the reason the OpenAI tests
(test_o3_reasoning_effort, test_streaming_response[o1/o3-mini],
TestOpenAIO1::test_streaming, TestOpenAIChatCompletion::test_web_search,
TestOpenAIO3::test_web_search, etc.) ran live every CI build despite
the cassette cache being healthy.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 00:32:03 +00:00
Cursor Agent
b637d9f64a
test(vcr): classify cache verdicts, detect live calls, surface cost leaks
Convert the per-test VCR verdict line from a single 'NOOP / HIT / MISS /
PARTIAL' tag into a classified outcome that distinguishes the cases that
silently bill the live API on every CI run from the ones that don't:

  HIT                         pure replay
  PARTIAL                     mixed replay + new recordings
  MISS:RECORDED               new cassette saved to Redis (cached next run)
  MISS:OVERFLOW               cassette > MAX_EPISODES_PER_CASSETTE; persister
                              refused to save; re-bills every run
  MISS:NOT_PERSISTED          test failed; save_cassette skipped; re-bills
  NOOP                        VCR-marked but no HTTP traffic (mocked elsewhere)
  UNMARKED:LIVE_CALL          test bypassed VCR AND opened a TCP connection
                              to a known LLM provider host -> wasted spend
  UNMARKED:NO_TRAFFIC         test bypassed VCR but didn't call out

The UNMARKED:LIVE_CALL signal is what converts 'this test probably hits
live' into 'this test connected to api.openai.com'. We install a
socket.connect / socket.create_connection wrapper for the duration of
each non-VCR-marked test and record any outbound TCP to a known LLM
provider hostname. The probe sits below the httpx layer so vcrpy and
respx (which both patch above the socket) are unaffected.

Replace the file-level _RESPX_CONFLICTING_FILES blacklists in the
llm_translation and local_testing conftests with per-item respx
detection in apply_vcr_auto_marker_to_items. A test now skips VCR when
it actually carries @pytest.mark.respx or has respx_mock in its fixture
chain - not just because some other test in the same file imports
MockRouter. Items skipped by skip_files are split into respx_conflict
(real conflict, the module wires up respx) vs file_opt_out (dead skip-
list entry whose module never touches respx) so the session summary
makes pruning obvious.

Stabilize the AWS SigV4 fingerprint: the Authorization header on
Bedrock requests rotates its Credential date and Signature on every
call, which previously pushed every Bedrock test past the 50-episode
overflow threshold. Extract the access-key id only
('aws-sigv4:AKIA...') so two requests with the same identity match.

Always emit verdict logging when VCR is active (set
LITELLM_VCR_VERBOSE=0 to opt back into the legacy quiet mode). Add a
session-end classification summary that lists overflow tests, unmarked
live-call tests, and the skip-reason breakdown.

Wire the live-call probe + summary hook into every test directory that
already uses the Redis-backed VCR cache (audio_tests, guardrails_tests,
image_gen_tests, litellm_utils_tests, llm_responses_api_testing,
llm_translation, local_testing, logging_callback_tests, ocr_tests,
pass_through_unit_tests, router_unit_tests, search_tests,
unified_google_tests).

Add tests/llm_translation/test_vcr_classification.py covering the
verdict classifier, skip-reason tagging, AWS SigV4 fingerprint stability,
live-host classification, and session summary rendering.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-13 00:31:47 +00:00
user
7738b03501
chore(proxy): close /key/regenerate ownership-rebind + premium-gate bypass
A non-admin caller could rebind their own key's ``user_id`` via
``/key/regenerate``. ``_execute_virtual_key_regeneration`` had org/team
guards but no ``user_id`` guard, and ``prepare_key_update_data`` did not
strip the field — it survived ``model_dump(exclude_unset=True)`` into
the Prisma update. On the next request,
``_return_user_api_key_auth_obj`` resolved the rebound ``user_id``
against ``litellm_usertable`` and returned ``PROXY_ADMIN`` whenever
the target row's ``user_role`` was admin (e.g. the default
``user_id="default_user_id"`` created on first password-UI login).

``/key/update`` had the equivalent guard inline at
``_validate_update_key_data``; extract it to a shared helper
``_validate_caller_can_change_key_ownership`` and call from both
``/key/update`` and ``_execute_virtual_key_regeneration``. Future
regenerate-style endpoints inherit the guard for free.

Also tighten the premium gate that allowed the master-key rotation
branch to skip the enterprise check. The previous predicate was
``data.new_master_key is not None`` — a field-presence test, not an
identity check. Any non-premium caller could send any value in that
field and the premium check would no-op. Verify the caller actually
holds the master key via ``_is_master_key`` before allowing the
non-premium path.

Tests:
- ``test_regenerate_user_id_rebind_guard`` — parametrized table over
  cross-user rebind (blocked), empty-string removal (blocked), and
  same-user no-op rebind (allowed).
- ``test_regenerate_premium_gate_requires_actual_master_key`` /
  ``test_regenerate_premium_gate_allows_actual_master_key_holder`` —
  ensure the premium check requires the caller actually present the
  master key, and that legitimate master-key rotation still works.
2026-05-13 00:27:34 +00:00
harish-berri
8f25942ecf
Litellm key rotation bug (#27756)
* fix(proxy): resolve cache handling issues in _lookup_deprecated_key

- Updated the in-memory cache for deprecated key lookups to store a 3-tuple (active_token_id, cache_expires_at_ts, revoke_at_ts) instead of a 2-tuple, ensuring proper unpacking and backward compatibility.
- Removed duplicate cache reads and added logic to handle legacy cache entries gracefully.
- Enhanced unit tests to cover scenarios for cache hits, DB misses, and respect for revoke_at timestamps, ensuring robust handling of the grace-period key-rotation feature.

* refactor(proxy): streamline cache handling in _lookup_deprecated_key

- Simplified the cache retrieval logic by directly unpacking the 3-tuple cache entries, removing the need for backward compatibility checks for 2-tuple entries.
- Updated unit tests to ensure that pre-warmed 3-tuple cache entries are served correctly without unnecessary database lookups.

* chore(ci): add new unit test for deprecated key grace period

- Included `test_deprecated_key_grace_period.py` in the CI workflow to enhance coverage for deprecated key handling scenarios.

* fix(proxy): remove unnecessary check for revoke_at in _lookup_deprecated_key

- Eliminated the redundant check for None on revoke_at, streamlining the logic for handling deprecated keys in the cache. This change enhances the efficiency of the key lookup process.

* test(proxy): add end-to-end tests for deprecated key lookup behavior

- Introduced a new test class `TestDeprecatedKeyLookupDbE2E` to validate the behavior of deprecated key lookups against a real Prisma-backed database.
- The test ensures that old key hashes resolve correctly and that repeated lookups utilize the in-memory cache without errors.
- Cleaned up the `_lookup_deprecated_key` function by removing an unnecessary check for `revoke_at`, enhancing the efficiency of the key lookup process.
2026-05-12 17:16:37 -07:00
Yuneng Jiang
75da2f3840
fix(proxy): always merge caller-supplied tags into request metadata
Caller-supplied tags (`x-litellm-tags` header, body `tags`, `metadata.tags`)
were silently dropped unless the key/team had
`metadata.allow_client_tags: true` set. Restore the documented behavior:
tags from the request always flow into `metadata.tags` and union with any
admin-configured static tags from key/team/project metadata.

Removes the `allow_client_tags` opt-in flag from the pre-call pipeline.
The flag was only ever read here; it has no schema or endpoint footprint,
so leftover values in existing key metadata are inert.

Test cleanup mirrors the simplification: drop the three tests that
verified the strip-when-not-opted-in path, drop the `allow_client_tags`
fixture lines from the merge/union tests.
2026-05-12 16:55:19 -07:00
Yuneng Jiang
10a106a1e5
fix(tests): drop dall-e-only test classes; route live image tests via gpt-image-1
Second wave of failures from the 2026-05-12 DALL-E shutdown:
- tests/image_gen_tests/test_image_edits.py::TestOpenAIImageEditDallE2
  and tests/image_gen_tests/test_image_generation.py::TestOpenAIDalle3
  are explicitly named for the deprecated models and can't pass; remove.
  gpt-image-1 coverage already exists in sibling classes.
- tests/local_testing/test_router.py image gen tests use dall-e-3 only
  as a routing example; swap to gpt-image-1.
- tests/local_testing/test_custom_callback_input.py image_generation
  success/failure paths swapped to gpt-image-1.
2026-05-12 16:55:19 -07:00
Yuneng Jiang
7066587f5c
fix(tests): swap dall-e to gpt-image-1 after openai deprecation
DALL-E 2 and DALL-E 3 were removed from the OpenAI API on 2026-05-12,
causing e2e image-generation tests to fail with "model does not exist".
Swap all live-API DALL-E references in proxy-backed tests to gpt-image-1
and update the dall-e-2 alias in proxy_server_config.yaml to point at
openai/gpt-image-1 (preserves any historical dall-e-2 callers).
2026-05-12 16:55:18 -07:00
Krrish Dholakia
0deffd3618
chore: reject bare str at file-input sinks to prevent local-file read (#27762)
* chore: reject bare str at file-input sinks to prevent local-file read (#27667)

Squash-merged by litellm-agent from stuxf's PR.

* fix: use os.PathLike in ocr sink and check truthy reasoningSummary for bridge

- ocr/main.py: widen Path check to os.PathLike for consistency with other sinks
- main.py: bridge condition checks truthiness of reasoning_summary, not just None

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: remove unused pathlib.Path import in ocr/main.py

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
Co-authored-by: stuxf <70670632+stuxf@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-12 16:40:07 -07:00
yuneng-jiang
ad2a74ce2e
Merge pull request #27784 from BerriAI/litellm_/vibrant-bose-d1a024
fix(proxy): always merge caller-supplied tags into request metadata
2026-05-12 16:30:45 -07:00
Yuneng Jiang
945b10ded4
fix(tests): drop dall-e-only test classes; route live image tests via gpt-image-1
Second wave of failures from the 2026-05-12 DALL-E shutdown:
- tests/image_gen_tests/test_image_edits.py::TestOpenAIImageEditDallE2
  and tests/image_gen_tests/test_image_generation.py::TestOpenAIDalle3
  are explicitly named for the deprecated models and can't pass; remove.
  gpt-image-1 coverage already exists in sibling classes.
- tests/local_testing/test_router.py image gen tests use dall-e-3 only
  as a routing example; swap to gpt-image-1.
- tests/local_testing/test_custom_callback_input.py image_generation
  success/failure paths swapped to gpt-image-1.
2026-05-12 16:16:59 -07:00
Yuneng Jiang
8c8621ece3
fix(tests): swap dall-e to gpt-image-1 after openai deprecation
DALL-E 2 and DALL-E 3 were removed from the OpenAI API on 2026-05-12,
causing e2e image-generation tests to fail with "model does not exist".
Swap all live-API DALL-E references in proxy-backed tests to gpt-image-1
and update the dall-e-2 alias in proxy_server_config.yaml to point at
openai/gpt-image-1 (preserves any historical dall-e-2 callers).
2026-05-12 16:07:59 -07:00
Yuneng Jiang
4a78bfcd28
fix(proxy): always merge caller-supplied tags into request metadata
Caller-supplied tags (`x-litellm-tags` header, body `tags`, `metadata.tags`)
were silently dropped unless the key/team had
`metadata.allow_client_tags: true` set. Restore the documented behavior:
tags from the request always flow into `metadata.tags` and union with any
admin-configured static tags from key/team/project metadata.

Removes the `allow_client_tags` opt-in flag from the pre-call pipeline.
The flag was only ever read here; it has no schema or endpoint footprint,
so leftover values in existing key metadata are inert.

Test cleanup mirrors the simplification: drop the three tests that
verified the strip-when-not-opted-in path, drop the `allow_client_tags`
fixture lines from the merge/union tests.
2026-05-12 14:38:50 -07:00
Shivam Rawat
6bffa3e998
Merge branch 'litellm_internal_staging' into litellm_tag_budget_header_enforcement 2026-05-12 14:22:19 -07:00
ryan-crabbe-berri
63a2d1ddc9
fix(tests): use canonical litellm_enterprise import path (#27699)
The enterprise package is installed as `litellm_enterprise` (per
enterprise/pyproject.toml), but several tests imported it as
`enterprise.litellm_enterprise.*` — a path that only resolves
because the repo root happens to sit on sys.path, letting Python's
implicit namespace package machinery discover `enterprise/` as a
directory.

This breaks any test runner that relocates source (e.g. the
mutation-testing workflow, which copies tests under `mutants/`) and
also caused two `patch()` strings to target a module path that does
not match what production code imports — meaning those mocks were
never actually patching the production module's attribute.

Replace `from enterprise.litellm_enterprise.` with the canonical
`from litellm_enterprise.` across 6 test files, and fix two
`patch()` target strings (and one `sys.modules` patch key in the
SSO test) to match.
2026-05-12 12:32:57 -07:00
ryan-crabbe-berri
9c4faeabc9
feat(ui): search teams by team ID alongside name (#27684)
* feat(ui): search teams by team ID alongside name

The Teams page search box only matched team_alias, so pasting a team UUID
returned zero results. Detect when the input is a full UUID and route it
to the team_id filter instead; otherwise keep the existing alias substring
search. Placeholder now reads "Search teams by name or ID...".

Resolves LIT-2648

* refactor: search teams via backend OR clause, drop client-side UUID detection

Adds a `search` query param to /v2/team/list that ORs across team_id
(exact) and team_alias (case-insensitive contains), so the search box
sends one param regardless of input format. Removes the isLikelyTeamId
helper and the client-side branching it fed.
2026-05-12 11:14:29 -07:00
Jorge Yero Salazar
fc8a9a3406
Match litellm.completion supported model parameters with proxy model info (#27720)
* Use base_model for supported optional params

* Add test

* Formatting
2026-05-12 08:25:01 -07:00
Sameer Kankute
a47cf03838
Merge pull request #27703 from BerriAI/litellm_lit-2531-affinity-cross-group-fix
Some checks are pending
Unit Tests: Caching (Redis) / caching-redis (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / schema-migration (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests: Security / security (push) Waiting to run
fix(router): pin Responses API affinity to Azure resource on model-group switch
2026-05-12 10:39:06 +05:30
Sameer Kankute
aa9e7b9808
feat: litellm shin agent oss staging 05 10 2026 (#27631)
* fix: invalidate cached tag object on tag budget reset (#27481) (#27572)

Squash-merged by litellm-agent from oss-agent-shin's PR.

* chore(mcp): tighten stdio server registration paths (#27570)

Squash-merged by litellm-agent from stuxf's PR.

* fix(proxy): clear MCP OpenAPI mappings on server eviction; widen budget cache invalidation

Evict OpenAPI tools from global_mcp_tool_registry and strip tool_name_to_mcp_server_name_mapping entries when a server leaves the runtime registry (remove_server and approval-status eviction). Invalidate user_api_key_cache for keys, orgs, and team members on budget-tier spend resets alongside tags.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(mcp): align update_server eviction with remove_server name fallback

Document budget-reset test assertion flip (cross-pod cache staleness).

Greptile: eviction now pops by server_id then server_name like remove_server;
test docstring explains assert_not_awaited -> assert_any_await change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix org budget cache invalidation

---------

Co-authored-by: oss-agent-shin <ext-agent-shin@berri.ai>
Co-authored-by: stuxf <70670632+stuxf@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 20:31:43 -07:00
Cursor Agent
40db114a23
fix(router): accept Pydantic LiteLLM_Params in encryption-boundary key lookup
Greptile flagged that the strict isinstance(dict) guard in
_encryption_boundary_key would silently return None for any non-dict input,
including a LiteLLM_Params Pydantic instance, which exposes a custom .get()
method and is intended to be used dict-style in some router paths. If such
an instance ever flowed into healthy_deployments, the guard would drop every
candidate from boundary matching and fall through to the full deployment
pool, i.e. trigger the exact invalid_encrypted_content failure this check
exists to prevent.

Loosen the guard to accept any object exposing a callable .get(): plain
dicts (the common case) and LiteLLM_Params-style Pydantic instances. The
function still returns None for non-dict-like values (None, lists, strings,
ints, bare objects).

Adds regression tests covering:
  - LiteLLM_Params Pydantic instance resolves to the same boundary tuple as
    an equivalent plain dict
  - non-dict-like values and dicts missing required fields still return None
2026-05-12 03:09:08 +00:00
mateo-berri
f3b8aad883
fix(router): pin Responses API affinity to Azure resource on model-group switch
When a Responses API follow-up switches model_name (e.g. gpt-5.3-codex ->
gpt-5.4, or to a LiteLLM-side alias of the same Azure deployment), the
router has already filtered healthy_deployments to the new group, so the
originating model_id is no longer present. The encrypted_content_affinity
check would log "decoded deployment not found" and fall back to the full
deployment pool, where simple-shuffle could land on a different Azure
resource and trip a 400 invalid_encrypted_content.

Fall back to pinning by the originating deployment's encryption boundary
(api_base + api_key) when the model_id miss is across model groups. The
encrypted_content travels with the Azure resource, not the model_name,
so any deployment on the same resource accepts it.

LIT-2531
2026-05-12 02:26:06 +00:00
Mateo Wang
6de00e24b4
fix(ci): unbreak realtime + bedrock batch tests (#27690)
* fix(tests): drop deprecated OpenAI-Beta realtime header

OpenAI deprecated the 'OpenAI-Beta: realtime=v1' header; the live
service now returns code 4000 invalid_beta with
"Unknown beta requested: 'realtime'.". Two integration tests in
tests/llm_translation/realtime/test_realtime_guardrails_openai.py
hardcoded the header and started failing across all PRs.

Library code is unaffected: the OpenAI realtime handler only
forwards 'OpenAI-Beta: realtime=v1' upstream when the proxy *client*
sends it (litellm/llms/openai/realtime/handler.py). Default proxy
behavior uses the GA protocol.

Connect to OpenAI without the deprecated header, and accept the GA
event name 'response.output_audio_transcript.delta' alongside the
beta-protocol name 'response.audio_transcript.delta' for the
transcript-delta assertion.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(logging): post_call tolerates non-JSON-serializable values

post_call() did json.dumps(original_response) without default=str, so
any provider passing a dict containing datetime/Decimal/etc. would
raise TypeError. Bedrock batch retrieval hits this with
get_model_invocation_job() responses that include datetime fields
(submitTime, lastModifiedTime, endTime), failing
tests/batches_tests/test_bedrock_files_and_batches.py::test_async_file_and_batch
across all PRs.

Pass default=str so non-serializable values fall back to str().

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(tests): mock boto3 in bedrock retrieve batch test

The test patched AsyncHTTPHandler.get, but the bedrock retrieve
handler uses boto3.client('bedrock').get_model_invocation_job
directly, so the real AWS call was being made on every run, failing
with AccessDeniedException because the hardcoded test ARN belongs to
a different AWS account.

- Mock boto3.client and BedrockBatchesConfig.get_credentials so the
  test never touches AWS.
- Use status=Completed in the mock response so output_file_id is
  populated (the handler intentionally leaves it None for
  non-completed jobs).
- Assert the predicted per-job output object URI (matches what the
  handler actually returns) instead of the bare output prefix.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* docs(tests): include GA event name in guardrail-block test docstring

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-11 18:08:14 -07:00
oss-agent-shin
473cfca969
Add Bedrock Claude Platform route (#27678)
* Add Claude Platform AWS Bedrock route

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Use Bedrock Claude Platform route

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Move Claude Platform route under Bedrock

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Split Claude Platform messages config

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Centralize Claude Platform Bedrock route

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Address Claude Platform review feedback

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: oss-agent-shin <279349115+oss-agent-shin@users.noreply.github.com>
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
2026-05-11 15:50:54 -07:00
oss-pr-review-agent-shin[bot]
9ac4092536
[litellm-agent] Staging → litellm_internal_staging (5/11/2026) (#27677)
* Revert "feat(mavvrik): add Mavvrik integration for automatic LLM spend export…" (#27672)

This reverts commit cf6fd9d87816ca37d37472c104b8652552cce3f2.

* fix(proxy): update database connection timeout handling (#27507)

Squash-merged by litellm-agent from harish-berri's PR.

---------

Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
Co-authored-by: harish-berri <harish@berri.ai>
2026-05-11 14:49:38 -07:00
Dawei Gu
0751886680
feat(batch-job): bedrock batch model invocation job retrieval (#26834)
* feat(bedrock): support retrieve for model-invocation-job batch ARNs

`bedrock.retrieve_batch` previously only handled `:async-invoke/` ARNs
(Twelve Labs Marengo embeddings). The `:model-invocation-job/` ARNs
returned by `CreateModelInvocationJob` (the bulk batch inference API
behind `bedrock.create_batch`) fell through and returned a misleading
data-plane error, leaving created jobs unretrievable through the
LiteLLM batches API.

The two ARN families live on different AWS service endpoints
(`bedrock-runtime` data plane vs `bedrock` control plane), so they need
distinct handlers. This adds:

* `BedrockBatchesHandler._handle_model_invocation_job_status` — calls
  the control plane via boto3 (`bedrock:GetModelInvocationJob`),
  reusing `BaseAWSLLM.get_credentials` for credential resolution so
  model_list / env / role-assumption configs continue to apply. The
  response is reshaped into a `LiteLLMBatch` with the same status
  mapping `transform_create_batch_response` already uses.

* Output-file-URI prediction. Bedrock surfaces the user-supplied
  `s3OutputDataConfig.s3Uri` *prefix* in `GetModelInvocationJob`, but
  results actually land at `<prefix>/<job-id>/<basename(input)>.out`.
  We compute that single-file URI client-side and surface it as
  `output_file_id`, so OpenAI-style `client.files.content(...)` works
  without an extra `ListObjectsV2` round-trip. The bare prefix stays
  in metadata for callers that want the manifest.

* Dispatch in `litellm/batches/main.py` for the new ARN family,
  alongside the existing async-invoke branch.

* Unit tests covering ARN parsing, output-URI prediction (incl. edge
  cases), the full status mapping, region resolution precedence, and
  failure-message propagation.

Note: `request_counts` is intentionally `(0, 0, 0)` —
`GetModelInvocationJob` does not report per-record counts; getting
accurate numbers requires parsing `manifest.json.out` from the output
S3 prefix, which is left to callers.

Made-with: Cursor

* fix(bedrock): address PR feedback on model-invocation-job retrieve

Addresses Greptile P2 findings on #26834:

1. Use the bare job id (not the full ARN) when constructing the
   `api_base` URL for `pre_call` logging. Passing the full ARN double-
   counts the `model-invocation-job/` segment and embeds colons in the
   path, producing misleading log lines.

2. Drop the `or output_prefix` fallback when `_predict_output_file_uri`
   returns None. A bare prefix is not a downloadable object and surfacing
   it as `output_file_id` re-creates the very NoSuchKey bug this handler
   exists to fix. The bare prefix is still preserved in
   `metadata["output_s3_uri"]` for callers that want to do their own S3
   listing or read `manifest.json.out`.

   `metadata["output_file_uri"]` uses "" rather than None to satisfy the
   OpenAI Batch metadata schema (`dict[str, str]`); callers should branch
   on the typed `output_file_id` field instead.

Also expands test coverage on the new code path:
- new "stay None" regression test for the prediction-fail case
- pre_call/post_call logging hook assertions (incl. the bare-id URL)
- explicit cancelled_at / expired_at coverage
- _to_epoch type-handling matrix and the boto3 ImportError branch
- defensive _extract_region_from_bedrock_arn exception path
- empty-basename case for _predict_output_file_uri

Patch coverage on the changed lines is now 100% (the only remaining
uncovered lines in the file belong to the pre-existing
`_handle_async_invoke_status` method, which this PR does not touch).

Made-with: Cursor

* test(bedrock): cover retrieve_batch dispatch for both ARN families

Codecov flagged 8 uncovered lines on `litellm/batches/main.py` after
this PR refactored the Bedrock dispatch into a single guard with two
sub-branches (`async-invoke` + `model-invocation-job`). Existing tests
exercised the handlers directly but not the dispatch in `main.py`.

Adds `tests/test_litellm/batches/test_retrieve_batch_bedrock_dispatch.py`
with 6 mocked tests that exercise `litellm.retrieve_batch` end-to-end
for the dispatch logic:

- async-invoke ARN routes to `_handle_async_invoke_status`
- async-invoke ARN with no region falls back to "us-east-1" (preserves
  prior behavior on this branch)
- model-invocation-job ARN routes to the new
  `_handle_model_invocation_job_status` handler
- model-invocation-job ARN with no region forwards None (so the new
  handler can sniff region from the ARN itself, rather than getting
  silently routed to us-east-1)
- unrelated bedrock ARN family falls through to the generic
  provider-config retrieve path (neither special handler invoked)
- non-bedrock batch ids skip the bedrock dispatch entirely

Both handlers are mocked at the import site so the tests don't hit
AWS — the focus here is purely the new dispatch logic in main.py.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(bedrock): move retrieve_batch dispatch test to tests/test_litellm/

The dispatch test landed under `tests/test_litellm/batches/`, a new
directory that no upstream `test-unit-*.yml` workflow's `test-path`
allow-list includes. As a result, the test was never executed in CI
and codecov reported `litellm/batches/main.py` patch coverage at
11.11% (8 lines uncovered) — the lines belonging to this PR's
dispatch refactor itself.

Move the file up one level so it matches the
`tests/test_litellm/test_*.py` glob that `test-unit-misc.yml`
already runs, and adjust `sys.path.insert` for the new depth.

The companion handler tests under
`tests/test_litellm/llms/bedrock/batches/test_handler.py` are
unaffected — they're picked up by the `llms` directory in
`test-unit-llm-providers.yml`.

Made-with: Cursor

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 13:22:26 -07:00
Sameer Kankute
5833d3eadd
Merge pull request #27618 from BerriAI/litellm_reasoning_summary_chat_bridge
fix(openai): route reasoningSummary for gpt-5.4+ chat without tools to Responses API
2026-05-12 00:23:51 +05:30
ishaan-berri
12e59c8798
Fix internal tag usage scoping (#27315)
* Scope internal tag usage to own keys

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Add internal tag usage unowned key regression test

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Handle empty internal tag usage scopes safely

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

* Add tag activity database guard

Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: oss-agent-shin <279349115+oss-agent-shin@users.noreply.github.com>
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
2026-05-11 10:44:50 -07:00
Sameer Kankute
5e016f9f74
fix(responses): normalize chat tool_choice for completions→responses bridge (#27634)
* fix(responses): map chat tool_choice to Responses API when bridging from completions

OpenAI /v1/responses rejects tool_choice.function. Normalize forced-function
choice from chat shape to {type, name} in LiteLLMResponsesTransformationHandler.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(responses): strip tool_choice.function when top-level name is set

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 10:24:34 -07:00
Sameer Kankute
4c1d91d96f
fix(anthropic): inject dummy tool without modify_params (#27620)
Anthropic rejects tool_use/tool_result when tools is omitted. Always map
and attach the dummy tool in transform_request so CLIs work without
litellm.modify_params.

- Add unit test for transform_request dummy tool with modify_params off
- Adjust parallel function calling integration expectations: Bedrock
  Converse still requires modify_params for this path

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 09:50:16 -07:00
Sameer Kankute
79618b1c38
Merge pull request #27658 from BerriAI/litellm_internal_staging
merge main
2026-05-11 22:11:02 +05:30