2026-01-06 23:16:58 -08:00
|
|
|
|
[build-system]
|
refactor: single-wheel maturin build backend (fixes #355)
Eliminates the dual-package architecture that was the root cause of #355.
`pip install headroom-ai` now produces ONE wheel containing both the Python
source (headroom/*.py) and the compiled Rust extension (headroom/_core.so).
No more separate `headroom-core-py` package, no more chicken-and-egg with
PyPI publication, no more wheelhouse / PIP_FIND_LINKS / composite-action
plumbing in CI.
This is the canonical pattern used by cryptography, polars, ruff,
pydantic-core, and other Rust-as-core Python packages. Honors the
"Rust as core engine" direction.
## What changed
- pyproject.toml: `[build-system]` swapped from hatchling to maturin.
`[tool.hatch.*]` deleted; `[tool.maturin]` added pointing at
`crates/headroom-py/Cargo.toml` for the cdylib. `python-source = "."`
picks up the root `headroom/` package directly (dashboard HTML
templates and other non-Python files included automatically).
- crates/headroom-py/pyproject.toml: deleted. The crate is no longer a
separate published package; its Cargo.toml stays as the cdylib build
target invoked via `[tool.maturin] manifest-path`.
- crates/headroom-py/python/: deleted (placeholder layout for the old
separate package).
## CI updates
- ci.yml: `test` / `test-extras` / `test-agno` jobs simplified — Rust
toolchain set up before `pip install -e .` (which now invokes maturin
via build-system). Removed the "build wheel + symlink .so" dance.
`build` job swapped from `python -m build` (hatch) to
`maturin build` + `maturin sdist`.
- release.yml: collapsed dual-package matrix into one. New `build-wheels`
matrix produces cross-platform wheels for cp310/11/12/13 ×
{linux x86_64, linux aarch64, macos x86_64, macos aarch64}. New
`collect-dist` aggregator merges artifacts. publish-pypi consumes the
merged dist.
- init-native-e2e.yml: dropped windows-latest from the matrix —
upstream `esaxx-rs` (/MT) and `ort-sys` (/MD) link with conflicting
MSVC C runtime libraries, so the Rust extension cannot build for
win_amd64 today. Tracked as a follow-up; not a blocker for Linux+macOS.
- headroom-e2e-setup: composite action now sets up Rust toolchain +
Swatinem/rust-cache before `pip install -e .[proxy]`.
- eval.yml, publish.yml, rust.yml: same pattern — rust toolchain before
install. rust.yml's wheels job builds from root pyproject.toml (no
more `-m crates/headroom-py/Cargo.toml`).
- e2e/init/Dockerfile, e2e/wrap/Dockerfile: install rust + maturin in
the build stage; copy `crates/` + workspace `Cargo.toml/lock` so the
install can build the extension. Dropped `HEADROOM_REQUIRE_RUST_CORE=false`
from wrap-e2e — the image now ships the full Rust core.
- Dockerfile (main): simplified — no more Layer 2/3 dance with
`headroom-core-py` install + symlink. Single `uv pip install` builds
+ installs everything.
- .devcontainer/Dockerfile: rust toolchain + libssl-dev + maturin
added so `uv sync` builds the extension inside the devcontainer.
## Lockfile + script
- uv.lock: regenerated. No `headroom-core-py` entries remain.
- scripts/build_rust_extension.sh: simplified from a symlink-into-tree
workaround to a thin wrapper around `pip install -e .`. The maturin
build-backend handles placement automatically.
## Local validation (all green on macOS aarch64)
1. Clean venv `pip install -e .` → `from headroom._core import …` works.
2. `maturin build --release` → 13.8 MB wheel, 336 files including
`headroom/_core.cpython-311-darwin.so` (32 MB cdylib) and
`headroom/dashboard/templates/dashboard.html`.
3. `pip install <wheel>` in fresh venv → import works.
4. Wheel contents verified via `unzip -l`.
5. `pytest tests/test_transforms/test_diff_compressor.py` — 29 passed.
6. `pytest tests/test_relevance.py` — 30 passed.
7. `cargo build --workspace` + `cargo test --workspace` — all green.
8. `make ci-precheck` — 176 Python tests + Rust + commitlint green.
## Migration notes
Users on `pip install headroom-ai` get the Rust core automatically
(linux + macos wheels). sdist installs require rust toolchain available
locally — pip will build via maturin.
Closes #355
Supersedes #357 (workarounds-based fix abandoned in favor of
architectural fix)
2026-05-03 13:16:41 -07:00
|
|
|
|
requires = ["maturin>=1.5,<2.0"]
|
|
|
|
|
|
build-backend = "maturin"
|
2026-01-06 23:16:58 -08:00
|
|
|
|
|
|
|
|
|
|
[project]
|
2026-01-10 14:51:08 -08:00
|
|
|
|
name = "headroom-ai"
|
2026-06-08 11:21:23 -07:00
|
|
|
|
version = "0.24.0"
|
2026-01-07 11:36:44 -08:00
|
|
|
|
description = "The Context Optimization Layer for LLM Applications - Cut costs by 50-90%"
|
2026-01-06 23:16:58 -08:00
|
|
|
|
readme = "README.md"
|
2026-01-07 11:36:44 -08:00
|
|
|
|
license = "Apache-2.0"
|
2026-01-06 23:16:58 -08:00
|
|
|
|
requires-python = ">=3.10"
|
|
|
|
|
|
authors = [
|
2026-01-07 11:36:44 -08:00
|
|
|
|
{ name = "Headroom Contributors" }
|
|
|
|
|
|
]
|
|
|
|
|
|
maintainers = [
|
|
|
|
|
|
{ name = "Headroom Contributors" }
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
|
|
|
|
|
keywords = [
|
|
|
|
|
|
"llm",
|
|
|
|
|
|
"openai",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"anthropic",
|
|
|
|
|
|
"claude",
|
|
|
|
|
|
"gpt",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"context",
|
|
|
|
|
|
"token",
|
|
|
|
|
|
"optimization",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"compression",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"caching",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"proxy",
|
|
|
|
|
|
"ai",
|
|
|
|
|
|
"machine-learning",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
|
|
|
|
|
classifiers = [
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"Development Status :: 4 - Beta",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"Intended Audience :: Developers",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"License :: OSI Approved :: Apache Software License",
|
|
|
|
|
|
"Operating System :: OS Independent",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"Programming Language :: Python :: 3",
|
|
|
|
|
|
"Programming Language :: Python :: 3.10",
|
|
|
|
|
|
"Programming Language :: Python :: 3.11",
|
|
|
|
|
|
"Programming Language :: Python :: 3.12",
|
2026-06-09 15:33:16 +01:00
|
|
|
|
"Programming Language :: Python :: 3.13",
|
|
|
|
|
|
"Programming Language :: Python :: 3.14",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"Typing :: Typed",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
|
|
|
|
|
dependencies = [
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Core: lightweight compression (SmartCrusher, ContentRouter, CCR, TOIN)
|
|
|
|
|
|
"tiktoken>=0.5.0", # Tokenizer for all compressors
|
|
|
|
|
|
"pydantic>=2.0.0", # Config and data models
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"litellm>=1.86.2,<2.0", # Model registry, pricing, and provider support
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
"click>=8.1.0", # CLI framework
|
|
|
|
|
|
"rich>=13.0.0", # Rich terminal output
|
2026-04-09 21:20:34 -05:00
|
|
|
|
"opentelemetry-api>=1.24.0", # Safe no-op OTEL API for instrumentation
|
fix: bundle ast-grep/difftastic/scc + generic tool_result interceptor framework
What this does, in plain terms:
Headroom's proxy now ships with three CLI tools (ast-grep, difftastic,
scc) that it can use to shrink tool_result payloads before they reach
the model. The goal is simple: when Claude Code (or Codex, Aider, etc.)
asks the model to reason about a big file or diff, we swap the verbose
output for a compact, same-meaning version. Fewer tokens per turn, same
answers, lower bill.
Today a single interceptor is wired: ast-grep on Read. When an agent
reads a large code file, the proxy replaces the file body with an
outline of its top-level functions/classes plus docstrings. In live
tests that cut prompt tokens 74–76% on both OpenAI and Anthropic,
same answer either way.
How it works:
- `pip install headroom-ai` now installs ast-grep via a PyPI wheel
(core dep). difftastic and scc are fetched once at proxy startup
from pinned upstream GitHub releases and cached per-user.
- A generic registry (`headroom/proxy/interceptors/`) lets us add more
tool-aware rewrites in one file each: declare `matches()` and
`transform()`, call `register()`, done. No proxy or metrics plumbing
per tool.
- Safety rails built in: pass-through when a Read specifies a line
range; second Read of the same file in a conversation returns full
content (progressive disclosure); any failing interceptor logs and
skips, never crashes a request.
Opt-in for now:
- Off by default while this ships. Turn on with
`headroom proxy --intercept-tool-results` or
`HEADROOM_INTERCEPT_ENABLED=1`, so we can measure before flipping
defaults.
What users see after turning it on:
- First `headroom wrap claude` boot is ~5s longer (binaries fetched).
Every subsequent run is cache-only.
- Existing `transforms_applied` field in metrics gets entries like
`interceptor:ast-grep`, so savings show up in current dashboards
and HTML reports with no UI change.
Other housekeeping in this PR:
- uv.lock moved to .gitignore — regenerated locally per environment.
- 35 unit + integration tests, ruff + mypy clean.
- Dead-code audit done: removed `binaries.run()`, `needs_filesystem`
plumbing, unused `_kind` tuple elements, unused `tool_output`
parameter, and the never-set HEADROOM_SKIP_TOOLS_BOOTSTRAP env.
2026-04-20 13:41:29 -07:00
|
|
|
|
"ast-grep-cli>=0.30.0", # AST-aware code slicing (CodeCompressor); binary wheel
|
2026-04-21 20:42:56 -05:00
|
|
|
|
"tomli>=2.0.0; python_version < '3.11'", # tomllib backport for helper scripts
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
[project.optional-dependencies]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Proxy server (most common install: pip install headroom-ai[proxy])
|
2026-01-07 11:36:44 -08:00
|
|
|
|
proxy = [
|
|
|
|
|
|
"fastapi>=0.100.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"uvicorn>=0.23.0,<1.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"httpx[http2]>=0.24.0",
|
|
|
|
|
|
"openai>=2.14.0", # OpenAI API format support
|
2026-03-16 22:56:30 -07:00
|
|
|
|
"mcp>=1.0.0", # MCP server (headroom_compress, retrieve, stats)
|
2026-03-25 01:04:26 -07:00
|
|
|
|
"magika>=0.6.0", # ML content detection for ContentRouter
|
2026-03-30 16:01:51 -07:00
|
|
|
|
"zstandard>=0.20.0", # Decompress zstd request bodies (Codex, etc.)
|
2026-03-30 22:00:12 -07:00
|
|
|
|
"websockets>=13.0", # WebSocket proxy for /v1/responses (Codex gpt-5.4+)
|
2026-03-30 22:50:13 -07:00
|
|
|
|
"onnxruntime>=1.16.0", # Kompress ONNX INT8 text compression (no torch needed)
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"transformers>=4.30.0,<6.0", # Tokenizer only (for Kompress)
|
2026-04-11 19:30:38 -07:00
|
|
|
|
"watchdog>=4.0.0", # File watcher for live code graph reindexing (--code-graph)
|
2026-04-12 09:17:15 -07:00
|
|
|
|
"sqlite-vec>=0.1.6", # Vector index for memory (--memory). Lightweight, no torch.
|
2026-01-07 11:36:44 -08:00
|
|
|
|
]
|
2026-06-08 02:49:14 -04:00
|
|
|
|
# Production ASGI/WSGI server — Unix-only (gunicorn does not support Windows).
|
|
|
|
|
|
# Kept separate from [proxy] so that dev, CI, and Windows users are not forced
|
|
|
|
|
|
# to install a non-functional package. Production deployments should use:
|
|
|
|
|
|
# pip install headroom-ai[proxy,proxy-prod]
|
|
|
|
|
|
proxy-prod = [
|
|
|
|
|
|
"headroom-ai[proxy]",
|
|
|
|
|
|
"gunicorn>=21.0.0; sys_platform != 'win32'",
|
|
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# AST-based code compression (tree-sitter)
|
|
|
|
|
|
code = [
|
|
|
|
|
|
"tree-sitter-language-pack>=0.10.0",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
]
|
2026-05-07 16:43:35 -07:00
|
|
|
|
# ML-based compression with Kompress (ModernBERT).
|
|
|
|
|
|
# (The legacy [llmlingua] extra was removed in 0.9.x — no live code path used it.
|
|
|
|
|
|
# Use [ml] for the supported ML compression dependencies.)
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
ml = [
|
|
|
|
|
|
"torch>=2.0.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"transformers>=4.30.0,<6.0",
|
fix(proxy): Strands MCP bundle + backend path fixes + Codex fail-closed protection
Three logically-related sets of proxy changes ship in this branch:
1. Strands integration on the Bedrock path (HeadroomBundle + 4 OpenAI
handler fixes + LiteLLM cache stats + dep pin)
2. /stats MCP aggregation (cross-process events log → proxy summary)
3. Codex compression-failure fail-closed (WS + HTTP /v1/responses)
== 1. Strands integration on the Bedrock path ==
* HeadroomBundle (headroom/integrations/strands/bundle.py): single-helper
MCP wiring for a Strands Agent — Headroom MCP server (headroom_compress
/ headroom_retrieve / headroom_stats) plus optional Serena MCP and
optional in-process compression hook. Constructor builds unstarted
MCPClient instances per server; Strands' Agent owns the subprocess
lifecycle. Default config: MCP enabled, Serena enabled, hook OFF
(proxy is the single source of truth for compression). User-side
integration is two lines in any Strands app.
* headroom/proxy/handlers/openai.py — backend path now:
- calls PrefixCacheTracker.update_from_response (was direct-OpenAI only)
- intercepts CCR headroom_retrieve tool_calls server-side, mirroring
the Anthropic handler pattern; NO silent fallback, re-raises on
CCR errors (per feedback_no_silent_fallbacks)
- works for both non-streaming and streaming paths
* headroom/proxy/handlers/streaming.py: _stream_openai_via_backend now
accepts prefix_tracker + optimized_messages, parses cache stats from
the SSE final-usage frame (cache_creation_input_tokens added to the
state machine), records CCR retrieve feedback via a new
_record_ccr_feedback_from_openai_sse helper. Streaming CCR intercept
is intentionally out of scope (mirrors Anthropic streaming behaviour).
* headroom/backends/litellm.py: send_openai_message response usage block
now carries cache_read_input_tokens / cache_creation_input_tokens
(Anthropic/Bedrock dialect) and prompt_tokens_details.cached_tokens
(OpenAI dialect). Backwards-compatible — cold-start callers see the
same 3-key shape; cache keys appear only when the underlying provider
returns them. Pinned by test_no_cache_fields_means_no_cache_keys.
* headroom/proxy/auth_mode.py: ("strands-agents/", "strands") added to
CLIENT_UA_MAP. Production callers should also set X-Client: strands
since the default openai-python UA carries no Strands signal.
* pyproject.toml: huggingface-hub>=1.5.0,<2.0 pinned in [ml] so a sibling
install (e.g. strands-agents) can't drag the version below the floor
transformers 5.x requires (otherwise Kompress silently goes
"unavailable").
== 2. /stats MCP aggregation ==
* headroom/proxy/cost.py: _aggregate_mcp_events() reads the cross-process
shared events file the Headroom MCP server already writes to and
surfaces summary.mcp with three new keys:
- compressions (count of headroom_compress invocations)
- tokens_removed (sum of input - output across those)
- retrievals (count of headroom_retrieve — the load-bearing
over-compression alarm; if it grows linearly
with turn count, lossy compressors are
dropping info the model actually needs)
Defensive on every axis — missing MCP SDK, missing file, malformed
events, read errors — never blocks /stats.
* examples/strands_bundle_demo.py: stats panel prints the new fields so
the demo shows the full proxy-HTTP + MCP-tool story in one view.
== 3. Codex compression-failure fail-closed protection ==
Reported by Camille (2026-05-21): Codex threads were locking with
"ran out of room in the model's context window" after Headroom's
compression timed out on an oversized response.create frame and
forwarded the original ~1.7 MB frame to the upstream, which then
rejected it. Codex's auto-compact heuristic gates on the upstream-
reported total_usage_tokens (which Headroom had been shrinking on
earlier turns), so its compaction never fired and the thread locked.
Validated against open Codex issues (CLI + Desktop share codex-rs/core):
* #16068 — confirms compaction gates on total_usage_tokens,
estimated_token_count is computed but only logged
* #19806 — confirms image token estimator unbounded, contributes to
the same ContextManager.get_total_token_usage → auto-compaction chain
* headroom/proxy/helpers.py: decide_compression_failure_action() with a
unit-tested decision matrix:
- asyncio.TimeoutError → refuse, always
- non-timeout failure + frame > 256 KiB (configurable) → refuse
- non-timeout failure + small frame → forward (legacy)
Operator escape hatches:
- HEADROOM_WS_FAIL_OPEN_ON_COMPRESSION_FAILURE=1 restores legacy
- HEADROOM_WS_COMPRESSION_FAIL_THRESHOLD_BYTES tunes the threshold
* headroom/proxy/handlers/openai.py (WS /v1/responses): consults the
helper after compression failure. On refuse: close client websocket
code 1009 with "headroom: compression <reason> — please compact
context and retry" reason; set termination_cause for the outer
lifecycle finally; return.
* headroom/proxy/handlers/openai.py (HTTP /v1/responses): same helper.
On refuse: raise HTTPException(413) with a structured error body so
FastAPI's HTTPException handler emits a clean 413. The existing
`except HTTPException: raise` guard in this handler already ensures
the 413 propagates without being swallowed by the 502 catch-all.
Anthropic /v1/messages NOT changed in this branch: no equivalent bug
report on Anthropic-protocol clients, Claude Code (Anthropic-owned)
handles context overflow via its own cache_control/ephemeral
primitives, and Cursor/Aider don't maintain the local-Y estimate the
Codex bug requires. Deferred until a real report lands; the patch is
a one-liner reusing the same helper.
== Tests + verification ==
* tests/test_backends/test_litellm_cache_stats.py — 3 tests pinning
cache-stat surfacing across Anthropic/OpenAI dialects + backwards-
compat for no-cache responses.
* tests/test_proxy/test_openai_backend_path.py — 5 tests (Bedrock cache
fields, OpenAI fallback shape, CCR intercept with provider="openai",
CCR re-raise on exception, streaming signature contract).
* tests/test_proxy/test_mcp_stats_aggregation.py — 5 tests pinning the
aggregator across compress+retrieve mixes, empty events, unknown event
types, missing token fields, and read failures.
* tests/test_proxy/test_compression_failure_action.py — 12 tests pinning
the fail-closed decision matrix (timeout always refuses, small
transient passes through, oversize refuses, env override variants,
custom threshold, invalid threshold falls back, 0/negative ignored).
* examples/strands_bedrock_demo.py — model_id bumped from deprecated
Claude 3 Haiku to Sonnet 4.5 (the deprecated model now errors on
account access).
* examples/strands_via_proxy_demo.py — proxy + Bedrock cache + streaming
smoke test.
* examples/strands_mcp_dispatch_test.py — pure MCP round-trip probe.
* examples/strands_bundle_demo.py — full Strands + HeadroomBundle E2E
demo (this is the shape a real Strands user copies into their app).
Full pytest: 5327 passed, 178 skipped. The previously-failing
test_core_operations.py::TestAddBatch::test_add_batch_basic passes now
that the huggingface-hub pin in pyproject.toml unblocks transformers
imports.
E2E verified live against AWS Bedrock (Sonnet 4.5):
* cache_write=10,438 on turn A → cache_read=10,438 on turn B
* streaming SSE final usage frame carries cache_read_input_tokens
* 78.7% reduction on a 50 KB JSON tool_result via SmartCrusher (
dispatched per-content-type by ContentRouter)
* Strands Agent + HeadroomBundle: model autonomously called
headroom_compress + headroom_retrieve via MCP; CompressionStore
round-trip succeeded; final answer correct.
2026-05-21 11:00:14 -07:00
|
|
|
|
# transformers >= 5.x requires huggingface-hub >= 1.5.0,<2.0; pinning
|
|
|
|
|
|
# the floor here prevents Kompress from silently falling back to
|
|
|
|
|
|
# "unavailable" when a sibling install (e.g. `pip install
|
|
|
|
|
|
# strands-agents`) drags huggingface-hub backwards.
|
|
|
|
|
|
"huggingface-hub>=1.5.0,<2.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
]
|
|
|
|
|
|
# Memory system (hierarchical memory with vector search)
|
|
|
|
|
|
memory = [
|
|
|
|
|
|
"hnswlib>=0.8.0",
|
|
|
|
|
|
"sqlite-vec>=0.1.6",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"sentence-transformers>=2.2.0,<6.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
]
|
2026-04-10 12:58:35 -05:00
|
|
|
|
# Qdrant + Neo4j memory backend helpers
|
|
|
|
|
|
memory-stack = [
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"mem0ai>=1.0.0,<2.0",
|
|
|
|
|
|
"qdrant-client>=1.9.0,<2.0",
|
|
|
|
|
|
"neo4j>=5.20.0,<7.0",
|
2026-04-10 12:58:35 -05:00
|
|
|
|
]
|
2026-04-26 23:36:22 -07:00
|
|
|
|
# Semantic relevance scoring with embeddings.
|
|
|
|
|
|
# Uses `fastembed` (BAAI/bge-small-en-v1.5 by default — 33M params,
|
|
|
|
|
|
# 384 dims, ~30 MB int8-quantized ONNX). Same library + model used by
|
|
|
|
|
|
# the Rust SmartCrusher (`fastembed` crate), giving byte-equal embeddings
|
|
|
|
|
|
# across the language boundary. Replaced sentence-transformers in
|
|
|
|
|
|
# Stage 3c.1 — fastembed is faster (~2-3x), smaller (no torch
|
|
|
|
|
|
# dependency), and outranks all-MiniLM-L6-v2 on MTEB by ~6 points.
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
relevance = [
|
2026-04-26 23:36:22 -07:00
|
|
|
|
"fastembed>=0.4.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"numpy>=1.24.0",
|
|
|
|
|
|
]
|
2026-04-09 16:22:18 -07:00
|
|
|
|
# Image compression (ML-based routing + OCR)
|
fix: PR #372 — restore [image] extra on Python 3.13 via rapidocr 3.x adapter
Root cause: `headroom-ai[all]==0.20.16` fails to install on Python 3.13
because `rapidocr-onnxruntime` 1.4.0–1.4.4 wheels declare
`requires-python: <3.13,>=3.6`. After 1.4.x the rapidocr ecosystem
split: `rapidocr-onnxruntime` (bundled-ORT, capped at <3.13) vs
`rapidocr` 3.x (engine-agnostic core, supports 3.13+, returns
RapidOCROutput dataclass instead of v1's tuple).
Fix:
1. pyproject.toml — environment-marker hybrid in [image]:
- rapidocr-onnxruntime>=1.4.0,<2; python_version<'3.13'
- rapidocr>=3.0,<4; python_version>='3.13'
- onnxruntime>=1.7,<2; python_version>='3.13'
ORT remains the engine on every Python version; bundle and speed
unchanged, just split into two packages on 3.13+.
2. headroom/image/compressor.py — runtime adapter:
_resolve_rapidocr() tries v1 first, falls back to v3 when v1 is
missing, returns (None, None) when neither installed. Cached at
module scope. Detection at runtime (not Python-version-based) so
users can install either package on any Python version.
_ocr_extract branches on resolved api_version:
- v1: (list[(box, text, score)], elapsed) tuple — unchanged
- v3: RapidOCROutput dataclass with .txts / .scores / .boxes
attrs (each may be None when nothing detected)
Defensive None-handling, length-mismatch detection, structured
log events for both branches.
Smoke test (real install verified before commit):
pip install rapidocr onnxruntime pillow
→ result type: RapidOCROutput
→ fields: txts (None when empty), scores (None when empty), boxes
Confirms the v3 None-coercion is necessary.
Tests: 11 new unit tests in tests/test_image_ocr_api_compat.py covering:
- Resolver: v1 preferred, v3 fallback, both missing
- v1 path: tuple parses, low-confidence None, empty result None
- v3 path: dataclass parses, low-confidence None, None attrs handled,
mismatched lengths logged + None
- Backend missing: returns None gracefully
All 11 pass; `make ci-precheck` PASSED.
Closes #372.
2026-05-04 08:20:01 -07:00
|
|
|
|
#
|
|
|
|
|
|
# OCR backend uses ONNX Runtime regardless of Python version. The
|
|
|
|
|
|
# rapidocr ecosystem split into two flavors after 1.4.x:
|
|
|
|
|
|
# * rapidocr-onnxruntime 1.4.x — bundled-ORT package, capped at
|
|
|
|
|
|
# Python <3.13 by its requires-python metadata. Drop-in for our
|
|
|
|
|
|
# existing v1 tuple-shaped API call.
|
|
|
|
|
|
# * rapidocr 3.x — engine-agnostic core, supports Python 3.13+.
|
|
|
|
|
|
# Returns a RapidOCROutput dataclass (txts, scores, boxes, ...).
|
|
|
|
|
|
# Needs `onnxruntime` installed separately to use the ORT backend.
|
|
|
|
|
|
#
|
|
|
|
|
|
# `headroom/image/compressor.py` adapts both API shapes at runtime via
|
|
|
|
|
|
# a try/except cascade. See issue #372 for context.
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
image = [
|
|
|
|
|
|
"pillow>=10.0.0",
|
2026-04-08 11:33:28 -07:00
|
|
|
|
"sentencepiece>=0.1.99", # Required by SigLIP tokenizer (SiglipTokenizer)
|
fix: PR #372 — restore [image] extra on Python 3.13 via rapidocr 3.x adapter
Root cause: `headroom-ai[all]==0.20.16` fails to install on Python 3.13
because `rapidocr-onnxruntime` 1.4.0–1.4.4 wheels declare
`requires-python: <3.13,>=3.6`. After 1.4.x the rapidocr ecosystem
split: `rapidocr-onnxruntime` (bundled-ORT, capped at <3.13) vs
`rapidocr` 3.x (engine-agnostic core, supports 3.13+, returns
RapidOCROutput dataclass instead of v1's tuple).
Fix:
1. pyproject.toml — environment-marker hybrid in [image]:
- rapidocr-onnxruntime>=1.4.0,<2; python_version<'3.13'
- rapidocr>=3.0,<4; python_version>='3.13'
- onnxruntime>=1.7,<2; python_version>='3.13'
ORT remains the engine on every Python version; bundle and speed
unchanged, just split into two packages on 3.13+.
2. headroom/image/compressor.py — runtime adapter:
_resolve_rapidocr() tries v1 first, falls back to v3 when v1 is
missing, returns (None, None) when neither installed. Cached at
module scope. Detection at runtime (not Python-version-based) so
users can install either package on any Python version.
_ocr_extract branches on resolved api_version:
- v1: (list[(box, text, score)], elapsed) tuple — unchanged
- v3: RapidOCROutput dataclass with .txts / .scores / .boxes
attrs (each may be None when nothing detected)
Defensive None-handling, length-mismatch detection, structured
log events for both branches.
Smoke test (real install verified before commit):
pip install rapidocr onnxruntime pillow
→ result type: RapidOCROutput
→ fields: txts (None when empty), scores (None when empty), boxes
Confirms the v3 None-coercion is necessary.
Tests: 11 new unit tests in tests/test_image_ocr_api_compat.py covering:
- Resolver: v1 preferred, v3 fallback, both missing
- v1 path: tuple parses, low-confidence None, empty result None
- v3 path: dataclass parses, low-confidence None, None attrs handled,
mismatched lengths logged + None
- Backend missing: returns None gracefully
All 11 pass; `make ci-precheck` PASSED.
Closes #372.
2026-05-04 08:20:01 -07:00
|
|
|
|
# Python 3.6–3.12: keep the proven ORT-bundled package directly.
|
|
|
|
|
|
# ~15 MB ONNX models auto-downloaded on first use.
|
|
|
|
|
|
"rapidocr-onnxruntime>=1.4.0,<2; python_version<'3.13'",
|
|
|
|
|
|
# Python 3.13+: rapidocr-onnxruntime is unavailable (its wheels
|
|
|
|
|
|
# declare requires-python<3.13). Use the successor `rapidocr` 3.x
|
|
|
|
|
|
# core + `onnxruntime` engine; same ORT backend, just split into
|
|
|
|
|
|
# two packages. Total install size and inference speed unchanged.
|
|
|
|
|
|
"rapidocr>=3.0,<4; python_version>='3.13'",
|
|
|
|
|
|
"onnxruntime>=1.7,<2; python_version>='3.13'",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
]
|
|
|
|
|
|
# Report generation
|
|
|
|
|
|
reports = [
|
|
|
|
|
|
"jinja2>=3.0.0",
|
Add AST-based code compression and custom model configuration
CodeAwareCompressor:
- Tree-sitter based AST parsing for Python, JS, TS, Go, Rust, Java, C, C++
- Preserves imports, signatures, type annotations, error handlers
- Guarantees syntactically valid output
- Uses tree-sitter-language-pack for broad language support
ContentRouter:
- Intelligent compression orchestrator
- Auto-routes content to optimal compressor based on type detection
- Source hint support for high-confidence routing
Custom Model Configuration:
- HEADROOM_MODEL_LIMITS env var and ~/.headroom/models.json support
- Pattern-based inference for unknown models (opus/sonnet/haiku tiers)
- Support for Claude 4.5, Claude 4, o3, o3-mini
- Graceful fallback - never crashes on unknown models
2026-01-14 13:46:55 -08:00
|
|
|
|
]
|
2026-04-09 21:20:34 -05:00
|
|
|
|
# OpenTelemetry metrics export
|
|
|
|
|
|
otel = [
|
|
|
|
|
|
"opentelemetry-sdk>=1.24.0",
|
|
|
|
|
|
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
|
|
|
|
|
|
]
|
2026-02-06 11:40:23 -06:00
|
|
|
|
# any-llm multi-provider backend (requires Python 3.11+)
|
|
|
|
|
|
anyllm = [
|
2026-04-04 13:45:37 -05:00
|
|
|
|
"any-llm-sdk>=1.0.0; python_version >= '3.11'",
|
2026-02-06 11:40:23 -06:00
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# LangChain integration
|
|
|
|
|
|
langchain = [
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"langchain-core>=1.3.3,<4.0",
|
|
|
|
|
|
"langchain-openai>=1.1.14,<2.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
]
|
2026-01-16 16:00:13 -08:00
|
|
|
|
# Agno agent framework integration
|
|
|
|
|
|
agno = [
|
|
|
|
|
|
"agno>=1.0.0",
|
|
|
|
|
|
]
|
feat: Add AWS Strands Agents SDK integration
## Description
Add Headroom integration with AWS Strands Agents SDK, enabling automatic
context optimization and tool output compression for Strands-based agents.
Fixes #14
## Type of Change
- [x] New feature (non-breaking change that adds functionality)
- [x] Documentation update
## Changes Made
### Core Integration (`headroom/integrations/strands/`)
- **HeadroomHookProvider** - Implements Strands `HookProvider` interface for
automatic tool output compression via `AfterToolCallEvent`. Compresses
verbose tool outputs before they enter conversation context.
- **HeadroomStrandsModel** - Model wrapper that extends Strands `Model` base
class for message-level optimization. Implements all required abstract
methods: `stream()`, `get_config()`, `update_config()`, `structured_output()`.
- **Provider auto-detection** - Automatically detects appropriate Headroom
provider (Anthropic, OpenAI, Google) based on wrapped Strands model type.
- **`strands-agents` as optional dependency** - Install with
`pip install headroom-ai[strands]`
### Testing (`tests/integrations/test_strands/`)
- **Real integration tests (25 tests)** - Use actual AWS Bedrock API calls
with Claude 3 Haiku. Skip automatically when credentials unavailable.
- **Unit tests (57 tests)** - Mock-based tests for internal logic, edge cases,
and error handling. No credentials required.
### Demo (`examples/strands_bedrock_demo.py`)
- Interactive demo showcasing both integration patterns
- Visual before/after compression comparison with token savings
- 4 verbose tools (search, logs, database, metrics) demonstrating real savings
- Supports `--hook` and `--model` flags for individual demos
## Testing
All tests verified:
- [x] Unit tests pass (57 tests)
- [x] Integration tests pass (25 tests with real Bedrock API)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom/integrations/strands/`)
- [x] Formatting passes (`ruff format --check`)
- [x] Demo runs successfully with ~50% token savings
## Test Output
```
$ pytest tests/integrations/test_strands/ -v
=================== 82 passed in 90.09s ===================
$ ruff check headroom/integrations/strands/ --ignore E402
All checks passed!
$ mypy headroom/integrations/strands/ --ignore-missing-imports
Success: no issues found
```
## Demo Results
```
╭────────────────────────────────────────────────────────────╮
│ HeadroomHookProvider Results │
│────────────────────────────────────────────────────────────│
│ Tokens BEFORE compression: 51,961 │
│ Tokens AFTER compression: 25,658 │
│ Tokens SAVED: 26,303 (50.6%) │
╰────────────────────────────────────────────────────────────╯
```
2026-01-31 00:31:37 -08:00
|
|
|
|
# AWS Strands Agents SDK integration
|
|
|
|
|
|
strands = [
|
|
|
|
|
|
"strands-agents>=0.1.0",
|
|
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# MCP server for Claude Code integration
|
2026-02-02 11:05:24 -08:00
|
|
|
|
mcp = [
|
|
|
|
|
|
"mcp>=1.0.0",
|
|
|
|
|
|
"httpx>=0.24.0",
|
|
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Voice filler detection
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
voice = [
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"onnxruntime>=1.16.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"transformers>=4.30.0,<6.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"torch>=2.0.0",
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Voice training (includes voice deps + training extras)
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
voice-train = [
|
|
|
|
|
|
"headroom-ai[voice]",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"datasets>=2.14.0",
|
|
|
|
|
|
"accelerate>=0.20.0",
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Evaluation framework
|
Add comprehensive evaluation framework for compression accuracy
- Add headroom.evals module with 12+ dataset loaders (HotpotQA, SQuAD,
Natural Questions, TriviaQA, MS MARCO, LongBench, NarrativeQA, BFCL,
ToolBench, CodeSearchNet, HumanEval, built-in tool outputs)
- Add before/after evaluation runner that compares LLM responses with
original vs compressed context
- Add metrics: F1 score, semantic similarity, exact match, ground truth
- Add CLI: python -m headroom.evals quick|benchmark|list|report
- Add [evals] extra to pyproject.toml for pip install headroom-ai[evals]
Fix ContentRouter to use LLMLingua for plain text compression:
- Route TEXT strategy through LLMLingua instead of heuristic TextCompressor
- Adjust LLMLingua compression rates for better accuracy (0.5 vs 0.25)
- HotpotQA now achieves 95% accuracy with 44% compression
Update documentation with evaluation framework section
Fix test isolation in test_toin.py (TOIN singleton persistence)
2026-01-22 09:17:28 -08:00
|
|
|
|
evals = [
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"datasets>=2.14.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"sentence-transformers>=2.2.0,<6.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"numpy>=1.24.0",
|
|
|
|
|
|
"scikit-learn>=1.3.0",
|
|
|
|
|
|
"anthropic>=0.18.0",
|
|
|
|
|
|
"openai>=1.0.0",
|
2026-01-22 23:58:54 -08:00
|
|
|
|
]
|
Add Click-based CLI with memory management commands
Refactor CLI from argparse to Click for better extensibility:
- New headroom/cli/ package with modular command structure
- Memory commands: list, show, stats, edit, delete, prune, purge, export, import
- Rich terminal output with tables, colors, and formatted stats
- Duration parsing for --older-than and --since flags (7d, 2w, 1m)
- Comprehensive tests using Click's CliRunner (55 tests, no mocks)
CLI structure:
headroom proxy - Start optimization proxy (migrated from argparse)
headroom memory ... - Memory management (new)
headroom evals ... - Evaluation commands (migrated, now under evals group)
Backwards compatibility maintained via headroom/cli.py shim.
2026-01-29 21:19:27 -08:00
|
|
|
|
# AWS Bedrock backend
|
|
|
|
|
|
bedrock = [
|
|
|
|
|
|
"boto3>=1.28.0",
|
|
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# HTML content extraction
|
2026-01-31 10:08:29 -08:00
|
|
|
|
html = [
|
|
|
|
|
|
"trafilatura>=1.6.0",
|
|
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# Comprehensive LLM benchmarks
|
2026-02-01 21:23:09 -08:00
|
|
|
|
benchmark = [
|
2026-05-11 13:35:24 -05:00
|
|
|
|
"lm-eval[api]>=0.4.0",
|
2026-02-01 21:23:09 -08:00
|
|
|
|
"openai>=1.0.0",
|
|
|
|
|
|
"anthropic>=0.18.0",
|
|
|
|
|
|
]
|
2026-01-07 11:36:44 -08:00
|
|
|
|
# Development dependencies
|
2026-01-06 23:16:58 -08:00
|
|
|
|
dev = [
|
|
|
|
|
|
"pytest>=7.0.0",
|
|
|
|
|
|
"pytest-cov>=4.0.0",
|
|
|
|
|
|
"pytest-asyncio>=0.21.0",
|
|
|
|
|
|
"ruff>=0.1.0",
|
|
|
|
|
|
"mypy>=1.0.0",
|
2026-01-21 21:49:29 -08:00
|
|
|
|
"pre-commit>=3.0.0",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
"openai>=1.0.0",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"anthropic>=0.18.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"litellm>=1.86.2,<2.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"fastapi>=0.100.0",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"uvicorn>=0.23.0,<1.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"httpx[http2]>=0.24.0",
|
2026-04-18 08:22:04 +07:00
|
|
|
|
"websockets>=13.0",
|
2026-04-09 21:20:34 -05:00
|
|
|
|
"opentelemetry-sdk>=1.24.0",
|
|
|
|
|
|
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"ollama>=0.4.0",
|
|
|
|
|
|
"langchain-ollama>=0.2.0",
|
|
|
|
|
|
"hnswlib>=0.8.0",
|
|
|
|
|
|
"sqlite-vec>=0.1.6",
|
chore(deps): loosen over-pinned constraints and add upper bounds (#538)
## What
Loosen over-pinned Python dependency constraints and add missing upper
bounds in `pyproject.toml`. Also bump the neo4j Docker image and uv
builder version.
## Why
Several dependencies had constraints that either blocked security
patches or allowed silent major-version jumps:
- `litellm==1.82.3` was an exact pin — every security patch release
requires a manual lockfile bump
- `transformers`, `sentence-transformers` had no upper bound and have
already crossed major version boundaries without a constraint gate
- `neo4j>=5.20.0` had no upper cap; the driver has already reached 6.x
in the wild
- `mem0ai>=0.1.100` had a pre-1.0 floor while the locked version is
already 1.0.11
- `langchain-core`, `langchain-openai`, `qdrant-client`, `uvicorn` had
no upper bound on a range with active major-version churn
- `docker-compose.yml` pinned neo4j at `5.15.0`, which is 11 patch
releases behind the current 5.x LTS
- `Dockerfile` pinned uv at `0.11.16`; latest stable is `0.11.18`
## How
Constraint changes only — no code changes, no `uv lock --upgrade`. The
existing locked versions all satisfy the new bounds (we added caps, not
floors). `uv` re-resolved the lockfile to format revision 3 (adds
`upload-time` metadata fields) and cleaned up the defunct `llmlingua`
extra entries.
| Dependency | Before | After |
|---|---|---|
| `litellm` | `==1.82.3` | `>=1.82.3,<2.0` |
| `transformers` | `>=4.30.0` | `>=4.30.0,<6.0` |
| `sentence-transformers` | `>=2.2.0` | `>=2.2.0,<6.0` |
| `neo4j` | `>=5.20.0` | `>=5.20.0,<7.0` |
| `mem0ai` | `>=0.1.100` | `>=1.0.0,<2.0` |
| `langchain-core` | `>=0.2.0` | `>=0.2.0,<4.0` |
| `langchain-openai` | `>=0.1.0` | `>=0.1.0,<2.0` |
| `qdrant-client` | `>=1.9.0` | `>=1.9.0,<2.0` |
| `uvicorn` | `>=0.23.0` | `>=0.23.0,<1.0` |
| neo4j Docker image | `5.15.0` | `5.26` |
| uv (Dockerfile ARG) | `0.11.16` | `0.11.18` |
## Breaking changes
None. All currently installed versions fall within the new ranges.
Installers that previously resolved `litellm` to an older exact pin may
now resolve newer patch releases — which is the desired behavior.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-09 02:06:24 -04:00
|
|
|
|
"sentence-transformers>=2.2.0,<6.0",
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
"numpy>=1.24.0",
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).
New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.
Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
|
|
|
|
# All optional dependencies (everything you need)
|
2026-01-07 11:36:44 -08:00
|
|
|
|
all = [
|
2026-04-09 21:20:34 -05:00
|
|
|
|
"headroom-ai[proxy,code,ml,memory,relevance,image,reports,otel,evals,voice,html,benchmark,mcp]",
|
2026-01-07 11:36:44 -08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
[project.scripts]
|
|
|
|
|
|
headroom = "headroom.cli:main"
|
2026-01-06 23:16:58 -08:00
|
|
|
|
|
|
|
|
|
|
[project.urls]
|
docs: improve discoverability for AI agents and search crawlers
Several signals AI agents and search engines use to discover and
install a project were misaligned or missing:
* ``docs/app/layout.tsx`` set ``metadataBase`` to
``https://chopratejas.github.io/headroom/`` while the live docs run
on Vercel — every page's ``og:url`` and ``twitter:url`` resolved to
a URL that returns 404 for ``/llms.txt``. Now points at the live
Vercel host (overridable via ``NEXT_PUBLIC_SITE_URL`` for a future
custom domain). Adds explicit ``openGraph`` and ``twitter`` metadata
so social shares render a card with the project's pitch.
* No ``llms.txt`` at the GitHub repo root. AI agents crawling
``github.com/chopratejas/headroom/`` saw only the README. The new
``llms.txt`` follows the llmstxt.org convention: 1-line pitch,
canonical docs links, copy-paste install commands (pip / npm /
Docker / proxy / ``headroom wrap``), and entry points for the
library, proxy, MCP server, and SDK integrations. Points at the
Fumadocs-generated ``/llms.txt`` and ``/llms-full.txt`` for the
full picture.
* ``pyproject.toml`` ``Documentation`` URL pointed at the GitHub
README anchor. Updated to point at the docs site so PyPI visitors
land on searchable docs, and adds an ``AI / LLM Index`` URL
pointing at the Fumadocs ``/llms.txt``.
* No explicit AI-bot allow list. Added ``docs/app/robots.ts`` (Next
13+ App Router convention) with explicit allows for GPTBot,
ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot,
ChatGPT-User, Cohere-AI, CCBot, and Applebot-Extended. Wildcard
allow as the catch-all. Advertises the sitemap.
* No ``sitemap.xml`` route. Added ``docs/app/sitemap.ts`` that pulls
every Fumadocs page out of ``source`` (same source backing
``/llms.txt``, search, and OG images) so search and AI crawlers
can enumerate doc pages without scraping HTML.
* README didn't tell AI agents where to look. Added a 2-line
pointer near the top nav row: read ``/llms.txt`` here, or fetch
the live index / full docs blob.
Also tightened the GitHub repo description and added five topics
(``claude-code``, ``cursor``, ``tokens``, ``prompt-engineering``,
``typescript``) via ``gh repo edit`` — that's already live on the
repo, not part of this commit.
No Python or Rust code changes; ``make ci-precheck`` was run to
confirm the test slice still passes.
2026-05-13 17:36:06 -07:00
|
|
|
|
Homepage = "https://headroom-docs.vercel.app"
|
|
|
|
|
|
Documentation = "https://headroom-docs.vercel.app/docs"
|
2026-01-10 15:11:08 -08:00
|
|
|
|
Repository = "https://github.com/chopratejas/headroom"
|
|
|
|
|
|
Issues = "https://github.com/chopratejas/headroom/issues"
|
|
|
|
|
|
Changelog = "https://github.com/chopratejas/headroom/blob/main/CHANGELOG.md"
|
docs: improve discoverability for AI agents and search crawlers
Several signals AI agents and search engines use to discover and
install a project were misaligned or missing:
* ``docs/app/layout.tsx`` set ``metadataBase`` to
``https://chopratejas.github.io/headroom/`` while the live docs run
on Vercel — every page's ``og:url`` and ``twitter:url`` resolved to
a URL that returns 404 for ``/llms.txt``. Now points at the live
Vercel host (overridable via ``NEXT_PUBLIC_SITE_URL`` for a future
custom domain). Adds explicit ``openGraph`` and ``twitter`` metadata
so social shares render a card with the project's pitch.
* No ``llms.txt`` at the GitHub repo root. AI agents crawling
``github.com/chopratejas/headroom/`` saw only the README. The new
``llms.txt`` follows the llmstxt.org convention: 1-line pitch,
canonical docs links, copy-paste install commands (pip / npm /
Docker / proxy / ``headroom wrap``), and entry points for the
library, proxy, MCP server, and SDK integrations. Points at the
Fumadocs-generated ``/llms.txt`` and ``/llms-full.txt`` for the
full picture.
* ``pyproject.toml`` ``Documentation`` URL pointed at the GitHub
README anchor. Updated to point at the docs site so PyPI visitors
land on searchable docs, and adds an ``AI / LLM Index`` URL
pointing at the Fumadocs ``/llms.txt``.
* No explicit AI-bot allow list. Added ``docs/app/robots.ts`` (Next
13+ App Router convention) with explicit allows for GPTBot,
ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot,
ChatGPT-User, Cohere-AI, CCBot, and Applebot-Extended. Wildcard
allow as the catch-all. Advertises the sitemap.
* No ``sitemap.xml`` route. Added ``docs/app/sitemap.ts`` that pulls
every Fumadocs page out of ``source`` (same source backing
``/llms.txt``, search, and OG images) so search and AI crawlers
can enumerate doc pages without scraping HTML.
* README didn't tell AI agents where to look. Added a 2-line
pointer near the top nav row: read ``/llms.txt`` here, or fetch
the live index / full docs blob.
Also tightened the GitHub repo description and added five topics
(``claude-code``, ``cursor``, ``tokens``, ``prompt-engineering``,
``typescript``) via ``gh repo edit`` — that's already live on the
repo, not part of this commit.
No Python or Rust code changes; ``make ci-precheck`` was run to
confirm the test slice still passes.
2026-05-13 17:36:06 -07:00
|
|
|
|
# llms.txt convention (llmstxt.org) — point AI agents / LLM crawlers
|
|
|
|
|
|
# at the auto-generated docs index so they can resolve install paths
|
|
|
|
|
|
# and entry points without a follow-up fetch.
|
|
|
|
|
|
"AI / LLM Index" = "https://headroom-docs.vercel.app/llms.txt"
|
2026-01-06 23:16:58 -08:00
|
|
|
|
|
refactor: single-wheel maturin build backend (fixes #355)
Eliminates the dual-package architecture that was the root cause of #355.
`pip install headroom-ai` now produces ONE wheel containing both the Python
source (headroom/*.py) and the compiled Rust extension (headroom/_core.so).
No more separate `headroom-core-py` package, no more chicken-and-egg with
PyPI publication, no more wheelhouse / PIP_FIND_LINKS / composite-action
plumbing in CI.
This is the canonical pattern used by cryptography, polars, ruff,
pydantic-core, and other Rust-as-core Python packages. Honors the
"Rust as core engine" direction.
## What changed
- pyproject.toml: `[build-system]` swapped from hatchling to maturin.
`[tool.hatch.*]` deleted; `[tool.maturin]` added pointing at
`crates/headroom-py/Cargo.toml` for the cdylib. `python-source = "."`
picks up the root `headroom/` package directly (dashboard HTML
templates and other non-Python files included automatically).
- crates/headroom-py/pyproject.toml: deleted. The crate is no longer a
separate published package; its Cargo.toml stays as the cdylib build
target invoked via `[tool.maturin] manifest-path`.
- crates/headroom-py/python/: deleted (placeholder layout for the old
separate package).
## CI updates
- ci.yml: `test` / `test-extras` / `test-agno` jobs simplified — Rust
toolchain set up before `pip install -e .` (which now invokes maturin
via build-system). Removed the "build wheel + symlink .so" dance.
`build` job swapped from `python -m build` (hatch) to
`maturin build` + `maturin sdist`.
- release.yml: collapsed dual-package matrix into one. New `build-wheels`
matrix produces cross-platform wheels for cp310/11/12/13 ×
{linux x86_64, linux aarch64, macos x86_64, macos aarch64}. New
`collect-dist` aggregator merges artifacts. publish-pypi consumes the
merged dist.
- init-native-e2e.yml: dropped windows-latest from the matrix —
upstream `esaxx-rs` (/MT) and `ort-sys` (/MD) link with conflicting
MSVC C runtime libraries, so the Rust extension cannot build for
win_amd64 today. Tracked as a follow-up; not a blocker for Linux+macOS.
- headroom-e2e-setup: composite action now sets up Rust toolchain +
Swatinem/rust-cache before `pip install -e .[proxy]`.
- eval.yml, publish.yml, rust.yml: same pattern — rust toolchain before
install. rust.yml's wheels job builds from root pyproject.toml (no
more `-m crates/headroom-py/Cargo.toml`).
- e2e/init/Dockerfile, e2e/wrap/Dockerfile: install rust + maturin in
the build stage; copy `crates/` + workspace `Cargo.toml/lock` so the
install can build the extension. Dropped `HEADROOM_REQUIRE_RUST_CORE=false`
from wrap-e2e — the image now ships the full Rust core.
- Dockerfile (main): simplified — no more Layer 2/3 dance with
`headroom-core-py` install + symlink. Single `uv pip install` builds
+ installs everything.
- .devcontainer/Dockerfile: rust toolchain + libssl-dev + maturin
added so `uv sync` builds the extension inside the devcontainer.
## Lockfile + script
- uv.lock: regenerated. No `headroom-core-py` entries remain.
- scripts/build_rust_extension.sh: simplified from a symlink-into-tree
workaround to a thin wrapper around `pip install -e .`. The maturin
build-backend handles placement automatically.
## Local validation (all green on macOS aarch64)
1. Clean venv `pip install -e .` → `from headroom._core import …` works.
2. `maturin build --release` → 13.8 MB wheel, 336 files including
`headroom/_core.cpython-311-darwin.so` (32 MB cdylib) and
`headroom/dashboard/templates/dashboard.html`.
3. `pip install <wheel>` in fresh venv → import works.
4. Wheel contents verified via `unzip -l`.
5. `pytest tests/test_transforms/test_diff_compressor.py` — 29 passed.
6. `pytest tests/test_relevance.py` — 30 passed.
7. `cargo build --workspace` + `cargo test --workspace` — all green.
8. `make ci-precheck` — 176 Python tests + Rust + commitlint green.
## Migration notes
Users on `pip install headroom-ai` get the Rust core automatically
(linux + macos wheels). sdist installs require rust toolchain available
locally — pip will build via maturin.
Closes #355
Supersedes #357 (workarounds-based fix abandoned in favor of
architectural fix)
2026-05-03 13:16:41 -07:00
|
|
|
|
# Maturin builds a single wheel containing both the Python source under
|
|
|
|
|
|
# `headroom/` AND the compiled Rust extension `headroom/_core.so` (cdylib
|
|
|
|
|
|
# from `crates/headroom-py`). One `pip install headroom-ai` ships everything
|
|
|
|
|
|
# atomically — no separate `headroom-core-py` package, no chicken-and-egg,
|
|
|
|
|
|
# no PIP_FIND_LINKS plumbing. Phase A0's runtime fail-loud check still
|
|
|
|
|
|
# exists but only fires if someone forces an sdist install on a platform
|
|
|
|
|
|
# without a wheel and the rust toolchain isn't available to compile it.
|
2026-05-03 13:52:16 -07:00
|
|
|
|
# Pin the project's package index to public PyPI. Without this, `uv lock`
|
|
|
|
|
|
# inherits the developer's user-level `~/.config/uv/uv.toml` index
|
|
|
|
|
|
# setting — including private/internal mirrors like
|
|
|
|
|
|
# `pypi.netflix.net/simple` — and bakes those URLs into uv.lock, which
|
|
|
|
|
|
# then breaks CI on every public runner that can't reach the mirror.
|
|
|
|
|
|
# Declaring the index in pyproject.toml makes the project authoritative
|
|
|
|
|
|
# regardless of who runs `uv lock`.
|
|
|
|
|
|
[[tool.uv.index]]
|
|
|
|
|
|
name = "pypi"
|
|
|
|
|
|
url = "https://pypi.org/simple/"
|
|
|
|
|
|
default = true
|
|
|
|
|
|
|
refactor: single-wheel maturin build backend (fixes #355)
Eliminates the dual-package architecture that was the root cause of #355.
`pip install headroom-ai` now produces ONE wheel containing both the Python
source (headroom/*.py) and the compiled Rust extension (headroom/_core.so).
No more separate `headroom-core-py` package, no more chicken-and-egg with
PyPI publication, no more wheelhouse / PIP_FIND_LINKS / composite-action
plumbing in CI.
This is the canonical pattern used by cryptography, polars, ruff,
pydantic-core, and other Rust-as-core Python packages. Honors the
"Rust as core engine" direction.
## What changed
- pyproject.toml: `[build-system]` swapped from hatchling to maturin.
`[tool.hatch.*]` deleted; `[tool.maturin]` added pointing at
`crates/headroom-py/Cargo.toml` for the cdylib. `python-source = "."`
picks up the root `headroom/` package directly (dashboard HTML
templates and other non-Python files included automatically).
- crates/headroom-py/pyproject.toml: deleted. The crate is no longer a
separate published package; its Cargo.toml stays as the cdylib build
target invoked via `[tool.maturin] manifest-path`.
- crates/headroom-py/python/: deleted (placeholder layout for the old
separate package).
## CI updates
- ci.yml: `test` / `test-extras` / `test-agno` jobs simplified — Rust
toolchain set up before `pip install -e .` (which now invokes maturin
via build-system). Removed the "build wheel + symlink .so" dance.
`build` job swapped from `python -m build` (hatch) to
`maturin build` + `maturin sdist`.
- release.yml: collapsed dual-package matrix into one. New `build-wheels`
matrix produces cross-platform wheels for cp310/11/12/13 ×
{linux x86_64, linux aarch64, macos x86_64, macos aarch64}. New
`collect-dist` aggregator merges artifacts. publish-pypi consumes the
merged dist.
- init-native-e2e.yml: dropped windows-latest from the matrix —
upstream `esaxx-rs` (/MT) and `ort-sys` (/MD) link with conflicting
MSVC C runtime libraries, so the Rust extension cannot build for
win_amd64 today. Tracked as a follow-up; not a blocker for Linux+macOS.
- headroom-e2e-setup: composite action now sets up Rust toolchain +
Swatinem/rust-cache before `pip install -e .[proxy]`.
- eval.yml, publish.yml, rust.yml: same pattern — rust toolchain before
install. rust.yml's wheels job builds from root pyproject.toml (no
more `-m crates/headroom-py/Cargo.toml`).
- e2e/init/Dockerfile, e2e/wrap/Dockerfile: install rust + maturin in
the build stage; copy `crates/` + workspace `Cargo.toml/lock` so the
install can build the extension. Dropped `HEADROOM_REQUIRE_RUST_CORE=false`
from wrap-e2e — the image now ships the full Rust core.
- Dockerfile (main): simplified — no more Layer 2/3 dance with
`headroom-core-py` install + symlink. Single `uv pip install` builds
+ installs everything.
- .devcontainer/Dockerfile: rust toolchain + libssl-dev + maturin
added so `uv sync` builds the extension inside the devcontainer.
## Lockfile + script
- uv.lock: regenerated. No `headroom-core-py` entries remain.
- scripts/build_rust_extension.sh: simplified from a symlink-into-tree
workaround to a thin wrapper around `pip install -e .`. The maturin
build-backend handles placement automatically.
## Local validation (all green on macOS aarch64)
1. Clean venv `pip install -e .` → `from headroom._core import …` works.
2. `maturin build --release` → 13.8 MB wheel, 336 files including
`headroom/_core.cpython-311-darwin.so` (32 MB cdylib) and
`headroom/dashboard/templates/dashboard.html`.
3. `pip install <wheel>` in fresh venv → import works.
4. Wheel contents verified via `unzip -l`.
5. `pytest tests/test_transforms/test_diff_compressor.py` — 29 passed.
6. `pytest tests/test_relevance.py` — 30 passed.
7. `cargo build --workspace` + `cargo test --workspace` — all green.
8. `make ci-precheck` — 176 Python tests + Rust + commitlint green.
## Migration notes
Users on `pip install headroom-ai` get the Rust core automatically
(linux + macos wheels). sdist installs require rust toolchain available
locally — pip will build via maturin.
Closes #355
Supersedes #357 (workarounds-based fix abandoned in favor of
architectural fix)
2026-05-03 13:16:41 -07:00
|
|
|
|
[tool.maturin]
|
|
|
|
|
|
# Where the Python package lives. With `python-source = "."` and the
|
|
|
|
|
|
# package directory `headroom/` at repo root, maturin includes every file
|
|
|
|
|
|
# under `headroom/` in the wheel — that picks up the dashboard HTML
|
2026-05-07 16:29:05 -07:00
|
|
|
|
# templates and bundled YAML configs. `LICENSE` and `NOTICE` are listed
|
|
|
|
|
|
# explicitly because maturin sdists do not get the package-directory
|
|
|
|
|
|
# treatment wheels do, and PEP 639 auto-discovery emits both files into
|
|
|
|
|
|
# `License-File:` metadata — PyPI rejects sdists whose declared license
|
|
|
|
|
|
# files are missing from the tarball with `400 License-File X does not
|
|
|
|
|
|
# exist in distribution file`.
|
|
|
|
|
|
include = [
|
|
|
|
|
|
{ path = "LICENSE", format = "sdist" },
|
|
|
|
|
|
{ path = "NOTICE", format = "sdist" },
|
|
|
|
|
|
]
|
refactor: single-wheel maturin build backend (fixes #355)
Eliminates the dual-package architecture that was the root cause of #355.
`pip install headroom-ai` now produces ONE wheel containing both the Python
source (headroom/*.py) and the compiled Rust extension (headroom/_core.so).
No more separate `headroom-core-py` package, no more chicken-and-egg with
PyPI publication, no more wheelhouse / PIP_FIND_LINKS / composite-action
plumbing in CI.
This is the canonical pattern used by cryptography, polars, ruff,
pydantic-core, and other Rust-as-core Python packages. Honors the
"Rust as core engine" direction.
## What changed
- pyproject.toml: `[build-system]` swapped from hatchling to maturin.
`[tool.hatch.*]` deleted; `[tool.maturin]` added pointing at
`crates/headroom-py/Cargo.toml` for the cdylib. `python-source = "."`
picks up the root `headroom/` package directly (dashboard HTML
templates and other non-Python files included automatically).
- crates/headroom-py/pyproject.toml: deleted. The crate is no longer a
separate published package; its Cargo.toml stays as the cdylib build
target invoked via `[tool.maturin] manifest-path`.
- crates/headroom-py/python/: deleted (placeholder layout for the old
separate package).
## CI updates
- ci.yml: `test` / `test-extras` / `test-agno` jobs simplified — Rust
toolchain set up before `pip install -e .` (which now invokes maturin
via build-system). Removed the "build wheel + symlink .so" dance.
`build` job swapped from `python -m build` (hatch) to
`maturin build` + `maturin sdist`.
- release.yml: collapsed dual-package matrix into one. New `build-wheels`
matrix produces cross-platform wheels for cp310/11/12/13 ×
{linux x86_64, linux aarch64, macos x86_64, macos aarch64}. New
`collect-dist` aggregator merges artifacts. publish-pypi consumes the
merged dist.
- init-native-e2e.yml: dropped windows-latest from the matrix —
upstream `esaxx-rs` (/MT) and `ort-sys` (/MD) link with conflicting
MSVC C runtime libraries, so the Rust extension cannot build for
win_amd64 today. Tracked as a follow-up; not a blocker for Linux+macOS.
- headroom-e2e-setup: composite action now sets up Rust toolchain +
Swatinem/rust-cache before `pip install -e .[proxy]`.
- eval.yml, publish.yml, rust.yml: same pattern — rust toolchain before
install. rust.yml's wheels job builds from root pyproject.toml (no
more `-m crates/headroom-py/Cargo.toml`).
- e2e/init/Dockerfile, e2e/wrap/Dockerfile: install rust + maturin in
the build stage; copy `crates/` + workspace `Cargo.toml/lock` so the
install can build the extension. Dropped `HEADROOM_REQUIRE_RUST_CORE=false`
from wrap-e2e — the image now ships the full Rust core.
- Dockerfile (main): simplified — no more Layer 2/3 dance with
`headroom-core-py` install + symlink. Single `uv pip install` builds
+ installs everything.
- .devcontainer/Dockerfile: rust toolchain + libssl-dev + maturin
added so `uv sync` builds the extension inside the devcontainer.
## Lockfile + script
- uv.lock: regenerated. No `headroom-core-py` entries remain.
- scripts/build_rust_extension.sh: simplified from a symlink-into-tree
workaround to a thin wrapper around `pip install -e .`. The maturin
build-backend handles placement automatically.
## Local validation (all green on macOS aarch64)
1. Clean venv `pip install -e .` → `from headroom._core import …` works.
2. `maturin build --release` → 13.8 MB wheel, 336 files including
`headroom/_core.cpython-311-darwin.so` (32 MB cdylib) and
`headroom/dashboard/templates/dashboard.html`.
3. `pip install <wheel>` in fresh venv → import works.
4. Wheel contents verified via `unzip -l`.
5. `pytest tests/test_transforms/test_diff_compressor.py` — 29 passed.
6. `pytest tests/test_relevance.py` — 30 passed.
7. `cargo build --workspace` + `cargo test --workspace` — all green.
8. `make ci-precheck` — 176 Python tests + Rust + commitlint green.
## Migration notes
Users on `pip install headroom-ai` get the Rust core automatically
(linux + macos wheels). sdist installs require rust toolchain available
locally — pip will build via maturin.
Closes #355
Supersedes #357 (workarounds-based fix abandoned in favor of
architectural fix)
2026-05-03 13:16:41 -07:00
|
|
|
|
python-source = "."
|
|
|
|
|
|
module-name = "headroom._core"
|
|
|
|
|
|
# The cdylib source lives under `crates/headroom-py`. Maturin invokes
|
|
|
|
|
|
# `cargo build` with this manifest to produce `_core.cdylib`, then injects
|
|
|
|
|
|
# the resulting `.so` into the wheel at `headroom/_core.so`.
|
|
|
|
|
|
manifest-path = "crates/headroom-py/Cargo.toml"
|
|
|
|
|
|
features = ["extension-module"]
|
|
|
|
|
|
# Forbid building without the cdylib feature — bare `cargo build` won't
|
|
|
|
|
|
# produce a usable Python extension. Maturin's default `bindings` is "pyo3"
|
|
|
|
|
|
# which is correct here (see `crates/headroom-py/src/`).
|
|
|
|
|
|
bindings = "pyo3"
|
2026-01-07 11:36:44 -08:00
|
|
|
|
|
2026-01-06 23:16:58 -08:00
|
|
|
|
[tool.ruff]
|
|
|
|
|
|
target-version = "py310"
|
|
|
|
|
|
line-length = 100
|
|
|
|
|
|
|
|
|
|
|
|
[tool.ruff.lint]
|
|
|
|
|
|
select = [
|
|
|
|
|
|
"E", # pycodestyle errors
|
|
|
|
|
|
"W", # pycodestyle warnings
|
|
|
|
|
|
"F", # pyflakes
|
|
|
|
|
|
"I", # isort
|
|
|
|
|
|
"B", # flake8-bugbear
|
|
|
|
|
|
"C4", # flake8-comprehensions
|
|
|
|
|
|
"UP", # pyupgrade
|
|
|
|
|
|
]
|
|
|
|
|
|
ignore = [
|
|
|
|
|
|
"E501", # line too long (handled by formatter)
|
|
|
|
|
|
"B008", # do not perform function calls in argument defaults
|
2026-01-07 11:36:44 -08:00
|
|
|
|
"B905", # zip without strict parameter
|
2026-01-06 23:16:58 -08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
[tool.ruff.lint.isort]
|
|
|
|
|
|
known-first-party = ["headroom"]
|
|
|
|
|
|
|
2026-01-07 11:36:44 -08:00
|
|
|
|
[tool.ruff.format]
|
|
|
|
|
|
quote-style = "double"
|
|
|
|
|
|
indent-style = "space"
|
|
|
|
|
|
|
2026-01-06 23:16:58 -08:00
|
|
|
|
[tool.mypy]
|
|
|
|
|
|
python_version = "3.10"
|
|
|
|
|
|
warn_return_any = true
|
|
|
|
|
|
warn_unused_configs = true
|
|
|
|
|
|
disallow_untyped_defs = true
|
2026-01-07 11:36:44 -08:00
|
|
|
|
ignore_missing_imports = true
|
2026-01-06 23:16:58 -08:00
|
|
|
|
|
2026-01-10 15:53:38 -08:00
|
|
|
|
# Per-module overrides for modules with dynamic typing patterns
|
|
|
|
|
|
[[tool.mypy.overrides]]
|
|
|
|
|
|
module = [
|
|
|
|
|
|
"headroom.proxy.server",
|
2026-04-03 16:17:50 -07:00
|
|
|
|
"headroom.proxy.cost",
|
|
|
|
|
|
"headroom.proxy.prometheus_metrics",
|
|
|
|
|
|
"headroom.proxy.semantic_cache",
|
|
|
|
|
|
"headroom.proxy.rate_limiter",
|
|
|
|
|
|
"headroom.proxy.request_logger",
|
|
|
|
|
|
"headroom.proxy.helpers",
|
2026-01-10 15:53:38 -08:00
|
|
|
|
"headroom.integrations.langchain",
|
|
|
|
|
|
"headroom.integrations.mcp",
|
|
|
|
|
|
"headroom.ccr.mcp_server",
|
|
|
|
|
|
"headroom.relevance.embedding",
|
|
|
|
|
|
"headroom.reporting.generator",
|
|
|
|
|
|
]
|
|
|
|
|
|
disallow_untyped_defs = false
|
|
|
|
|
|
|
|
|
|
|
|
[[tool.mypy.overrides]]
|
|
|
|
|
|
module = [
|
|
|
|
|
|
"headroom.tokenizers.*",
|
|
|
|
|
|
"headroom.providers.litellm",
|
|
|
|
|
|
"headroom.providers.google",
|
|
|
|
|
|
]
|
|
|
|
|
|
disallow_untyped_defs = false
|
|
|
|
|
|
warn_return_any = false
|
|
|
|
|
|
|
2026-04-03 17:08:39 -07:00
|
|
|
|
# Handler mixins use self.* from HeadroomProxy via duck typing — mypy can't resolve these
|
|
|
|
|
|
[[tool.mypy.overrides]]
|
|
|
|
|
|
module = ["headroom.proxy.handlers.*"]
|
|
|
|
|
|
disallow_untyped_defs = false
|
|
|
|
|
|
ignore_errors = true
|
|
|
|
|
|
|
2026-01-10 15:53:38 -08:00
|
|
|
|
# Ignore third-party stubs with syntax errors
|
|
|
|
|
|
[[tool.mypy.overrides]]
|
|
|
|
|
|
module = ["mlx.*"]
|
|
|
|
|
|
ignore_errors = true
|
|
|
|
|
|
|
2026-01-06 23:16:58 -08:00
|
|
|
|
[tool.pytest.ini_options]
|
|
|
|
|
|
testpaths = ["tests"]
|
|
|
|
|
|
python_files = ["test_*.py"]
|
|
|
|
|
|
python_functions = ["test_*"]
|
|
|
|
|
|
addopts = "-v --tb=short"
|
2026-01-07 11:36:44 -08:00
|
|
|
|
asyncio_mode = "auto"
|
2026-06-10 19:30:00 -04:00
|
|
|
|
filterwarnings = [
|
|
|
|
|
|
# pyo3 Unsendable parsers emit an unraisable warning when GC drops them on a
|
|
|
|
|
|
# test-teardown thread; this is a test-harness artifact, not a production issue
|
|
|
|
|
|
# (production threads are long-lived and drop their parsers on themselves).
|
|
|
|
|
|
"ignore::pytest.PytestUnraisableExceptionWarning",
|
|
|
|
|
|
]
|
chore(proxy): add third-party extension point
Adds `headroom/proxy/extensions.py` — a generic entry-point hook under
the `headroom.proxy_extension` group. External packages register an
`install(app, config)` callable that runs once at proxy startup and is
free to add ASGI middleware, routes, mutate config, or raise to
fail-closed (e.g., license check failure aborts startup).
Why: Headroom OSS keeps a deliberately minimal surface, but several
high-value capabilities — PII redaction + tool-call vaulting, data-
residency routing, multi-tenant RBAC, compliance audit, vision/voice
privacy — only make sense as separately-distributed packages. A small,
stable plugin contract lets those live outside this repo without
requiring the OSS to know about them.
Contract:
[project.entry-points."headroom.proxy_extension"]
my_extension = "my_pkg.extension:install"
def install(app: FastAPI, config: ProxyConfig) -> None: ...
An extension that raises from install() is a deliberate fail-closed
signal and aborts startup. Entry-point load failures are logged and
skipped so one broken third-party package cannot take the proxy down.
Changes:
* New: headroom/proxy/extensions.py (~55 LOC)
* headroom/proxy/server.py: invoke install_all(app, config) in
create_app() immediately after CORS middleware registration
* pyproject.toml: register slow and real_llm pytest markers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:01:27 -07:00
|
|
|
|
markers = [
|
|
|
|
|
|
"slow: slow tests (model loads, large fixtures)",
|
|
|
|
|
|
"real_llm: tests that hit real LLM APIs; skipped unless explicitly enabled",
|
fix: Wave 3 — multi-turn live integration tests for A+B realignment
Adds tests/test_realignment_live_multi_turn.py with 9 OPT-IN live tests
that validate the load-bearing claims of the Phase A+B megamerge against
real upstream APIs (Anthropic, OpenAI, Gemini). Each test maps to one or
more realignment PRs:
1. test_anthropic_cache_hit_across_two_turns — A2/A6/E
Identical cache_control'd system+messages on two turns must
eventually produce cache_read_input_tokens > 0. Guards the cache
hot zone invariant (I2): proxy must not mutate frozen prefix bytes.
Uses a bounded retry loop (max 4 attempts) to absorb Anthropic's
eventually-consistent prompt-cache write latency without masking
a real "proxy broke cache stability" regression.
2. test_anthropic_cache_stable_when_live_zone_compresses — B2/B3
Turn 2 mutates only the LATEST user content (8KB+ JSON tail);
cache_read on turn 2 must still be > 0 AND the proxy must emit
compression headers — proving the live-zone block dispatcher
ran on the new tail without disturbing the cached prefix.
3. test_anthropic_cache_control_passthrough_byte_faithful — A3/A4
Wraps proxy._retry_request to snapshot the upstream-bound body
and assert cache_control on system blocks survives verbatim,
and user content is not flattened from list to string form.
4. test_openai_chat_completions_multi_turn_through_proxy — A8/B
Three-turn conversation through /v1/chat/completions; each
turn returns valid content, prior assistant turns survive in
the messages list (proxy doesn't drop them).
5. test_openai_streaming_sse_chunks_arrive_in_order — A8 (SSE wire)
Streams /v1/chat/completions; asserts each event is
'data: ...\\n\\n', terminator is 'data: [DONE]\\n\\n',
reassembled content non-empty, no malformed events.
6. test_gemini_multi_turn_through_proxy — Gemini reach
Two-turn conversation through native
/v1beta/models/{model}:generateContent. Proves Gemini handler
wiring stayed intact through the megamerge.
7. test_ccr_marker_round_trip_live — B7 (CCR)
Pre-populates compression_store with a fixture entry, embeds
a CCR marker on a tool_result, verifies (a) headroom_retrieve
tool is injected into the upstream tools array (PR-B7
always-on), and (b) /v1/retrieve returns the original bytes
by hash with all rows intact. Pre-populating the Python store
(vs. driving SmartCrusher's internal Rust store) matches the
established pattern in tests/test_proxy_ccr.py and exercises
the surface served by /v1/retrieve.
8. test_memory_tail_injection_does_not_modify_system_prompt_live — B6/A2
Spins up a memory-enabled proxy with MemoryMode.AUTO_TAIL,
seeds LocalBackend, captures upstream-bound body. Asserts:
(a) system prompt byte-identical to input; (b) memory text
lands on latest user message tail; (c) earlier messages
untouched. Guards the live-zone-only injection contract.
9. test_classify_auth_mode_routes_payg_vs_oauth — Phase F-prep / B5
NOT a live API call. Sends three header shapes through the
proxy (x-api-key=..., Bearer sk-ant-oat01-..., Bearer
sk-ant-api03-...), captures dispatcher headers via a wrap on
_retry_request, and asserts the canonical auth-mode classifier
maps each correctly. Codifies the Phase F contract.
Conventions:
* file-level pytestmark = pytest.mark.live → excluded by default
via 'pytest -m "not live"'. Adds a 'live' marker registration in
pyproject.toml's [tool.pytest.ini_options].markers.
* each test skipif's on the relevant API key — no silent fallbacks,
no real-API runs against fake keys.
* uses tests/_dotenv.py helpers (load_env_overrides + autouse_apply_env)
rather than re-implementing env loading.
* model IDs and thresholds live in a top-of-file LIVE_CONFIG dict
(no hardcodes); Anthropic primary/fallback resolves at runtime per
key entitlement.
* assertions are direction-only (cache_read > 0, tokens_after <=
tokens_before) — never tied to upstream pricing/tokenizer drift.
* shared module-scoped TestClient fixture for performance; CCR and
memory tests build dedicated proxies for their config-specific paths.
Verification:
* pytest tests/test_realignment_live_multi_turn.py -v
→ 9 passed, 0 skipped, 0 failed in ~25s (with all keys set)
* pytest -m "not live" --tb=short -q
→ 4694 passed, 265 skipped, 9 deselected — same baseline as today
* make ci-precheck → green (rust + python + commitlint)
Per-realignment-plan: REALIGNMENT/04-phase-B-live-zone.md.
2026-05-02 17:38:15 -07:00
|
|
|
|
"live: opt-in multi-turn tests that hit real upstream APIs; require provider keys",
|
chore(proxy): add third-party extension point
Adds `headroom/proxy/extensions.py` — a generic entry-point hook under
the `headroom.proxy_extension` group. External packages register an
`install(app, config)` callable that runs once at proxy startup and is
free to add ASGI middleware, routes, mutate config, or raise to
fail-closed (e.g., license check failure aborts startup).
Why: Headroom OSS keeps a deliberately minimal surface, but several
high-value capabilities — PII redaction + tool-call vaulting, data-
residency routing, multi-tenant RBAC, compliance audit, vision/voice
privacy — only make sense as separately-distributed packages. A small,
stable plugin contract lets those live outside this repo without
requiring the OSS to know about them.
Contract:
[project.entry-points."headroom.proxy_extension"]
my_extension = "my_pkg.extension:install"
def install(app: FastAPI, config: ProxyConfig) -> None: ...
An extension that raises from install() is a deliberate fail-closed
signal and aborts startup. Entry-point load failures are logged and
skipped so one broken third-party package cannot take the proxy down.
Changes:
* New: headroom/proxy/extensions.py (~55 LOC)
* headroom/proxy/server.py: invoke install_all(app, config) in
create_app() immediately after CORS middleware registration
* pyproject.toml: register slow and real_llm pytest markers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:01:27 -07:00
|
|
|
|
]
|
2026-01-07 11:36:44 -08:00
|
|
|
|
|
|
|
|
|
|
[tool.coverage.run]
|
|
|
|
|
|
source = ["headroom"]
|
|
|
|
|
|
branch = true
|
|
|
|
|
|
omit = [
|
|
|
|
|
|
"headroom/cli.py",
|
|
|
|
|
|
"*/tests/*",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
[tool.coverage.report]
|
|
|
|
|
|
exclude_lines = [
|
|
|
|
|
|
"pragma: no cover",
|
|
|
|
|
|
"def __repr__",
|
|
|
|
|
|
"raise NotImplementedError",
|
|
|
|
|
|
"if TYPE_CHECKING:",
|
|
|
|
|
|
"if __name__ == .__main__.:",
|
|
|
|
|
|
]
|