Skip to content

chore(typing): clear 1.6k basedpyright Any errors across 56 files - #36543

Merged
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_decrease_anys_fable5
Aug 11, 2026
Merged

chore(typing): clear 1.6k basedpyright Any errors across 56 files#36543
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_decrease_anys_fable5

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Backend Any counts keep drifting toward their basedpyright ceilings
  • The tree carried 22,409 reportAny/reportExplicitAny errors after the last cleanup round

How it solves it:

  • Real types at each Any source across 56 files; zero casts, ignores, or suppressions
  • Ratchets budgets down: basedpyright -2,187 across 48 rules, ruff-strict -186, type-discipline -57
  • No basedpyright rule increased, repo-wide or in any individual file

User Flow

Before: the flow succeeds, and every response is byte-identical to what this branch produces

  1. Send POST https://litellm-domain/v1/chat/completions with {"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}]} and a proxy key in the Authorization header
  2. Receive 200 with a chatcmpl-... completion, token usage filled in, and the spend for the call visible shortly after at GET https://litellm-domain/ui/?page=logs
  3. Stream the same request with "stream": true and watch data: chunks arrive, ending in data: [DONE]
  4. Hit a rate-limited key hard enough to trip its TPM ceiling and receive the usual 429 with the retry guidance text

After: the flow succeeds identically at every step because this PR only changes type annotations

  1. Send POST https://litellm-domain/v1/chat/completions with {"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}]} and a proxy key in the Authorization header
  2. Receive 200 with a chatcmpl-... completion, token usage filled in, and the spend for the call visible shortly after at GET https://litellm-domain/ui/?page=logs
  3. Stream the same request with "stream": true and watch data: chunks arrive, ending in data: [DONE]
  4. Hit a rate-limited key hard enough to trip its TPM ceiling and receive the usual 429 with the retry guidance text

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Whole-tree basedpyright before and after this branch, measured with the gate's own command (basedpyright --outputjson over litellm/), counting in-tree diagnostics:

rule                 before   after   delta
reportAny            16,720  15,482  -1,238
reportExplicitAny     5,689   5,316    -373
all rules combined  147,713 145,526  -2,187

No basedpyright rule increased, repo-wide or in any individual file; a per-file-per-rule diff of the two whole-tree runs shows zero regression rows. make lint-budget-update output confirming the ceilings now hold the fixes:

Ratcheted strict-rule limits down by 186 violations this branch fixed
Ratcheted LIT-rule limits down by 57 violations this branch fixed
Ratcheted basedpyright limits down by 2187 errors this branch fixed across 48 rules

make check is green on the full staged diff. The mapped test suites for every touched module pass: 8,082 passed with 7 failures that reproduce identically on the unmodified base (a missing optional vertexai module in the local venv and one env-sensitive test), plus 137 passing follow-up runs covering the files edited after the main sweep and a rerun of the rate limiter's mapped tests after its final refactor. After the first CI round flagged a streaming logging validator this branch had introduced as too strict for duck-typed logging objects, the validator was removed and those seams restored to their base expressions; the three CI-failing test files plus the endpoint modules touched in the fix now pass locally (277 passed, 1 xfailed)

Type

🧹 Refactoring

Changes

Typing-only changes concentrated where reportAny/reportExplicitAny density was highest: the LLM handler seams (azure, anthropic, openai, openai_like, codestral, predibase, replicate, sagemaker, oci, github_copilot handlers now carry real LiteLLMLoggingObj and typed parameter annotations instead of implicit Any), the passthrough logging pipeline, the responses-MCP streaming wrapper, the parallel request limiter, and the completion dispatch path in main.py. No runtime behavior changes: annotations, narrowed locals, and isinstance guards that are runtime-equivalent to the attribute accesses they replace, converting only would-be crash paths into graceful defaults

Forbidden constructs were not used anywhere in the diff: no cast(), no # type: ignore, no # pyright: ignore, no # noqa, no new Any annotations, no new **kwargs. Diagnostics that could not be fixed without one of those were left in place rather than hidden

Budget files are ratcheted by make lint-budget-update so the cleared headroom cannot silently grow back

Caveats (if any)

  • Three micro-hardenings ride along, exposed by the honest annotations
  • MCP stream iterator: __aiter__ was wrongly async, breaking direct async for
  • Rate limiter success hook no longer crashes on usage-less responses
  • Non-callable azure_ad_token_provider is now ignored instead of crashing
  • Each degrades gracefully; no working path changes

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Note

Low Risk
Changes are overwhelmingly annotations and budget ratchets on non-critical paths; any runtime delta is limited to defensive guards on edge-case inputs (tokens, usage-less responses) rather than core request routing.

Overview
This PR tightens static typing across the LLM call path, HTTP layer, and integrations, and lowers basedpyright-code-budget.json ceilings (notably reportAny and reportExplicitAny) so the cleared headroom cannot regress silently.

Handler and bridge seams now use concrete types instead of implicit Any: logging_obj is annotated as LiteLLMLoggingObj on Azure, Anthropic, OpenAI-family, Codestral, Predibase, Replicate, SageMaker, and related handlers; the responses→chat bridge adds encoding to its validated TypedDict and passes it through transform_response instead of reading raw kwargs.

Core utilities swap many dict[str, Any] / bare Any parameters for object, Mapping, or small TypedDicts (reasoning items, OTEL span kwargs, web-search tool config, streaming chunk shapes, HTTP content bodies, aiohttp connector kwargs). litellm_logging types in-memory logger lists and uses TYPE_CHECKING aliases for enterprise callback factories so isinstance checks stay accurate without import-time Any.

Small behavior-adjacent fixes ride along where typing exposed gaps: Azure image azure_ad_token is only used when it is a non-empty string; GenericAPILogger posts with explicit keyword args; OpenAI run_thread_stream passes named arguments instead of a loosely typed **data dict.

Reviewed by Cursor Bugbot for commit 262d1b4. Bugbot is set up for automated code reviews on this repo. Configure here.

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
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR replaces broad or implicit typing across provider handlers, proxy request processing, streaming, and logging paths while ratcheting the static-analysis budgets downward.

  • Adds concrete parameter, response, callback, and intermediate-value types across the provider dispatch stack.
  • Narrows typing in proxy passthrough, response, rate-limiting, and logging flows.
  • Includes small defensive changes for streaming iteration, missing usage data, and Azure token-provider validation.
  • Reduces basedpyright, Ruff strict, and type-discipline budgets to preserve the cleanup.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/main.py Refines types around completion dispatch and defensively accepts the Azure AD token provider only when callable.
litellm/completion_extras/litellm_responses_transformation/handler.py Introduces typed bridge inputs and consistently carries the optional encoding value through response transformation.
litellm/completion_extras/litellm_responses_transformation/transformation.py Replaces broad response-transformation types with typed dictionaries, sequences, mappings, and concrete choice types.
litellm/integrations/generic_api/generic_api_callback.py Types the Generic API callback’s request path and explicitly forwards its configured timeout.
litellm/proxy/hooks/parallel_request_limiter.py Narrows rate-limiter state and handles responses whose usage or total-token data is absent.
litellm/responses/streaming_iterator.py Corrects the asynchronous iterator protocol declaration while preserving delegated stream consumption.
litellm/litellm_core_utils/streaming_handler.py Removes a concrete logging-object validator so existing duck-typed logging seams retain their prior compatibility.
basedpyright-code-budget.json Ratchets static-analysis ceilings downward to retain the typing improvements.

Reviews (2): Last reviewed commit: "fix: remove over-strict stream logging v..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_decrease_anys_fable5 (b554dd3) with litellm_internal_staging (b144b15)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (5c70405) during the generation of this report, so b144b15 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: TPM undercount for image responses
    • Extended _response_total_tokens to accept ImageResponse with ImageUsage, so image generation successes now contribute their total_tokens to per-key/user/team/customer TPM counters, and added a parametrized regression test covering that path.

Create PR

Or push these changes by commenting:

@cursor push f43045f357
Preview (f43045f357)
diff --git a/litellm/proxy/hooks/parallel_request_limiter.py b/litellm/proxy/hooks/parallel_request_limiter.py
--- a/litellm/proxy/hooks/parallel_request_limiter.py
+++ b/litellm/proxy/hooks/parallel_request_limiter.py
@@ -20,7 +20,7 @@
 from litellm.proxy.auth.budget_throttle import throttled_limit
 from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError
 from litellm.proxy.hooks.rate_limiter_utils import resolve_llm_provider_for_rate_limit
-from litellm.types.utils import Usage
+from litellm.types.utils import ImageResponse, ImageUsage, Usage
 
 if TYPE_CHECKING:
     from opentelemetry.trace import Span as _Span
@@ -35,10 +35,10 @@
 
 
 def _response_total_tokens(response_obj: object) -> int:
-    if not isinstance(response_obj, (ModelResponse, EmbeddingResponse, TextCompletionResponse)):
+    if not isinstance(response_obj, (ModelResponse, EmbeddingResponse, TextCompletionResponse, ImageResponse)):
         return 0
     response_usage: Final = getattr(response_obj, "usage", None)
-    return response_usage.total_tokens if isinstance(response_usage, Usage) else 0
+    return response_usage.total_tokens if isinstance(response_usage, (Usage, ImageUsage)) else 0
 
 
 class CacheObject(TypedDict):

diff --git a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter.py b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter.py
--- a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter.py
+++ b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter.py
@@ -11,7 +11,14 @@
     _PROXY_MaxParallelRequestsHandler,
 )
 from litellm.proxy.utils import InternalUsageCache, hash_token
-from litellm.types.utils import EmbeddingResponse, TextCompletionResponse, Usage
+from litellm.types.utils import (
+    EmbeddingResponse,
+    ImageResponse,
+    ImageUsage,
+    ImageUsageInputTokensDetails,
+    TextCompletionResponse,
+    Usage,
+)
 
 
 @pytest.mark.parametrize(
@@ -25,14 +32,24 @@
             model="gpt-3.5-turbo-instruct",
             usage=Usage(prompt_tokens=20, completion_tokens=30, total_tokens=50),
         ),
+        ImageResponse(
+            usage=ImageUsage(
+                input_tokens=20,
+                input_tokens_details=ImageUsageInputTokensDetails(
+                    image_tokens=0, text_tokens=20
+                ),
+                output_tokens=30,
+                total_tokens=50,
+            ),
+        ),
     ],
 )
 @pytest.mark.asyncio
 async def test_async_log_success_event_counts_non_chat_response_tokens(response_obj):
     """
-    Embedding and text completion responses must increment the per key, user,
-    team, and end user TPM counters, not just chat completion ModelResponse
-    objects.
+    Embedding, text completion, and image responses must increment the per key,
+    user, team, and end user TPM counters, not just chat completion
+    ModelResponse objects.
     """
     _api_key = hash_token("sk-12345")
     user_id = "ishaan"

You can send follow-ups to the cloud agent here.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 262d1b4. Configure here.

if not isinstance(response_obj, (ModelResponse, EmbeddingResponse, TextCompletionResponse)):
return 0
response_usage: Final = getattr(response_obj, "usage", None)
return response_usage.total_tokens if isinstance(response_usage, Usage) else 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TPM undercount for image responses

Medium Severity

_response_total_tokens only accepts ModelResponse, EmbeddingResponse, and TextCompletionResponse with a litellm Usage instance. ImageResponse carries ImageUsage.total_tokens, which the previous unguarded response_obj.usage.total_tokens read counted. Image (and similar) successes now add 0 TPM, so keys can exceed configured TPM limits on those endpoints.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 262d1b4. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pre-existing gap. We don't count TPM for image tokens. Belongs in a separate PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this is the legacy limiter which you have to opt into via env flag

…itellm_decrease_anys_fable5

# Conflicts:
#	litellm/proxy/common_request_processing.py
#	litellm/proxy/pass_through_endpoints/streaming_handler.py
…itellm_decrease_anys_fable5

# Conflicts:
#	ruff-strict-budget.json
#	type-discipline-budget.json
@mateo-berri
mateo-berri merged commit e37ae03 into litellm_internal_staging Aug 11, 2026
80 checks passed
@mateo-berri
mateo-berri deleted the litellm_decrease_anys_fable5 branch August 11, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants