* fix: don't close HTTP/SDK clients on LLMClientCache eviction
Removing the _remove_key override that eagerly called aclose()/close()
on evicted clients. Evicted clients may still be held by in-flight
streaming requests; closing them causes:
RuntimeError: Cannot send a request, as the client has been closed.
This is a regression from commit fb72979432. Clients that are no longer
referenced will be garbage-collected naturally. Explicit shutdown cleanup
happens via close_litellm_async_clients().
Fixes production crashes after the 1-hour cache TTL expires.
* test: update LLMClientCache unit tests for no-close-on-eviction behavior
Flip the assertions: evicted clients must NOT be closed. Replace
test_remove_key_closes_async_client → test_remove_key_does_not_close_async_client
and equivalents for sync/eviction paths.
Add test_remove_key_removes_plain_values for non-client cache entries.
Remove test_background_tasks_cleaned_up_after_completion (no more _background_tasks).
Remove test_remove_key_no_event_loop variant that depended on old behavior.
* test: add e2e tests for OpenAI SDK client surviving cache eviction
Add two new e2e tests using real AsyncOpenAI clients:
- test_evicted_openai_sdk_client_stays_usable: verifies size-based eviction
doesn't close the client
- test_ttl_expired_openai_sdk_client_stays_usable: verifies TTL expiry
eviction doesn't close the client
Both tests sleep after eviction so any create_task()-based close would
have time to run, making the regression detectable.
Also expand the module docstring to explain why the sleep is required.
* docs(AGENTS.md): add rule — never close HTTP/SDK clients on cache eviction
* docs(CLAUDE.md): add HTTP client cache safety guideline
6.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Development Commands
Installation
make install-dev- Install core development dependenciesmake install-proxy-dev- Install proxy development dependencies with full feature setmake install-test-deps- Install all test dependencies
Testing
make test- Run all testsmake test-unit- Run unit tests (tests/test_litellm) with 4 parallel workersmake test-integration- Run integration tests (excludes unit tests)pytest tests/- Direct pytest execution
Code Quality
make lint- Run all linting (Ruff, MyPy, Black, circular imports, import safety)make format- Apply Black code formattingmake lint-ruff- Run Ruff linting onlymake lint-mypy- Run MyPy type checking only
Single Test Files
poetry run pytest tests/path/to/test_file.py -v- Run specific test filepoetry run pytest tests/path/to/test_file.py::test_function -v- Run specific test
Running Scripts
poetry run python script.py- Run Python scripts (use for non-test files)
GitHub Issue & PR Templates
When contributing to the project, use the appropriate templates:
Bug Reports (.github/ISSUE_TEMPLATE/bug_report.yml):
- Describe what happened vs. what you expected
- Include relevant log output
- Specify your LiteLLM version
Feature Requests (.github/ISSUE_TEMPLATE/feature_request.yml):
- Describe the feature clearly
- Explain the motivation and use case
Pull Requests (.github/pull_request_template.md):
- Add at least 1 test in
tests/litellm/ - Ensure
make test-unitpasses
Architecture Overview
LiteLLM is a unified interface for 100+ LLM providers with two main components:
Core Library (litellm/)
- Main entry point:
litellm/main.py- Contains core completion() function - Provider implementations:
litellm/llms/- Each provider has its own subdirectory - Router system:
litellm/router.py+litellm/router_utils/- Load balancing and fallback logic - Type definitions:
litellm/types/- Pydantic models and type hints - Integrations:
litellm/integrations/- Third-party observability, caching, logging - Caching:
litellm/caching/- Multiple cache backends (Redis, in-memory, S3, etc.)
Proxy Server (litellm/proxy/)
- Main server:
proxy_server.py- FastAPI application - Authentication:
auth/- API key management, JWT, OAuth2 - Database:
db/- Prisma ORM with PostgreSQL/SQLite support - Management endpoints:
management_endpoints/- Admin APIs for keys, teams, models - Pass-through endpoints:
pass_through_endpoints/- Provider-specific API forwarding - Guardrails:
guardrails/- Safety and content filtering hooks - UI Dashboard: Served from
_experimental/out/(Next.js build)
Key Patterns
Provider Implementation
- Providers inherit from base classes in
litellm/llms/base.py - Each provider has transformation functions for input/output formatting
- Support both sync and async operations
- Handle streaming responses and function calling
Error Handling
- Provider-specific exceptions mapped to OpenAI-compatible errors
- Fallback logic handled by Router system
- Comprehensive logging through
litellm/_logging.py
Configuration
- YAML config files for proxy server (see
proxy/example_config_yaml/) - Environment variables for API keys and settings
- Database schema managed via Prisma (
proxy/schema.prisma)
Development Notes
Code Style
- Uses Black formatter, Ruff linter, MyPy type checker
- Pydantic v2 for data validation
- Async/await patterns throughout
- Type hints required for all public APIs
- Avoid imports within methods — place all imports at the top of the file (module-level). Inline imports inside functions/methods make dependencies harder to trace and hurt readability. The only exception is avoiding circular imports where absolutely necessary.
Testing Strategy
- Unit tests in
tests/test_litellm/ - Integration tests for each provider in
tests/llm_translation/ - Proxy tests in
tests/proxy_unit_tests/ - Load tests in
tests/load_tests/ - Always add tests when adding new entity types or features — if the existing test file covers other entity types, add corresponding tests for the new one
UI / Backend Consistency
- When wiring a new UI entity type to an existing backend endpoint, verify the backend API contract (single value vs. array, required vs. optional params) and ensure the UI controls match — e.g., use a single-select dropdown when the backend accepts a single value, not a multi-select
Database Migrations
- Prisma handles schema migrations
- Migration files auto-generated with
prisma migrate dev - Always test migrations against both PostgreSQL and SQLite
Proxy database access
- Do not write raw SQL for proxy DB operations. Use Prisma model methods instead of
execute_raw/query_raw. - Use the generated client:
prisma_client.db.<model>(e.g.litellm_tooltable,litellm_usertable) with.upsert(),.find_many(),.find_unique(),.update(),.update_many()as appropriate. This avoids schema/client drift, keeps code testable with simple mocks, and matches patterns used in spend logs and other proxy code.
Enterprise Features
- Enterprise-specific code in
enterprise/directory - Optional features enabled via environment variables
- Separate licensing and authentication for enterprise features
HTTP Client Cache Safety
- Never close HTTP/SDK clients on cache eviction.
LLMClientCache._remove_key()must not callclose()/aclose()on evicted clients — they may still be used by in-flight requests. Doing so causesRuntimeError: Cannot send a request, as the client has been closed.after the 1-hour TTL expires. Cleanup happens at shutdown viaclose_litellm_async_clients().
Troubleshooting: DB schema out of sync after proxy restart
litellm-proxy-extras runs prisma migrate deploy on startup using its own bundled migration files, which may lag behind schema changes in the current worktree. Symptoms: Unknown column, Invalid prisma invocation, or missing data on new fields.
Diagnose: Run \d "TableName" in psql and compare against schema.prisma — missing columns confirm the issue.
Fix options:
- Create a Prisma migration (permanent) — run
prisma migrate dev --name <description>in the worktree. The generated file will be picked up byprisma migrate deployon next startup. - Apply manually for local dev —
psql -d litellm -c "ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..."after each proxy start. Fine for dev, not for production. - Update litellm-proxy-extras — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it.