litellm-ducklake-sink
An out-of-tree LiteLLM proxy logging callback that batches request telemetry and appends it to DuckLake tables through an Arrow Flight SQL endpoint. Metrics land in llm_requests, optional payloads in llm_payloads, both partitioned by day and queryable with SQL the moment they land.
It works with any DuckLake database exposed through an Arrow Flight SQL server (for example QoD - quack-on-demand). The SQL it emits is plain CREATE TABLE IF NOT EXISTS, INSERT ... VALUES, and DELETE, plus DuckLake's ALTER TABLE ... SET PARTITIONED BY at bootstrap, so the backend must be DuckDB with the DuckLake extension, but nothing in the write path is specific to one server. A few optional settings integrate with QoD deployments and are marked as such below.
Install
pip install litellm-ducklake-sink
Install it into the same environment that runs the LiteLLM proxy.
Configure
Reference the callback instance in the proxy YAML:
litellm_settings:
callbacks: litellm_ducklake_sink.callback.instance
All settings come from DUCKLAKE_SINK_* environment variables.
| Setting | Env var | Default |
|---|---|---|
| endpoint | DUCKLAKE_SINK_ENDPOINT | required, e.g. grpc+tls://host:31338 |
| username / password | DUCKLAKE_SINK_USERNAME / _PASSWORD | required |
| schema_name | DUCKLAKE_SINK_SCHEMA_NAME | main |
| tls_skip_verify | DUCKLAKE_SINK_TLS_SKIP_VERIFY | false |
| enabled | DUCKLAKE_SINK_ENABLED | true |
| capture_payloads | DUCKLAKE_SINK_CAPTURE_PAYLOADS | false |
| batch_rows | DUCKLAKE_SINK_BATCH_ROWS | 1000 |
| batch_interval | DUCKLAKE_SINK_BATCH_INTERVAL | 10 seconds |
| batch_max_bytes | DUCKLAKE_SINK_BATCH_MAX_BYTES | 2 MiB, early-flush guard |
| payload_max_bytes | DUCKLAKE_SINK_PAYLOAD_MAX_BYTES | 1 MiB per text field, truncation |
| spool_dir | DUCKLAKE_SINK_SPOOL_DIR | <tempdir>/litellm_ducklake_spool |
| spool_max_bytes | DUCKLAKE_SINK_SPOOL_MAX_BYTES | 512 MiB |
| drain_timeout | DUCKLAKE_SINK_DRAIN_TIMEOUT | 10 seconds |
| flush_max_attempts | DUCKLAKE_SINK_FLUSH_MAX_ATTEMPTS | 5 (backoff 1s, 2s, 4s... capped 30s) |
| retention_days | DUCKLAKE_SINK_RETENTION_DAYS | 30 |
QoD-only settings
These settings only apply when the endpoint is a QoD (quack-on-demand) gateway. Leave them all unset on any other Flight SQL server.
| Setting | Env var | Default |
|---|---|---|
| tenant / pool | DUCKLAKE_SINK_TENANT / _POOL | unset; sent as QoD routing headers on every connection |
| tenant_db | DUCKLAKE_SINK_TENANT_DB | unset; needed only for the maintenance trigger |
| maintenance_url / api key | DUCKLAKE_SINK_MAINTENANCE_URL / _MAINTENANCE_API_KEY | unset; QoD manager REST endpoint for the maintenance trigger |
Table creation
No manual DDL is needed. On the first flush of each process the sink runs CREATE TABLE IF NOT EXISTS for llm_requests and llm_payloads in schema_name, then ALTER TABLE ... SET PARTITIONED BY (request_day) on each, before the first insert. The schema itself must already exist (main always does). The connecting user therefore needs DDL and write privileges on that schema; under QoD with ACL enabled, grant CREATE/ALTER alongside INSERT (and DELETE for the retention job).
Delivery semantics
Delivery is at-least-once, not exactly-once. Duplicates are possible when a drain times out, the process is killed mid-flush, or several proxy workers replay spooled batches concurrently after a crash. Exact spend accounting downstream should deduplicate by request_id.
The disk spool lives at spool_dir and is capped at spool_max_bytes; once full, the oldest batches are evicted first, and evicted batches are dropped by design rather than retried.
Replay of spooled batches runs on the periodic flush cycle, which starts on the first logged request after process start rather than immediately at boot. A proxy that restarts and then sits idle only replays its spool once it handles its first request.
Multi-worker deployments share one spool safely: workers claim a batch before replaying it, so the same batch is never replayed twice, and a claim left behind by a worker that crashes mid-replay is automatically recovered by another worker after 15 minutes.
Retention
python -m litellm_ducklake_sink.retention
Run it out-of-band (cron or a Kubernetes CronJob). It deletes rows older than DUCKLAKE_SINK_RETENTION_DAYS.
Deletes in DuckLake are logical: old snapshots still reference the deleted rows' Parquet files until snapshot expiry and file cleanup run. On QoD, set DUCKLAKE_SINK_MAINTENANCE_URL, DUCKLAKE_SINK_TENANT, and DUCKLAKE_SINK_TENANT_DB and the retention job triggers QoD's managed maintenance chain after the deletes so the files are physically removed. On any other DuckLake setup, run ducklake_expire_snapshots and ducklake_cleanup_old_files yourself on whatever schedule your deployment uses.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file litellm_ducklake_sink-0.1.1.tar.gz.
File metadata
- Download URL: litellm_ducklake_sink-0.1.1.tar.gz
- Upload date:
- Size: 23.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81cd9d86a392aa9a5023267b7b4a49a0efbd3c484c154541bcd7f077710c164e
|
|
| MD5 |
be34d28869fca65825d24eebe8b90484
|
|
| BLAKE2b-256 |
72560d9f53a58fa0a2ed41a9b7027c6c4228ded91ac3f0e5504249c2591a5dfe
|
File details
Details for the file litellm_ducklake_sink-0.1.1-py3-none-any.whl.
File metadata
- Download URL: litellm_ducklake_sink-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccc767ab33e04c367e87f1ed34cc1350cea4140a8bab94da3316c5a400ab4d33
|
|
| MD5 |
4ed2b7dff85d861e66cd39d16faf4179
|
|
| BLAKE2b-256 |
c272f7ad576097cb7e082497216254cb14798e3eddf6369e883cb7170faf6560
|