Commit graph

64 commits

Author SHA1 Message Date
mateo-berri
215bf03373 refactor(types): replace Any with precise types across 73 modules
Narrows reportAny / reportExplicitAny hot spots in provider transformations,
proxy endpoints, integrations and secret managers by introducing TypedDicts,
Protocols and object-typed boundaries instead of Any, then ratchets the
budget ceilings down to match.

reportAny 14765 -> 14076, reportExplicitAny 4493 -> 4128, ANN401 387 -> 307
2026-09-01 11:05:02 +00:00
mateo-berri
6b7159323b fix(proxy): match OpenAI on empty input and skip budget reservation for token counting
/v1/responses/input_tokens returned 200 with a count for an empty
"input" ("" or []), while OpenAI returns a 400 missing_required_parameter.
The route also went through optimistic budget reservation, which is only
released by LLM success/failure callbacks that a token count never
reaches, so every call leaked a reservation until TTL expiry and could
429 real traffic. Both routes plus the /openai alias now join
/utils/token_counter in the reservation exemption set.
2026-08-31 12:25:45 -07:00
mateo-berri
c7c382402a feat(proxy): add /v1/responses/input_tokens token counting endpoint 2026-08-31 12:04:44 -07:00
mateo-berri
e4f2ea12bc fix(responses_api): map bridged chat usage on guardrail-blocked replies
Move the blocked-usage mapping for /v1/responses next to
blocked_response_usage in guardrail_translation utils, map bridged chat
prompt/completion tokens to Responses API input/output tokens, and let
raise_passthrough_exception attach the blocked response so post-call
guardrail blocks report real usage
2026-08-14 17:04:27 -07:00
Ishaan
81c27fc4a0 fix: report real token usage on guardrail-blocked /v1/responses replies
## TLDR

Signed-off-by: Ishaan <ishaangupta0408@gmail.com>
2026-08-14 09:15:33 +00:00
tin-berri
06943b6468
feat(router): make routing groups callable as virtual models and list them in /v1/models (#36519)
* feat(router): make routing groups callable as virtual models and list them in /v1/models

* fix(router): traffic-scoped cooldown exemption, live model_names on delete, group-info cache invalidation

* fix(router): share one recognized-model predicate across proxy gates, resolve aliases in group cooldown, read metadata via the dual-bucket owner

* fix(router): close the gate and cache families for callable groups, strip member access_groups from group rows, prove cooldown wiring end to end

* refactor(router): cache materialized group rows under the model-group cache owner and drop the redundant wiring test

* fix(router): warn-and-shadow on group name collisions, name-level test coverage for group helpers, faithful router doubles in a2a and cursor tests

* test(router): pin group cooldown metadata across the retry path
2026-08-11 18:41:19 -07:00
mateo-berri
262d1b4ca0 fix: remove over-strict stream logging validator, restore base seam behavior 2026-08-11 08:22:57 -07:00
mateo-berri
e5ac4e0068 chore(typing): clear 1.6k basedpyright Any errors across 56 files
reportAny 16720 -> 15482 and reportExplicitAny 5689 -> 5316 with real types only: no casts, no ignores, no new Any. Whole-tree basedpyright drops 2173 diagnostics with zero per-rule or per-file regressions. Budgets ratcheted: basedpyright -2173, ruff-strict -188, type-discipline -55
2026-08-11 06:47:39 -07:00
mateo-berri
338e411103 chore(lint): strip inert type: ignore comments and zero LIT009, LIT010, LIT011 headroom 2026-08-05 02:37:24 -07:00
mateo-berri
334e10470b refactor(proxy): make the cursor responses-path body single-assignment 2026-08-04 15:21:35 -07:00
mateo-berri
1cd481d4f2 fix(proxy): enforce per-model budgets against resolved cursor model variants 2026-08-04 14:36:04 -07:00
mateo-berri
2708620d6a feat(lint): enforce Final on locals and freeze function parameters (LIT010, LIT011) 2026-08-04 12:54:39 -07:00
mateo-berri
9eeff06263 Merge origin/litellm_internal_staging into litellm_lit4395_cursor_agent 2026-08-04 10:20:03 -07:00
Classic298
c9887a1f94
perf: build log messages lazily so filtered-out log records cost nothing (#35703) 2026-08-04 04:34:52 +00:00
mateo-berri
f25f1d2921 feat(proxy): resolve Cursor thinking/fast model-name suffixes on /cursor/chat/completions
Cursor appends -thinking-<level> and -fast to custom model names when the
user picks a thinking level or fast mode, so a model configured as
claude-opus-5 arrives as claude-opus-5-thinking-xhigh-fast and fails
routing with no healthy deployments. When the raw name is not servable by
the router but the suffix-stripped base name is, rewrite the body to the
base model and carry the thinking level into reasoning_effort (chat
bodies) or reasoning.effort (Responses bodies), never clobbering an
effort the client already sent. Explicitly configured aliases keep
winning because the raw-name servability check runs first.
2026-08-01 17:42:03 -07:00
mateo-berri
23d26d5e64 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit4395_cursor_agent
# Conflicts:
#	litellm/completion_extras/litellm_responses_transformation/transformation.py
#	litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py
#	litellm/litellm_core_utils/prompt_templates/common_utils.py
#	litellm/litellm_core_utils/streaming_chunk_builder_utils.py
#	litellm/llms/openai/chat/gpt_transformation.py
#	litellm/main.py
#	litellm/proxy/response_api_endpoints/endpoints.py
#	ruff-strict-budget.json
#	type-discipline-budget.json
2026-08-01 17:06:53 -07:00
mateo-berri
7b2d3440cb refactor(logging): drop redundant !s conversion flags from f-strings 2026-08-01 16:59:05 -07:00
mateo-berri
075babd00f chore(lint): clear the new LIT001/LIT002 violations and ratchet the lint budgets
The type-discipline gate flagged 17 new mutable-collection annotations and 31
new mutable-collection constructions added by this branch. Replace raw dict
literals with the OpenAI SDK's TypedDict call forms, annotate read-only params
as Mapping/Sequence, precompute the custom tool call id set as a frozenset,
and accumulate streamed arguments as tuples. The few places where a plain
list/dict is a hard contract (pydantic response fields, fastapi route tags,
parsed request bodies, in-place tool call patching) carry reasoned mutable-ok
suppressions instead. Ratchet the ruff, type-discipline, and basedpyright
budgets down by the violations this branch now fixes on net
2026-08-01 16:53:35 -07:00
mateo-berri
b604e2b20c refactor(lint): apply every safe ruff autofix and zero 28 strict-rule budgets
About 35,000 fixes ruff marks safe across 32 rules (UP006/UP045/UP007
modern annotations, UP032 f-strings, SIM114/SIM118, RET501, and
friends), removal of the 1,296 typing imports the rewrite orphaned, and
hand fixes for what the fixers could not see: five star-import
freeloaders of typing names, two F823 late-import annotations, the
/get/config/list introspection crash on types.UnionType, redundant
function-local RoleMappings imports in ui_sso.py that shadowed the
module-level name once the annotation lost its quotes, and one FURB168
tautology.

B009/B010/PIE804/RUF019 are excluded on purpose: their safe fixes
rewrite getattr/setattr/**-splat/key-in-dict escape hatches into forms
basedpyright then rejects (283 new errors measured), so their budgets
stay at base values.

ruff-strict-budget.json drops by 39,579 this commit (39,968 across the
branch) with 28 rules at an actual 0 and 9 more sharply down.
type-discipline-budget.json ratchets LIT002/LIT006/LIT009 down; LIT001
moves to the now-honest total: the checker matches the spelling `set`
but not the alias `Set`, so the 160 typing.Set annotations rewritten to
set[...] were always mutable-set annotations and only now count.
2026-08-01 15:43:29 -07:00
mateo-berri
397e8e4918 refactor(imports): move collections.abc names out of typing and ratchet the lint budgets 2026-08-01 13:17:42 -07:00
Tin Chi Lo
c27f1b7b6d fix(tools): classify custom tool calls by one shared rule and make envelope payload extraction total
A chat tool-call dict was classified custom-vs-function with four different
spellings: the non-streaming parser required type == "custom", the streaming
Delta coercion also accepted a custom payload without type, and the stream
assembler required a type that later chunks never carry. The same payload could
be a custom tool call mid-stream, a TypeError on the completed message, and
silently dropped from the assembled message. is_custom_tool_call_dict() is now
the single discriminator (explicit custom type, or a custom payload present)
used by both parsers, and the assembler classifies from the accumulated custom
payload, matching how the deltas it consumes were classified.

The tool envelope converter picked one exclusive payload source: the nested
dict when present, else the top level. An empty nested envelope therefore
shadowed top-level fields and the normalized tool lost its name. Payload
extraction is now total over both locations, nested first, and an envelope
with no name anywhere passes through unchanged instead of being emitted
stripped.
2026-08-01 11:26:09 -07:00
Tin Chi Lo
c7c656e8a9 fix(cursor): convert tools and tool_choice through one envelope rule
Chat Completions nests a named tool_choice under its tool type while the
Responses API keeps it flat; ChatCompletionNamedToolChoiceParam and
ChatCompletionNamedToolChoiceCustomParam both mark the nested key required. The
messages arm normalized tool definitions but forwarded tool_choice at whatever
level Cursor sent it, so a flat {"type": "custom", "name": "ApplyPatch"} reached
OpenAI unchanged and was rejected while the tool defs beside it nested correctly

A tool definition and a named tool_choice carry the same envelope, so both now
convert through a single _convert_tool_envelope, and _normalize_tool_dialect
moves tools and tool_choice together on each arm. That covers all four cells of
{tool def, tool_choice} x {to chat, to responses} and removes the shape where
one field can be converted while the other is missed, replacing three helpers
with two and cutting 24 lines

Also restores the end-to-end assertion that a flat tool_choice reaches
chat_completion nested, which had been flipped to pin the passthrough behavior
2026-08-01 11:26:09 -07:00
tin
56cc475c80 refactor(cursor): trim LOC in cursor byok tool normalization
- share one _CustomToolCallAccess mixin across the 4 new custom-tool
  classes instead of hand-rolling dict access on each
- inline the single-use _nest_flat_chat_tools / _flatten_chat_tools_for_responses
  list wrappers at their call sites
- drop _nest_flat_chat_tool_choice: it rewrote object-form chat tool_choice
  into {type,custom:{name}}, a shape OpenAI rejects; real Cursor never sends
  tool_choice on the messages arm, so pass it through unchanged
2026-08-01 11:26:09 -07:00
Tin Chi Lo
e9d16bc35c fix(litellm): make the responses bridge and cursor routing total over the surfaces they now serve
Three gaps from the bridge becoming a mainstream path for chat traffic.
The chat to responses message converter only mapped function tool_calls,
so history carrying the native custom tool calls this PR introduced
raised "tool call not supported" on follow-up turns; custom entries now
map to custom_tool_call items and their results to
custom_tool_call_output. The stream translator returned an empty delta
for output_item.done on tool items, which left the responses guardrail
handler's tool extraction permanently empty (dead on staging too, where
the built chunk was discarded); stateless callers now receive the
complete tool call while per-stream callers keep the suppressed delta
that prevents client-side duplication. Cursor routing keyed on the
presence of a messages key, so a null or empty stub next to a real
agent-mode input array picked the chat arm; routing now keys on
messages content
2026-08-01 11:26:09 -07:00
Tin
6d102ea559 fix(litellm): bridge gpt-5.4+ chat requests with tools when reasoning defaults on
OpenAI enables reasoning by default for gpt-5.4+ (unset reasoning_effort
means medium server-side) and Chat Completions rejects function tools
whenever reasoning is on, so a tools request without an explicit
reasoning_effort 400d instead of auto-bridging to the Responses API; the
bridge heuristic now treats unset effort as reasoning-active and honors
the documented escape hatch by keeping explicit "none" on chat
completions. The cursor input arm also gains the mirror of the
messages-arm normalization: chat-nested tool envelopes, grammar formats,
and object tool_choice flatten to the Responses dialect before dispatch
2026-08-01 11:26:09 -07:00
Tin Chi Lo
ebe48d67de fix(proxy): normalize each tool shape level independently on the Cursor messages arm
Live Cursor Ask-mode captures show the shape dialects mix PER LEVEL: the
tool envelope arrives chat-nested while the grammar format inside it is
still Responses-flat, so a normalizer that pattern-matches whole-tool
templates misses every hybrid. The cursor arm now normalizes the envelope
level and the format level independently and idempotently, making it
total over the envelope x format matrix; a parametrized 8-cell test pins
every combination. The reference BYOK bridge was checked and forwards
chat bodies verbatim, so there is no prior art for these hybrids
2026-08-01 11:26:09 -07:00
Tin Chi Lo
b79b01e38a fix(proxy): translate custom tool grammar formats and tool_choice across API surfaces
Cursor's ApplyPatch is a grammar-constrained custom tool; the Responses
surface carries the grammar flat while chat completions wraps the same
fields in a grammar object, so the nested envelope from the previous
commit still 400d at OpenAI (tools[N].custom.format.grammar). Adds a
shared flat to nested format helper pair in prompt_templates/common_utils
used by the cursor messages arm and the chat-to-responses bridge, nests
flat Responses-style tool_choice objects on the cursor arm, flattens chat
custom tool_choice on the chat-to-responses bridge, and maps custom
tool_choice to function tool_choice on the responses-to-chat bridge to
match that bridge's custom-to-function tool downgrade
2026-08-01 11:26:09 -07:00
Tin Chi Lo
b45c99f6c5 fix(litellm): support OpenAI chat completions custom tool calls end to end
Cursor Ask mode sends chat bodies whose tools array mixes nested function
tools with flat Responses-style custom tools; the /cursor messages arm now
nests those before delegating, published via the request parsed-body cache.
Core chat parsing gains first-class custom tool call types mirroring the
openai SDK union: a single dict dispatch feeds the provider-dict sinks,
Delta dispatch stops both stream re-parse sites from silently swallowing
custom deltas, the chunk builder accumulates custom input for spend logs,
function-assuming consumers (json-mode gate, multi_tool_use repair,
helicone, lunary) skip custom entries, and the chat-to-responses bridge
flattens nested custom tools to the Responses flat shape
2026-08-01 11:26:09 -07:00
Tin Chi Lo
96916f29a6 feat(proxy): serve the OpenAI model list at /cursor/models for BYOK base URLs 2026-08-01 11:26:09 -07:00
Tin Chi Lo
af1b7f1347 fix(proxy): strip stream_options without mutating the cached request body 2026-08-01 11:26:08 -07:00
Tin Chi Lo
14c97ba8db fix(proxy): make /cursor/chat/completions work with Cursor agent mode
- delegate messages-shaped bodies to the standard chat completions handler
- strip chat-only stream_options before the Responses pipeline
- fix cursor_data_generator signature (request kwarg) and duck-type the
  stream gate so router-wrapped streams convert instead of leaking raw
  Responses events
- convert custom_tool_call items and events to chat tool_calls in the
  streaming and non-streaming paths; remap streamed tool_call indices to
  0-based sequential; accumulate raw and pydantic tool calls into one choice
- normalize generic pydantic output items through the raw-dict handler
2026-08-01 11:26:08 -07:00
Mateo Wang
48b5a5a0cc
style: unify ruff format width on 120 (#31518)
The repo linted at 120 (E501, isort) but ran ruff format at 88 via a
--line-length 88 override in the Makefile and CI, leaving the formatter
and the linter disagreeing on wrap width. Drop the override so ruff.toml's
line-length = 120 is the single source of truth and reformat the tree to
match.
2026-06-27 12:39:29 -07:00
Sameer Kankute
2453936a82
Litellm websocket improvements (#29563)
* Add support for websocket via codex

* Add model alias and creds support

* fix: skip cost tracking for WS session wrapper call types

The @client decorator on _aresponses_websocket fires async_success_handler
with result=None after the session ends. This triggered cost tracking errors
because standard_logging_object is never built for None results.

Per-turn costs are correctly tracked by individual litellm.aresponses calls
inside the session. The outer session-level logging obj should not attempt
cost tracking.

Fix: skip _aresponses_websocket and _arealtime call types in deployment_callback_on_success,
RouterBudgetLimiting.async_log_success_event, and _PROXY_track_cost_callback.

* fix: address Greptile review comments

Fix JSON injection: use json.dumps instead of f-string interpolation for model name in WS body.

Add 30s timeout for first WS frame to prevent unbounded connection resource tie-up.

Restore per-event model override in streaming_iterator; fall back to connection-level model when event omits it.

Strengthen regression test: inject alias into kwargs via _update_kwargs_with_deployment mock so the test would fail on un-fixed code.

* fix: handle nested response.create format in first-frame model extraction

When ?model= is omitted, the first WS frame can carry the model in either flat
format (first_event["model"]) or nested format (first_event["response"]["model"]).
The flat-only check would silently reject clients using the nested wire format.

Mirrors the same two-format logic in _build_base_call_kwargs.

* fix: don't force connection-level custom_llm_provider on per-event model overrides

If a client sends a different model per response.create turn, litellm needs to
re-resolve the provider from that model string. Forcing the connection-level
custom_llm_provider would silently route the request to the wrong backend.

Only inject custom_llm_provider when the per-event model matches the
connection-level model.

* refactor: extract WS model extraction into testable function

Pull the flat/nested model extraction into _extract_model_from_first_ws_event
so tests import and exercise the real function rather than a copy.

* fix: compare providers not full model strings in _inject_credentials

The model == self.model guard was too strict: same-provider model variants
(e.g., vertex_ai/gemini-2.0 -> vertex_ai/gemini-1.5 on one connection) would
lose custom_llm_provider, breaking routing when a custom api_base is in use.

Compare the provider extracted by get_llm_provider instead, so same-provider
variants still inherit the connection-level provider while cross-provider
overrides let litellm re-resolve.

* style: black formatting

* refactor: extract first-frame model resolution to fix PLR0915 (too many statements)

* Fix responses WebSocket first-frame validation

* fix: classify WS first-frame read errors and clarify cost-skip log

Distinguish client disconnects from server errors when reading the
responses WebSocket first frame, make the cost-tracking skip log message
accurate for session wrappers (which do carry a model), and resolve the
connection-level provider once per session instead of on every
response.create event.

* test: cover WS first-frame read errors and same-provider credential injection

Adds regression tests for the still-uncovered responses WebSocket paths:
the timeout, invalid-JSON and missing-model branches of
_read_ws_model_from_first_frame, plus the provider comparison in
ManagedResponsesWebSocketHandler._same_provider and _inject_credentials
(same-provider model variants keep the connection provider; cross-provider
models re-resolve).

* fix(responses-ws): fall back to explicit custom_llm_provider when connection model is unresolvable

When a WebSocket session is opened with a custom deployment alias that litellm
cannot resolve to a provider, _connection_provider was None, so _same_provider
returned False for every resolvable per-event model and the connection-level
custom_llm_provider was dropped. Use the explicitly-set custom_llm_provider as
the connection provider in that case so same-provider per-event models still
inherit it while genuinely cross-provider models continue to re-resolve.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-06-03 11:48:35 -07:00
Sameer Kankute
c12717f494 fix: address Greptile review comments
- Guard logging_obj for None when skip_pre_call_logic=True: raise ValueError
  if litellm_logging_obj not in data, preventing AttributeError downstream
- Add model=None to common_processing_pre_call_logic call in endpoints.py
  to match style of other call sites
- Add test verifying rate-limited request never receives polling ID
2026-03-19 14:10:58 +05:30
Sameer Kankute
4dc645fc33 feat(polling): check rate limits before creating polling ID
Move pre-call checks (rate limits, guardrails, budget) to run BEFORE
polling ID creation in the background streaming flow. This prevents the
edge case where a rate-limited request receives a polling ID that
immediately fails.

Changes:
- Add skip_pre_call_logic parameter to base_process_llm_request to allow
  skipping pre-call checks (avoiding double-counting of RPM/parallel requests)
- Run common_processing_pre_call_logic before generating polling ID in the
  responses API endpoint. If rate limits/guardrails fail, return error
  immediately without creating a polling ID
- Background streaming task passes skip_pre_call_logic=True to avoid re-running
  pre-call checks that were already done before polling ID creation
- Add tests verifying skip_pre_call_logic parameter works correctly

Fixes the edge case where polling_via_cache would return a polling ID
for a request that immediately fails due to rate limiting.
2026-03-19 13:59:59 +05:30
Chesars
2e9f057fbd style: run black formatter on entire codebase 2026-03-11 17:07:57 -03:00
Sameer Kankute
512a438935 feat(proxy): add WebSocket endpoint for responses API and route_llm_request support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 17:22:44 +05:30
Xianzong Xie
0b8c10c488 Add native_background_mode to override polling_via_cache for specific models
This follow-up to PR #16862 allows users to specify models that should use
the native provider's background mode instead of polling via cache.

Config example:
  litellm_settings:
    responses:
      background_mode:
        polling_via_cache: ["openai"]
        native_background_mode: ["o4-mini-deep-research"]
        ttl: 3600

When a model is in native_background_mode list, should_use_polling_for_request
returns False, allowing the request to fall through to native provider handling.

Committed-By-Agent: cursor
2026-01-27 15:54:46 -08:00
Ishaan Jaff
76eda472be
[Feat] New API Endpoint - Responses API (v1/responses/compact) (#18697)
* init transform_compact_response_api_request

* init acompact_responses

* init async_compact_response_api_handler in llm http handler

* init transform_compact_response_api_request for openai

* init acompact_responses

* fix acompact_responses

* add OAI Compact API

* docs responses API Compact

* code qa checks

* test_openai_compact_responses_api

* fix mypy linting
2026-01-06 16:24:04 +05:30
Sameer Kankute
7d0f41f437 Add cost tracking for responses api in background mode 2025-12-19 13:35:48 +05:30
Sameer Kankute
8e2a91c0c1 fix:mypy errors for litellm_staging_12_17_2025 2025-12-17 22:16:46 +05:30
Xiaohan Fu
aac17e5e22
various fixes for openrouter driven models (/messages and its streaming) and introduce the option to send all chunked responses in stream towards the guardrail. (#18085) 2025-12-17 11:21:26 +05:30
Ishaan Jaff
24d6ec67c7
[QA] Cursor Integration x LiteLLM (#17855)
* fix utils.py

* ValidUserMessageContentTypesLiteral

* add _transform_tool_choice

* _transform_responses_api_content_to_chat_completion_content

* TestContentTypeTransformation

* test_map_tool_choice_string_auto

* fix validate_chat_completion_user_messages

* fix _is_input_item_tool_call_output

* fix LiteLLMCompletionResponsesConfig
2025-12-13 12:49:45 -08:00
Sameer Kankute
b83bc10562
Merge pull request #16862 from xianzongxie-stripe/add_polling_via_cache_feature
Add polling via cache feature
2025-12-08 08:41:25 +05:30
Xianzong Xie
7c9b70bfdc chore: remove unused datetime import
Committed-By-Agent: cursor
2025-12-05 11:25:59 -08:00
Xianzong Xie
508414d3a4 refactor: use typed DeleteResponseResult for polling delete response
Committed-By-Agent: cursor
2025-12-05 09:24:46 -08:00
Xianzong Xie
5d59f47db4 refactor: extract should_use_polling_for_request to polling_handler module
Committed-By-Agent: cursor
2025-12-05 09:02:15 -08:00
Sameer Kankute
a6006e698c Add support for cursor BYOK with its own configuration 2025-12-05 09:34:49 +05:30
Xianzong Xie
a8a38778a3 fix: resolve provider from router for polling_via_cache
- Fix bug where model names without slash (e.g., 'gpt-5') couldn't
  match providers in polling_via_cache list
- Look up model in llm_router.model_name_to_deployment_indices
- Check ALL deployments for matching provider (supports load balancing)
- Check custom_llm_provider first, then extract from model string
- Add comprehensive tests for provider resolution logic

Committed-By-Agent: cursor
2025-12-04 17:47:30 -08:00
Xianzong Xie
1c3c12bb1b refactor: move background_streaming_task to separate module
- Create new background_streaming.py in response_polling/
- Update endpoints.py to import from new location
- Update __init__.py to export background_streaming_task
- Add tests for module imports and structure

Committed-By-Agent: cursor
2025-12-03 22:50:26 -08:00