From ff17961cd76a7cea1cff0a9dcfb7338929f37c5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:38:08 -0500 Subject: [PATCH] deps: bump ruff from 0.15.22 to 0.16.2 in the pip-minor-patch group across 1 directory (#2962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the pip-minor-patch group with 1 update in the / directory: [ruff](https://github.com/astral-sh/ruff). Updates `ruff` from 0.15.22 to 0.16.2
Release notes

Sourced from ruff's releases.

0.16.2

Release Notes

Released on 2026-08-06.

Bug fixes

Server

Contributors

Install ruff 0.16.2

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.sh
| sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm
https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.ps1
| iex"

Download ruff 0.16.2

File Platform Checksum
ruff-aarch64-apple-darwin.tar.gz Apple Silicon macOS checksum
ruff-x86_64-apple-darwin.tar.gz Intel macOS checksum
ruff-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
ruff-i686-pc-windows-msvc.zip x86 Windows checksum
ruff-x86_64-pc-windows-msvc.zip x64 Windows checksum
ruff-aarch64-unknown-linux-gnu.tar.gz ARM64 Linux checksum
ruff-i686-unknown-linux-gnu.tar.gz x86 Linux checksum
ruff-powerpc64-unknown-linux-gnu.tar.gz PPC64 Linux checksum
ruff-powerpc64le-unknown-linux-gnu.tar.gz PPC64LE Linux checksum
ruff-riscv64gc-unknown-linux-gnu.tar.gz RISCV Linux checksum
ruff-s390x-unknown-linux-gnu.tar.gz S390x Linux checksum

... (truncated)

Changelog

Sourced from ruff's changelog.

0.16.2

Released on 2026-08-06.

Bug fixes

Server

Contributors

0.16.1

Released on 2026-07-30.

Preview features

Bug fixes

Rule changes

Server

... (truncated)

Commits

--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JerrettDavis --- .pre-commit-config.yaml | 2 +- headroom/testing/README.md | 6 +- pyproject.toml | 2 +- uv.lock | 236 ++++++++++++++-------------- wiki/ARCHITECTURE.md | 67 ++++---- wiki/agno.md | 2 + wiki/api.md | 16 +- wiki/compression.md | 77 ++++----- wiki/configuration.md | 17 +- wiki/errors.md | 33 ++-- wiki/image-compression.md | 38 +++-- wiki/integration-guide.md | 25 +-- wiki/langchain.md | 137 +++++++++------- wiki/memory.md | 79 +++++----- wiki/metrics.md | 8 +- wiki/plans/dynamic-smart-crusher.md | 118 +++++--------- wiki/proxy.md | 17 +- wiki/quickstart.md | 24 +-- wiki/sdk.md | 30 ++-- wiki/shared-context.md | 40 ++--- wiki/strands.md | 2 + wiki/text-compression.md | 10 +- wiki/transforms.md | 56 +++---- wiki/troubleshooting.md | 3 + 24 files changed, 526 insertions(+), 519 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 08ee4e215..58d46b666 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: # unconditionally, so installing hooks is not required for enforcement. args: [--assume-in-merge] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.22 + rev: v0.16.2 hooks: - id: ruff args: [--fix] diff --git a/headroom/testing/README.md b/headroom/testing/README.md index 7b9ee7f99..37c398669 100644 --- a/headroom/testing/README.md +++ b/headroom/testing/README.md @@ -33,7 +33,11 @@ assert report.passed suite = ( Headroom.Suite("phase-1") .Add(Headroom.WithOpenAI().named("openai-cache").WithCompression(mode="cache")) - .Add(Headroom.WithBedrock(region="us-east-1").named("bedrock-token").WithCompression(mode="token")) + .Add( + Headroom.WithBedrock(region="us-east-1") + .named("bedrock-token") + .WithCompression(mode="token") + ) ) suite.write_manifest_bundle("headroom-testing-bundle.json", provider="openai", port_start=19000) diff --git a/pyproject.toml b/pyproject.toml index e36dca4ae..c4450a1e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -277,7 +277,7 @@ dev = [ "pytest>=7.0.0", "pytest-cov>=4.0.0", "pytest-asyncio>=0.21.0", - "ruff==0.15.22", + "ruff==0.16.2", "mypy>=1.0.0", "pre-commit>=3.0.0", "openai>=1.0.0", diff --git a/uv.lock b/uv.lock index edd0b3646..45feb1dd2 100644 --- a/uv.lock +++ b/uv.lock @@ -280,12 +280,12 @@ name = "any-llm-sdk" version = "1.12.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "httpx" }, - { name = "openai" }, - { name = "openresponses-types" }, - { name = "pydantic" }, - { name = "rich" }, - { name = "typing-extensions" }, + { name = "httpx", marker = "python_full_version >= '3.11'" }, + { name = "openai", marker = "python_full_version >= '3.11'" }, + { name = "openresponses-types", marker = "python_full_version >= '3.11'" }, + { name = "pydantic", marker = "python_full_version >= '3.11'" }, + { name = "rich", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/d5/21ef27d031b72f0054ba0015a66cc1f10cda9410e4a3acb6e795d848ad0d/any_llm_sdk-1.12.1.tar.gz", hash = "sha256:76e043fcaa56fccfb375a908511869dc7dbf393dd97a82d06bb764d8201724e8", size = 152078, upload-time = "2026-03-18T13:13:01.735Z" } wheels = [ @@ -811,7 +811,7 @@ name = "coloredlogs" version = "15.0.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "humanfriendly" }, + { name = "humanfriendly", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } wheels = [ @@ -823,7 +823,7 @@ name = "colorlog" version = "6.10.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version >= '3.13' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } wheels = [ @@ -1045,7 +1045,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, @@ -1080,43 +1080,43 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-runtime", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufft = [ - { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufft", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cufile = [ - { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-cupti", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] curand = [ - { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-curand", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusolver = [ - { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusolver", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvtx", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64' and sys_platform == 'win32') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] [[package]] @@ -1220,7 +1220,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1637,7 +1637,7 @@ name = "gunicorn" version = "26.0.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "packaging" }, + { name = "packaging", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } wheels = [ @@ -1988,7 +1988,7 @@ requires-dist = [ { name = "rapidocr-onnxruntime", marker = "python_full_version < '3.13' and extra == 'image'", specifier = ">=1.4.0,<2" }, { name = "respx", marker = "extra == 'dev'", specifier = ">=0.20.0" }, { name = "rich", specifier = ">=13.0.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.22" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.2" }, { name = "scikit-learn", marker = "extra == 'evals'", specifier = ">=1.3.0" }, { name = "sentence-transformers", marker = "sys_platform == 'darwin' and extra == 'pytorch-mps'", specifier = ">=2.2.0" }, { name = "sentence-transformers", marker = "(platform_machine != 'x86_64' and extra == 'evals') or (sys_platform != 'darwin' and extra == 'evals')", specifier = ">=2.2.0,<6.0" }, @@ -2207,7 +2207,7 @@ name = "humanfriendly" version = "10.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, + { name = "pyreadline3", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } wheels = [ @@ -2690,18 +2690,18 @@ name = "litellm" version = "1.88.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fastuuid" }, - { name = "httpx" }, - { name = "importlib-metadata" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "tiktoken" }, - { name = "tokenizers" }, + { name = "aiohttp", marker = "python_full_version < '3.14'" }, + { name = "click", marker = "python_full_version < '3.14'" }, + { name = "fastuuid", marker = "python_full_version < '3.14'" }, + { name = "httpx", marker = "python_full_version < '3.14'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.14'" }, + { name = "jinja2", marker = "python_full_version < '3.14'" }, + { name = "jsonschema", marker = "python_full_version < '3.14'" }, + { name = "openai", marker = "python_full_version < '3.14'" }, + { name = "pydantic", marker = "python_full_version < '3.14'" }, + { name = "python-dotenv", marker = "python_full_version < '3.14'" }, + { name = "tiktoken", marker = "python_full_version < '3.14'" }, + { name = "tokenizers", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/ea/f99ececb7f22703fe120f1d8be9ffb749ec9453fbbbbbebc0d6a6b4d7864/litellm-1.88.1.tar.gz", hash = "sha256:89c6b74cc7912d6365793006ff951c0450fe847625008dfe49de8a7dc4529aa5", size = 13885969, upload-time = "2026-06-09T01:06:25.192Z" } wheels = [ @@ -3583,7 +3583,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -3622,7 +3622,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -3634,7 +3634,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -3664,9 +3664,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -3678,7 +3678,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -3757,8 +3757,8 @@ name = "omegaconf" version = "2.3.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, + { name = "antlr4-python3-runtime", marker = "python_full_version >= '3.13'" }, + { name = "pyyaml", marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } wheels = [ @@ -3773,12 +3773,12 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" } }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, + { name = "coloredlogs", marker = "python_full_version < '3.11'" }, + { name = "flatbuffers", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "protobuf", marker = "python_full_version < '3.11'" }, + { name = "sympy", marker = "python_full_version < '3.11'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/35/d6/311b1afea060015b56c742f3531168c1644650767f27ef40062569960587/onnxruntime-1.23.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:a7730122afe186a784660f6ec5807138bf9d792fa1df76556b27307ea9ebcbe3", size = 17195934, upload-time = "2025-10-27T23:06:14.143Z" }, @@ -3824,10 +3824,10 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "flatbuffers" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" } }, - { name = "packaging" }, - { name = "protobuf" }, + { name = "flatbuffers", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "protobuf", marker = "python_full_version >= '3.11'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/d4/81/29a9eb470994a75eb7b3ccf32be314d7c66675a00ac7b50294816cc2db27/onnxruntime-1.26.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ee1109ef4ef27cad90e823399e61e03b3c6c7bfe0fb820b4baf3678c15be8b3c", size = 18005108, upload-time = "2026-05-08T19:08:11.728Z" }, @@ -3911,7 +3911,7 @@ name = "openresponses-types" version = "2.3.0.post1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "pydantic" }, + { name = "pydantic", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/26/b612c3215f5599714fa94d63eb5ee59b4eb66dbdeeaf86bb4d848359484d/openresponses_types-2.3.0.post1.tar.gz", hash = "sha256:11b8896d3621d2ac2439f6ff106f34ddcb1bbd517c317a6c852a9df2e98a0753", size = 19254, upload-time = "2026-01-22T20:02:03.933Z" } wheels = [ @@ -4154,10 +4154,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" } }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ @@ -4229,9 +4229,9 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" } }, - { name = "python-dateutil" }, - { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } wheels = [ @@ -5393,17 +5393,17 @@ name = "rapidocr" version = "3.8.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "colorlog" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" } }, - { name = "omegaconf" }, - { name = "opencv-python" }, - { name = "pillow" }, - { name = "pyclipper" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "shapely" }, - { name = "six" }, - { name = "tqdm" }, + { name = "colorlog", marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.13'" }, + { name = "omegaconf", marker = "python_full_version >= '3.13'" }, + { name = "opencv-python", marker = "python_full_version >= '3.13'" }, + { name = "pillow", marker = "python_full_version >= '3.13'" }, + { name = "pyclipper", marker = "python_full_version >= '3.13'" }, + { name = "pyyaml", marker = "python_full_version >= '3.13'" }, + { name = "requests", marker = "python_full_version >= '3.13'" }, + { name = "shapely", marker = "python_full_version >= '3.13'" }, + { name = "six", marker = "python_full_version >= '3.13'" }, + { name = "tqdm", marker = "python_full_version >= '3.13'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ea/4a/fa521d947f0fc7bb304bf11bec4cb66266bd81494588b4cb48dc01001719/rapidocr-3.8.1-py3-none-any.whl", hash = "sha256:650044b1fbce9e6bae5cae462dcf8be754cde11e2f23fc51f65dcc08deae2c46", size = 15080319, upload-time = "2026-04-11T07:13:22.56Z" }, @@ -5414,17 +5414,17 @@ name = "rapidocr-onnxruntime" version = "1.4.4" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11' or python_full_version >= '3.13'" }, + { name = "onnxruntime", version = "1.23.2", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, { name = "onnxruntime", version = "1.26.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, - { name = "opencv-python" }, - { name = "pillow" }, - { name = "pyclipper" }, - { name = "pyyaml" }, - { name = "shapely" }, - { name = "six" }, - { name = "tqdm" }, + { name = "opencv-python", marker = "python_full_version < '3.13'" }, + { name = "pillow", marker = "python_full_version < '3.13'" }, + { name = "pyclipper", marker = "python_full_version < '3.13'" }, + { name = "pyyaml", marker = "python_full_version < '3.13'" }, + { name = "shapely", marker = "python_full_version < '3.13'" }, + { name = "six", marker = "python_full_version < '3.13'" }, + { name = "tqdm", marker = "python_full_version < '3.13'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ba/12/1e5497183bdbe782dbb91bad1d0d2297dba4d2831b2652657f7517bfc6df/rapidocr_onnxruntime-1.4.4-py3-none-any.whl", hash = "sha256:971d7d5f223a7a808662229df1ef69893809d8457d834e6373d3854bc1782cbf", size = 14915192, upload-time = "2025-01-17T01:48:25.104Z" }, @@ -5754,27 +5754,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" +version = "0.16.2" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, ] [[package]] @@ -5821,10 +5821,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "joblib" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" } }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple/" } }, - { name = "threadpoolctl" }, + { name = "joblib", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -5879,10 +5879,10 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "joblib" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" } }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple/" } }, - { name = "threadpoolctl" }, + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -5932,7 +5932,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" } }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -6002,7 +6002,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" } }, + { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } wheels = [ diff --git a/wiki/ARCHITECTURE.md b/wiki/ARCHITECTURE.md index b5cd360cd..c8dccb921 100644 --- a/wiki/ARCHITECTURE.md +++ b/wiki/ARCHITECTURE.md @@ -226,8 +226,8 @@ analysis = { "data": [ {"ts": 45, "cpu": 92}, # Keep the spike! {"ts": 46, "cpu": 95}, - ... - ] + ..., + ], } ``` @@ -397,11 +397,9 @@ def analyze_field(key, items): "unique_ratio": len(set(values)) / len(values), # 0.0 = all same (constant) # 1.0 = all different (unique IDs) - "variance": statistics.variance(values), # For numbers # Low = stable # High = changing - "change_points": detect_spikes(values), # Indices where value jumps significantly } @@ -502,14 +500,14 @@ When SmartCrusher compresses, the original content is stored for on-demand retri ```python @dataclass class CompressionEntry: - hash: str # 16-char SHA256 for retrieval - original_content: str # Full JSON before compression - compressed_content: str # Compressed JSON + hash: str # 16-char SHA256 for retrieval + original_content: str # Full JSON before compression + compressed_content: str # Compressed JSON original_item_count: int compressed_item_count: int - tool_name: str | None # For feedback tracking + tool_name: str | None # For feedback tracking created_at: float - ttl: int = 300 # 5 minute default + ttl: int = 300 # 5 minute default ``` **Features:** @@ -625,12 +623,12 @@ The feedback system learns from retrieval patterns to improve future compression @dataclass class ToolPattern: tool_name: str - total_compressions: int # Times we compressed this tool - total_retrievals: int # Times LLM asked for more - full_retrievals: int # Retrieved everything (all retrievals — hash-only) - search_retrievals: int # Legacy; always 0 (retrieval is hash-only, no search) - common_queries: dict[str, int] # Legacy query-pattern frequency (no longer populated) - queried_fields: dict[str, int] # Legacy queried-field frequency (no longer populated) + total_compressions: int # Times we compressed this tool + total_retrievals: int # Times LLM asked for more + full_retrievals: int # Retrieved everything (all retrievals — hash-only) + search_retrievals: int # Legacy; always 0 (retrieval is hash-only, no search) + common_queries: dict[str, int] # Legacy query-pattern frequency (no longer populated) + queried_fields: dict[str, int] # Legacy queried-field frequency (no longer populated) ``` **Key Metrics:** @@ -644,12 +642,12 @@ class ToolPattern: ```python @dataclass class CompressionHints: - max_items: int = 15 # Target item count + max_items: int = 15 # Target item count suggested_items: int | None # Calculated optimal - skip_compression: bool # Don't compress at all - preserve_fields: list[str] # Always keep these fields - aggressiveness: float # 0.0 = aggressive, 1.0 = conservative - reason: str # Explanation + skip_compression: bool # Don't compress at all + preserve_fields: list[str] # Always keep these fields + aggressiveness: float # 0.0 = aggressive, 1.0 = conservative + reason: str # Explanation ``` **Feedback-Driven Adjustment:** @@ -733,24 +731,26 @@ if self.config.use_feedback_hints and tool_name: ```python @dataclass class CCRToolCall: - tool_call_id: str # For matching response - hash_key: str # CCR hash to retrieve + tool_call_id: str # For matching response + hash_key: str # CCR hash to retrieve + @dataclass class CCRToolResult: tool_call_id: str - content: str # Retrieved data as JSON + content: str # Retrieved data as JSON success: bool items_retrieved: int + class CCRResponseHandler: async def handle_response( self, - response: dict, # Initial LLM response - messages: list, # Conversation history - tools: list, # Tool definitions - api_call_fn: Callable, # Function to make API calls - provider: str, # "anthropic" or "openai" + response: dict, # Initial LLM response + messages: list, # Conversation history + tools: list, # Tool definitions + api_call_fn: Callable, # Function to make API calls + provider: str, # "anthropic" or "openai" ) -> dict: """Handle CCR tool calls until final response.""" ``` @@ -762,11 +762,14 @@ The handler also supports streaming responses via `StreamingCCRHandler`: ```python class StreamingCCRBuffer: """Buffers streaming chunks to detect CCR tool calls.""" + chunks: list[bytes] detected_ccr: bool + class StreamingCCRHandler: """Handles CCR in streaming responses.""" + async def process_stream(self, stream, messages, tools, api_call_fn): """Yields chunks, switching to buffered mode if CCR detected.""" ``` @@ -848,11 +851,11 @@ The tracker uses simple but effective heuristics: @dataclass class ContextTrackerConfig: enabled: bool = True - max_tracked_contexts: int = 100 # LRU eviction - relevance_threshold: float = 0.3 # Min score to recommend - max_context_age_seconds: float = 300 # 5 minutes + max_tracked_contexts: int = 100 # LRU eviction + relevance_threshold: float = 0.3 # Min score to recommend + max_context_age_seconds: float = 300 # 5 minutes proactive_expansion: bool = True - max_proactive_expansions: int = 2 # Per query + max_proactive_expansions: int = 2 # Per query ``` --- diff --git a/wiki/agno.md b/wiki/agno.md index 386d30bfe..028f36ef7 100644 --- a/wiki/agno.md +++ b/wiki/agno.md @@ -167,6 +167,7 @@ Full async support for high-throughput applications: import asyncio from headroom.integrations.agno import HeadroomAgnoModel + async def process_async(): model = HeadroomAgnoModel(OpenAIChat(id="gpt-4o")) @@ -179,6 +180,7 @@ async def process_async(): print(f"\nTokens saved: {model.total_tokens_saved}") + asyncio.run(process_async()) ``` diff --git a/wiki/api.md b/wiki/api.md index 66288cea7..28dd29ec1 100644 --- a/wiki/api.md +++ b/wiki/api.md @@ -101,9 +101,9 @@ config = CacheAlignerConfig( from headroom import RelevanceScorerConfig config = RelevanceScorerConfig( - scorer_type="bm25", # "bm25", "embedding", or "hybrid" - embedding_model=None, # Model name for embedding scorer - hybrid_alpha=0.5, # Weight for hybrid scoring + scorer_type="bm25", # "bm25", "embedding", or "hybrid" + embedding_model=None, # Model name for embedding scorer + hybrid_alpha=0.5, # Weight for hybrid scoring ) ``` @@ -286,10 +286,12 @@ result = aligner.align(messages) ```python from headroom import TransformPipeline -pipeline = TransformPipeline([ - SmartCrusher(), - CacheAligner(), -]) +pipeline = TransformPipeline( + [ + SmartCrusher(), + CacheAligner(), + ] +) result = pipeline.transform(messages) ``` diff --git a/wiki/compression.md b/wiki/compression.md index a76a173c3..5de8bd55e 100644 --- a/wiki/compression.md +++ b/wiki/compression.md @@ -79,21 +79,18 @@ from headroom.compression import UniversalCompressorConfig config = UniversalCompressorConfig( # Detection - use_magika=True, # Use ML-based detection (requires magika) - + use_magika=True, # Use ML-based detection (requires magika) # Compression # (Note: the legacy `use_llmlingua` flag was retired with the # LLMLingua-2 integration. The optional ML compressor is now Kompress, # installed via `headroom-ai[ml]` and configured separately.) compression_ratio_target=0.3, # Keep 30% of content (70% reduction) - min_content_length=100, # Skip content shorter than this - + min_content_length=100, # Skip content shorter than this # Structure preservation - use_entropy_preservation=True, # Preserve high-entropy tokens - entropy_threshold=0.85, # Entropy threshold for preservation - + use_entropy_preservation=True, # Preserve high-entropy tokens + entropy_threshold=0.85, # Entropy threshold for preservation # CCR - ccr_enabled=True, # Store originals for retrieval + ccr_enabled=True, # Store originals for retrieval ) ``` @@ -121,12 +118,12 @@ Preserves JSON structure while compressing values: from headroom.compression.handlers.json_handler import JSONStructureHandler handler = JSONStructureHandler( - preserve_short_values=True, # Keep values < 20 chars - short_value_threshold=20, # Threshold for "short" - preserve_high_entropy=True, # Keep UUIDs, hashes - entropy_threshold=0.85, # Entropy threshold - max_array_items_full=3, # Keep first N array items full - max_number_digits=10, # Preserve numbers up to N digits + preserve_short_values=True, # Keep values < 20 chars + short_value_threshold=20, # Threshold for "short" + preserve_high_entropy=True, # Keep UUIDs, hashes + entropy_threshold=0.85, # Entropy threshold + max_array_items_full=3, # Keep first N array items full + max_number_digits=10, # Preserve numbers up to N digits ) ``` @@ -141,18 +138,10 @@ handler = JSONStructureHandler( ```python # Before -{ - "id": "usr_abc123", - "name": "Alice Johnson", - "bio": "A long description that goes on and on..." -} +{"id": "usr_abc123", "name": "Alice Johnson", "bio": "A long description that goes on and on..."} # After (structure preserved, long values compressed) -{ - "id": "usr_abc123", - "name": "Alice Johnson", - "bio": "A long...[compressed]..." -} +{"id": "usr_abc123", "name": "Alice Johnson", "bio": "A long...[compressed]..."} ``` ### Code Handler @@ -163,9 +152,9 @@ Preserves code structure using AST parsing (tree-sitter) or regex fallback: from headroom.compression.handlers.code_handler import CodeStructureHandler handler = CodeStructureHandler( - preserve_comments=False, # Preserve comments as structural - use_tree_sitter=True, # Use tree-sitter for parsing - default_language="python", # Default when detection fails + preserve_comments=False, # Preserve comments as structural + use_tree_sitter=True, # Use tree-sitter for parsing + default_language="python", # Default when detection fails ) ``` @@ -226,24 +215,24 @@ from headroom.compression import compress result = compress(content) # Access result fields -print(result.compressed) # Compressed content -print(result.original) # Original content -print(result.compression_ratio) # e.g., 0.35 (35% of original size) -print(result.tokens_before) # Estimated tokens before -print(result.tokens_after) # Estimated tokens after -print(result.tokens_saved) # tokens_before - tokens_after -print(result.savings_percentage) # e.g., 65.0 (65% savings) +print(result.compressed) # Compressed content +print(result.original) # Original content +print(result.compression_ratio) # e.g., 0.35 (35% of original size) +print(result.tokens_before) # Estimated tokens before +print(result.tokens_after) # Estimated tokens after +print(result.tokens_saved) # tokens_before - tokens_after +print(result.savings_percentage) # e.g., 65.0 (65% savings) # Detection info -print(result.content_type) # ContentType.JSON, CODE, etc. -print(result.detection_confidence) # 0.0-1.0 +print(result.content_type) # ContentType.JSON, CODE, etc. +print(result.detection_confidence) # 0.0-1.0 # Structure info -print(result.handler_used) # "json", "code", etc. -print(result.preservation_ratio) # Fraction preserved as structure +print(result.handler_used) # "json", "code", etc. +print(result.preservation_ratio) # Fraction preserved as structure # CCR info -print(result.ccr_key) # Key for retrieval (if CCR enabled) +print(result.ccr_key) # Key for retrieval (if CCR enabled) ``` --- @@ -259,8 +248,8 @@ compressor = UniversalCompressor() contents = [ '{"users": [...]}', - 'def hello(): pass', - 'Plain text content', + "def hello(): pass", + "Plain text content", ] results = compressor.compress_batch(contents) @@ -393,11 +382,11 @@ json_content = """ result = compressor.compress(json_content) -print(f"Type: {result.content_type}") # ContentType.JSON -print(f"Handler: {result.handler_used}") # json +print(f"Type: {result.content_type}") # ContentType.JSON +print(f"Handler: {result.handler_used}") # json print(f"Saved: {result.savings_percentage:.0f}%") # ~60% print(f"Structure: {result.preservation_ratio:.0%} preserved") # ~40% -print(f"CCR Key: {result.ccr_key}") # For retrieval +print(f"CCR Key: {result.ccr_key}") # For retrieval ``` --- diff --git a/wiki/configuration.md b/wiki/configuration.md index 58b011e04..b74c2e89f 100644 --- a/wiki/configuration.md +++ b/wiki/configuration.md @@ -31,22 +31,17 @@ from openai import OpenAI client = HeadroomClient( original_client=OpenAI(), provider=OpenAIProvider(), - # Mode: "audit" (observe only) or "optimize" (apply transforms) default_mode="optimize", - # Enable provider-specific cache optimization enable_cache_optimizer=True, - # Enable query-level semantic caching enable_semantic_cache=False, - # Override default context limits per model model_context_limits={ "gpt-4o": 128000, "gpt-4o-mini": 128000, }, - # Database location (defaults to temp directory) # store_url="sqlite:////absolute/path/to/headroom.db", ) @@ -136,20 +131,14 @@ Override configuration for specific requests: response = client.chat.completions.create( model="gpt-4o", messages=[...], - # Override mode for this request headroom_mode="audit", - # Reserve more tokens for output headroom_output_buffer_tokens=8000, - # Keep last N turns (don't compress) headroom_keep_turns=5, - # Skip compression for specific tools - headroom_tool_profiles={ - "important_tool": {"skip_compression": True} - } + headroom_tool_profiles={"important_tool": {"skip_compression": True}}, ) ``` @@ -186,13 +175,10 @@ from headroom.transforms import SmartCrusherConfig config = SmartCrusherConfig( # Maximum items to keep after compression max_items_after_crush=15, - # Minimum tokens before applying compression min_tokens_to_crush=200, - # Relevance scoring tier: "bm25" (fast) or "embedding" (accurate) relevance_tier="bm25", - # Always keep items with these field values preserve_fields=["error", "warning", "failure"], ) @@ -208,7 +194,6 @@ from headroom.transforms import CacheAlignerConfig config = CacheAlignerConfig( # Enable/disable cache alignment enabled=True, - # Patterns to extract from system prompt dynamic_patterns=[ r"Today is \w+ \d+, \d{4}", diff --git a/wiki/errors.md b/wiki/errors.md index 345e593a5..c5642d362 100644 --- a/wiki/errors.md +++ b/wiki/errors.md @@ -6,12 +6,12 @@ Headroom provides explicit exceptions for debugging, with a safety guarantee tha ```python from headroom import ( - HeadroomError, # Base class - catch all Headroom errors - ConfigurationError, # Invalid configuration - ProviderError, # Provider issues (unknown model, etc.) - StorageError, # Database/storage failures - CompressionError, # Compression failures (rare) - ValidationError, # Setup validation failures + HeadroomError, # Base class - catch all Headroom errors + ConfigurationError, # Invalid configuration + ProviderError, # Provider issues (unknown model, etc.) + StorageError, # Database/storage failures + CompressionError, # Compression failures (rare) + ValidationError, # Setup validation failures ) ``` @@ -75,10 +75,7 @@ Raised for provider-specific issues. # - Token counting failure try: - response = client.chat.completions.create( - model="unknown-model-xyz", - messages=[...] - ) + response = client.chat.completions.create(model="unknown-model-xyz", messages=[...]) except ProviderError as e: print(f"Provider error: {e}") print(f"Provider: {e.details.get('provider')}") @@ -122,10 +119,7 @@ Raised when setup validation fails. ```python result = client.validate_setup() if not result["valid"]: - raise ValidationError( - "Setup validation failed", - details={"issues": result["issues"]} - ) + raise ValidationError("Setup validation failed", details={"issues": result["issues"]}) ``` ## Safety Guarantee @@ -136,16 +130,11 @@ This is a core design principle. Your LLM calls never fail due to Headroom: ```python # Even if SmartCrusher encounters unexpected data: -messages = [ - {"role": "tool", "content": "malformed json {{{"} -] +messages = [{"role": "tool", "content": "malformed json {{{"}] # This will NOT raise an exception # Instead, the malformed content passes through unchanged -response = client.chat.completions.create( - model="gpt-4o", - messages=messages -) +response = client.chat.completions.create(model="gpt-4o", messages=messages) ``` ## Logging Errors @@ -154,6 +143,7 @@ Enable logging to see error details: ```python import logging + logging.basicConfig(level=logging.WARNING) # Now you'll see warnings when compression is skipped: @@ -232,6 +222,7 @@ response = client.chat.completions.create(...) ```python import logging + logging.basicConfig(level=logging.DEBUG) # Shows detailed transform decisions diff --git a/wiki/image-compression.md b/wiki/image-compression.md index 4d50b8b30..0fa22ae9c 100644 --- a/wiki/image-compression.md +++ b/wiki/image-compression.md @@ -61,13 +61,15 @@ client = HeadroomClient(provider="openai") response = client.chat.completions.create( model="gpt-4o", - messages=[{ - "role": "user", - "content": [ - {"type": "text", "text": "What animal is this?"}, - {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}} - ] - }] + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "What animal is this?"}, + {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}, + ], + } + ], ) # Image automatically compressed with detail="low" (87% savings) ``` @@ -106,8 +108,8 @@ from headroom.image import ImageCompressor compressor = ImageCompressor( model_id="chopratejas/technique-router", # HuggingFace model - use_siglip=True, # Enable image analysis - device="cuda", # Use GPU if available + use_siglip=True, # Enable image analysis + device="cuda", # Use GPU if available ) ``` @@ -229,9 +231,11 @@ The HuggingFace model downloads on first use: ```python # Force a specific cache directory import os + os.environ["HF_HOME"] = "/path/to/cache" from headroom.image import ImageCompressor + compressor = ImageCompressor() ``` @@ -290,10 +294,10 @@ class ImageCompressor: ```python @dataclass class CompressionResult: - technique: Technique # full_low, preserve, crop, transcode - original_tokens: int # Estimated tokens before - compressed_tokens: int # Estimated tokens after - confidence: float # Router confidence (0-1) + technique: Technique # full_low, preserve, crop, transcode + original_tokens: int # Estimated tokens before + compressed_tokens: int # Estimated tokens after + confidence: float # Router confidence (0-1) @property def savings_percent(self) -> float: @@ -304,10 +308,10 @@ class CompressionResult: ```python class Technique(Enum): - FULL_LOW = "full_low" # 87% savings - PRESERVE = "preserve" # 0% savings - CROP = "crop" # 50-90% savings - TRANSCODE = "transcode" # 99% savings + FULL_LOW = "full_low" # 87% savings + PRESERVE = "preserve" # 0% savings + CROP = "crop" # 50-90% savings + TRANSCODE = "transcode" # 99% savings ``` ## See Also diff --git a/wiki/integration-guide.md b/wiki/integration-guide.md index c1d0885bf..6d076ab06 100644 --- a/wiki/integration-guide.md +++ b/wiki/integration-guide.md @@ -92,22 +92,26 @@ import httpx from headroom import compress compressed = compress(messages, model="claude-sonnet-4-5-20250929") -httpx.post("https://api.anthropic.com/v1/messages", json={ - "model": "claude-sonnet-4-5-20250929", - "messages": compressed.messages, -}, headers={"X-Api-Key": api_key, "anthropic-version": "2023-06-01"}) +httpx.post( + "https://api.anthropic.com/v1/messages", + json={ + "model": "claude-sonnet-4-5-20250929", + "messages": compressed.messages, + }, + headers={"X-Api-Key": api_key, "anthropic-version": "2023-06-01"}, +) ``` ### What compress() returns ```python result = compress(messages, model="gpt-4o") -result.messages # list[dict] — compressed messages, same format as input -result.tokens_before # int — original token count -result.tokens_after # int — compressed token count -result.tokens_saved # int — tokens removed +result.messages # list[dict] — compressed messages, same format as input +result.tokens_before # int — original token count +result.tokens_after # int — compressed token count +result.tokens_saved # int — tokens removed result.compression_ratio # float — 0.0 (no savings) to 1.0 (100% removed) -result.transforms_applied # list[str] — what ran (e.g., ["router:smart_crusher:0.35"]) +result.transforms_applied # list[str] — what ran (e.g., ["router:smart_crusher:0.35"]) ``` --- @@ -169,6 +173,7 @@ app.add_middleware(CompressionMiddleware) # LiteLLM proxy from litellm.proxy.proxy_server import app + app.add_middleware(CompressionMiddleware) ``` @@ -309,6 +314,7 @@ Customize compression behavior without modifying Headroom's code: ```python from headroom import compress, CompressionHooks, CompressContext + class MyHooks(CompressionHooks): def pre_compress(self, messages, ctx): # Modify messages before compression (dedup, filter, inject) @@ -323,6 +329,7 @@ class MyHooks(CompressionHooks): # Observe results (logging, analytics, learning) print(f"Saved {event.tokens_saved} tokens") + result = compress(messages, model="gpt-4o", hooks=MyHooks()) ``` diff --git a/wiki/langchain.md b/wiki/langchain.md index 1d79f1da3..c89bf3fc8 100644 --- a/wiki/langchain.md +++ b/wiki/langchain.md @@ -91,11 +91,13 @@ Works seamlessly with LangChain tool calling: ```python from langchain_core.tools import tool + @tool def search(query: str) -> str: """Search the web.""" return {"results": [...]} # Large JSON response + llm_with_tools = llm.bind_tools([search]) response = llm_with_tools.invoke("Search for Python tutorials") # Tool outputs are automatically compressed in subsequent turns @@ -117,7 +119,7 @@ base_history = ChatMessageHistory() compressed_history = HeadroomChatMessageHistory( base_history, compress_threshold_tokens=4000, # Compress when over 4K tokens - keep_recent_turns=5, # Always keep last 5 turns + keep_recent_turns=5, # Always keep last 5 turns ) # Use with any memory class @@ -152,9 +154,9 @@ base_retriever = vectorstore.as_retriever(search_kwargs={"k": 50}) # Wrap with Headroom compression (keep best for precision) compressor = HeadroomDocumentCompressor( - max_documents=10, # Keep top 10 - min_relevance=0.3, # Minimum relevance score - prefer_diverse=True, # MMR-style diversity + max_documents=10, # Keep top 10 + min_relevance=0.3, # Minimum relevance score + prefer_diverse=True, # MMR-style diversity ) retriever = ContextualCompressionRetriever( @@ -179,18 +181,21 @@ from langchain.agents import create_openai_tools_agent, AgentExecutor from langchain_core.tools import tool from headroom.integrations import wrap_tools_with_headroom + @tool def search_database(query: str) -> str: """Search the database.""" # Returns 1000 results as JSON return json.dumps({"results": [...], "total": 1000}) + @tool def fetch_logs(service: str) -> str: """Fetch service logs.""" # Returns 500 log entries return json.dumps({"logs": [...]}) + # Wrap tools with compression tools = [search_database, fetch_logs] wrapped_tools = wrap_tools_with_headroom( @@ -296,26 +301,33 @@ from langchain_core.tools import tool from langgraph.prebuilt import create_react_agent from headroom.integrations import HeadroomChatModel, wrap_tools_with_headroom + # Define tools that return large outputs @tool def search_web(query: str) -> str: """Search the web for information.""" # Simulating large search results - return json.dumps({ - "results": [ - {"title": f"Result {i}", "snippet": "..." * 100, "url": f"https://..."} - for i in range(100) - ], - "total": 1000, - }) + return json.dumps( + { + "results": [ + {"title": f"Result {i}", "snippet": "..." * 100, "url": f"https://..."} + for i in range(100) + ], + "total": 1000, + } + ) + @tool def query_database(sql: str) -> str: """Execute SQL query.""" - return json.dumps({ - "rows": [{"id": i, "data": "..." * 50} for i in range(500)], - "total": 500, - }) + return json.dumps( + { + "rows": [{"id": i, "data": "..." * 50} for i in range(500)], + "total": 500, + } + ) + # Wrap model with Headroom llm = HeadroomChatModel(ChatOpenAI(model="gpt-4o")) @@ -327,9 +339,9 @@ tools = wrap_tools_with_headroom([search_web, query_database]) agent = create_react_agent(llm, tools) # Run - tool outputs are automatically compressed between iterations -result = agent.invoke({ - "messages": [("user", "Find all users who signed up last week and their activity")] -}) +result = agent.invoke( + {"messages": [("user", "Find all users who signed up last week and their activity")]} +) # Check savings print(f"Tokens saved: {llm.get_metrics()['tokens_saved']}") @@ -352,23 +364,29 @@ from langchain_core.messages import HumanMessage from langgraph.graph import StateGraph, MessagesState, START, END from headroom.integrations.langchain import create_compress_tool_messages_node + # Define your agent and tools nodes def agent_node(state: MessagesState): llm = ChatOpenAI(model="gpt-4o") response = llm.invoke(state["messages"]) return {"messages": [response]} + def tools_node(state: MessagesState): # Your tool execution logic here ... + # Build the graph with a compression step graph = StateGraph(MessagesState) graph.add_node("agent", agent_node) graph.add_node("tools", tools_node) -graph.add_node("compress", create_compress_tool_messages_node( - min_tokens_to_compress=100, # Only compress outputs > ~100 tokens -)) +graph.add_node( + "compress", + create_compress_tool_messages_node( + min_tokens_to_compress=100, # Only compress outputs > ~100 tokens + ), +) # Wire: tools -> compress -> agent (instead of tools -> agent directly) graph.add_edge(START, "agent") @@ -411,9 +429,9 @@ base_retriever = vectorstore.as_retriever(search_kwargs={"k": 50}) # Headroom compressor for precision compressor = HeadroomDocumentCompressor( - max_documents=5, # Keep only top 5 - min_relevance=0.4, # Must be 40%+ relevant - prefer_diverse=True, # Avoid redundant docs + max_documents=5, # Keep only top 5 + min_relevance=0.4, # Must be 40%+ relevant + prefer_diverse=True, # Avoid redundant docs ) # Combine into compression retriever @@ -461,7 +479,7 @@ base_history = ChatMessageHistory() compressed_history = HeadroomChatMessageHistory( base_history, compress_threshold_tokens=8000, # Compress when over 8K - keep_recent_turns=10, # Always keep last 10 turns + keep_recent_turns=10, # Always keep last 10 turns ) memory = ConversationBufferMemory( @@ -500,30 +518,45 @@ from headroom.integrations import ( reset_tool_metrics, ) + @tool def search_arxiv(query: str) -> str: """Search arXiv for papers.""" - return json.dumps({"papers": [{"title": f"Paper {i}", "abstract": "..." * 200} for i in range(50)]}) + return json.dumps( + {"papers": [{"title": f"Paper {i}", "abstract": "..." * 200} for i in range(50)]} + ) + @tool def search_github(query: str) -> str: """Search GitHub repositories.""" - return json.dumps({"repos": [{"name": f"repo-{i}", "description": "..." * 100, "stars": i * 100} for i in range(100)]}) + return json.dumps( + { + "repos": [ + {"name": f"repo-{i}", "description": "..." * 100, "stars": i * 100} + for i in range(100) + ] + } + ) + @tool def fetch_documentation(url: str) -> str: """Fetch documentation from URL.""" return "..." * 5000 # Large doc content + # Wrap everything llm = HeadroomChatModel(ChatOpenAI(model="gpt-4o")) tools = wrap_tools_with_headroom([search_arxiv, search_github, fetch_documentation]) -prompt = ChatPromptTemplate.from_messages([ - ("system", "You are a research assistant. Use tools to gather information."), - ("human", "{input}"), - ("placeholder", "{agent_scratchpad}"), -]) +prompt = ChatPromptTemplate.from_messages( + [ + ("system", "You are a research assistant. Use tools to gather information."), + ("human", "{input}"), + ("placeholder", "{agent_scratchpad}"), + ] +) agent = create_openai_tools_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, verbose=True) @@ -532,9 +565,11 @@ executor = AgentExecutor(agent=agent, tools=tools, verbose=True) reset_tool_metrics() # Run complex research task -result = executor.invoke({ - "input": "Research the latest advances in LLM context compression and find relevant GitHub projects" -}) +result = executor.invoke( + { + "input": "Research the latest advances in LLM context compression and find relevant GitHub projects" + } +) # Check per-tool metrics metrics = get_tool_metrics().get_summary() @@ -550,9 +585,9 @@ print(f"Per-tool breakdown: {metrics['by_tool']}") ```python HeadroomChatModel( - wrapped_model, # Any LangChain BaseChatModel + wrapped_model, # Any LangChain BaseChatModel headroom_config=HeadroomConfig(), # Headroom configuration - auto_detect_provider=True, # Auto-detect from wrapped model + auto_detect_provider=True, # Auto-detect from wrapped model ) ``` @@ -560,10 +595,10 @@ HeadroomChatModel( ```python HeadroomChatMessageHistory( - base_history, # Any BaseChatMessageHistory - compress_threshold_tokens=4000, # Token threshold for compression - keep_recent_turns=5, # Minimum turns to preserve - model="gpt-4o", # Model for token counting + base_history, # Any BaseChatMessageHistory + compress_threshold_tokens=4000, # Token threshold for compression + keep_recent_turns=5, # Minimum turns to preserve + model="gpt-4o", # Model for token counting ) ``` @@ -571,9 +606,9 @@ HeadroomChatMessageHistory( ```python HeadroomDocumentCompressor( - max_documents=10, # Maximum docs to return - min_relevance=0.0, # Minimum relevance score (0-1) - prefer_diverse=False, # Use MMR for diversity + max_documents=10, # Maximum docs to return + min_relevance=0.0, # Minimum relevance score (0-1) + prefer_diverse=False, # Use MMR for diversity ) ``` @@ -581,9 +616,9 @@ HeadroomDocumentCompressor( ```python wrap_tools_with_headroom( - tools, # List of LangChain tools - min_chars_to_compress=1000, # Minimum output size - smart_crusher_config=None, # SmartCrusher configuration + tools, # List of LangChain tools + min_chars_to_compress=1000, # Minimum output size + smart_crusher_config=None, # SmartCrusher configuration ) ``` @@ -595,27 +630,21 @@ wrap_tools_with_headroom( from headroom.integrations import ( # Chat Model HeadroomChatModel, - # Memory HeadroomChatMessageHistory, - # Retrievers HeadroomDocumentCompressor, - # Agents HeadroomToolWrapper, wrap_tools_with_headroom, get_tool_metrics, reset_tool_metrics, - # Streaming StreamingMetricsTracker, StreamingMetricsCallback, track_streaming_response, - # LangSmith HeadroomLangSmithCallbackHandler, - # Provider Detection detect_provider, get_headroom_provider, @@ -660,7 +689,7 @@ Check that your message count exceeds the threshold: history = HeadroomChatMessageHistory( base_history, compress_threshold_tokens=1000, # Lower threshold - keep_recent_turns=2, # Fewer preserved turns + keep_recent_turns=2, # Fewer preserved turns ) ``` diff --git a/wiki/memory.md b/wiki/memory.md index f699c4015..30d35e7f7 100644 --- a/wiki/memory.md +++ b/wiki/memory.md @@ -159,15 +159,13 @@ client = with_memory(OpenAI(), user_id="alice") # Use exactly like normal response = client.chat.completions.create( - model="gpt-4o", - messages=[{"role": "user", "content": "I prefer Python for backend work"}] + model="gpt-4o", messages=[{"role": "user", "content": "I prefer Python for backend work"}] ) # Memory extracted INLINE - zero extra latency # Later, in a new conversation... response = client.chat.completions.create( - model="gpt-4o", - messages=[{"role": "user", "content": "What language should I use?"}] + model="gpt-4o", messages=[{"role": "user", "content": "What language should I use?"}] ) # → Response uses the Python preference from memory ``` @@ -228,7 +226,7 @@ client1 = with_memory( ) response = client1.chat.completions.create( model="gpt-4o", - messages=[{"role": "user", "content": "I prefer Go for performance-critical code"}] + messages=[{"role": "user", "content": "I prefer Go for performance-critical code"}], ) # Memory stored at USER level (persists across sessions) @@ -239,8 +237,7 @@ client2 = with_memory( session_id="afternoon-session", # Different session ) response = client2.chat.completions.create( - model="gpt-4o", - messages=[{"role": "user", "content": "What language for my new microservice?"}] + model="gpt-4o", messages=[{"role": "user", "content": "What language for my new microservice?"}] ) # → Recalls Go preference from morning session! ``` @@ -270,17 +267,21 @@ new = await memory.supersede( ) # Query current state (excludes superseded) -current = await memory.query(MemoryFilter( - user_id="alice", - include_superseded=False, # Default -)) +current = await memory.query( + MemoryFilter( + user_id="alice", + include_superseded=False, # Default + ) +) # → Returns only "User now works at Anthropic" # Query full history (includes superseded) -history = await memory.query(MemoryFilter( - user_id="alice", - include_superseded=True, -)) +history = await memory.query( + MemoryFilter( + user_id="alice", + include_superseded=True, + ) +) # → Returns both memories with validity timestamps # Get the chain @@ -362,6 +363,7 @@ from headroom.memory import ( ) from headroom.memory.ports import MemoryFilter, VectorFilter + async def main(): # Create with custom configuration config = MemoryConfig( @@ -402,17 +404,20 @@ async def main(): ) # Query with filters - memories = await memory.query(MemoryFilter( - user_id="alice", - categories=[MemoryCategory.PREFERENCE, MemoryCategory.FACT], - min_importance=0.7, - limit=10, - )) + memories = await memory.query( + MemoryFilter( + user_id="alice", + categories=[MemoryCategory.PREFERENCE, MemoryCategory.FACT], + min_importance=0.7, + limit=10, + ) + ) # Convenience methods await memory.remember("Likes coffee", user_id="alice", importance=0.6) relevant = await memory.recall("beverage preferences", user_id="alice") + asyncio.run(main()) ``` @@ -482,13 +487,13 @@ Apple GPU instead of the default ONNX CPU embedder. Notes: ```python config = MemoryConfig( - db_path="memory.db", # SQLite database path - vector_dimension=384, # Must match embedder output - hnsw_ef_construction=200, # HNSW index quality (higher = better, slower) - hnsw_m=16, # HNSW connections per node - hnsw_ef_search=50, # HNSW search quality - cache_enabled=True, # Enable LRU cache - cache_max_size=1000, # Max cached memories + db_path="memory.db", # SQLite database path + vector_dimension=384, # Must match embedder output + hnsw_ef_construction=200, # HNSW index quality (higher = better, slower) + hnsw_m=16, # HNSW connections per node + hnsw_ef_search=50, # HNSW search quality + cache_enabled=True, # Enable LRU cache + cache_max_size=1000, # Max cached memories ) ``` @@ -499,7 +504,7 @@ client = with_memory( OpenAI(), user_id="alice", db_path="memory.db", - top_k=5, # Memories to inject per request + top_k=5, # Memories to inject per request session_id="optional-session", agent_id="optional-agent", embedder_backend=EmbedderBackend.LOCAL, @@ -659,6 +664,7 @@ client = with_memory( # Groq from groq import Groq + client = with_memory(Groq(), user_id="alice") # Any OpenAI-compatible client @@ -678,10 +684,12 @@ client = with_memory(OpenAI(), user_id="developer_jane") # Conversation 1: User shares context response = client.chat.completions.create( model="gpt-4o", - messages=[{ - "role": "user", - "content": "I'm a Python developer at a fintech startup. We use PostgreSQL and FastAPI." - }] + messages=[ + { + "role": "user", + "content": "I'm a Python developer at a fintech startup. We use PostgreSQL and FastAPI.", + } + ], ) # Memories extracted: # - [FACT] Python developer at fintech startup @@ -691,10 +699,7 @@ response = client.chat.completions.create( # Conversation 2 (new session): User asks question response = client.chat.completions.create( model="gpt-4o", - messages=[{ - "role": "user", - "content": "What database should I use for my new project?" - }] + messages=[{"role": "user", "content": "What database should I use for my new project?"}], ) # Response references PostgreSQL preference from memory: # → "Given your experience with PostgreSQL at your fintech company, diff --git a/wiki/metrics.md b/wiki/metrics.md index 01c09612e..12f8d0c5f 100644 --- a/wiki/metrics.md +++ b/wiki/metrics.md @@ -336,19 +336,19 @@ print(stats) "tokens_saved_total": 15000, "tokens_output_total": 8000, "cache_hits": 3, - "compression_ratio_avg": 0.70 + "compression_ratio_avg": 0.70, }, "config": { "mode": "optimize", "provider": "openai", "cache_optimizer_enabled": True, - "semantic_cache_enabled": False + "semantic_cache_enabled": False, }, "transforms": { "smart_crusher_enabled": True, "cache_aligner_enabled": True, - "rolling_window_enabled": True - } + "rolling_window_enabled": True, + }, } ``` diff --git a/wiki/plans/dynamic-smart-crusher.md b/wiki/plans/dynamic-smart-crusher.md index 88754bf79..648927eb2 100644 --- a/wiki/plans/dynamic-smart-crusher.md +++ b/wiki/plans/dynamic-smart-crusher.md @@ -72,17 +72,18 @@ Different data patterns need different position importance: ```python class AnchorStrategy(Enum): - FRONT_HEAVY = "front_heavy" # Search results: top items matter most - BACK_HEAVY = "back_heavy" # Logs: recent items matter most - BALANCED = "balanced" # Time series: both ends matter - MIDDLE_AWARE = "middle_aware" # Database: order might be arbitrary + FRONT_HEAVY = "front_heavy" # Search results: top items matter most + BACK_HEAVY = "back_heavy" # Logs: recent items matter most + BALANCED = "balanced" # Time series: both ends matter + MIDDLE_AWARE = "middle_aware" # Database: order might be arbitrary + def get_anchor_strategy(pattern: DataPattern) -> AnchorStrategy: return { DataPattern.SEARCH_RESULTS: AnchorStrategy.FRONT_HEAVY, # Top N by score - DataPattern.LOGS: AnchorStrategy.BACK_HEAVY, # Recency matters - DataPattern.TIME_SERIES: AnchorStrategy.BALANCED, # Both ends for trend - DataPattern.GENERIC: AnchorStrategy.MIDDLE_AWARE, # Don't assume order + DataPattern.LOGS: AnchorStrategy.BACK_HEAVY, # Recency matters + DataPattern.TIME_SERIES: AnchorStrategy.BALANCED, # Both ends for trend + DataPattern.GENERIC: AnchorStrategy.MIDDLE_AWARE, # Don't assume order }.get(pattern, AnchorStrategy.BALANCED) ``` @@ -150,7 +151,7 @@ def select_informative_anchors( items: list[dict], region: str, # "front", "back", "middle" slots: int, - all_items_hash: set[str] + all_items_hash: set[str], ) -> list[int]: """Select most informative items from a region.""" @@ -195,7 +196,7 @@ def calculate_information_score(item: dict, all_items: list[dict]) -> float: for field, value in item.items(): field_values = [i.get(field) for i in all_items if field in i] value_frequency = field_values.count(value) / len(field_values) - score += (1 - value_frequency) # Rare values score higher + score += 1 - value_frequency # Rare values score higher # 2. Structural uniqueness - different fields than typical typical_fields = get_typical_fields(all_items) @@ -221,6 +222,7 @@ Track which positions users actually retrieve and learn from it: @dataclass class PositionRetrievalPattern: """Learned position importance from retrieval data.""" + tool_name: str total_compressions: int position_retrievals: dict[str, int] # "front_10%", "middle", "back_10%" @@ -231,21 +233,13 @@ class PositionRetrievalPattern: if total == 0: return {"front": 0.5, "middle": 0.0, "back": 0.5} - return { - position: count / total - for position, count in self.position_retrievals.items() - } + return {position: count / total for position, count in self.position_retrievals.items()} class TOINPositionLearning: """Learn position importance from retrieval patterns.""" - def record_retrieval( - self, - tool_name: str, - original_size: int, - retrieved_indices: list[int] - ): + def record_retrieval(self, tool_name: str, original_size: int, retrieved_indices: list[int]): """Record which positions were retrieved.""" for idx in retrieved_indices: position = self._classify_position(idx, original_size) @@ -287,9 +281,7 @@ For large arrays, sample strategically from middle: ```python def stratified_middle_sample( - items: list[dict], - num_samples: int, - analysis: ArrayAnalysis + items: list[dict], num_samples: int, analysis: ArrayAnalysis ) -> list[int]: """Sample middle positions using stratified approach.""" @@ -310,9 +302,7 @@ def stratified_middle_sample( if analysis.numeric_fields: variance_scores = calculate_position_variance(items, analysis.numeric_fields) sorted_by_variance = sorted( - middle_items, - key=lambda i: variance_scores.get(i, 0), - reverse=True + middle_items, key=lambda i: variance_scores.get(i, 0), reverse=True ) return sorted(sorted_by_variance[:num_samples]) @@ -383,11 +373,7 @@ class TestAdversarialPositions: items[42]["name"] = "target_item" items[42]["description"] = "This is what user asked about" - result = smart_crusher.crush( - items, - max_items=10, - query="find target_item" - ) + result = smart_crusher.crush(items, max_items=10, query="find target_item") # Query-matched item MUST be preserved assert any("target_item" in item.get("name", "") for item in result) @@ -399,12 +385,15 @@ class TestAdversarialPositions: class TestSizeAdaptation: """Test that anchor allocation scales with array size.""" - @pytest.mark.parametrize("size,expected_min_anchors", [ - (20, 3), # Small array: at least 3 anchors - (100, 4), # Medium array: at least 4 anchors - (500, 5), # Large array: at least 5 anchors - (2000, 6), # Very large: at least 6 anchors - ]) + @pytest.mark.parametrize( + "size,expected_min_anchors", + [ + (20, 3), # Small array: at least 3 anchors + (100, 4), # Medium array: at least 4 anchors + (500, 5), # Large array: at least 5 anchors + (2000, 6), # Very large: at least 6 anchors + ], + ) def test_anchor_count_scales(self, size, expected_min_anchors): """Anchor count should increase with array size.""" items = [{"id": i, "value": i * 10} for i in range(size)] @@ -413,8 +402,7 @@ class TestSizeAdaptation: # Count items from first 10% and last 10% anchor_count = sum( - 1 for item in result - if item["id"] < size * 0.1 or item["id"] > size * 0.9 + 1 for item in result if item["id"] < size * 0.1 or item["id"] > size * 0.9 ) assert anchor_count >= expected_min_anchors @@ -454,10 +442,7 @@ class TestPatternAwareAnchoring: def test_search_results_front_heavy(self): """Search results should preserve more from front.""" - items = [ - {"title": f"Result {i}", "score": 1.0 - (i * 0.01)} - for i in range(100) - ] + items = [{"title": f"Result {i}", "score": 1.0 - (i * 0.01)} for i in range(100)] result = smart_crusher.crush(items, max_items=10) @@ -485,10 +470,7 @@ class TestPatternAwareAnchoring: def test_time_series_balanced(self): """Time series should have balanced front/back.""" - items = [ - {"timestamp": f"2024-01-01T{i:02d}:00:00", "value": 100 + i} - for i in range(24) - ] + items = [{"timestamp": f"2024-01-01T{i:02d}:00:00", "value": 100 + i} for i in range(24)] result = smart_crusher.crush(items, max_items=8) @@ -510,11 +492,7 @@ class TestQueryAwareAnchoring: """'Latest' in query should preserve more recent items.""" items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)] - result = smart_crusher.crush( - items, - max_items=8, - query="Show me the latest entries" - ) + result = smart_crusher.crush(items, max_items=8, query="Show me the latest entries") ids = [item["id"] for item in result] recent_count = sum(1 for id in ids if id > 20) @@ -525,11 +503,7 @@ class TestQueryAwareAnchoring: """'First' in query should preserve earlier items.""" items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)] - result = smart_crusher.crush( - items, - max_items=8, - query="Show me the first entries" - ) + result = smart_crusher.crush(items, max_items=8, query="Show me the first entries") ids = [item["id"] for item in result] early_count = sum(1 for id in ids if id < 10) @@ -540,11 +514,7 @@ class TestQueryAwareAnchoring: """Query for specific ID should find it regardless of position.""" items = [{"id": f"item_{i:04d}", "value": i} for i in range(1000)] - result = smart_crusher.crush( - items, - max_items=10, - query="Find item_0567" - ) + result = smart_crusher.crush(items, max_items=10, query="Find item_0567") assert any(item["id"] == "item_0567" for item in result) ``` @@ -574,8 +544,7 @@ class TestCoverageMetrics: def test_category_coverage(self): """Preserved items should represent all categories.""" items = [ - {"category": cat, "id": i} - for i, cat in enumerate(["A"] * 30 + ["B"] * 30 + ["C"] * 40) + {"category": cat, "id": i} for i, cat in enumerate(["A"] * 30 + ["B"] * 30 + ["C"] * 40) ] result = smart_crusher.crush(items, max_items=10) @@ -608,10 +577,7 @@ class TestRetrievalSimulation: def test_retrieval_hit_rate_random_queries(self): """Measure how often preserved items satisfy random queries.""" - items = [ - {"id": i, "name": f"Item {i}", "category": f"cat_{i % 5}"} - for i in range(100) - ] + items = [{"id": i, "name": f"Item {i}", "category": f"cat_{i % 5}"} for i in range(100)] compressed = smart_crusher.crush(items, max_items=15) compressed_ids = {item["id"] for item in compressed} @@ -636,9 +602,9 @@ class TestRetrievalSimulation: # Weight queries toward front (30%), back (30%), anomalies (40%) hits = 0 queries = ( - list(range(10)) * 3 + # Front queries - list(range(90, 100)) * 3 + # Back queries - [50] * 4 # Middle anomaly queries + list(range(10)) * 3 # Front queries + + list(range(90, 100)) * 3 # Back queries + + [50] * 4 # Middle anomaly queries ) for target_id in queries: @@ -718,12 +684,12 @@ class AnchorConfig: time_series_balance: float = 0.5 # Query keyword detection - recency_keywords: list[str] = field(default_factory=lambda: [ - "latest", "recent", "last", "newest", "current" - ]) - historical_keywords: list[str] = field(default_factory=lambda: [ - "first", "oldest", "earliest", "original", "initial" - ]) + recency_keywords: list[str] = field( + default_factory=lambda: ["latest", "recent", "last", "newest", "current"] + ) + historical_keywords: list[str] = field( + default_factory=lambda: ["first", "oldest", "earliest", "original", "initial"] + ) # Information density selection use_information_density: bool = True diff --git a/wiki/proxy.md b/wiki/proxy.md index 184640916..03c586b68 100644 --- a/wiki/proxy.md +++ b/wiki/proxy.md @@ -323,13 +323,18 @@ The provider caches the bytes you *forwarded*, which compression already changed ```python forwarded = [] + + def next_turn(new_messages): - r = requests.post(f"{proxy}/v1/compress", json={ - "messages": forwarded + new_messages, - "model": "claude-sonnet-4-6", - "config": {"frozen_message_count": len(forwarded)}, - }).json() - forwarded[:] = r["messages"] # next turn's frozen prefix + r = requests.post( + f"{proxy}/v1/compress", + json={ + "messages": forwarded + new_messages, + "model": "claude-sonnet-4-6", + "config": {"frozen_message_count": len(forwarded)}, + }, + ).json() + forwarded[:] = r["messages"] # next turn's frozen prefix return forwarded ``` diff --git a/wiki/quickstart.md b/wiki/quickstart.md index 417b779b0..405b32404 100644 --- a/wiki/quickstart.md +++ b/wiki/quickstart.md @@ -149,19 +149,21 @@ messages = [ { "role": "assistant", "content": None, - "tool_calls": [{ - "id": "call_1", - "type": "function", - "function": {"name": "search", "arguments": '{"q": "python"}'}, - }], + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "search", "arguments": '{"q": "python"}'}, + } + ], }, { "role": "tool", "tool_call_id": "call_1", # This is where Headroom shines - compressing large outputs - "content": json.dumps({ - "results": [{"title": f"Result {i}", "score": 100-i} for i in range(500)] - }), + "content": json.dumps( + {"results": [{"title": f"Result {i}", "score": 100 - i} for i in range(500)]} + ), }, {"role": "user", "content": "What are the top 3 results?"}, ] @@ -188,7 +190,9 @@ plan = client.chat.completions.simulate( print(f"Tokens before: {plan.tokens_before}") print(f"Tokens after: {plan.tokens_after}") -print(f"Would save: {plan.tokens_saved} tokens ({plan.tokens_saved/plan.tokens_before*100:.0f}%)") +print( + f"Would save: {plan.tokens_saved} tokens ({plan.tokens_saved / plan.tokens_before * 100:.0f}%)" +) print(f"Transforms: {plan.transforms}") print(f"Estimated savings: {plan.estimated_savings}") ``` @@ -227,6 +231,7 @@ print(response.content[0].text) ```python import logging + logging.basicConfig(level=logging.INFO) # Now you'll see: @@ -347,6 +352,7 @@ print(stats["config"]["mode"]) # Should be "optimize" # 2. Enable logging to see what's happening import logging + logging.basicConfig(level=logging.DEBUG) ``` diff --git a/wiki/sdk.md b/wiki/sdk.md index 81fe5af90..674d5c3e6 100644 --- a/wiki/sdk.md +++ b/wiki/sdk.md @@ -45,30 +45,26 @@ messages = [ { "role": "assistant", "content": None, - "tool_calls": [{ - "id": "call_123", - "type": "function", - "function": {"name": "search", "arguments": '{"q": "python"}'}, - }], + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": {"name": "search", "arguments": '{"q": "python"}'}, + } + ], }, { "role": "tool", "tool_call_id": "call_123", - "content": json.dumps({ - "results": [ - {"title": f"Tutorial {i}", "score": 100-i} - for i in range(500) - ] - }), + "content": json.dumps( + {"results": [{"title": f"Tutorial {i}", "score": 100 - i} for i in range(500)]} + ), }, {"role": "user", "content": "What are the top 3?"}, ] # Headroom compresses 500 results to ~15, keeping highest-scoring items -response = client.chat.completions.create( - model="gpt-4o-mini", - messages=messages -) +response = client.chat.completions.create(model="gpt-4o-mini", messages=messages) # Check savings stats = client.get_stats() @@ -187,13 +183,10 @@ print(f"Transforms: {plan.transforms}") response = client.chat.completions.create( model="gpt-4o", messages=[...], - # Override mode for this request headroom_mode="audit", - # Reserve more tokens for output headroom_output_buffer_tokens=8000, - # Keep last N turns headroom_keep_turns=5, ) @@ -203,6 +196,7 @@ response = client.chat.completions.create( ```python import logging + logging.basicConfig(level=logging.INFO) # Now you'll see: diff --git a/wiki/shared-context.md b/wiki/shared-context.md index 72a9f133f..949180e9c 100644 --- a/wiki/shared-context.md +++ b/wiki/shared-context.md @@ -28,10 +28,10 @@ Store content under a key. Compresses automatically using Headroom's full pipeli ```python entry = ctx.put("findings", big_json_output, agent="researcher") -entry.original_tokens # 20,000 -entry.compressed_tokens # 4,000 -entry.savings_percent # 80.0 -entry.transforms # ["router:json:0.20"] +entry.original_tokens # 20,000 +entry.compressed_tokens # 4,000 +entry.savings_percent # 80.0 +entry.transforms # ["router:json:0.20"] ``` ### `get(key, *, full=False)` @@ -39,9 +39,9 @@ entry.transforms # ["router:json:0.20"] Retrieve content. Returns compressed version by default, original with `full=True`. ```python -compressed = ctx.get("findings") # 4K tokens +compressed = ctx.get("findings") # 4K tokens original = ctx.get("findings", full=True) # 20K tokens -missing = ctx.get("nonexistent") # None +missing = ctx.get("nonexistent") # None ``` ### `get_entry(key)` @@ -50,13 +50,13 @@ Get the full `ContextEntry` with metadata. ```python entry = ctx.get_entry("findings") -entry.key # "findings" -entry.agent # "researcher" -entry.original_tokens # 20000 +entry.key # "findings" +entry.agent # "researcher" +entry.original_tokens # 20000 entry.compressed_tokens # 4000 -entry.savings_percent # 80.0 -entry.timestamp # 1710000000.0 -entry.transforms # ["router:json:0.20"] +entry.savings_percent # 80.0 +entry.timestamp # 1710000000.0 +entry.transforms # ["router:json:0.20"] ``` ### `keys()` @@ -69,11 +69,11 @@ Aggregated stats across all entries. ```python stats = ctx.stats() -stats.entries # 3 -stats.total_original_tokens # 60000 +stats.entries # 3 +stats.total_original_tokens # 60000 stats.total_compressed_tokens # 12000 -stats.total_tokens_saved # 48000 -stats.savings_percent # 80.0 +stats.total_tokens_saved # 48000 +stats.savings_percent # 80.0 ``` ### `clear()` @@ -85,8 +85,8 @@ Remove all entries. ```python ctx = SharedContext( model="claude-sonnet-4-5-20250929", # For token counting - ttl=3600, # 1 hour (default) - max_entries=100, # Evicts oldest when full + ttl=3600, # 1 hour (default) + max_entries=100, # Evicts oldest when full ) ``` @@ -113,11 +113,13 @@ from headroom import SharedContext ctx = SharedContext() + def researcher_node(state): result = do_research() ctx.put("research", result) return {"research_summary": ctx.get("research")} + def coder_node(state): # Compressed summary in state, full details on demand full = ctx.get("research", full=True) @@ -131,6 +133,7 @@ from headroom import SharedContext ctx = SharedContext() + def compress_handoff(messages): for msg in messages: if len(msg.content) > 1000: @@ -138,6 +141,7 @@ def compress_handoff(messages): msg.content = ctx.get(msg.id) return messages + handoff(agent=coder, input_filter=compress_handoff) ``` diff --git a/wiki/strands.md b/wiki/strands.md index 585340c35..172e21b2b 100644 --- a/wiki/strands.md +++ b/wiki/strands.md @@ -114,11 +114,13 @@ HeadroomStrandsModel supports Strands' structured output feature: ```python from pydantic import BaseModel + class Analysis(BaseModel): severity: str root_cause: str recommendation: str + result = optimized.structured_output(Analysis, messages) ``` diff --git a/wiki/text-compression.md b/wiki/text-compression.md index 8fd4d743a..8656ea7e9 100644 --- a/wiki/text-compression.md +++ b/wiki/text-compression.md @@ -136,10 +136,14 @@ elif detection.content_type == ContentType.PLAIN_TEXT: ```python from headroom.transforms import ( - detect_content_type, ContentType, - SearchCompressor, LogCompressor, TextCompressor + detect_content_type, + ContentType, + SearchCompressor, + LogCompressor, + TextCompressor, ) + def compress_tool_output(content: str, context: str = "") -> str: """Application-level compression with explicit control.""" detection = detect_content_type(content) @@ -166,7 +170,7 @@ Each compressor accepts configuration options: from headroom.transforms import SearchCompressor, SearchCompressorConfig config = SearchCompressorConfig( - max_results=50, # Keep up to 50 matches + max_results=50, # Keep up to 50 matches preserve_file_diversity=True, # Ensure different files represented relevance_threshold=0.3, # Minimum relevance score to keep ) diff --git a/wiki/transforms.md b/wiki/transforms.md index 30ea74f7f..b599b5164 100644 --- a/wiki/transforms.md +++ b/wiki/transforms.md @@ -22,13 +22,13 @@ SmartCrusher analyzes JSON arrays and selectively keeps important items: from headroom import SmartCrusherConfig config = SmartCrusherConfig( - min_tokens_to_crush=200, # Only compress if > 200 tokens - max_items_after_crush=50, # Keep at most 50 items - keep_first=3, # Always keep first 3 items - keep_last=2, # Always keep last 2 items - relevance_threshold=0.3, # Keep items with relevance > 0.3 - anomaly_std_threshold=2.0, # Keep items > 2 std dev from mean - preserve_errors=True, # Always keep error items + min_tokens_to_crush=200, # Only compress if > 200 tokens + max_items_after_crush=50, # Keep at most 50 items + keep_first=3, # Always keep first 3 items + keep_last=2, # Always keep last 2 items + relevance_threshold=0.3, # Keep items with relevance > 0.3 + anomaly_std_threshold=2.0, # Keep items > 2 std dev from mean + preserve_errors=True, # Always keep error items ) ``` @@ -94,9 +94,9 @@ result = aligner.align(messages) from headroom import CacheAlignerConfig config = CacheAlignerConfig( - extract_dates=True, # Move dates to dynamic section - normalize_whitespace=True, # Consistent spacing - stable_prefix_min_tokens=100, # Min prefix size for alignment + extract_dates=True, # Move dates to dynamic section + normalize_whitespace=True, # Consistent spacing + stable_prefix_min_tokens=100, # Min prefix size for alignment ) ``` @@ -170,16 +170,16 @@ pip install "headroom-ai[code]" # Adds tree-sitter-language-pack from headroom.transforms import CodeAwareCompressor, CodeCompressorConfig, DocstringMode config = CodeCompressorConfig( - preserve_imports=True, # Always keep imports - preserve_signatures=True, # Always keep function signatures - preserve_type_annotations=True, # Keep type hints - preserve_error_handlers=True, # Keep try/except blocks - preserve_decorators=True, # Keep decorators + preserve_imports=True, # Always keep imports + preserve_signatures=True, # Always keep function signatures + preserve_type_annotations=True, # Keep type hints + preserve_error_handlers=True, # Keep try/except blocks + preserve_decorators=True, # Keep decorators docstring_mode=DocstringMode.FIRST_LINE, # FULL, FIRST_LINE, REMOVE - target_compression_rate=0.2, # Keep 20% of tokens - max_body_lines=5, # Lines to keep per function body - min_tokens_for_compression=100, # Skip small content - language_hint=None, # Auto-detect if None + target_compression_rate=0.2, # Keep 20% of tokens + max_body_lines=5, # Lines to keep per function body + min_tokens_for_compression=100, # Skip small content + language_hint=None, # Auto-detect if None ) compressor = CodeAwareCompressor(config) @@ -263,10 +263,10 @@ ContentRouter analyzes content and selects the best compression strategy: from headroom.transforms import ContentRouter, ContentRouterConfig, CompressionStrategy config = ContentRouterConfig( - min_section_tokens=100, # Minimum tokens to compress - enable_code_aware=True, # Use CodeAwareCompressor for code - enable_search_compression=True, # Use SearchCompressor for grep output - enable_log_compression=True, # Use LogCompressor for logs + min_section_tokens=100, # Minimum tokens to compress + enable_code_aware=True, # Use CodeAwareCompressor for code + enable_search_compression=True, # Use SearchCompressor for grep output + enable_log_compression=True, # Use LogCompressor for logs default_strategy=CompressionStrategy.TEXT, # Fallback strategy ) @@ -331,10 +331,12 @@ Combine transforms for optimal results. ```python from headroom import TransformPipeline, SmartCrusher, CacheAligner -pipeline = TransformPipeline([ - SmartCrusher(), # First: compress tool outputs - CacheAligner(), # Then: stabilize prefix -]) +pipeline = TransformPipeline( + [ + SmartCrusher(), # First: compress tool outputs + CacheAligner(), # Then: stabilize prefix + ] +) result = pipeline.transform(messages) print(f"Saved {result.tokens_saved} tokens") diff --git a/wiki/troubleshooting.md b/wiki/troubleshooting.md index 35907f057..ca505fcb0 100644 --- a/wiki/troubleshooting.md +++ b/wiki/troubleshooting.md @@ -224,6 +224,7 @@ client = HeadroomClient( # 2. For temp directory storage import tempfile import os + db_path = os.path.join(tempfile.gettempdir(), "headroom.db") client = HeadroomClient( original_client=OpenAI(), @@ -287,6 +288,7 @@ pip install --upgrade headroom-ai ```python # Check available imports import headroom + print(dir(headroom)) # Common imports: @@ -397,6 +399,7 @@ print(f"Waste signals: {plan.waste_signals}") # See the actual optimized messages import json + print(json.dumps(plan.messages_optimized, indent=2)) ```