mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(install): add orjson to [proxy] extra for LiteLLM provider backends (#2074)
## Description Add `orjson` to the `[proxy]` extra so `uv tool install "headroom-ai[all]"` installs a runtime dependency required by LiteLLM provider backends (e.g. OpenRouter). ## Type of Change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Changes Made - Add `orjson>=3.9.14; platform_python_implementation != 'PyPy'` to `[proxy]`. - Regression test in `tests/test_optional_dependencies.py`. - Minimal `uv.lock` update (proxy/all optional-deps + metadata only). ## Motivation Fixes #2056. `headroom-ai[all]` installs `litellm` (core dep) but not `orjson`. LiteLLM provider backends import `orjson` at runtime; LiteLLM only declares it under `litellm[proxy]`, not base deps. Headroom does not depend on `litellm[proxy]` (would pull the full LiteLLM proxy server stack). ## Testing - `uv run --extra dev python -m pytest tests/test_optional_dependencies.py -q` — 2 passed - `uvx --from ruff==0.15.17 ruff check tests/test_optional_dependencies.py` - `uvx --from ruff==0.15.17 ruff format --check tests/test_optional_dependencies.py` ## Real Behavior Proof - **Setup:** Ubuntu, Python 3.12.3 - **Verified:** dependency graph test confirms `orjson` is selected for `[proxy]` and `[all]` extras after this patch. - **Not tested locally:** full `uv tool install` end-to-end (local sdist build requires Rust/C++ toolchain unavailable in this environment). Reporter workaround `uv tool install ... --with orjson` confirms the missing transitive dep diagnosis. ## Review Readiness - [x] I have performed a self-review of my code - [x] This PR is ready for human review ## Notes - Reporter used Python 3.14.4; `litellm` is intentionally skipped on 3.14 (GH #956). This PR fixes the missing `orjson` install path for supported Python versions using `[all]`. Co-authored-by: syf2211 <syf2211@users.noreply.github.com> Co-authored-by: JerrettDavis <mxjerrett@gmail.com> Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
This commit is contained in:
parent
099c66432b
commit
4f3d5ab341
4 changed files with 32 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- **install:** include `orjson` in the `[proxy]` extra so `uv tool install "headroom-ai[all]"` satisfies LiteLLM OpenRouter/provider backends that import it at runtime ([#2056](https://github.com/headroomlabs-ai/headroom/issues/2056)).
|
||||||
- The dashboard's per-request metadata (the `recent_requests` / `request_logs`
|
- The dashboard's per-request metadata (the `recent_requests` / `request_logs`
|
||||||
tail and the `config` block with upstream URLs) is gated to loopback callers
|
tail and the `config` block with upstream URLs) is gated to loopback callers
|
||||||
via `_request_is_loopback`. When Headroom runs in a bridge-network container
|
via `_request_is_loopback`. When Headroom runs in a bridge-network container
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ dependencies = [
|
||||||
proxy = [
|
proxy = [
|
||||||
"fastapi>=0.100.0",
|
"fastapi>=0.100.0",
|
||||||
"uvicorn>=0.23.0,<1.0",
|
"uvicorn>=0.23.0,<1.0",
|
||||||
|
# LiteLLM provider backends (e.g. openrouter) expect orjson at runtime but
|
||||||
|
# litellm only declares it under its own [proxy] extra (GH #2056).
|
||||||
|
"orjson>=3.9.14; platform_python_implementation != 'PyPy'",
|
||||||
"httpx[http2]>=0.24.0",
|
"httpx[http2]>=0.24.0",
|
||||||
"openai>=2.14.0", # OpenAI API format support
|
"openai>=2.14.0", # OpenAI API format support
|
||||||
"mcp>=1.0.0", # MCP server (headroom_compress, retrieve, stats)
|
"mcp>=1.0.0", # MCP server (headroom_compress, retrieve, stats)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ SYS_PLATFORM_MARKER = "sys_platform"
|
||||||
PLATFORM_MACHINE_MARKER = "platform_machine"
|
PLATFORM_MACHINE_MARKER = "platform_machine"
|
||||||
TORCH_PACKAGE_NAME = "torch"
|
TORCH_PACKAGE_NAME = "torch"
|
||||||
TORCH_TRANSITIVE_PACKAGE_NAMES = frozenset({"sentence-transformers"})
|
TORCH_TRANSITIVE_PACKAGE_NAMES = frozenset({"sentence-transformers"})
|
||||||
|
ORJSON_PACKAGE_NAME = "orjson"
|
||||||
|
PROXY_EXTRA = "proxy"
|
||||||
UV_LOCK_FILE = "uv.lock"
|
UV_LOCK_FILE = "uv.lock"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -103,3 +105,25 @@ def test_all_extra_does_not_require_torch_on_macos_x86_64() -> None:
|
||||||
assert locked_torch_transitive_dependency_names
|
assert locked_torch_transitive_dependency_names
|
||||||
assert TORCH_PACKAGE_NAME not in selected_all_dependency_names
|
assert TORCH_PACKAGE_NAME not in selected_all_dependency_names
|
||||||
assert selected_all_dependency_names.isdisjoint(locked_torch_transitive_dependency_names)
|
assert selected_all_dependency_names.isdisjoint(locked_torch_transitive_dependency_names)
|
||||||
|
|
||||||
|
|
||||||
|
def test_proxy_extra_includes_orjson_for_litellm_backends() -> None:
|
||||||
|
"""`headroom-ai[all]` must ship orjson for LiteLLM provider backends (GH #2056)."""
|
||||||
|
|
||||||
|
pyproject = tomllib.loads((ROOT / PYPROJECT_FILE).read_text(encoding="utf-8"))
|
||||||
|
optional_deps = pyproject["project"]["optional-dependencies"]
|
||||||
|
environment = default_environment()
|
||||||
|
|
||||||
|
selected_proxy_dependency_names = _selected_dependency_names_for_extra(
|
||||||
|
optional_deps,
|
||||||
|
PROXY_EXTRA,
|
||||||
|
environment,
|
||||||
|
)
|
||||||
|
selected_all_dependency_names = _selected_dependency_names_for_extra(
|
||||||
|
optional_deps,
|
||||||
|
ALL_EXTRA,
|
||||||
|
environment,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ORJSON_PACKAGE_NAME in selected_proxy_dependency_names
|
||||||
|
assert ORJSON_PACKAGE_NAME in selected_all_dependency_names
|
||||||
|
|
|
||||||
4
uv.lock
generated
4
uv.lock
generated
|
|
@ -1558,6 +1558,7 @@ all = [
|
||||||
{ name = "openpyxl" },
|
{ name = "openpyxl" },
|
||||||
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
||||||
{ name = "opentelemetry-sdk" },
|
{ name = "opentelemetry-sdk" },
|
||||||
|
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "rapidocr", marker = "python_full_version >= '3.13'" },
|
{ name = "rapidocr", marker = "python_full_version >= '3.13'" },
|
||||||
{ name = "rapidocr-onnxruntime", marker = "python_full_version < '3.13'" },
|
{ name = "rapidocr-onnxruntime", marker = "python_full_version < '3.13'" },
|
||||||
|
|
@ -1673,6 +1674,7 @@ proxy = [
|
||||||
{ name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.14'" },
|
{ name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.14'" },
|
||||||
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.14'" },
|
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.14'" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
|
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "sqlite-vec" },
|
{ name = "sqlite-vec" },
|
||||||
{ name = "transformers" },
|
{ name = "transformers" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
|
|
@ -1689,6 +1691,7 @@ proxy-prod = [
|
||||||
{ name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.14'" },
|
{ name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.14'" },
|
||||||
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.14'" },
|
{ name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.14'" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
|
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "sqlite-vec" },
|
{ name = "sqlite-vec" },
|
||||||
{ name = "transformers" },
|
{ name = "transformers" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
|
|
@ -1784,6 +1787,7 @@ requires-dist = [
|
||||||
{ name = "openai", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
{ name = "openai", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
||||||
{ name = "openai", marker = "extra == 'evals'", specifier = ">=1.0.0" },
|
{ name = "openai", marker = "extra == 'evals'", specifier = ">=1.0.0" },
|
||||||
{ name = "openai", marker = "extra == 'proxy'", specifier = ">=2.14.0" },
|
{ name = "openai", marker = "extra == 'proxy'", specifier = ">=2.14.0" },
|
||||||
|
{ name = "orjson", marker = "platform_python_implementation != 'PyPy' and extra == 'proxy'", specifier = ">=3.9.14" },
|
||||||
{ name = "openpyxl", marker = "extra == 'dev'", specifier = ">=3.1.0" },
|
{ name = "openpyxl", marker = "extra == 'dev'", specifier = ">=3.1.0" },
|
||||||
{ name = "openpyxl", marker = "extra == 'spreadsheet'", specifier = ">=3.1.0" },
|
{ name = "openpyxl", marker = "extra == 'spreadsheet'", specifier = ">=3.1.0" },
|
||||||
{ name = "opentelemetry-api", specifier = ">=1.24.0" },
|
{ name = "opentelemetry-api", specifier = ">=1.24.0" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue