litellm/tests/test_litellm/proxy/proxy_server
devin-ai-integration[bot] ae01882535
feat(proxy): offload spend tracking to a pod-local collector sidecar (#40545)
* feat(proxy): offload spend tracking to a pod-local spend worker sidecar

py-spy on the gateway showed the post-response _PROXY_track_cost_callback,
spend-log and DBSpendUpdateWriter work running on the inference workers'
event loop, so a DB or Redis stall backed up the request path.

When LITELLM_SPEND_WORKER_ENABLED=true, _ProxyDBLogger serializes one compact
typed SpendEvent per success and hands it to a SpendEventProducer that ships
it over a unix socket (default) or loopback-only TCP to a sidecar started as
`python -m gateway.spend_worker`. The sidecar runs the unchanged
_ProxyDBLogger pipeline against the pod's PgBouncer (pooled_database_url).
When the sidecar is unreachable, the buffer is full, or the gateway shuts
down with events still queued or in flight, the producer applies
LITELLM_SPEND_WORKER_ON_UNAVAILABLE (fallback in-process, or drop). The
sidecar half-closes producers on SIGTERM and drains, the producer treats
EOF as unavailable, and the gateway flushes buffered spend counters on
shutdown. The sidecar honors LITELLM_LOG so its writes are visible in its
own process log.

Helm: both charts gain an opt-in spend-worker sidecar container sharing an
emptyDir socket dir, and the componentized chart's HPA uses a
ContainerResource CPU metric scoped to the gateway container so sidecar
CPU does not drive inference scaling.

* feat(terraform): opt-in spend-worker sidecar for the AWS and GCP gateway stacks

Adds spend_worker_* inputs to both modules. On ECS Fargate the sidecar is a second, non-essential container in the gateway task; on Cloud Run it is a second container in the gateway service. Both listen on loopback TCP, share the gateway's DB/Redis/secret env, and set LITELLM_JOB_ROLE=spend_worker. Disabled by default. Plan-only tests cover both, and the terraform CI workflow now runs the gcp module too

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): retrieve a completed batch in the in-process spend path test

The base now defers cost tracking for batches that are still in flight, so an in_progress batch never reaches update_database

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(proxy): rename the spend worker sidecar to collector

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): run the collector from the installed litellm package and finish in-flight fallbacks on shutdown

The sidecar command becomes python -m litellm.proxy.collector so the classic image, whose runtime
stage copies only the installed package, can run it. The module now assembles DATABASE_URL and the
pod-local pgbouncer URL itself, replacing gateway/collector.py

The componentized collector sidecar inherits gateway.volumeMounts so custom CA mounts reach it.
SpendEventProducer shields an in-progress fallback from the writer task cancellation so close()
no longer loses an event already handed to the in-process pipeline

Helpers used across modules (address_argument, should_store_prompts_and_responses_in_spend_logs,
flush_spend_counters_on_shutdown) become public so the change adds no reportPrivateUsage errors

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci(terraform): drop the gcp job duplicated by the aws/gcp matrix

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(collector): keep metrics env off the classic sidecar and reject shared loopback ports

The classic chart no longer hands PROMETHEUS_METRICS_PORT and the billing metrics env to the collector container, and gives it the same /.npm scratch mount as the proxy on a read-only root. AWS and GCP now refuse a plan where the spend collector and the metrics sidecar bind the same loopback port. A regression test drives a sidecar crash mid-stream on asyncio and uvloop and checks no event is billed by both the sidecar and the in-process fallback; the producer docstring spells out why a failed drain() cannot double count

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style(proxy): format pooled_database_url after the pgbouncer rebase

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): keep the cache-hit preset key and survive dead producers on collector drain

Cache hits updated the logging object after the early return, so the offloaded spend event carried
preset_cache_key=None and the collector re-hashed reconstructed kwargs. Also guard write_eof() against
producer transports uvloop already closed so one dead connection cannot abort the drain

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(terraform): keep the gcp collector port off the metrics sidecar health port

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): collector connects to Postgres directly under IAM or Entra token auth

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): mark the collector's DATABASE_URL as pooled when it uses the pod's pgbouncer

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 17:14:13 -07:00
..
.coverage_baseline
__init__.py
_coverage_check.py
_pin_check.py
conftest.py
test_background_health.py fix(proxy): release the health check save window lock on failure or cancel 2026-09-03 12:36:04 +02:00
test_exception_handlers.py test(proxy): pin both branches of the validation exception handler 2026-07-27 09:59:47 -07:00
test_harness_smoke.py
test_lifecycle.py feat(proxy): offload spend tracking to a pod-local collector sidecar (#40545) 2026-09-10 17:14:13 -07:00
test_openapi_customization.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_proxy_config.py Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit_4116_drop_params_string_coerce 2026-09-08 15:08:38 -07:00
test_routes_anthropic_beta.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_assistants.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_audio.py fix(proxy): narrow audio_speech response before reading upstream content-type 2026-08-30 12:46:11 -07:00
test_routes_chat_completions.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_completions.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_config.py fix(proxy): surface runtime-registered callbacks in UI Logging page (#38974) 2026-09-09 22:03:16 -07:00
test_routes_embeddings.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_invitation.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_login_sso.py feat(auth): enforce configurable password policy and SSO-only login (#39381) 2026-09-02 14:28:13 -07:00
test_routes_misc.py feat(ui): let admins supply a dark-mode variant of their custom logo (#37662) 2026-08-20 12:48:55 -07:00
test_routes_model_cost_map.py test(cost_map): assert provenance without patching module state 2026-09-08 12:47:31 -07:00
test_routes_model_info.py fix(ui): make Admin UI table pagination honor the selected page size 2026-09-03 23:59:58 +00:00
test_routes_model_metrics.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_models.py Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_v1_models_alias_resolution 2026-09-10 13:26:18 +02:00
test_routes_moderations.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_onboarding.py feat(auth): enforce configurable password policy and SSO-only login (#39381) 2026-09-02 14:28:13 -07:00
test_routes_queue.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_threads.py tests(proxy_server): surface current behavior in tests (#29309) 2026-05-29 23:17:24 -07:00
test_routes_utils.py fix(router): bare authenticating-provider names declare nothing 2026-08-31 13:34:14 -07:00
test_spend_counters.py perf(proxy): pipeline spend counter increments into one Redis call per request (#40371) 2026-09-09 10:35:50 -07:00
test_streaming_helpers.py fix(proxy): emit SSE keepalives on queue, rag, azure passthrough, usage chat and policy enrich streams (#39273) 2026-09-03 18:23:37 -07:00
test_team_model_name_translation.py Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_v1_models_alias_resolution 2026-09-10 13:26:18 +02:00