You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(proxy): make /v1/compress usable as a gateway/Kong sidecar (headroomlabs-ai#2458)
## Description
Makes the compression-only `POST /v1/compress` endpoint usable as a
**network compression sidecar** behind an API gateway (Kong, LiteLLM,
...), and fixes a latent content-detector hang that silently zeroed
compression on non-Windows hosts.
Motivated by a LiteLLM-sidecar deployment whose team documented five
build-time patches; this ports the ones that belong upstream,
generalized so they cover any aliasing gateway (not just LiteLLM).
Closes #
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- **`lossy_inline` compress mode** (`config.mode="lossy_inline"`, alias
`"lossless_then_lossy"`): lossless byte/data fold first, then Kompress
the folded remainder, with `ccr_inject_marker=False` so every compressor
emits **inline, marker-free** output — no `<<ccr:…>>` markers and no CCR
store write, so the result is safe to forward straight to a provider
with no retrieval round-trip. The mode inherits the deployment's
`enable_kompress`.
- **`HEADROOM_COMPRESS_ALLOW_REMOTE`** opt-in: drops the loopback
dependency on the `/v1/compress` route **only** so an authorized
in-network gateway can reach it. Default is unchanged (loopback-only);
inbound `HEADROOM_PROXY_TOKEN` auth still applies.
- **`HEADROOM_MODEL_ALIAS_MAP`** (gateway-agnostic, fail-soft): one
shared resolver in `pricing/litellm_pricing.py` reduces a
gateway-aliased model name (e.g. `claude-opus`) to a priced
`litellm.model_cost` key, trying the mapped target as-is and with a
`bedrock/` / `vertex_ai/` prefix stripped. `proxy/savings_tracker.py`
now delegates to it, so the live (`/stats`) and persisted
(`/stats-history`) dollar figures price identically.
- **`get_context_limit`**: an operator-configured limit
(`HEADROOM_MODEL_LIMITS` / `~/.headroom/models.json`) now wins
**before** the dynamic LiteLLM lookup, so an aliased name no longer
falls through to the 128K default and skews compression.
- **fix(content_router): first-call detector watchdog on all
platforms.** The native content detector can deadlock on first use
(headroomlabs-ai#575, previously flagged Windows-only). The watchdog was `win32`-only,
so on macOS/Linux a first-use hang was unbounded → `_detect_content`
never returned → the `/v1/compress` executor timeout fired → fail-open →
**`tokens_before=0`, silent zero compression**. Now the native detector
runs under the watchdog on the first call on every platform; once it
returns it is marked verified and the direct fast path is used (zero
steady-state overhead). A hang degrades to pure-Python detection with a
clear warning. `win32` behavior is unchanged.
- Thread `waste_signals` / `pipeline_timing` into the already-present
`/v1/compress` outcome record so the guardrail path populates the
dashboard panels like the forward-proxy paths.
Deliberately **not** ported: the sidecar's LiteLLM-specific `GET
/model/info` HTTP fetch (urllib/ssl/threading/TTL). Kong has no such
endpoint; the static `HEADROOM_MODEL_ALIAS_MAP` covers any gateway with
no network dependency on the pricing path.
## Testing
- [x] Unit tests pass (targeted — see output)
- [x] Linting passes (`ruff check`)
- [x] Type checking passes (`mypy`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ ruff check <changed files>
All checks passed!
$ mypy <changed source files>
Success: no issues found in 6 source files
$ pytest tests/test_gateway_sidecar_ports.py tests/test_proxy_compress_endpoint.py -q
tests/test_gateway_sidecar_ports.py ........ [ 34%]
tests/test_proxy_compress_endpoint.py ............... [100%]
============================= 23 passed in 20.62s ==============================
```
## Real Behavior Proof
- **Environment:** macOS (darwin/arm64), Python 3.12, `.venv`; Kompress
offloaded to a Modal endpoint via `HEADROOM_KOMPRESS_ENDPOINT`.
- **Exact command / steps:** posted typical tool-output payloads to
`POST /v1/compress` (via the FastAPI `TestClient`, loopback) in both
`default` and `lossy_inline` modes; separately reproduced the detector
hang with `faulthandler.dump_traceback_later`.
- **Observed result:**
- Real savings through the endpoint (structural/lossless, Kompress off):
**JSON 150 records 13,982→9,514 (32.0%)**, **logs 314 lines 12,240→9,549
(22.0%)**, **search 200 hits 5,231→3,471 (33.6%)**. `lossy_inline` emits
**zero** CCR markers.
- `faulthandler` pinned the pre-fix hang to
`content_router.py:_detect_content` → native `_rust_detect`. With the
fix, the first call degrades at the 5s watchdog with `"Native content
detector hung … using pure-Python detection"` and compression proceeds
(previously it hung and the endpoint returned `tokens_before=0`).
- Modal Kompress warm latency measured ~0.8s/call; the learned pass
compresses prose further (62→56 words on a sample).
- **Not tested:** full `pytest` suite (ran the two affected test files
only); the native-detector hang was reproduced on a local macOS/arm64
build — the fix's degrade path is verified, but a healthy-native CI
Linux run should confirm the fast (verified) path there.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md`
## Additional Notes
- The five-item context comes from a downstream LiteLLM sidecar's
`PATCHES.md`; item #3 (record an outcome from the guardrail path) was
already upstreamed — this PR only adds the missing
`waste_signals`/`pipeline_timing` threading. Item #2 (observability
read-only exemption when `HEADROOM_PROXY_TOKEN` is set) is not addressed
here.
- All new config is opt-in and fail-soft; with nothing set, behavior is
byte-identical to today.
0 commit comments