litellm's provider routing and secret handling carry the highest live risk

Two Python god-functions — get_llm_provider (CC 231) and get_secret (CC 63) — are both structurally extreme and were each touched within the last 2 days, making them live regression risks in litellm's core routing layer.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk24.84Low
Hottest Functionget_llm_provider

Antipatterns Detected

complex_branching5deeply_nested5exit_heavy5god_function4long_function4hub_function4cyclic_hub1

Run this on your own codebase

See if your own repo has a get_llm_provider-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in litellm?

A god function is a single function that has accumulated so many responsibilities that it controls a disproportionate share of the system's behavior — typically identified by very high cyclomatic complexity, deep nesting, and high fan-out simultaneously. In litellm, `get_llm_provider` has a cyclomatic complexity of 231 and calls 27 other functions directly, meaning it is simultaneously hard to reason about and tightly coupled to a large portion of the provider routing layer. When a god function changes — and `get_llm_provider` was touched 2 days ago — the risk of unintended side effects is high because no individual engineer can mentally model all 231 execution paths. God functions also make test coverage extremely difficult: achieving even 70% branch coverage on a CC-231 function requires an impractical number of test cases.

How do I reduce cyclomatic complexity in Python?

The most effective technique is the extract-method refactoring: identify groups of conditionals that share a single concern (e.g. all Azure-specific routing logic in `get_llm_provider`) and move them into a dedicated function with a descriptive name. A cyclomatic complexity above 15 is a reasonable threshold to flag for splitting; above 30, splitting is urgent; and at 231 as found in `get_llm_provider`, the function should be treated as a multi-sprint refactoring effort. A concrete first step today: count the top-level if-elif chains in `get_llm_provider` and extract each named provider's detection logic into its own `_resolve_<provider>_provider(model, ...)` function — this alone should reduce the primary function's CC below 50. Using Python's `flake8-bugbear` or `radon` in CI to gate on CC > 15 prevents the same accumulation from recurring.

Is litellm actively maintained?

Yes — the data is unambiguous on this. Every function in the top-5 hotspot list is in the fire quadrant, meaning all of them combine structural complexity with recent commit activity. `get_llm_provider`, `get_secret`, and `convert_to_model_response_object` were each touched 1 time in the last 30 days as recently as 2 days ago; `ChatUI` was touched 3 times in the same window. Active maintenance and high structural complexity are not mutually exclusive — in fact, the fire quadrant is created precisely by that combination. The practical implication is that litellm is being actively developed in code that is already difficult to reason about, which raises the probability of introducing regressions with each commit.

How do I reproduce this analysis?

This analysis was produced from commit `d6f498f` of BerriAI/litellm using the Hotspots CLI, available at github.com/hotspots-dev/hotspots. To reproduce it, run `git checkout d6f498f` in a local clone of the repository, then execute `hotspots analyze . --mode snapshot --explain-patterns --force`. The same command works on any local git repository without additional configuration, and will produce band, quadrant, and metric outputs comparable to those shown here.

What does activity_risk mean?

activity_risk multiplies a function's structural complexity score — derived from cyclomatic complexity, nesting depth, and fan-out — by how frequently the function has been changed in recent commits. A function with cyclomatic complexity 231 that was committed to 2 days ago scores much higher than a CC-231 function that has been untouched for two years, because the recently-changed version has a near-term probability of introducing bugs that the dormant one does not. This prioritization is intentional: it focuses engineering attention on code that is both hard to understand and currently in motion, rather than on code that is merely complicated but stable. The `activity_risk` values in this analysis range from 21.52 to 24.84 across the top five functions, all of which are fire-quadrant — structurally complex and recently touched.

Every function in litellm’s top-5 hotspot list lands in the fire quadrant — meaning each one is both structurally complex and actively changing right now. get_llm_provider carries an activity-weighted risk score of 24.84, a cyclomatic complexity of 231, and was touched 1 time in the last 30 days as recently as 2 days ago. That is not a cleanup item; it is a live regression surface. litellm is a Python proxy and unified SDK for calling 100+ LLM providers, and at 26,353 total functions — with 4,763 rated critical — the codebase has meaningful structural debt concentrated in exactly the integration paths that change most frequently. I would start with get_llm_provider because no other single function combines a CC of 231 with fan-out of 27 and active recent commits.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
get_llm_providerlitellm/litellm_core_utils/get_llm_provider_logic.py24.8231527
get_secretlitellm/secret_managers/main.py22.463527
convert_to_model_response_objectlitellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py21.6167549
ChatUIui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx21.518911151
createDOMPurifylitellm/proxy/swagger/swagger-ui-bundle.js21.5196878

Large Repo Analysis

litellm is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 10 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Codemod / Tooling Files in Results

The fifth-ranked function, createDOMPurify, lives in litellm/proxy/swagger/swagger-ui-bundle.js — this is Swagger UI’s compiled JavaScript bundle, a vendored third-party artifact committed to the repository for serving the API documentation UI. Its high CC and nesting depth reflect minified bundler output, not litellm application logic. To exclude it from future Hotspots runs, add { "exclude": ["litellm/proxy/swagger/"] } to your .hotspotsrc.json file.

Risk Distribution

Triage Band Distribution
Fire11615Watch14738

26,353 functions analyzed

Every function Hotspots scored lands in either fire or watch — there is no debt quadrant at all, meaning litellm has no dormant-but-complex functions sitting quietly in the background. The risk is entirely live: structurally complex code that is actively being changed. That is the hardest category to manage because each commit is a bet that the engineer making the change has fully understood a function they almost certainly cannot hold in working memory.

Detected Antipatterns
Complex Branching×5Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
God Function×4God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Hub Function×4Hub Function
Many other functions call this one — a change here ripples widely through callers.
Cyclic Hub×1Cyclic Hub
Participates in a call cycle with other high-traffic functions, creating circular dependency risk.

All five top hotspots share the same cluster of antipatterns: complex branching, deep nesting, and multiple exit paths. Four of the five are classified as god functions — single functions that own far too much logic and are called or call so many other functions that a change anywhere ripples unpredictably.


get_llm_providerlitellm/litellm_core_utils/get_llm_provider_logic.py

get_llm_provider
litellm/litellm_core_utils/get_llm_provider_logic.py
24.84
fire
CC 231
ND 5
FO 27
touches/30d 1

This is the highest-priority function in the repository. A cyclomatic complexity of 231 means there are at least 231 independent execution paths through the function — that is not a slight overextension, it is a function that has absorbed years of provider-specific edge-case logic without decomposition. Looking at the source excerpt, the function handles a cascade of provider detection scenarios: early null checks, proxy config lookups, Azure AI Studio overrides, Cohere chat routing, Anthropic text model routing, OpenRouter prefix stripping, a JSON-configured provider registry lookup, and then falls into the main provider list enumeration — all inside a single try block.

With fan-out of 27, this function directly calls 27 distinct other functions. In Python’s duck-typed environment, that fan-out is almost certainly an undercount of the real coupling because type resolution happens at runtime. The patterns here are exit-heavy (multiple early returns with different tuple shapes), deeply nested (ND 5, which makes tracing any single path non-trivial), and cyclic_hub — meaning it is both heavily called and calls heavily outward. A misrouted provider at this layer means the wrong LLM endpoint gets hit silently, which is exactly the class of bug that surfaces in production rather than in tests.

Cyclomatic Complexity 231
threshold: 30

The immediate refactoring goal should be to decompose by provider family. Each named provider (Azure, Cohere, Anthropic, OpenRouter, JSON-configured providers) should become its own resolver function, with get_llm_provider reduced to a dispatcher that tries each resolver in order and returns the first match. This alone would cut CC below 50 and make each resolver independently testable.


get_secretlitellm/secret_managers/main.py

get_secret
litellm/secret_managers/main.py
22.38
fire
CC 63
ND 5
FO 27
touches/30d 1

With a cyclomatic complexity of 63 and maximum nesting depth of 5, get_secret is the second-highest risk function and arguably the most security-sensitive one. The source excerpt shows the function handling at least five distinct OIDC provider flows (Google, CircleCI, CircleCI v2, GitHub Actions, Azure workload identity) inside deeply nested conditionals, each making outbound HTTP calls to metadata endpoints or reading environment variables. Fan-out of 27 means it also dispatches to a variety of key management system backends based on runtime configuration.

The exit-heavy and deeply-nested patterns here are a test-coverage problem: exercising all 63 paths requires mocking combinations of environment variables, HTTP responses, and key management configurations that are difficult to compose in isolation. Any time a new OIDC provider or secret backend is added — which happens frequently given the 1 touch in 30 days — there is genuine risk of breaking an existing provider’s flow through an untested interaction inside the nesting.

The practical fix is to extract each OIDC provider into its own function (e.g. _get_secret_google_oidc, _get_secret_github_oidc) and introduce a registry pattern so adding a new provider does not require editing the main dispatch chain. That would bring CC below 15 for the primary function.


convert_to_model_response_objectlitellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py

convert_to_model_response_object
litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py
21.6
fire
CC 167
ND 5
FO 49
touches/30d 1

This function has the highest fan-out in the top five at 49 — meaning it directly invokes 49 other functions. Combined with a cyclomatic complexity of 167, it is the broadest coupling point in the codebase. The source excerpt reveals why: the function must handle every response type (completion, embedding, image generation, audio transcription, reranking), multiple provider quirks (OpenRouter error objects, Apertis empty-error-on-success patterns, provider-specific response headers), and a tool-call-to-JSON-mode conversion path — all in one place.

Fan-out (distinct function calls) 49
threshold: 15

In Python, a fan-out of 49 means 49 different modules or classes have implicit coupling to this function’s behavior. If any one of those 49 callees changes its signature or return contract, this function may silently produce a wrong result rather than raising an error. The god_function and hub_function patterns compound each other here: it owns too much logic and reaches too far outward simultaneously.

I would prioritize splitting this by response_type first — each of the five response types (completion, embedding, image, transcription, rerank) already appears as a branch in the source. Each branch should become its own function, leaving convert_to_model_response_object as a 10-line dispatcher. That alone would cut the CC by roughly 70% and reduce fan-out proportionally.


ChatUIui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx

ChatUI
ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx
21.52
fire
CC 189
ND 11
FO 151
touches/30d 3

This is the only frontend entry in the top five, and it stands out sharply. ChatUI was touched 3 times in the last 30 days — the most active function in the list — with a cyclomatic complexity of 189, a maximum nesting depth of 11, and a fan-out of 151. A nesting depth of 11 in a React component is a strong signal that multiple layers of conditional rendering, effect hooks, and callback definitions have compounded without extraction into child components or custom hooks.

Max Nesting Depth 11
threshold: 4

The source excerpt confirms this: the component manages MCP server state, MCP toolset state, API key sources, session storage reads with error handling, proxy base URL state, and at least a dozen other concerns — all initialized in the component body before a single JSX element is rendered. With 3 touches in 30 days, one-third of recent commits to this file were fixing bugs, which is a meaningful historical signal at the file level.

The most impactful decomposition here is to extract the MCP server management logic into a useMCPServers custom hook, the API key source logic into a useAPIKeySource hook, and to split rendering into ChatInputPanel, ChatHistoryPanel, and MCPToolPanel child components. Each extracted piece reduces both the CC and the nesting depth of the parent component.


Vendor Note

The fifth entry — createDOMPurify in litellm/proxy/swagger/swagger-ui-bundle.js — is a vendored third-party bundle (Swagger UI’s compiled distribution). Its CC of 196 and ND of 8 reflect the bundler’s output, not litellm source code. I would exclude it from future analyses using the .hotspotsrc.json pattern { "exclude": ["litellm/proxy/swagger/"] } to keep the hotspot list focused on first-party code.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching5
deeply_nested5
exit_heavy5
god_function4
long_function4
hub_function4
cyclic_hub1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/BerriAI/litellm
cd litellm
git checkout d6f498ff5cef544ab2238a0771d80387d51a5955
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 10

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses