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
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
get_llm_provider | litellm/litellm_core_utils/get_llm_provider_logic.py | 24.8 | 231 | 5 | 27 |
get_secret | litellm/secret_managers/main.py | 22.4 | 63 | 5 | 27 |
convert_to_model_response_object | litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py | 21.6 | 167 | 5 | 49 |
ChatUI | ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx | 21.5 | 189 | 11 | 151 |
createDOMPurify | litellm/proxy/swagger/swagger-ui-bundle.js | 21.5 | 196 | 8 | 78 |
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
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.
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_provider — litellm/litellm_core_utils/get_llm_provider_logic.py
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.
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_secret — litellm/secret_managers/main.py
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_object — litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py
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.
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.
ChatUI — ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx
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.
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:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
exit_heavy | 5 |
god_function | 4 |
long_function | 4 |
hub_function | 4 |
cyclic_hub | 1 |
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 →