Repository navigation
fix(proxy): unregister logging callbacks removed from the stored config - #43429
Conversation
POST /config/callback/delete saved the config and resynced, but the resync only ever added callbacks, so a deleted callback kept exporting and kept showing in /get/config/callbacks as read-only on every worker. ProxyConfig now tracks which callback list entries each DB config sync registered and unregisters them once the stored config stops listing them. Callbacks it did not register (YAML, code) are never touched, and a failed config load skips the sync instead of treating the config as empty.
|
| before_ids: Final = frozenset((list_name, id(entry)) for list_name, entry in before) | ||
| return tuple( | ||
| (list_name, entry) | ||
| for list_name, entry in _registered_callback_entries() | ||
| if (list_name, id(entry)) not in before_ids |
There was a problem hiding this comment.
If code already registered "langfuse_otel" as a success callback, registering the same callback from the DB replaces that string with a logger instance. This snapshot records the instance as DB-owned. When an admin deletes the DB entry, the new removal logic deletes the instance, so the code-configured callback stops running too.
…ig (#43429) (#43432) * fix(proxy): unregister logging callbacks removed from the stored config POST /config/callback/delete saved the config and resynced, but the resync only ever added callbacks, so a deleted callback kept exporting and kept showing in /get/config/callbacks as read-only on every worker. ProxyConfig now tracks which callback list entries each DB config sync registered and unregisters them once the stored config stops listing them. Callbacks it did not register (YAML, code) are never touched, and a failed config load skips the sync instead of treating the config as empty. * refactor(proxy): keep callback sync comprehensions to one for clause (cherry picked from commit c991f4b)
TLDR
Problem this solves:
How it solves it:
User Flow
Before: an admin deletes a logging callback, gets a success message, and the proxy keeps exporting to it
langfuse_otelwith POST http://localhost:4000/config/update and traffic starts reaching LangfuseSuccessfully deleted callback: langfuse_otellangfuse_otelon every workerAfter: the delete sticks on every worker
langfuse_otelwith POST http://localhost:4000/config/update and traffic starts reaching LangfuseSuccessfully deleted callback: langfuse_otelRelevant issues
Backport of #43428
Affected release
The read-only listing appears since v1.102.0-rc.1 (#38974). The leftover exports are older and also happen on v1.101.0
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)The source and test additions are line for line the same as #43428, both commits included. The only cherry-pick conflict was test file placement: the anchor test on main does not exist on rc, so the new tests sit at the same spot without it. On this branch the new tests fail with the source change reverted (8 failed) and pass with it, and
test_proxy_server.py,test_proxy_config.pyandtest_delete_callbacks_endpoint.pypass (724 tests)Screenshots / Proof of Fix
Proxy started with
--num_workers 2, a local Postgres,STORE_MODEL_IN_DB=True, amock_responsemodel namedmock-gpt, andLANGFUSE_OTEL_HOSTpointing at a local OTLP capture server that counts export POSTs. No real provider calls are needed since the bug is in callback registration, not the LLM call. "OTLP exports" counts export batches the capture server receivedBefore (cc111d1)
curl -s localhost:4000/config/update -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"litellm_settings":{"success_callback":["langfuse_otel"]}}'returns{"message":"Config updated successfully"}, and 6 chats produce 1 export batchcurl -s localhost:4000/config/callback/delete -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"callback_name":"langfuse_otel"}'returns{"message":"Successfully deleted callback: langfuse_otel","removed_callback":"langfuse_otel","remaining_callbacks":[],...}curl -s localhost:4000/get/config/callbacks -H "Authorization: Bearer $LITELLM_MASTER_KEY"still returns[{"name": "langfuse_otel", ..., "type": "success"}]/get/config/callbackscalls across workers all return["langfuse_otel"]After (8823a69)
/config/updatecall returns{"message":"Config updated successfully"}, and 6 chats produce 1 export batch/config/callback/deletecall returns the same success message/get/config/callbacksreturns[]/get/config/callbackscalls across workers all return[]/config/updatecall, then waiting 35 seconds, 10 chats produce 2 export batchesType
🐛 Bug Fix
Caveats (if any)
Medium
Low