Commit Graph
1126 Commits
Author SHA1 Message Date
183092d797 fix(proxy): normalize batch file IDs before ManagedObjectTable write (#28339)
* fix(proxy): normalize batch file IDs before ManagedObjectTable write

Run post_call_success_hook before update_batch_in_database on retrieve/cancel,
and ensure_batch_response_managed_file_ids so file_object never stores raw
provider output_file_id or error_file_id.

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

* fix(proxy): address Greptile review on batch file ID normalization

Remove redundant resolve_* calls after update_batch_in_database and rename
loop variable to avoid shadowing hidden_params unified_file_id.

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

* fix(tests): add mistral/ministral-8b-2512 to cost map and backfill in conftest

Mistral rotated the 'mistral/mistral-tiny' alias to return
'ministral-8b-2512' as the response model, which was missing from the
cost map. This caused test_completion_mistral_api and
test_completion_mistral_api_modified_input to fail in
litellm.completion_cost lookup.

- Add mistral/ministral-8b-2512 entry to both the in-tree
  model_prices_and_context_window.json and the bundled
  litellm/model_prices_and_context_window_backup.json (mirrors the
  existing openrouter/mistralai/ministral-8b-2512 pricing).

- litellm.model_cost is loaded at import time from the URL pinned to
  main, so the new backup entry isn't visible at test runtime until
  it also lands on main. Backfill any entries missing from the
  remote-fetched map into litellm.model_cost in the local_testing
  conftest so cost-calculator lookups succeed on this branch.

* fix(tests): drop unnecessary del of conftest backfill loop vars

* fix: resolve batch response file IDs even when status unchanged

The status-unchanged early return in update_batch_in_database was
skipping ensure_batch_response_managed_file_ids, leaving raw provider
input_file_id (and other raw IDs) in the user-facing response when
polling an in-progress batch. Move the in-place file ID normalization
above the early return so the response always carries unified managed
IDs while still skipping the DB write when nothing changed.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* test(batches): cover ensure_batch_response_managed_file_ids branches

Add tests for the previously-uncovered paths in
ensure_batch_response_managed_file_ids: error_file_id normalization,
swallowed conversion errors, UserAPIKeyAuth fallback from
db_batch_object, model_name resolution from unified_file_id, and early
returns when managed_files_obj, model_id, or auth context are missing.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 12:13:56 -07:00
99a63d5180 feat(gemini): add gemini-3.1-flash-lite model cost map (#28320)
* feat(gemini): add gemini-3.1-flash-lite model cost map entries

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

* Update model_prices_and_context_window.json

* Update source URL for model pricing information

* Sync source URL for gemini-3.1-flash-lite in backup JSON

* fix(model_cost_map): add mistral/ministral-8b-2512 entry

Mistral rotated the 'mistral/mistral-tiny' alias to return
'ministral-8b-2512' as the response model, which is not in the cost map.
This caused test_completion_mistral_api and
test_completion_mistral_api_modified_input to fail in
completion_cost lookup. Add the entry mirroring the existing
openrouter/mistralai/ministral-8b-2512 pricing.

* test(cost_calculator): assert output_cost_per_reasoning_token for gemini-3.1-flash-lite

* fix(tests): backfill local backup entries into runtime model_cost

litellm.model_cost is loaded from LITELLM_MODEL_COST_MAP_URL (pinned to
main) at import time, so any pricing entries added to the in-tree backup
on this branch aren't visible at test runtime until they also land on
main. The Mistral cassette currently returns model=ministral-8b-2512
and the cost-calculator lookup in test_completion_mistral_api /
test_completion_mistral_api_modified_input fails despite the entry
existing in the local backup. Backfill missing backup entries into
litellm.model_cost in the local_testing conftest so these lookups
succeed against the cassette state the branch is being tested with.

* fix(tests): guard conftest backfill against empty local cost map

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-20 10:03:14 -07:00
yuneng-jiangandGitHub 92de7423ef fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5 (#28281)
* fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so the live audio
calls in test_stream_chunk_builder_openai_audio_output_usage and
test_standard_logging_payload_audio now hard-fail with a model-not-found
error on every PR. The error was not "openai-internal", so the except
block swallowed it and execution fell through to an unbound
completion/response (UnboundLocalError).

Switch both tests to gpt-audio-1.5, OpenAI's recommended successor
(GA, not deprecated, already present in the litellm cost map so the
response_cost assertion still resolves). Also broaden the except to
skip with the real error in the reason instead of crashing, so a
transient upstream blip can't reintroduce the UnboundLocalError.

* fix(tests): narrow audio-test skip to model-not-found, re-raise the rest

Address review feedback: an unconditional skip on any exception would
silently mask a litellm-internal regression in the audio path (broken
param transformation, serialization, bad header) instead of failing CI.

Skip only on the upstream-unavailable class (model_not_found / "does not
exist" / openai-internal) and re-raise everything else, so genuine
regressions still fail loudly. The UnboundLocalError is still fixed
because the handler either skips or raises - it never falls through.

* fix(tests): add budget_exceeded to expected Interaction status enum

Staging added budget_exceeded to the Interaction OpenAPI status enum; the staging merge into this branch picked up the spec change but not the matching test update, so test_status_enum_values failed in CI. Align the test's expected list (exact-match by design) with the live spec.

* fix(tests): mock HTTP fetch in test_img_url_token_counter

The test parameterized a live third-party image URL (blog.purpureus.net) which now 404s, causing get_image_dimensions to fall through to its base64 decode path and crash with 'not enough values to unpack' on every PR run. Mock safe_get with a tiny 1x1 PNG so the URL branch is still exercised without any network dependency.

* fix(tests): swap gpt-4o-audio-preview to gpt-audio-1.5 in test_gpt4o_audio

OpenAI shut down gpt-4o-audio-preview on 2026-05-07, so both live tests in test_gpt4o_audio.py (test_audio_output_from_model and test_audio_input_to_model) hard-fail model_not_found on every PR. Swap the hardcoded model to OpenAI's successor gpt-audio-1.5 (same chat-completions audio surface; already in the litellm cost map). Mirror the narrowed-skip pattern from the prior audio fixes: skip on model_not_found / does-not-exist / openai-internal, re-raise everything else so genuine litellm regressions still fail CI loudly.
2026-05-19 14:48:30 -07:00
477b63c5ea fix(caching): replay openai/responses bridge cache hits as chat streams (#28158)
* fix(caching): replay openai/responses bridge cache hits as chat streams

When chat completions route through openai/responses, cached ModelResponse
payloads under aresponses keys were deserialized as ResponsesAPIResponse
(500) or re-translated as responses events (empty streaming deltas). Deserialize
chat-shaped cache entries as acompletion and bypass the responses stream iterator
for cached CustomStreamWrapper replay.

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

* fix(caching): map responses bridge call_type for sync vs async stream replay

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix: handle ModelResponse cache return in responses bridge and drop dead acompletion check

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(caching): detect chat cache hits via object field before choices fallback

Prefer chat.completion object type over the broad choices-key heuristic so
Responses API cached payloads are not misclassified if their schema changes.

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

* test(caching): cover responses bridge cache-hit paths in CI-tracked test suite

The new bridge cache replay logic in caching_handler.py and the
preformatted-stream guard in litellm_responses_transformation/handler.py
were exercised only by tests under tests/local_testing/, which the
responses-caching-types and misc shards do not run. Codecov flagged the
patch as 29.72% covered.

Add equivalent unit tests under tests/test_litellm/ so the responses,
caching, types, and misc shards execute them and ship their coverage
data to Codecov:

- _is_chat_completion_cached_dict happy/sad paths
- aresponses streaming bridge cache hit -> CustomStreamWrapper
- responses non-streaming bridge cache hit -> ModelResponse
- legacy ResponsesAPIResponse stream + non-stream replay
- _is_preformatted_cached_chat_stream true/false
- completion/acompletion early return on cached ModelResponse
- completion/acompletion skip rewrap on preformatted cached stream

* fix: add negative guard on object field in _is_chat_completion_cached_dict

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(vcr): treat corrupt cassette payloads as cache miss

* test: bump EOL'd NVIDIA rerank and OpenAI realtime models in CI

The NVIDIA hosted rerank endpoint for nvidia/llama-3_2-nv-rerankqa-1b-v2
reached end-of-life on 2026-05-18 and now returns HTTP 410 Gone, breaking
TestNvidiaNim::test_basic_rerank. Switch to nvidia/nv-rerankqa-mistral-4b-v3,
which is still hosted on the NVIDIA API catalog and is already listed in
model_prices_and_context_window.json.

OpenAI also retired the gpt-4o-realtime-preview-2024-12-17 model used by
test_realtime_guardrails_openai (now returns model_not_found). Switch the
realtime test URL to the GA gpt-realtime alias.

Unrelated to the responses-bridge cache fix in this PR, but committing
here to unblock CI per maintainer guidance.

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

* test(realtime): switch retired gpt-4o-realtime-preview to gpt-realtime

OpenAI removed gpt-4o-realtime-preview and all its date snapshots on
2026-05-18 (every variant now returns model_not_found), breaking the
live-WebSocket OpenAI realtime tests in CI:

  - test_openai_realtime_direct_call_no_intent
  - test_openai_realtime_direct_call_with_intent
  - TestOpenAIRealtime.test_realtime_connection
  - TestOpenAIRealtime.test_realtime_with_query_params

Point each of those to the current GA alias gpt-realtime (verified live).
Pure unit/mock tests that just assert the string value (e.g. in
test_realtime_query_params_construction and the
test_realtime_query_params_use_normalized_model_name mock) are left
alone since they do not depend on model availability.

Also relax the AI-response assertion in
test_text_message_blocked_by_guardrail_no_ai_response: gpt-realtime
occasionally produces a polite refusal ("I'm sorry, but I can't say
that") when the cancel arrives after the model has already started
generating, which is the expected outcome (no real AI content) but does
not contain the words 'blocked' or 'guardrail'. The primary guardrail
behaviour (guardrail_violation error event + transcript_delta block
message) is still asserted unchanged.

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

* test(nvidia_nim): mock rerank live API instead of hitting EOL'd endpoint

NVIDIA reached end-of-life for the hosted nvidia/llama-3.2-nv-rerankqa-1b-v2
rerank API on 2026-05-18 (returns HTTP 410 Gone), and the proposed
replacement nv-rerankqa-mistral-4b-v3 returns HTTP 404 for the CI account,
breaking TestNvidiaNim::test_basic_rerank.

Override test_basic_rerank to mock the HTTP transport (same pattern as
test_nvidia_nim_rerank_ranking_endpoint above) so the request/response
transformation and cost calculation stay covered without depending on
NVIDIA's hosted catalog rotation. The model identifier reverts to the
original llama-3.2-nv-rerankqa-1b-v2 since the request never leaves
the test process.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-18 16:27:06 -07:00
bb448b0031 fix(tests): stabilize image-edit VCR cassettes to stop live gpt-image-1 spend (#28110)
* fix(tests): stabilize image-edit VCR cassettes to stop live gpt-image-1 spend

The image-edit cassettes for ``gpt-image-1`` were accumulating >50
episodes and being refused by the persister
(``tests/_vcr_redis_persister.py``), so every CI run was hitting the
real OpenAI endpoint. The async parametrize was the clearest tell:
``test_openai_image_edit_litellm_sdk[True]`` cached to 1 entry, but the
``[False]`` (async) sibling grew to 51 entries and never replayed.

Two non-deterministic sources were fueling the growth, both fixed
here. After this patch, the cassettes settle at one episode per
unique call and replay for the 24-hour TTL like every other suite.

1. Pin httpx's multipart boundary at the source. The existing
   ``_normalize_multipart_boundary`` rewrites the boundary in the
   ``Content-Type`` header reliably, but on the async transport path
   the body is not always a contiguous ``bytes`` object when
   ``before_record_request`` runs, so the body-side replacement
   silently no-ops and the recorded cassette retains the random
   ``boundary=<hex>`` string. The next CI run gets a fresh random
   boundary, the ``safe_body`` matcher misses, and
   ``record_mode="new_episodes"`` appends another episode. Wrapping
   ``httpx._multipart.MultipartStream.__init__`` so it always uses
   ``vcr-static-boundary`` when no boundary is supplied eliminates
   the variance for both sync and async paths and leaves the normalizer
   in place as a backstop. Exposed as
   ``pin_httpx_multipart_boundary`` so other multipart-heavy suites
   (audio, ocr, batches) can adopt the same fixture later.

2. Pass raw ``bytes`` (not ``BytesIO`` streams) through the
   image-edit fixtures. A ``BytesIO`` whose file pointer is at EOF
   after the first multipart upload silently encodes an empty image on
   the next SDK / Router retry — yet another divergent body that VCR
   records as a new episode. ``bytes`` are immutable and position-less,
   so retries re-encode an identical payload every time. This is also
   a small production-correctness improvement: a customer passing
   ``BytesIO`` today would hit the same empty-body retry bug. The
   BytesIO-specific smoke test
   (``test_openai_image_edit_with_bytesio``) is preserved by giving
   ``get_test_images_as_bytesio`` its own factory instead of aliasing
   the bytes one.

3. Add ``scripts/flush_image_edit_vcr_cassettes.py`` — a one-shot
   Redis SCAN/DEL helper that clears the bloated pre-fix cassettes
   under ``litellm:vcr:cassette:tests/image_gen_tests/test_image_edits/*``.
   Without this, the next CI run still loads the existing 51-entry
   cassette, the new fixed-boundary body still doesn't match any of
   the stale entries, the persister still refuses to save, and the
   bleed continues. Run once with the production
   ``CASSETTE_REDIS_URL`` after merge (dry-run by default).

* DIAGNOSTIC: log VCR body mismatches + per-episode body hashes

Temporary observability boost so we can root-cause why
``test_image_edits.py`` async parametrizes still record fresh
episodes on every CI run even though the multipart boundary is now
pinned (sync parametrizes cache cleanly as VCR HIT). The matcher
currently raises ``AssertionError("request bodies differ")`` with
zero context, so we cannot tell whether the live body genuinely
varies, the matcher is comparing a bytes object to a stream object,
or the normalizer is silently skipping the body because it is not
bytes/str.

Three logs added; the first two are worth keeping permanently, the
third is intended to be reverted after the diagnosis lands:

1. ``_safe_body_matcher`` now emits a structured stderr block on
   mismatch (type of each side, length, SHA-256, first divergent
   byte offset, ±100-byte window). Always-on -- mismatches are
   signal, not noise, and the existing per-test verdict already
   logs once per test. PERMANENT.

2. ``_normalize_multipart_boundary`` now logs to stderr when the
   body type is not bytes/bytearray/str -- the silent ``else:
   return`` branch was masking exactly the case we suspect is
   firing on async (httpx ``MultipartStream`` handed to vcrpy
   before the body is read). PERMANENT.

3. ``_RedisPersister.save_cassette`` now logs every episode's body
   SHA-256, length, and 120-byte preview at save time. This lets
   two consecutive CI runs be diffed: if the same test records a
   different hash run-to-run, the live body genuinely varies; if
   both runs record the same hash but the matcher still misses, the
   bug is in the matcher itself. TEMPORARY -- revert once the
   async variance is identified and fixed.

Once a single ``image_gen_testing`` CI run produces these logs,
revert this commit (or just the persister hash block) with a force
push so the cassette save path is not noisy in steady-state.

* DIAGNOSTIC: route VCR diagnostics through per-PID files (bypass xdist capture)

Re-push of the diagnostic logging from the previous commit, this
time wired so the output actually survives to the CI log. xdist
captures stdout/stderr from every passing test in the worker
process; the body-matcher and normalizer-skip diagnostics fire from
inside vcrpy machinery during the test, so for any test that
ultimately passes (which is all of them once the cassettes are
recorded), the diagnostic lines are silently swallowed.

Fix: write each diagnostic line to a per-PID file under
``test-results/vcr-diagnostics/<pid>.log`` instead of writing to
stderr. The controller's ``pytest_terminal_summary`` aggregates
those files and writes them through ``terminalreporter.write_line``,
which is not subject to per-test capture. As a bonus,
``test-results/`` is already collected by the ``store_test_results``
step in CircleCI, so the raw per-worker logs survive as build
artifacts even after the test session ends.

Three call sites updated:

1. ``_emit_body_mismatch_diagnostic`` (matcher) -- writes the
   structured type/length/sha/window block via ``vcr_diag_write_line``.
2. ``_normalize_multipart_boundary`` -- logs the silent-skip path
   (body not bytes/bytearray/str) the same way.
3. ``_maybe_log_episode_body_hashes`` (persister) -- replaces the
   ``_log.warning`` calls (which the root-logger config also
   swallows in CI) with ``vcr_diag_write_line``.

Image-gen conftest is the only suite wired to dump the aggregated
log at session end. Other suites can opt in by adding
``emit_vcr_diagnostic_log(terminalreporter)`` to their own
``pytest_terminal_summary``. The diagnostic dir is cleared at the
start of each session (controller-only) so a local rerun does not
mix output from prior runs.

Same revert plan as the previous diagnostic commit: keep the
matcher + normalizer skip diagnostics permanently (they only fire
on signal events), revert the persister body-hash dump once the
async variance is identified.

* fix(tests): coalesce iterable request bodies before matching/recording

Root cause of the residual async image-edit cassette leak. The
diagnostic run for ``ba3915d9`` printed:

  [vcr-safe-body-matcher] request body mismatch
    body[a]: type='list_iterator' length=unknown sha256=N/A
    body[b]: type='list_iterator' length=unknown sha256=N/A

httpx's async transport hands vcrpy a ``request.body`` that is a
``list_iterator`` over multipart chunks rather than a contiguous
``bytes`` blob. Two consequences:

1. ``_safe_body_matcher`` compares the two iterator objects with
   ``==``, which is identity comparison for arbitrary iterators -
   semantically identical multipart bodies never compare equal, and
   ``record_mode="new_episodes"`` appends a new episode on every CI
   run until the cassette crosses ``MAX_EPISODES_PER_CASSETTE`` and
   the persister refuses to save (this is exactly what the OVERFLOW
   warning has been catching).
2. ``_normalize_multipart_boundary`` short-circuits its
   ``else: return`` branch because the body is neither bytes nor
   str, so any residual random boundary characters in the body bytes
   are never rewritten.

Sync requests do not hit this code path: httpx's sync transport
hands vcrpy a single ``bytes`` body, so ``==`` works and the
boundary normalizer runs as intended. That is why
``test_openai_image_edit_litellm_sdk[True]`` records to ``entries=1``
and replays cleanly while ``[False]`` (async) kept growing by one
episode per run.

Fix: add ``_materialize_iterable_body`` which coalesces an iterable
``request.body`` into ``bytes`` in-place. Call it from two places:

* The top of ``_before_record_request``, so the boundary normalizer
  and the cassette serializer both see bytes from then on.
* The top of ``_safe_body_matcher``, as defense in depth in case a
  future vcrpy code path invokes the matcher without first going
  through ``_before_record_request``.

The vcrpy ``Request`` is a wrapper used for matching and recording;
the underlying httpx transport sends its own request body
separately, so replacing the iterator on the vcrpy wrapper does
not starve the live HTTP send.

After this lands the async parametrizes should flip from
``[VCR MISS:RECORDED] entries=N+1`` to ``[VCR HIT] entries=N`` on
the next CI run, matching the sync side and dropping the residual
~$3/day to $0.

* fix(tests): handle bytes_iterator + never leave an exhausted body

Follow-up to 8e08272b. The previous attempt at coalescing iterable
request bodies bailed out (``return`` without writing
``request.body``) whenever it could not classify the chunk type.
That was the wrong failure mode for one critical case: vcrpy
sometimes presents the body as ``iter(some_bytes)``, whose Python
type is ``bytes_iterator`` and which yields ``int`` byte values
(0-255), not byte chunks. The old code saw an ``int`` chunk, hit
the ``else: return`` branch, and left ``request.body`` pointing at
the now-exhausted iterator.

The post-fix diagnostic run made this loud:

  [vcr-safe-body-matcher] request body mismatch
    body[a]: type='bytes_iterator' length=unknown sha256=N/A
    body[b]: type='bytes_iterator' length=unknown sha256=N/A

Every async image-edit test then ballooned from entries=2 to
entries=10 in that single CI run -- the exhausted iterator meant
the live multipart upload went out as an empty body, OpenAI
returned 400, the SDK + flaky retries fired, each retry got a
fresh iterator that my hook exhausted again, and ``new_episodes``
recorded each failed attempt as a new cassette episode.

This patch:

* Recognizes ``bytes_iterator`` (chunks are ``int``) and
  reconstructs the buffer via ``bytes(chunks)``.
* Keeps the existing ``list_iterator``-over-bytes-chunks handling
  via ``b"".join(...)``.
* **Always writes a bytes value back to ``request.body`` after
  consuming the iterator.** If the chunk shape is unrecognized,
  ``request.body`` is set to ``b""`` rather than left as an
  exhausted iterator. That is wrong in the sense of "we lost the
  body" but right in the sense of "the failure mode is now visible
  (live API call sends empty body and fails fast) instead of
  invisible (corrupt cassette grows silently)". Combined with the
  matcher diagnostic, any future regression in this code path will
  surface in the CI log immediately.

Local verification covers ``bytes_iterator``, ``list_iterator``
over bytes chunks, generator over bytes chunks, empty iterator,
already-bytes (idempotent), identical-content iterator equality
in the matcher (now matches), and differing-content iterator
inequality (still raises).

* fix(tests): clear vcrpy's sticky _was_iter flag so materialized bodies stay bytes

Actual root cause of the async image-edit cassette leak. The
previous diagnostic run produced this dead giveaway:

  [vcr-episode-body-hash] ... episode[0]: body type='bytes_iterator'
    is not bytes/bytearray/str -- cannot hash
  [vcr-safe-body-matcher] request body mismatch
    body[a]: type='bytes_iterator' length=unknown sha256=N/A
    body[b]: type='bytes_iterator' length=unknown sha256=N/A

Both sides of the matcher were ``bytes_iterator`` **after** the
materializer had supposedly converted them to bytes. That made no
sense until I read vcrpy's ``Request`` class.

vcrpy's ``Request`` keeps two private flags that are set in
``__init__`` from the original body's type and **never cleared by
the setter**:

  def __init__(self, method, uri, body, headers):
      self._was_file = hasattr(body, "read")
      self._was_iter = _is_nonsequence_iterator(body)
      ...

  @property
  def body(self):
      if self._was_file: return BytesIO(self._body)
      if self._was_iter: return iter(self._body)
      return self._body

  @body.setter
  def body(self, value):
      if isinstance(value, str): value = value.encode("utf-8")
      self._body = value   # <-- does NOT touch _was_iter / _was_file

So when httpx's async transport hands vcrpy an iterator body,
``_was_iter`` becomes ``True`` and stays there forever. Even after
``_materialize_iterable_body`` writes plain bytes via
``request.body = out``, the next read of ``.body`` re-wraps the
stored bytes in ``iter()`` -- producing a fresh ``bytes_iterator``
that compares unequal to any other ``bytes_iterator`` via object
identity. The matcher missed every time, the cassette grew by one
episode per run, and the persister saw the same iterator type when
trying to hash the body for the diagnostic log.

Fix: after writing the materialized bytes, also force
``_was_iter`` and ``_was_file`` to ``False``. vcrpy exposes no
public API for this, so we touch the private flags directly --
acknowledged as a pragmatic test-only hack with a clear unit
boundary (the only call site is ``_materialize_iterable_body``).

Local repro reproduces the exact production setup:
``Request('POST', url, iter(b'multipart-content'), {})`` on two
sides, runs the matcher, asserts HIT. Verified the matcher hits on
identical content and still raises on differing content.

Should be the last fix needed. Existing cassettes that contain
oddly-shaped bodies (lists of int chunks, etc. from the previous
``_was_iter=True`` save path) still match because the materializer
canonicalises both sides to bytes before comparison -- no fourth
re-flush required.

* revert(tests): drop the temp per-episode body-hash diagnostic

Removed now that 1c51ad13 has confirmed the root cause (vcrpy's
sticky ``_was_iter`` flag making the body getter re-wrap stored
bytes in ``iter()`` on every access). The hash dump did its job --
the post-1c51ad13 image_gen_testing run shows all five async
image-edit tests as ``[VCR HIT]`` with stable entry counts and
zero billing errors -- and is too noisy to keep on by default
(over 100 lines per session at steady state).

Kept permanently:

* ``_safe_body_matcher`` mismatch diagnostic in
  ``_vcr_conftest_common.py``. Only fires on a body mismatch,
  which is signal worth surfacing whenever it happens.
* ``_normalize_multipart_boundary`` "skipped" log line. Same
  rationale -- only fires when the body shape is something the
  normalizer cannot rewrite in place.
* The ``test-results/vcr-diagnostics/<pid>.log`` per-PID file
  plumbing (``vcr_diag_write_line`` /
  ``emit_vcr_diagnostic_log``). Useful for any future diagnostic
  that needs to bypass xdist stdout/stderr capture; cheap to keep.

* chore(tests): delete unused flush script + wire VCR diagnostic dump everywhere

* Remove ``scripts/flush_image_edit_vcr_cassettes.py``. It was a
  one-shot helper for the initial cassette flush; the iterator and
  ``_was_iter`` fixes mean no future flush should be required, and
  the script was never run anywhere (the actual flushes happened
  inside the CI conftest via the temp hacks that have since been
  reverted).

* The matcher mismatch + normalizer skip diagnostics already write
  per-PID files for every suite that imports the shared VCR
  plumbing, but ``emit_vcr_diagnostic_log`` -- the controller-side
  dump that surfaces those files into the CI log at session end --
  was only wired into ``image_gen_tests``. Add the one-line call to
  the 12 sibling conftests that already use VCR so the diagnostics
  surface in any suite's terminal output if a body matcher ever
  misses. No new output in steady state -- the dump is a no-op when
  no diagnostics were recorded that session.

* chore(tests): trim non-essential comments per project comment policy

Strips docstrings, inline comments, and block comments that this PR
introduced where the code itself was already self-evident. Keeps the
few lines that document non-obvious behaviour (raw-bytes-not-BytesIO
rationale on the image fixtures, the per-PID-files-bypass-xdist note
on the diagnostic directory). Touches only comments this PR added --
no pre-existing comment is removed.

Net: -161 lines of comment/docstring across 3 files, no code
behaviour change.

* chore(tests): forward **kwargs in pin_httpx_multipart_boundary wrapper

Defensive against future httpx MultipartStream.__init__ adding new
optional kwargs. Without the forward, the wrapper would silently drop
them. No behaviour change today.

* chore(tests): canonicalize VCR matchers and surface shouldn't-happen branches

Bundles the "follow-up cleanup PR" into this one so it does not get
lost. Four small changes:

1. Introduce ``_canonical_body(req) -> (bytes, pre_type)`` and route
   ``_safe_body_matcher`` through it. The matcher now operates on
   bytes by construction; the "compare two iterator objects via
   ``==`` and silently get object-identity semantics" failure mode
   (which cost us this entire PR to diagnose) is structurally
   impossible to reintroduce. ``pre_type`` is the body type *before*
   canonicalization, surfaced by the mismatch diagnostic so a future
   regression involving a new body shape is still visible.

2. Add a structured diagnostic to ``_key_fingerprint_matcher``. It
   was previously raising a bare ``AssertionError("API key
   fingerprints differ")`` with zero context -- exactly the
   anti-pattern the body matcher had before this PR.

3. Surface "shouldn't-happen" branches via ``vcr_diag_write_line``:

   * ``_strip_image_b64_payloads`` -- logs when ``response``,
     ``response['body']``, or ``response['body']['string']`` arrives
     in an unexpected shape (vcrpy contract violation).
   * ``_compute_key_fingerprint`` -- logs the ``"no-key"`` fallback
     with the request method/URL so a stripped-auth-header bug is
     visible instead of masked.
   * ``_canonical_body`` -- logs its own empty-bytes fallback when a
     body has a shape ``_materialize_iterable_body`` did not handle.

4. Re-introduce per-episode body-hash logging in
   ``_RedisPersister.save_cassette`` (was reverted in 927c5548 as
   "noisy"). Quantified cost: ~25 KB of CI log per session at peak,
   ~ms-scale CPU, zero output in steady state (no save = no log).
   Trade-off favours keeping it: lets two consecutive CI runs be
   diffed by body hash, which is how we will spot the next regression
   in the same class.

All call sites still work: local repro confirms iter==iter HIT,
iter!=iter raises, plain-bytes HIT, body-hash log emits via the same
per-PID file plumbing as the matcher diagnostics.

* chore(tests): symmetrize diag-log cleanup across every VCR-using conftest

``image_gen_tests/conftest.py`` was the only suite that cleared
``test-results/vcr-diagnostics/*.log`` at session start. The other 12
VCR-using conftests inherited any stale per-PID logs from a previous
local run and would dump them in the terminal summary -- harmless in
CI (fresh container) but confusing locally when running multiple
suites in sequence.

Extracts the cleanup into a ``reset_vcr_diag_dir`` helper in
``tests/_vcr_conftest_common.py`` and calls it from every VCR-using
conftest's ``pytest_configure``. Same single source of truth, no
inline duplication.

* fix(tests): gate body materialization on __next__ and strip PR comments

aiohttp/vcrpy stores the json kwarg as a dict; _materialize_iterable_body
was iterating it via __iter__ and joining the keys, replacing the request
body with concatenated key names ("textlanguageentities"). Gate on
__next__ so containers (dict/list/tuple) are left alone — only single-use
iterators like httpx's bytes_iterator / list_iterator are materialized.
Log diagnostic line when chunk type is unrecognized.

* fix(tests): JSON-encode dict bodies in canonical_body for stable matching

aiohttp stubs store the json kwarg as a dict; the fallback that compared
all dicts as b"" caused concurrent presidio analyze calls to be served
the wrong cassette episode. JSON-encode with sort_keys for stable bytes.

* fix(tests): guard emit_vcr_diagnostic_log against multi-conftest re-emission

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(tests): globalize multipart-boundary pin + stabilize whisper fixtures

Diagnostic shows audio_testing was silently re-recording 50+ live Whisper
episodes per CI run (over MAX_EPISODES_PER_CASSETTE, so the persister
refused to save). Two changes:

* Move the session-autouse _pin_multipart_boundary fixture into the
  shared _vcr_conftest_common module so every VCR-using suite picks it
  up via a single import. image_gen had it inline; the other 12 suites
  silently lacked it.
* Replace the module-level open("rb") audio file handles in test_whisper
  with cached bytes + a per-call (filename, bytes, mimetype) tuple,
  mirroring the image_edits raw-bytes pattern. Stops the file-pointer-
  at-EOF bug where the second test got an empty multipart body.

* chore(tests): drop per-episode body-hash dump and redundant emit guard

---------

Co-authored-by: shin-berri <shin-laptop@berri.ai>
Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-05-18 09:15:39 -07:00
Yuneng Jiang b5db7ed37d test(fireworks): mock remaining live smoke tests
test_completion_fireworks_ai and test_completion_cost_fireworks_ai
made real Fireworks calls and broke whenever Fireworks rotated its
serverless catalog (no externally-verifiable model list exists).
They also asserted nothing — just printed.

Mock the HTTP post and assert real behavior instead: the request is
built with the right model/messages and the OpenAI-compatible
response parses back; the cost path yields a non-zero cost against
the local cost map. No network, no model dependency, stronger than
the old smoke checks.
2026-05-15 22:28:27 -07:00
Yuneng Jiang 39a1d438f2 test(fireworks): replace deprecated llama-v3p3-70b-instruct model
Fireworks removed llama-v3p3-70b-instruct from serverless, so every
live test using it now fails with NotFoundError ("Model not found,
inaccessible, and/or not deployed").

Swap the 6 references (3 files) to the currently-served
accounts/fireworks/models/deepseek-v3p1 — the canonical model in
Fireworks' current docs examples and present in LiteLLM's cost map.
test_get_model_params_fireworks_ai is a pure pricing-heuristic test
(no network) asserting the >16b branch, so it uses llama-v3p1-70b-
instruct instead to keep the "fireworks-ai-above-16b" assertion and
branch coverage intact.
2026-05-15 22:17:10 -07:00
Mateo WangandGitHub 2c733c00f5 chore(ci): modernize model references in tests and configs (#27856)
* test: modernize models used in CircleCI e2e test suites

Replaces obsolete models (gpt-4o, gpt-4o-mini, gpt-3.5-turbo,
claude-3-5-sonnet-20240620, claude-sonnet-4-20250514) with current
equivalents across the e2e_openai_endpoints and
proxy_e2e_anthropic_messages_tests CircleCI jobs.

- gpt-4o -> gpt-5.5 (responses API e2e tests)
- gpt-4o-mini -> gpt-5-mini (websocket responses, oai_misc_config)
- gpt-4o-mini-2024-07-18 -> gpt-4.1-mini-2025-04-14 (fine-tuning,
  still actively fine-tunable)
- gpt-4 / gpt-3.5-turbo target_model_names example -> gpt-5.5 /
  gpt-5-mini
- bedrock claude-3-5-sonnet-20240620 batch entry -> haiku-4-5-20251001
  (also aligning oai_misc_config model_name with what
  test_bedrock_batches_api.py actually requests)
- bedrock claude-sonnet-4-20250514 (deprecated, retires 2026-06-15)
  -> claude-sonnet-4-5-20250929

* test: point bedrock-claude-sonnet-4 alias at Sonnet 4.6, not 4.5

Greptile/Cursor flagged that after the previous commit, the
bedrock-claude-sonnet-4 alias collided with bedrock-claude-sonnet-4.5
(both pointed to claude-sonnet-4-5-20250929). Rename to
bedrock-claude-sonnet-4.6 and point it at the Sonnet 4.6 Bedrock ID
(us.anthropic.claude-sonnet-4-6, already in the litellm model
registry) so the alias name matches the underlying model version.

* test: modernize models across remaining CI-mounted configs & tests

Expands the modernization sweep to all CircleCI-mounted proxy configs
and to test directories where the model literal is a fixture/route key
(not the test's subject).

Config changes:
- proxy_server_config.yaml: bump gpt-3.5-turbo / gpt-3.5-turbo-1106 /
  gpt-4o / gemini-1.5-flash / dall-e-3 underlying models; rename
  gpt-3.5-turbo-end-user-test alias to gpt-5-mini-end-user-test; bump
  text-embedding-ada-002 underlying to text-embedding-3-small. User-
  facing aliases (gpt-3.5-turbo, gpt-4, text-embedding-ada-002, etc.)
  preserved for backward compatibility with tests.
- simple_config.yaml, otel_test_config.yaml, spend_tracking_config.yaml:
  bump gpt-3.5-turbo underlying to gpt-5-mini.
- pass_through_config.yaml: claude-3-5-sonnet / claude-3-7-sonnet /
  claude-3-haiku entries replaced with claude-sonnet-4-5 / claude-
  haiku-4-5 / claude-opus-4-7.
- oai_misc_config.yaml: align alias name with the gpt-5-mini rename.

Test changes (proactive: claude-sonnet-4-20250514 / claude-opus-4-
20250514 retire 2026-06-15):
- tests/llm_translation/test_anthropic_completion.py: bump 3 references
  + paired Vertex AI ID to claude-sonnet-4-5.
- tests/llm_translation/test_optional_params.py: bump 2 references.
- tests/pass_through_unit_tests/test_anthropic_messages_passthrough.py
  and test_bedrock_anthropic_messages_test.py: bump router fixtures
  using the deprecated model IDs.
- tests/pass_through_unit_tests/base_anthropic_messages_tool_search_test.py:
  modernize docstring examples.
- tests/test_end_users.py: update references to renamed alias.

* test: modernize placeholder model literals in router_unit_tests

Mass replace_all on fixture/placeholder model literals across the
router_unit_tests/ suite (model name is a routing key / label, not the
test subject). Sub-agent sweep so far — additional commits will follow
for logging_callback_tests/, enterprise/, top-level tests/test_*.py,
and other CI-mounted dirs.

Mappings applied:
- gpt-3.5-turbo -> gpt-5-mini
- gpt-4 (bare) -> gpt-5.5
- gpt-4o (bare) -> gpt-5
- text-embedding-ada-002 -> text-embedding-3-small
- claude-3-sonnet-20240229 / claude-3-opus-20240229 /
  claude-3-haiku-20240307 / claude-3-5-sonnet-20240620 ->
  claude-sonnet-4-5-20250929 / claude-opus-4-7 /
  claude-haiku-4-5-20251001 as appropriate

Explicitly preserved:
- gpt-4o-mini-* variants (transcribe, tts, etc.) where they're current
- gpt-4-turbo / gpt-4-vision-preview / gpt-4-0613 (subject literals)
- JSONL batch body literals
- Mock LLM response model fields (must match upstream)
- Fake/mock identifiers

* test: modernize placeholder model literals across remaining CI suites

Sub-agent sweep across logging_callback_tests/, guardrails_tests/,
enterprise/, pass_through_unit_tests/, otel_tests/,
llm_responses_api_testing/, batches_tests/, spend_tracking_tests/,
litellm_utils_tests/, unified_google_tests/, and a few top-level
tests/test_*.py files where the model literal is a fixture or
placeholder (router model_list, mock standard logging payload, mock
callback data) rather than the test's subject.

Mappings applied (see scope notes below):
- gpt-3.5-turbo -> gpt-5-mini
- gpt-4 (bare) -> gpt-5.5
- gpt-4o (bare) -> gpt-5.5 (corrected from initial gpt-5 — bare gpt-5
  is not a valid OpenAI alias; only gpt-5.5 / gpt-5.4 / gpt-5.2-codex
  / gpt-5-mini exist)
- gpt-4o-mini (bare) -> gpt-5-mini
- text-embedding-ada-002 -> text-embedding-3-small
- claude-3-sonnet-20240229 -> claude-sonnet-4-5-20250929
- claude-3-opus-20240229 -> claude-opus-4-7
- claude-3-haiku-20240307 -> claude-haiku-4-5-20251001
- claude-3-5-sonnet-20240620/20241022 -> claude-sonnet-4-5-20250929
- claude-3-7-sonnet-20250219 -> claude-sonnet-4-6
- gemini-1.5-flash -> gemini-2.5-flash
- gemini-1.5-pro -> gemini-2.5-pro

Explicitly preserved (not modernized):
- llm_translation/ tests where model is the SUBJECT (provider-specific
  translation/transformation logic). Only the deprecated 20250514
  references were already bumped in a prior commit.
- Cost-calc / tokenizer subject tests in test_utils.py (skip-ranges
  documented by the sub-agent).
- Bedrock model IDs in test_health_check.py path-stripping tests.
- JSONL batch request bodies and mock LLM response bodies (must match
  upstream literal).
- Langfuse expected-request-body JSON fixtures (cost values are exact-
  match-asserted; changing the model would shift response_cost).
- gpt-3.5-turbo-instruct (text-completion endpoint; no modern OpenAI
  equivalent).
- Top-level tests calling the proxy through user-facing aliases
  (gpt-3.5-turbo, gpt-4, text-embedding-ada-002, dall-e-3) — aliases
  in proxy_server_config.yaml stay; only the underlying model was
  bumped.
- tests/test_gpt5_azure_temperature_support.py (the test's whole point
  is model-name handling).
- Fake / mock / openai/fake identifiers.

Notable side fixes:
- test_spend_accuracy_tests.py: UPSTREAM_MODEL now matches what
  spend_tracking_config.yaml's proxy actually routes to (gpt-5-mini),
  resolving a latent inconsistency.
- proxy_server_config.yaml: bare `gpt-5` alias renamed to `gpt-5.5`
  (bare gpt-5 is not a valid OpenAI alias).
- test_batches_logging_unit_tests.py: explicit_models list entries
  kept distinct (gpt-5-mini + gpt-5.5) after bulk rename.

* test: fix CI failures from model modernization sweep

CI surfaced 4 categories of regression from the bulk modernization:

1. Azure deployment names are customer-specific. Reverted:
   - tests/litellm_utils_tests/test_health_check.py: azure/text-
     embedding-3-small -> azure/text-embedding-ada-002 (the CI Azure
     account does not have a text-embedding-3-small deployment).
   - tests/logging_callback_tests/test_custom_callback_router.py:
     same revert for two router fixtures driving aembedding.

2. gpt-5 family does not accept temperature != 1. Tests that pass a
   custom temperature swapped from gpt-5-mini to gpt-4.1-mini (modern
   non-reasoning OpenAI mini that still accepts temperature/logprobs):
   - tests/logging_callback_tests/test_datadog.py
   - tests/logging_callback_tests/test_langsmith_unit_test.py
   - tests/logging_callback_tests/test_otel_logging.py

3. proxy_server_config.yaml's gpt-3.5-turbo-large alias was routing to
   gpt-5.5 (a reasoning model that rejects logprobs). The proxy test
   tests/test_openai_endpoints.py::test_chat_completion_streaming
   exercises logprobs/top_logprobs through that alias. Bumped the
   underlying model to gpt-4.1 (non-reasoning, still modern).

4. tests/logging_callback_tests/test_gcs_pub_sub.py asserts against a
   pinned JSON fixture (gcs_pub_sub_body/spend_logs_payload.json) with
   hardcoded model="gpt-4o" and a model-specific spend value. Reverted
   the litellm.acompletion calls in the test to model="gpt-4o" so the
   fixture's exact-match assertions still hold.

5. tests/pass_through_unit_tests/test_anthropic_messages_passthrough.py:
   anthropic.messages.create routing to openai/gpt-5-mini returned an
   empty content[0] with max_tokens=100 (reasoning-token consumption).
   Swapped to openai/gpt-4.1-mini.

* test: fix Assistants API model + 2 cursor[bot] review nits

1. pass_through_unit_tests/test_custom_logger_passthrough.py: gpt-5.5
   isn't accepted by the /v1/assistants endpoint
   ("unsupported_model"). Switch to gpt-4.1-mini (modern, Assistants-
   API-supported, non-reasoning).

2. example_config_yaml/pass_through_config.yaml: the previous sweep
   bumped the claude-3-7-sonnet alias to claude-opus-4-7, which is a
   tier change (Sonnet -> Opus). Map to claude-sonnet-4-6 to keep the
   Sonnet tier intact. (Cursor bugbot review.)

3. example_config_yaml/simple_config.yaml: model_name was left as
   gpt-3.5-turbo while the underlying was bumped to gpt-5-mini, which
   muddles the "simple" example. Make both sides gpt-5-mini so the
   most basic example is a straight 1:1 mapping again. (Cursor bugbot
   review.)

* fix: revert gpt-4/gpt-3.5-turbo alias underlying to non-reasoning models

tests/test_openai_endpoints.py::test_completion calls the proxy alias
"gpt-4" with temperature=0, and other tests call gpt-3.5-turbo with
custom temperature / logprobs / the legacy /v1/completions endpoint.
The earlier modernization mapped both aliases to gpt-5.5 / gpt-5-mini,
which are reasoning models that reject temperature != 1 and don't
expose /v1/completions. Map the aliases to gpt-4.1 / gpt-4.1-mini
(modern non-reasoning OpenAI models) instead — keeps user-facing
aliases preserved while picking a current underlying that still
supports the parameters/endpoints the tests exercise.
2026-05-15 15:44:28 -07:00
Cursor AgentandMateo Wang 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 AgentandMateo Wang 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
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
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
yuneng-jiangandGitHub 9a338e1b6b [Test] Tests: Stop parametrizing API keys into pytest test IDs (#27249)
Several tests parametrized over (model, api_key, ...) tuples or raw
token strings, causing pytest to embed those values in the test ID
and print them in CI logs. Refactored each affected test to keep the
same coverage without putting key material into parametrize.

- audio_tests/test_audio_speech.py: split env-var keys into separate
  azure/openai test functions sharing a helper; sync_mode parametrize
  preserved.
- audio_tests/test_whisper.py: split into openai_whisper /
  azure_whisper functions sharing a helper; response_format parametrize
  preserved.
- local_testing/test_embedding.py: single-case parametrize inlined.
- proxy_unit_tests/test_user_api_key_auth.py: 5 header parametrize
  cases split into 5 named tests sharing an _assert helper.
- proxy_unit_tests/test_proxy_utils.py: 4 api_key_value cases split
  into 4 named tests.
- test_litellm/proxy/auth/test_user_api_key_auth.py: 5 key-prefix
  cases (Bearer / Basic / lowercase bearer / raw / AWS SigV4) split
  into 5 named tests.

Verified: black clean; 14 refactored unit tests pass; pytest collects
audio/embedding tests with safe IDs (no key material in test IDs).
2026-05-05 17:21:18 -07:00
7e13256fee test: add 24hr Redis-backed VCR cache to additional test suites (#27159)
* test: add 24hr Redis-backed VCR cache to additional test suites

Extracts the existing llm_translation VCR plumbing into a reusable helper
(tests/_vcr_conftest_common.py) and wires it into the conftest.py files
of the test directories listed in LIT-2787:

  audio_tests, batches_tests, guardrails_tests, image_gen_tests,
  litellm_utils_tests, local_testing, logging_callback_tests,
  pass_through_unit_tests, router_unit_tests, unified_google_tests

The same helper is also adopted by the pre-existing llm_translation and
llm_responses_api_testing conftests to remove the copy-pasted VCR setup.

Each consuming conftest:
- registers the Redis persister via pytest_recording_configure
- auto-marks collected tests with pytest.mark.vcr (skipping respx-using
  files where applicable, since respx and vcrpy both patch httpx)
- gates cassette writes on test success via _vcr_outcome_gate

The cache is opt-in via CASSETTE_REDIS_URL; when unset, VCR is disabled
and tests hit live providers as before. LITELLM_VCR_DISABLE=1 still
forces a bypass for ad-hoc local runs.

Test directories that run LiteLLM proxy in Docker (build_and_test,
proxy_logging_guardrails_model_info_tests, proxy_store_model_in_db_tests)
are intentionally not included: VCR.py patches the in-process httpx
transport and cannot intercept calls made from inside a Docker container.
The installing_litellm_on_python* jobs make no LLM calls and don't
benefit from caching.

https://linear.app/litellm-ai/issue/LIT-2787/add-24hr-caching-to-additional-test-suites

* test(vcr): add safe-body matcher to handle JSONL and binary request bodies

vcrpy's stock body matcher inspects Content-Type and unconditionally
runs json.loads on application/json bodies. JSON Lines payloads (used
by the Bedrock batch S3 PUT and other upload paths) crash that with
json.JSONDecodeError: Extra data, before the matcher can return
'not a match'.

This was the root cause of the batches_testing CI job failing on
test_async_create_file once VCR auto-marking was applied to the
batches_tests directory.

Add a conservative byte-equality body matcher and use it in place of
'body' in the shared match_on tuple. The matcher is strictly more
conservative than vcrpy's default — the only thing it gives up is
'different JSON key order is treated as the same body', which doesn't
apply to deterministic litellm-built request payloads. It can never
produce a false positive that the default would have rejected, so
there is no cross-contamination risk.

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

* test(vcr): exclude tests that VCR replay actively breaks

A few tests are incompatible with cassette replay and were failing on
the latest CI run after VCR auto-marking was extended to local_testing
and logging_callback_tests:

- test_amazing_s3_logs.py (logging_callback_tests): the test asserts on
  a per-run response_id that should round-trip through a real S3
  PUT/LIST. vcrpy's boto3 stub intercepts the PUT and the LIST replays
  stale keys, so the freshly-generated id is never found.
- test_async_embedding_azure (logging_callback_tests) and
  test_amazing_sync_embedding (local_testing): the failure branches
  deliberately pass api_key='my-bad-key' to assert that the failure
  callback fires. We scrub auth headers from cassettes (so the bad-key
  request matches the prior good-key request), and vcrpy replays the
  recorded 200 — the failure callback never fires.
- test_assistants.py (local_testing): the OpenAI Assistants polling
  APIs mint fresh thread/run IDs every recording session and then poll
  until status=='completed'. Replays of those polled GETs can never
  match a freshly-generated run id, so every CI run effectively
  re-records and the suite blows past the 15m no_output_timeout.

Skip these from VCR auto-marking so they continue to hit live providers
as they did before this change. The remaining tests in each directory
still get cached.

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

* test(vcr): expand skip lists for second batch of incompatible tests

Followup to the previous commit. After re-running CI on the rebuilt
branch, three more tests surfaced as VCR-replay-incompatible:

- litellm_utils_testing :: test_get_valid_models_from_dynamic_api_key
  Calls GET /v1/models with api_key='123' to assert the result is empty.
  We scrub auth headers, so the bad-key request matches the prior
  good-key cassette and replays the recorded model list.
- litellm_utils_testing :: test_litellm_overhead.py
  Measures litellm_overhead_time_ms as a percentage of total wall-clock
  time. With cached responses the upstream 'network' time collapses to
  microseconds, blowing past the 40%% threshold the test asserts on.
  Skip the whole file (every parametrization is at risk).
- local_testing_part1 :: test_async_custom_handler_completion and
  test_async_custom_handler_embedding
  Same bad-key failure-callback pattern as the already-skipped
  test_amazing_sync_embedding.
- litellm_router_testing :: test_router_caching.py
  Asserts on litellm's own router-level response cache by comparing
  response1.id to response2.id across repeat upstream calls (test
  bypasses litellm cache via ttl=0 and expects upstream to return a
  *new* id). With VCR replay both upstream calls return the same
  cassette body, so the ids are identical. Skip the whole file.
- logging_callback_tests :: test_async_chat_azure (preemptive)
  Same shape as already-skipped test_async_embedding_azure; was masked
  by upstream OpenAI rate-limit failures on baseline.

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

* test(vcr): use item.path and tighten matcher docstring

- Replace pytest's deprecated item.fspath with item.path in
  apply_vcr_auto_marker_to_items so we don't emit deprecation
  warnings under pytest 8.
- Clarify _safe_body_matcher docstring to reflect actual behavior
  (direct == first, then UTF-8 bytes comparison, no repr fallback).

Addresses Greptile review feedback on PR #27159.

* test(vcr): swallow all RedisError on cassette save/load

Cassette persistence is strictly best-effort: any Redis-side failure
(connection blip, timeout, OutOfMemoryError when the maxmemory cap is
hit, READONLY replicas, etc.) should degrade to 'test passed but
cassette not cached' rather than fail the test on teardown.

Previously the persister only caught ConnectionError and TimeoutError,
so OutOfMemoryError — which Redis Cloud raises when the cassette cache
hits its memory cap and there are no evictable keys — propagated out of
vcrpy's autouse fixture and ERRORed otherwise-passing tests on
teardown. This caused the litellm_utils_testing CircleCI job to fail on
the latest commit's run, even though the underlying test was a unit
test that used mock_response and produced no real upstream traffic
(the cassette was dirtied by a background langfuse callback). The
rerun only succeeded because Redis evictions happened to free enough
room before the SET — i.e. it was timing-dependent flakiness.

Catch redis.exceptions.RedisError (the common base of all server- and
client-side Redis exceptions) on both save and load, and parametrize
the regression tests across ConnectionError, TimeoutError, and
OutOfMemoryError to pin the new behavior.

* test(vcr): surface cassette-cache failures with warnings + session banner

When the persister silently swallows a Redis OOM (or any RedisError) on
save/load there is otherwise no visible signal that the cache is
degraded — tests pass, the cassette just isn't persisted, and the next
session still hits the same Redis at the same near-cap memory.

Add three layers of observability so that failure mode is loud:

1. Per-process health counters ("save_failures", "load_failures", and
   the last error string for each), exposed via cassette_cache_health()
   and reset via reset_cassette_cache_health(). The persister
   increments these in addition to logging.

2. VCRCassetteCacheWarning (UserWarning subclass) emitted via
   warnings.warn() inside the persister's except block. Pytest's
   built-in warnings summary at session end automatically lists every
   such warning, so the failure is visible in CI logs without any
   conftest-level wiring.

3. Session-end banner via emit_cassette_cache_session_banner() and a
   stderr-fallback atexit handler registered from
   register_persister_if_enabled(). Two states:
     - red "VCR CASSETTE CACHE DEGRADED" when save_failures or
       load_failures > 0
     - yellow "VCR CASSETTE CACHE NEAR CAPACITY" (no failures, but
       used_memory >= 85% of maxmemory) so the next session knows
       the Redis is approaching OOM before any SET actually fails

Capacity comes from a best-effort INFO memory probe
(cassette_cache_capacity_snapshot) that returns None on any failure or
when maxmemory is uncapped. The atexit handler skips xdist workers so
only the controller emits.

Tests: parametrize the existing save/load swallow-error tests across
ConnectionError/TimeoutError/OutOfMemoryError, add direct tests for
the health counters and warning emission, and a new
test_vcr_conftest_common_banner.py covering banner output for every
state (silent/red/yellow/disabled/xdist-worker).

* test(vcr): bucket cassettes by API key fingerprint, drop bad-key skips

Tests that deliberately call an LLM API with a bad key (e.g. to assert
that the failure callback fires, or that check_valid_key returns False)
were being silently served the prior good-key cassette: we scrub the
real Authorization / x-api-key header from the cassette before storing
it, so a follow-up bad-key call is byte-identical to the good-key call
under the existing match_on tuple.

Add a 'key_fingerprint' custom matcher that distinguishes requests by
the SHA-256 of their API-key headers. The fingerprint is stamped into
a synthetic 'x-litellm-key-fp' header by a new before_record_request
hook, which then strips the real auth headers (we have to do the
scrubbing here instead of via vcrpy's filter_headers knob, because
filter_headers runs *first* and would erase the value we want to hash).

Bad-key requests now get a different cassette bucket than good-key
requests, so vcrpy will not replay a recorded 200 in place of the
expected 401. The fingerprint is a one-way hash of the secret, so
cassettes never contain the key.

This permanently removes the 'bad-key' category of skips:

- tests/local_testing: dropped ::test_amazing_sync_embedding,
  ::test_async_custom_handler_completion,
  ::test_async_custom_handler_embedding
- tests/logging_callback_tests: dropped ::test_async_chat_azure,
  ::test_async_embedding_azure
- tests/litellm_utils_tests: dropped
  ::test_get_valid_models_from_dynamic_api_key

Coverage: 7 new unit tests in tests/test_litellm/test_vcr_safe_body_matcher.py
covering header stripping, fingerprint determinism, no-auth bucketing,
good-vs-bad key discrimination, x-api-key (Anthropic/Azure) discrimination,
and idempotence under replay.

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

* test(vcr): drop redundant comments and docstrings

Trim narration of code that is already self-evident from function and
variable names. Keep the two genuinely non-obvious bits:

- ordering constraint between filter_headers and before_record_request,
  which would invite a maintainer to re-introduce the bug if removed
- the per-directory _VCR_INCOMPATIBLE_FILES rationale, since 'why
  exactly is this skipped' is not knowable from the test name alone

Also drop the 40-line commented-out drop-in conftest snippet at the
bottom of _vcr_conftest_common.py — the consuming conftests are the
canonical reference.

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

* test(vcr): make _before_record_request idempotent

vcrpy invokes before_record_request more than once per request:
can_play_response_for calls it, then __contains__ /
_responses (reached via play_response) call it again on the
result. The second invocation sees a request whose auth headers we
already stripped, so a naive recompute yields "no-key" and
overwrites the real fingerprint stored in the header.

This makes can_play_response_for and play_response disagree on
matchability — the former says "yes, we have a stored response for
this" (matching no-key to no-key) and the latter throws
UnhandledHTTPRequestError because it computes a fresh real
fingerprint that doesn't match the stored no-key.

In CI this manifested as ~30 failing tests across guardrails_testing,
audio_testing, batches_testing, image_gen_testing, llm_responses_api,
litellm_router_unit_testing, etc. Skip the recompute when the header
is already set, so re-applying the hook is a no-op.

Adds a regression test that fires the hook twice on the same dict and
asserts the fingerprint stays put.

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

* test(vcr): drop more redundant docstrings and headers

* test(vcr): enable 24hr cache for ocr_tests and search_tests

These two directories were the only non-dockerized test suites in the
build_and_test workflow that make live LLM/provider API calls but were
not VCR-enabled by this PR. Together they account for 96 tests:

- tests/ocr_tests/ (31): Mistral OCR, Azure AI OCR, Azure Document
  Intelligence, Vertex AI OCR. Pure-unit tests inside the same files
  (e.g. TestAzureDocumentIntelligencePagesParam) make no HTTP calls
  and become benign VCR NOOPs.
- tests/search_tests/ (65): Brave, DataForSEO, DuckDuckGo, Exa,
  Firecrawl, Google PSE, Linkup, Parallel.ai, Perplexity, SearchAPI,
  Searxng, Serper, Tavily.

Both directories use the canonical minimal conftest pattern from
tests/audio_tests/conftest.py with no skip lists. None of the test
files use respx, none assert on per-call upstream non-determinism
(no response1.id != response2.id, no overhead-as-fraction-of-total,
no live polling), so the default match_on tuple should cache cleanly.
If a flake surfaces during the first cassette-recording CI run, we
can add a targeted skip the same way we did for the other dirs.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-05-05 15:13:31 -07:00
Yuneng Jiang 7f550a5d67 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/peaceful-bell-ba8ca5 2026-05-04 18:21:33 -07:00
Yassin Kortam a56256e5ee feat: routing groups ui 2026-05-04 18:09:14 -07:00
Yuneng Jiang 22782f3c3f [Fix] Tests: Replace deprecated openrouter/claude-3.7-sonnet with claude-sonnet-4.5
OpenRouter has dropped active endpoints for anthropic/claude-3.7-sonnet,
causing test_reasoning_content_completion to fail with a 404 "No endpoints
found" error. Switch to anthropic/claude-sonnet-4.5, which is current and
supports reasoning streaming.
2026-05-04 17:51:50 -07:00
Yuneng Jiang e3917c9d08 [Test] Anthropic: Replace Legacy Claude-4-Sonnet Alias With Haiku 4.5
Three live-API tests pinned to claude-4-sonnet-20250514, which is a
non-canonical alias of claude-sonnet-4-20250514. Anthropic's main API
no longer resolves the legacy form under freshly issued keys, so the
tests fail with not_found_error. The token counter test pinned to
claude-sonnet-4-20250514 itself (deprecation_date 2026-05-14, two weeks
out) was on borrowed time too.

Bump all four to claude-haiku-4-5-20251001 — capability superset for what
these tests exercise (streaming, parallel tool calling, extended thinking,
token counting), no upcoming deprecation, cheaper per-token.
2026-05-01 19:10:27 -07:00
Mateo WangandGitHub 0b87bdff3d Merge pull request #26976 from BerriAI/litellm_default_embedding_encoding_format
feat(embedding): default OpenAI-path encoding_format to float
2026-05-01 15:37:59 -07:00
yuneng-jiangandGitHub 02582466c4 Merge pull request #24340 from BerriAI/litellm_staging_03_21_2026
Litellm staging 03 21 2026
2026-05-01 11:57:44 -07:00
Sameer Kankute e0398cade7 fix(caching): defer streaming cache-hit callbacks for all stream=True
Success handlers already run when CustomStreamWrapper or
CachedResponsesAPIStreamingIterator finishes replay. Logging at
cache-hit time for acompletion/completion streaming duplicated spend
and callbacks. Align tests with deferred behavior.

Made-with: Cursor
2026-05-01 17:03:32 +05:30
Sameer Kankute a1f0823393 test(embedding): align local_testing OpenAI encoding_format default
Made-with: Cursor
2026-05-01 16:27:13 +05:30
NoahandSameer Kankute 8947a74e13 fix(cache): persist and replay streamed Responses API requests (#24580)
* fix(cache): persist and replay streamed Responses API requests

* Add focused coverage for streamed responses cache

* Cover streamed responses cache helper branches

* Exercise streamed responses cache edge branches
2026-05-01 11:55:36 +05:30
Cursor AgentandMateo Wang 3215874e40 fix(test): scope ERROR log assertion to LiteLLM logger in test_model_alias_map
The test was flaking on unrelated asyncio ERROR records (e.g. "Unclosed
client session" from background tasks in other tests). Restrict the
assertion to records emitted by LiteLLM loggers so the test only fails
on errors actually produced by the code under test.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-04-29 03:48:41 +00:00
Ryan Crabbe dc46467235 fix(tests): replace deprecated Bedrock Claude 3.7 Sonnet model ID
AWS Bedrock has reached end-of-life for `claude-3-7-sonnet-20250219-v1:0`,
returning 404s with "This model version has reached the end of its life."
Update test references to `claude-sonnet-4-5-20250929-v1:0` (same capability
surface: thinking, tools, prompt caching, PDF input, vision, computer use).

The bedrock/invoke pass-through tests stay on Sonnet 3.5 since Sonnet 4.5
is converse-only on Bedrock.
2026-04-28 14:24:19 -07:00
Yuneng JiangandClaude Opus 4.7 1af11d4371 fix(vertex): synthesize items for array types missing items entirely
Companion to the prior commit. process_items only converted empty
`items: {}` to `{"type": "object"}`. But anyOf branches like
`{"type": "array"}` (no items field at all) were untouched, so after
convert_anyof_null_to_nullable stripped the null branch and added
nullable, the array branch was sent to Vertex as
`{"type": "array", "nullable": true}` — which Vertex rejects with
INVALID_ARGUMENT (`any_of[0].items: missing field`).

Make process_items synthesize `items: {"type": "object"}` for any
`type == "array"` schema where items is missing or empty.

Also:
- Convert test_gemini_tool_calling_working_demo to a hermetic mock
  test asserting items is present on the array branch in the sent
  body. Was previously a real-network call to Vertex and was the
  test the user reported still failing in CI.
- Add unit test test_build_vertex_schema_array_branch_missing_items_in_anyof
  covering the missing-items shape directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 09:23:55 -07:00
Yuneng JiangandClaude Opus 4.7 3ca985451e fix(vertex): preserve items on array branches inside anyOf with null
convert_anyof_null_to_nullable was stripping the items field from array
branches inside anyOf when a sibling null branch was present, leaving
{"type": "array"} without items. Vertex requires items whenever
type == "array" (even inside anyOf) and rejects the call with
INVALID_ARGUMENT.

Leave the (possibly empty) items in place so the downstream process_items
step can convert {} to {"type": "object"}, which is what Vertex wants.

Also:
- Update test_build_vertex_schema expected output, which was codifying
  the broken shape.
- Convert test_gemini_tool_calling_not_working to a hermetic mock test
  that asserts the request body sent to Vertex includes items inside
  the callbacks anyOf array branch. The previous form made a real
  network call and was flaky in CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:37:09 -07:00
Chesars c75f0c0566 test: drop duplicate openrouter prefix-strip test
The multi-segment case (openrouter/<provider>/<model> → <provider>/<model>)
is already covered by tests/test_litellm/llms/openrouter/test_openrouter_provider_routing.py
in internal_staging, with broader coverage including double-prefix native
models, wildcard deployments, and the bridge double-call scenario.
Keeping a duplicate in tests/local_testing/ adds maintenance load with no
extra coverage.
2026-04-25 18:06:25 -03:00
Chesars 232464e151 test: drop incorrect openrouter native-prefix test
The removed test asserted that get_llm_provider(model="openrouter/auto")
should return model="openrouter/auto" with the prefix preserved. That
contract is wrong: the LiteLLM convention strips one "openrouter/" prefix,
so a native OpenRouter model is reached via "openrouter/openrouter/auto"
(double-prefix) which the wire send as "openrouter/auto" — the ID the
OpenRouter API expects for natives. E2E checks against api.openrouter.ai
confirm this is the correct routing convention for all native models
(auto, bodybuilder, free, pareto-code).
2026-04-25 17:25:49 -03:00
Chesars 91e78eca3d Merge remote-tracking branch 'upstream/litellm_internal_staging' into upstream-litellm_staging_03_21_2026
# Conflicts:
#	.circleci/config.yml
#	.circleci/requirements.txt
#	.github/workflows/_test-unit-base.yml
#	.github/workflows/_test-unit-services-base.yml
#	.github/workflows/auto_update_price_and_context_window.yml
#	.github/workflows/create-release.yml
#	.github/workflows/llm-translation-testing.yml
#	.github/workflows/publish_to_pypi.yml
#	.github/workflows/scan_duplicate_issues.yml
#	.github/workflows/test-linting.yml
#	.github/workflows/test-litellm-matrix.yml
#	.github/workflows/test-litellm.yml
#	.github/workflows/test-mcp.yml
#	.github/workflows/test-model-map.yaml
#	.github/workflows/test-proxy-e2e-azure-batches.yml
#	.github/workflows/test-unit-core-utils.yml
#	.github/workflows/test-unit-documentation.yml
#	.github/workflows/test-unit-enterprise-routing.yml
#	.github/workflows/test-unit-integrations.yml
#	.github/workflows/test-unit-llm-providers.yml
#	.github/workflows/test-unit-misc.yml
#	.github/workflows/test-unit-proxy-auth.yml
#	.github/workflows/test-unit-proxy-db.yml
#	.github/workflows/test-unit-proxy-endpoints.yml
#	.github/workflows/test-unit-proxy-infra.yml
#	.github/workflows/test-unit-proxy-legacy.yml
#	.github/workflows/test-unit-responses-caching-types.yml
#	.github/workflows/test-unit-security.yml
#	.github/workflows/test_server_root_path.yml
#	docs/my-website/docs/embedding/supported_embedding.md
#	litellm/litellm_core_utils/get_llm_provider_logic.py
#	litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py
#	litellm/proxy/_experimental/out/404/index.html
#	litellm/proxy/_experimental/out/__next.__PAGE__.txt
#	litellm/proxy/_experimental/out/__next._full.txt
#	litellm/proxy/_experimental/out/__next._head.txt
#	litellm/proxy/_experimental/out/__next._index.txt
#	litellm/proxy/_experimental/out/__next._tree.txt
#	litellm/proxy/_experimental/out/_next/static/3qyC5Vtvhd5fSC6sPp1iW/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/3qyC5Vtvhd5fSC6sPp1iW/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/3qyC5Vtvhd5fSC6sPp1iW/_ssgManifest.js
#	litellm/proxy/_experimental/out/_next/static/aKKihXXKRJWLQThZgi8Rq/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/aKKihXXKRJWLQThZgi8Rq/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/aKKihXXKRJWLQThZgi8Rq/_ssgManifest.js
#	litellm/proxy/_experimental/out/_next/static/bmMTxs1O5fQKYcsMNTRMT/_buildManifest.js
#	litellm/proxy/_experimental/out/_next/static/bmMTxs1O5fQKYcsMNTRMT/_clientMiddlewareManifest.json
#	litellm/proxy/_experimental/out/_next/static/bmMTxs1O5fQKYcsMNTRMT/_ssgManifest.js
#	litellm/proxy/_experimental/out/_next/static/chunks/11362340846735c3.js
#	litellm/proxy/_experimental/out/_next/static/chunks/1a04d31843c96649.js
#	litellm/proxy/_experimental/out/_next/static/chunks/342c7d7210247a5e.js
#	litellm/proxy/_experimental/out/_next/static/chunks/39768ec0eebd2554.js
#	litellm/proxy/_experimental/out/_next/static/chunks/3b3c0b070b14da06.js
#	litellm/proxy/_experimental/out/_next/static/chunks/3bddc72a3ecc2253.js
#	litellm/proxy/_experimental/out/_next/static/chunks/4472ece1be7379b3.js
#	litellm/proxy/_experimental/out/_next/static/chunks/54e29148cb2f2582.js
#	litellm/proxy/_experimental/out/_next/static/chunks/67ddb5107368a659.js
#	litellm/proxy/_experimental/out/_next/static/chunks/6a167cef4b09b496.js
#	litellm/proxy/_experimental/out/_next/static/chunks/7174130ddef406dd.js
#	litellm/proxy/_experimental/out/_next/static/chunks/7c36bfe1ba5e3ba8.js
#	litellm/proxy/_experimental/out/_next/static/chunks/7e5fe5584502da06.js
#	litellm/proxy/_experimental/out/_next/static/chunks/8dda507c226082ca.js
#	litellm/proxy/_experimental/out/_next/static/chunks/8dfde809dc4ad794.js
#	litellm/proxy/_experimental/out/_next/static/chunks/99109c78121231a0.js
#	litellm/proxy/_experimental/out/_next/static/chunks/9dd55e1f36a7225c.js
#	litellm/proxy/_experimental/out/_next/static/chunks/a230559fcabaea23.js
#	litellm/proxy/_experimental/out/_next/static/chunks/a6c7f80b3968f639.js
#	litellm/proxy/_experimental/out/_next/static/chunks/ac9e96d21c200b48.js
#	litellm/proxy/_experimental/out/_next/static/chunks/ae9cf43b8c0c76aa.js
#	litellm/proxy/_experimental/out/_next/static/chunks/cf06797ce4e438f9.js
#	litellm/proxy/_experimental/out/_next/static/chunks/d069df5baead6d90.js
#	litellm/proxy/_experimental/out/_next/static/chunks/d2e3b7dd6499c245.js
#	litellm/proxy/_experimental/out/_next/static/chunks/d44e73d8ebac5747.js
#	litellm/proxy/_experimental/out/_next/static/chunks/dc8a270fee94ced6.js
#	litellm/proxy/_experimental/out/_next/static/chunks/df6546cd8a44d3b3.js
#	litellm/proxy/_experimental/out/_next/static/chunks/ea0f22bd4b3393bd.js
#	litellm/proxy/_experimental/out/_next/static/chunks/eaa9f9b9bb3e054b.js
#	litellm/proxy/_experimental/out/_next/static/chunks/turbopack-901b35f89c1f6751.js
#	litellm/proxy/_experimental/out/_next/static/chunks/turbopack-d1b22f5e0bd58c57.js
#	litellm/proxy/_experimental/out/_next/static/chunks/turbopack-ddedb29a5eb0118f.js
#	litellm/proxy/_experimental/out/_not-found.txt
#	litellm/proxy/_experimental/out/_not-found/__next._full.txt
#	litellm/proxy/_experimental/out/_not-found/__next._head.txt
#	litellm/proxy/_experimental/out/_not-found/__next._index.txt
#	litellm/proxy/_experimental/out/_not-found/__next._not-found.__PAGE__.txt
#	litellm/proxy/_experimental/out/_not-found/__next._not-found.txt
#	litellm/proxy/_experimental/out/_not-found/__next._tree.txt
#	litellm/proxy/_experimental/out/_not-found/index.html
#	litellm/proxy/_experimental/out/api-reference.html
#	litellm/proxy/_experimental/out/api-reference.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.api-reference.__PAGE__.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.api-reference.txt
#	litellm/proxy/_experimental/out/api-reference/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/api-reference/__next._full.txt
#	litellm/proxy/_experimental/out/api-reference/__next._head.txt
#	litellm/proxy/_experimental/out/api-reference/__next._index.txt
#	litellm/proxy/_experimental/out/api-reference/__next._tree.txt
#	litellm/proxy/_experimental/out/chat.html
#	litellm/proxy/_experimental/out/chat.txt
#	litellm/proxy/_experimental/out/chat/__next._full.txt
#	litellm/proxy/_experimental/out/chat/__next._head.txt
#	litellm/proxy/_experimental/out/chat/__next._index.txt
#	litellm/proxy/_experimental/out/chat/__next._tree.txt
#	litellm/proxy/_experimental/out/chat/__next.chat.__PAGE__.txt
#	litellm/proxy/_experimental/out/chat/__next.chat.txt
#	litellm/proxy/_experimental/out/experimental/api-playground.html
#	litellm/proxy/_experimental/out/experimental/api-playground.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.api-playground.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.api-playground.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/api-playground/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/budgets.html
#	litellm/proxy/_experimental/out/experimental/budgets.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.budgets.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.budgets.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/budgets/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/caching.html
#	litellm/proxy/_experimental/out/experimental/caching.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.caching.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.caching.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/caching/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins.html
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.claude-code-plugins.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.claude-code-plugins.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/claude-code-plugins/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/old-usage.html
#	litellm/proxy/_experimental/out/experimental/old-usage.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.old-usage.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.old-usage.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/old-usage/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/prompts.html
#	litellm/proxy/_experimental/out/experimental/prompts.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.prompts.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.prompts.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/prompts/__next._tree.txt
#	litellm/proxy/_experimental/out/experimental/tag-management.html
#	litellm/proxy/_experimental/out/experimental/tag-management.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.tag-management.__PAGE__.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.tag-management.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.experimental.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._full.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._head.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._index.txt
#	litellm/proxy/_experimental/out/experimental/tag-management/__next._tree.txt
#	litellm/proxy/_experimental/out/guardrails.html
#	litellm/proxy/_experimental/out/guardrails.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.guardrails.__PAGE__.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.guardrails.txt
#	litellm/proxy/_experimental/out/guardrails/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/guardrails/__next._full.txt
#	litellm/proxy/_experimental/out/guardrails/__next._head.txt
#	litellm/proxy/_experimental/out/guardrails/__next._index.txt
#	litellm/proxy/_experimental/out/guardrails/__next._tree.txt
#	litellm/proxy/_experimental/out/index.html
#	litellm/proxy/_experimental/out/index.txt
#	litellm/proxy/_experimental/out/login.html
#	litellm/proxy/_experimental/out/login.txt
#	litellm/proxy/_experimental/out/login/__next._full.txt
#	litellm/proxy/_experimental/out/login/__next._head.txt
#	litellm/proxy/_experimental/out/login/__next._index.txt
#	litellm/proxy/_experimental/out/login/__next._tree.txt
#	litellm/proxy/_experimental/out/login/__next.login.__PAGE__.txt
#	litellm/proxy/_experimental/out/login/__next.login.txt
#	litellm/proxy/_experimental/out/logs.html
#	litellm/proxy/_experimental/out/logs.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.logs.__PAGE__.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.logs.txt
#	litellm/proxy/_experimental/out/logs/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/logs/__next._full.txt
#	litellm/proxy/_experimental/out/logs/__next._head.txt
#	litellm/proxy/_experimental/out/logs/__next._index.txt
#	litellm/proxy/_experimental/out/logs/__next._tree.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._full.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._head.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._index.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next._tree.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.callback.__PAGE__.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.callback.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.oauth.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/__next.mcp.txt
#	litellm/proxy/_experimental/out/mcp/oauth/callback/index.html
#	litellm/proxy/_experimental/out/model-hub.html
#	litellm/proxy/_experimental/out/model-hub.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.model-hub.__PAGE__.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.model-hub.txt
#	litellm/proxy/_experimental/out/model-hub/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/model-hub/__next._full.txt
#	litellm/proxy/_experimental/out/model-hub/__next._head.txt
#	litellm/proxy/_experimental/out/model-hub/__next._index.txt
#	litellm/proxy/_experimental/out/model-hub/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub.html
#	litellm/proxy/_experimental/out/model_hub.txt
#	litellm/proxy/_experimental/out/model_hub/__next._full.txt
#	litellm/proxy/_experimental/out/model_hub/__next._head.txt
#	litellm/proxy/_experimental/out/model_hub/__next._index.txt
#	litellm/proxy/_experimental/out/model_hub/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub/__next.model_hub.__PAGE__.txt
#	litellm/proxy/_experimental/out/model_hub/__next.model_hub.txt
#	litellm/proxy/_experimental/out/model_hub_table.html
#	litellm/proxy/_experimental/out/model_hub_table.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._full.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._head.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._index.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next._tree.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next.model_hub_table.__PAGE__.txt
#	litellm/proxy/_experimental/out/model_hub_table/__next.model_hub_table.txt
#	litellm/proxy/_experimental/out/models-and-endpoints.html
#	litellm/proxy/_experimental/out/models-and-endpoints.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.models-and-endpoints.__PAGE__.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.models-and-endpoints.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._full.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._head.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._index.txt
#	litellm/proxy/_experimental/out/models-and-endpoints/__next._tree.txt
#	litellm/proxy/_experimental/out/onboarding.html
#	litellm/proxy/_experimental/out/onboarding.txt
#	litellm/proxy/_experimental/out/onboarding/__next._full.txt
#	litellm/proxy/_experimental/out/onboarding/__next._head.txt
#	litellm/proxy/_experimental/out/onboarding/__next._index.txt
#	litellm/proxy/_experimental/out/onboarding/__next._tree.txt
#	litellm/proxy/_experimental/out/onboarding/__next.onboarding.__PAGE__.txt
#	litellm/proxy/_experimental/out/onboarding/__next.onboarding.txt
#	litellm/proxy/_experimental/out/organizations.html
#	litellm/proxy/_experimental/out/organizations.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.organizations.__PAGE__.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.organizations.txt
#	litellm/proxy/_experimental/out/organizations/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/organizations/__next._full.txt
#	litellm/proxy/_experimental/out/organizations/__next._head.txt
#	litellm/proxy/_experimental/out/organizations/__next._index.txt
#	litellm/proxy/_experimental/out/organizations/__next._tree.txt
#	litellm/proxy/_experimental/out/playground.html
#	litellm/proxy/_experimental/out/playground.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.playground.__PAGE__.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.playground.txt
#	litellm/proxy/_experimental/out/playground/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/playground/__next._full.txt
#	litellm/proxy/_experimental/out/playground/__next._head.txt
#	litellm/proxy/_experimental/out/playground/__next._index.txt
#	litellm/proxy/_experimental/out/playground/__next._tree.txt
#	litellm/proxy/_experimental/out/policies.html
#	litellm/proxy/_experimental/out/policies.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.policies.__PAGE__.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.policies.txt
#	litellm/proxy/_experimental/out/policies/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/policies/__next._full.txt
#	litellm/proxy/_experimental/out/policies/__next._head.txt
#	litellm/proxy/_experimental/out/policies/__next._index.txt
#	litellm/proxy/_experimental/out/policies/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/admin-settings.html
#	litellm/proxy/_experimental/out/settings/admin-settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.admin-settings.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.admin-settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._full.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._head.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._index.txt
#	litellm/proxy/_experimental/out/settings/admin-settings/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts.html
#	litellm/proxy/_experimental/out/settings/logging-and-alerts.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.logging-and-alerts.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.logging-and-alerts.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._full.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._head.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._index.txt
#	litellm/proxy/_experimental/out/settings/logging-and-alerts/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/router-settings.html
#	litellm/proxy/_experimental/out/settings/router-settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.router-settings.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.router-settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._full.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._head.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._index.txt
#	litellm/proxy/_experimental/out/settings/router-settings/__next._tree.txt
#	litellm/proxy/_experimental/out/settings/ui-theme.html
#	litellm/proxy/_experimental/out/settings/ui-theme.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.ui-theme.__PAGE__.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.settings.ui-theme.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._full.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._head.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._index.txt
#	litellm/proxy/_experimental/out/settings/ui-theme/__next._tree.txt
#	litellm/proxy/_experimental/out/teams.html
#	litellm/proxy/_experimental/out/teams.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.teams.__PAGE__.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.teams.txt
#	litellm/proxy/_experimental/out/teams/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/teams/__next._full.txt
#	litellm/proxy/_experimental/out/teams/__next._head.txt
#	litellm/proxy/_experimental/out/teams/__next._index.txt
#	litellm/proxy/_experimental/out/teams/__next._tree.txt
#	litellm/proxy/_experimental/out/test-key.html
#	litellm/proxy/_experimental/out/test-key.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.test-key.__PAGE__.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.test-key.txt
#	litellm/proxy/_experimental/out/test-key/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/test-key/__next._full.txt
#	litellm/proxy/_experimental/out/test-key/__next._head.txt
#	litellm/proxy/_experimental/out/test-key/__next._index.txt
#	litellm/proxy/_experimental/out/test-key/__next._tree.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers.html
#	litellm/proxy/_experimental/out/tools/mcp-servers.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.mcp-servers.__PAGE__.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.mcp-servers.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.tools.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._full.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._head.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._index.txt
#	litellm/proxy/_experimental/out/tools/mcp-servers/__next._tree.txt
#	litellm/proxy/_experimental/out/tools/vector-stores.html
#	litellm/proxy/_experimental/out/tools/vector-stores.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.vector-stores.__PAGE__.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.tools.vector-stores.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._full.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._head.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._index.txt
#	litellm/proxy/_experimental/out/tools/vector-stores/__next._tree.txt
#	litellm/proxy/_experimental/out/usage.html
#	litellm/proxy/_experimental/out/usage.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.usage.__PAGE__.txt
#	litellm/proxy/_experimental/out/usage/__next.!KGRhc2hib2FyZCk.usage.txt
#	litellm/proxy/_experimental/out/usage/__next._full.txt
#	litellm/proxy/_experimental/out/usage/__next._head.txt
#	litellm/proxy/_experimental/out/usage/__next._index.txt
#	litellm/proxy/_experimental/out/usage/__next._tree.txt
#	litellm/proxy/_experimental/out/users.html
#	litellm/proxy/_experimental/out/users.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.users.__PAGE__.txt
#	litellm/proxy/_experimental/out/users/__next.!KGRhc2hib2FyZCk.users.txt
#	litellm/proxy/_experimental/out/users/__next._full.txt
#	litellm/proxy/_experimental/out/users/__next._head.txt
#	litellm/proxy/_experimental/out/users/__next._index.txt
#	litellm/proxy/_experimental/out/users/__next._tree.txt
#	litellm/proxy/_experimental/out/virtual-keys.html
#	litellm/proxy/_experimental/out/virtual-keys.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.virtual-keys.__PAGE__.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next.!KGRhc2hib2FyZCk.virtual-keys.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._full.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._head.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._index.txt
#	litellm/proxy/_experimental/out/virtual-keys/__next._tree.txt
#	scripts/install.sh
#	tests/local_testing/test_get_llm_provider.py
2026-04-25 17:15:24 -03:00
Chesars a2a506c4dc Revert "Merge pull request #16590 from Chesars/refactor/remove-backup-file-dry-principle"
This reverts commit 62df6325b1, reversing
changes made to a6462143be.
2026-04-25 17:10:41 -03:00
Chesars 40eeffe089 test(vertex): use valid dimension (128) for multimodalembedding live test
dimensions=1 was silently dropped before #24415 wired the param through
to Vertex. Now that it's forwarded, Vertex rejects it (valid values:
128, 256, 512, 1408). Switch the test to a valid dimension.
2026-04-25 16:10:25 -03:00
Cesar GarciaandGitHub 8bd58fb82d Merge branch 'litellm_internal_staging' into litellm_staging_03_22_2026 2026-04-24 13:12:19 -03:00
Yuneng Jiang c4ea0e93c8 fix: drain logging worker in test_router_caching_ttl to remove flake
The mocked async_increment_cache_pipeline is invoked from Router's
deployment_callback_on_success, registered as an async success callback.
Those callbacks are enqueued to GLOBAL_LOGGING_WORKER and run on a
background task, so the mock may not have been called yet when the test
asserts on it. Flush the worker before asserting.
2026-04-23 14:48:02 -07:00
Cesar GarciaandGitHub 25c0aa8bfd Merge pull request #26283 from BerriAI/litellm_internal_staging
Sync litellm_staging_03_22_2026 with litellm_internal_staging
2026-04-22 19:55:27 -03:00
Yuneng Jiang ee550e1949 [Test] CI: add v2 migration resolver coverage with local Postgres
Adds end-to-end CI coverage for `--use_v2_migration_resolver` via a new
job `installing_litellm_on_python_v2_migration_resolver`:

- Clones the pytest smoke path from `installing_litellm_on_python` but
  uses a local Postgres sidecar instead of the shared DB to prevent
  collisions with the v1 variant.
- Runs only the new `test_litellm_proxy_server_config_no_general_settings_v2_resolver`
  which spawns the proxy with `--use_v2_migration_resolver` and smoke-tests
  `/health/liveliness` and `/chat/completions`.

Refactors `test_basic_python_version.py`:

- Extracts the proxy spawn + smoke-test body into `_run_proxy_server_smoke_test`
  so the v1 and v2 tests share the same code path.
- The existing `test_litellm_proxy_server_config_no_general_settings` is
  now a thin wrapper that passes no extra args (v1 default, unchanged).
- Adds `..._v2_resolver` variant that passes `--use_v2_migration_resolver`.

The existing `installing_litellm_on_python` / `installing_litellm_on_python_3_13`
jobs filter out the v2 variant via `-k "not v2_resolver"` so they keep
running only against their shared DB, unchanged behavior.
2026-04-21 14:40:11 -07:00
Ryan Crabbe eee51a99ad replace retired claude-3-haiku-20240307 with claude-haiku-4-5-20251001 in local_testing part1 and router fallback tests 2026-04-20 16:10:45 -07:00
Ryan Crabbe 74169b114a replace retired claude-3-haiku-20240307 with claude-haiku-4-5-20251001 in streaming tests 2026-04-20 16:04:54 -07:00
Sameer KankuteandGitHub 57eae8d01c Merge branch 'litellm_internal_staging' into litellm_staging_03_22_2026 2026-04-20 19:56:00 +05:30
Ishaan Jaffer e8461b5b97 style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
Yuneng Jiang 95e1babf67 [Fix] TogetherAIConfig.get_supported_openai_params recursion
TogetherAIConfig.get_supported_openai_params called get_model_info(),
whose first line calls litellm.get_supported_openai_params() — which for
together_ai routes straight back into this method. The recursion only
terminated when Python's recursion limit was hit or when
_get_model_info_helper raised "not mapped" at the deepest level. Either
way the try/except caught it, so the bug stayed silent — but the cycle
ran ~332 deep every time, emitting hundreds of DEBUG log lines per
call. Surfaced as "infinite loop" in CI when the success_handler thread
emitted that log spam against an already-closed stderr during test
teardown.

Replace the get_model_info() call with supports_function_calling(),
which uses _get_model_info_helper directly and does not call
get_supported_openai_params. Measured drop from 332 to 2
_get_model_info_helper calls per first uncached lookup.

Also swap the test model from Qwen/Qwen3.5-9B (not in model_cost map)
back to a mapped serverless model, Qwen/Qwen2.5-7B-Instruct-Turbo. The
mapping gap is what made the recursion's tail end raise up into the
success handler during teardown in the first place.
2026-04-16 17:20:58 -07:00
Yuneng Jiang dafa1bf97c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_yj_apr15
# Conflicts:
#	litellm/litellm_core_utils/litellm_logging.py
#	uv.lock
2026-04-16 09:17:20 -07:00
Sameer Kankute 83d0f28aa8 Fix tests 2026-04-16 19:02:29 +05:30
Yuneng Jiang c8cfc5de21 fix(httpx): set response.request and strip content-encoding in MaskedHTTPStatusError
MaskedHTTPStatusError constructs a new httpx.Response from the original
error. Two bugs surfaced under real HTTP error responses:

1. The new Response was created without request=, so response.request
   raised RuntimeError("The .request property has not been set.") for
   any downstream caller (e.g. exception_mapping_utils) that inspected it.

2. The decoded response bytes were passed together with the original
   Content-Encoding header. On construction httpx tried to decompress
   the already-decoded bytes and raised httpx.DecodingError
   ("Error -3 while decompressing data: incorrect header check").

Set response.request to the masked Request and strip Content-Encoding
(and the now-stale Content-Length) before rebuilding the Response.
URL/message masking is unchanged; the new request carries the already
masked URL.

Also update test_logging_key_masking_gemini: the security commit
25f93bed91 moved Gemini API keys from ?key=... URL params to the
x-goog-api-key header, so api_base no longer contains the key.
2026-04-15 22:03:48 -07:00
Ishaan Jaffer def9c4ec47 chore: merge litellm_internal_staging, resolve uv.lock conflict 2026-04-15 18:51:19 -07:00
a588f76789 Litellm ishaan april15 2 (#25828)
* [Test] Add Azure async chat completion timeout test. WIP

* Capture TTFT for /v1/messages streaming responses

The pass-through streaming path for /v1/messages (Anthropic, Bedrock,
Vertex AI, Azure AI, Minimax) logged completion_start_time only after
the entire stream finished. async_success_handler then fell back to
end_time, making TTFT equal to total duration or null in the UI and
Prometheus.

Record the timestamp of the first chunk in async_sse_wrapper and
propagate it to model_call_details before the logging handler runs,
so gen_ai.response.time_to_first_token reflects the real first-chunk
latency.

Fixes #25598

* [Refactor] Implement timeout resolution logic in completion function

add fetch ``request_timeout`` from litellm_settings

* remove stale test case

* remove extra print statement

* default request timeout value in constants to 600s to match timeout defaults handled in the proxy

* fix request timeout if using default value from constants.py

* update code structure, test cases

* only override if the global timeout sets timeout to 6000s

* update code structure, move hard coded values to const and make the reslve function readable by moving fallback logic to a seperate function

* modify default timeout values, replacing hard coded ones with default values defined

---------

Co-authored-by: harish876 <harishgokul01@gmail.com>
Co-authored-by: Joaquin Hui Gomez <joaquinhuigomez@users.noreply.github.com>
2026-04-15 18:42:23 -07:00
Ishaan Jaffer 9977e63e3c Merge remote-tracking branch 'origin/main' into worktree-foamy-jumping-coral 2026-04-15 18:29:55 -07:00
Ishaan Jaffer c8a0fe193f style: black format test_unit_test_caching.py 2026-04-15 18:19:04 -07:00
Yuneng Jiang a9c6156137 [Fix] Test - Together AI: replace deprecated Mixtral with serverless Qwen3.5-9B
Mixtral-8x7B-Instruct-v0.1 is no longer on Together AI's serverless tier
and now requires a dedicated endpoint, causing multiple tests to fail in CI:

  - test_together_ai.py::TestTogetherAI::test_empty_tools
  - test_completion.py::test_completion_together_ai_stream
  - test_completion.py::test_customprompt_together_ai
  - test_completion.py::test_completion_custom_provider_model_name
  - test_text_completion.py::test_async_text_completion_together_ai

Qwen/Qwen3.5-9B is currently serverless on Together AI and supports
function calling, satisfying BaseLLMChatTest capability requirements.
2026-04-14 17:43:35 -07:00
Sameer KankuteandGitHub 1a9a31e4a2 Merge pull request #25665 from BerriAI/litellm_oss_staging_04_13_2026_p1
litellm oss staging 04/13/2026
2026-04-14 23:50:08 +05:30