Commit graph

1 commit

Author SHA1 Message Date
Tejas Chopra
3d23d76248
fix(kompress): let orgs run Kompress on their own inference stack (#2736)
## What this enables

An org pulls the Kompress weights from HuggingFace, serves them on their
own infrastructure, and points Headroom at it:

```bash
HEADROOM_KOMPRESS_ENDPOINT=https://ml.internal.acme.com
```

No credential needed, no local ML dependencies, and original content
never leaves their network (the CCR store stays proxy-local, so
`headroom_retrieve` keeps working).

## The one thing that was actually broken

Almost all of this already worked. The blocker was a hardcoded path:

```python
self._url = endpoint.rstrip("/") + "/compress"
```

Real inference servers don't serve at `/compress`:

| Stack | Path |
|---|---|
| TorchServe | `/predictions/kompress` |
| KServe / Seldon | `/v1/models/kompress:predict` |
| SageMaker | `/invocations` |

Appending `/compress` to those 404s. And because remote Kompress **fails
open**, that 404 is invisible — compression silently stops instead of
erroring. The only workaround was standing up a reverse proxy purely to
rename a path.

## Two new env vars, both defaulting to current behaviour

| Var | Default | Purpose |
|---|---|---|
| `HEADROOM_KOMPRESS_ENDPOINT_PATH` | `/compress` | Set empty to use the
endpoint URL verbatim |
| `HEADROOM_KOMPRESS_ENDPOINT_HEADERS` | *(none)* | `k=v,k2=v2`, merged
last so it can replace `Authorization` |

Headers are applied after the token deliberately, so a gateway wanting
`x-api-key` or `X-Tenant-Id` needs no separate auth-scheme setting.

## No regression

With only `HEADROOM_KOMPRESS_ENDPOINT` set, the request is
**byte-identical** to before — `POST <endpoint>/compress` with an
optional Bearer token. Existing Modal deployments need no change.

`os.environ.get` with a default distinguishes "unset" (use `/compress`)
from an explicit empty value (endpoint is a complete URL), so the escape
hatch can't fire by accident. The regression cases are deliberately the
*first* tests in the new file.

Verified through the real router wiring:

```
modal (today's config)           -> https://acme--kompress.modal.run/compress
modal + token                    -> …/compress  {'authorization': 'Bearer tok'}
self-hosted KServe (full URL)    -> https://ml.acme.com/v1/models/kompress:predict
self-hosted TorchServe (path)    -> https://ts.acme.com/predictions/kompress
self-hosted, x-api-key, no token -> …/compress  {'x-api-key': 'k', 'x-tenant-id': 'acme'}
```

## Documents the HTTP contract

The endpoint contract was only discoverable by reading the source. Now
in the module docstring:

```
request   {"content": "<text>", "target_ratio": 0.5 | null}
response  {"compressed": "<text>",       # REQUIRED
           "original_tokens": int,        # optional, derived if absent
           "compressed_tokens": int,      # optional
           "compression_ratio": float,    # optional
           "model_used": str}             # optional
```

`compressed` is the only required field, so a shim in front of an
existing inference server is a few lines.

Also logs the **resolved** URL at startup — with fail-open, a mistyped
path otherwise manifests as nothing happening at all.

## Notes

- `parse_endpoint_headers` reimplements the
`HEADROOM_OTEL_METRICS_HEADERS` format rather than importing it:
`observability.metrics` imports opentelemetry at module scope, and
remote Kompress exists precisely so a proxy can run without heavy
optional deps.
- 27 new tests. Pre-existing unrelated flake in
`test_content_router_single_item_deadline.py` (fails 3/3 on clean main).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:26:06 -07:00