deps: bump ruff from 0.15.22 to 0.16.2 in the pip-minor-patch group across 1 directory (#2962)
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 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/astral-sh/ruff/releases">ruff's releases</a>.</em></p> <blockquote> <h2>0.16.2</h2> <h2>Release Notes</h2> <p>Released on 2026-08-06.</p> <h3>Bug fixes</h3> <ul> <li>[<code>flake8-pyi</code>] Avoid false positives on <code>singledispatch</code> functions (<code>PYI041</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27335">#27335</a>)</li> </ul> <h3>Server</h3> <ul> <li>Register formatting capabilities dynamically to exclude TOML files (<a href="https://redirect.github.com/astral-sh/ruff/pull/27332">#27332</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/MeGaGiGaGon"><code>@MeGaGiGaGon</code></a></li> <li><a href="https://github.com/charliermarsh"><code>@charliermarsh</code></a></li> <li><a href="https://github.com/epage"><code>@epage</code></a></li> <li><a href="https://github.com/sharkdp"><code>@sharkdp</code></a></li> <li><a href="https://github.com/ntBre"><code>@ntBre</code></a></li> </ul> <h2>Install ruff 0.16.2</h2> <h3>Install prebuilt binaries via shell script</h3> <pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.sh | sh </code></pre> <h3>Install prebuilt binaries via powershell script</h3> <pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c "irm https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.ps1 | iex" </code></pre> <h2>Download ruff 0.16.2</h2> <table> <thead> <tr> <th>File</th> <th>Platform</th> <th>Checksum</th> </tr> </thead> <tbody> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-apple-darwin.tar.gz">ruff-aarch64-apple-darwin.tar.gz</a></td> <td>Apple Silicon macOS</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-apple-darwin.tar.gz">ruff-x86_64-apple-darwin.tar.gz</a></td> <td>Intel macOS</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-pc-windows-msvc.zip">ruff-aarch64-pc-windows-msvc.zip</a></td> <td>ARM64 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-pc-windows-msvc.zip">ruff-i686-pc-windows-msvc.zip</a></td> <td>x86 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-pc-windows-msvc.zip">ruff-x86_64-pc-windows-msvc.zip</a></td> <td>x64 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-unknown-linux-gnu.tar.gz">ruff-aarch64-unknown-linux-gnu.tar.gz</a></td> <td>ARM64 Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-unknown-linux-gnu.tar.gz">ruff-i686-unknown-linux-gnu.tar.gz</a></td> <td>x86 Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64-unknown-linux-gnu.tar.gz">ruff-powerpc64-unknown-linux-gnu.tar.gz</a></td> <td>PPC64 Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64le-unknown-linux-gnu.tar.gz">ruff-powerpc64le-unknown-linux-gnu.tar.gz</a></td> <td>PPC64LE Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-riscv64gc-unknown-linux-gnu.tar.gz">ruff-riscv64gc-unknown-linux-gnu.tar.gz</a></td> <td>RISCV Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-s390x-unknown-linux-gnu.tar.gz">ruff-s390x-unknown-linux-gnu.tar.gz</a></td> <td>S390x Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-s390x-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> </tbody> </table> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's changelog</a>.</em></p> <blockquote> <h2>0.16.2</h2> <p>Released on 2026-08-06.</p> <h3>Bug fixes</h3> <ul> <li>[<code>flake8-pyi</code>] Avoid false positives on <code>singledispatch</code> functions (<code>PYI041</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27335">#27335</a>)</li> </ul> <h3>Server</h3> <ul> <li>Register formatting capabilities dynamically to exclude TOML files (<a href="https://redirect.github.com/astral-sh/ruff/pull/27332">#27332</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/MeGaGiGaGon"><code>@MeGaGiGaGon</code></a></li> <li><a href="https://github.com/charliermarsh"><code>@charliermarsh</code></a></li> <li><a href="https://github.com/epage"><code>@epage</code></a></li> <li><a href="https://github.com/sharkdp"><code>@sharkdp</code></a></li> <li><a href="https://github.com/ntBre"><code>@ntBre</code></a></li> </ul> <h2>0.16.1</h2> <p>Released on 2026-07-30.</p> <h3>Preview features</h3> <ul> <li>Add an option to opt out of human-readable names (<a href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li> <li>[<code>flake8-pytest-style</code>] Make fixes safe by default and unsafe only when comments are present (<code>PT018</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li> <li>[<code>pyupgrade</code>] Skip fix when a defaulted <code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>, <code>UP046</code>, <code>UP047</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li> <li>[<code>ruff</code>] Fix false positive with unpacked arguments (<code>RUF065</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li> </ul> <h3>Bug fixes</h3> <ul> <li>Bump <code>gen-lsp-types</code> to gracefully handle unknown enumeration values in LSP messages (<a href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li> <li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable (<code>B008</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li> <li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in <code>C408</code> fix (<a href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li> <li>[<code>flake8-return</code>] Fix false positive when variable is read in <code>finally</code> clause (<code>RET504</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li> <li>[<code>pydocstyle</code>] Skip section detection inside RST directive bodies (<code>D214</code>, <code>D405</code>, <code>D413</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li> <li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in the <code>FURB192</code> fix (<a href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li> </ul> <h3>Rule changes</h3> <ul> <li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as unsafe (<a href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li> <li>[<code>refurb</code>] Mark fixes that remove unknown separators as unsafe (<code>FURB105</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li> </ul> <h3>Server</h3> <ul> <li>Fix indexing of excluded nested Ruff workspaces (<a href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li> <li>Lint TOML files in the LSP (<a href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/astral-sh/ruff/commit/5b48a040974781ba90b47c8df628f8fd9b6c95dd"><code>5b48a04</code></a> Bump 0.16.2 (<a href="https://redirect.github.com/astral-sh/ruff/issues/27555">#27555</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/1b9e5fc483b95a01fe02ff104820280b1b32e8ae"><code>1b9e5fc</code></a> Update Swatinem/rust-cache action to v2.9.2 (<a href="https://redirect.github.com/astral-sh/ruff/issues/27568">#27568</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/c4e86fc0394c92a9334ba2eb026c77c21db403be"><code>c4e86fc</code></a> [ty] Add helper extension methods for half-range and equality constraints (<a href="https://redirect.github.com/astral-sh/ruff/issues/2">#2</a>...</li> <li><a href="https://github.com/astral-sh/ruff/commit/17a00de2e298612201a8fe30790e9399204af1b9"><code>17a00de</code></a> [ty] Reuse primer commands in memory reports (<a href="https://redirect.github.com/astral-sh/ruff/issues/27553">#27553</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/6ea296b96923e142eb13af2bc6ad261c280d8eb1"><code>6ea296b</code></a> [ty] Normalize type labels in structured docstrings (<a href="https://redirect.github.com/astral-sh/ruff/issues/26923">#26923</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/2fc445f0053f4ec27c717fae0de3671d73c103be"><code>2fc445f</code></a> [ty] Diagnose invalid <strong>getattr</strong> calls (<a href="https://redirect.github.com/astral-sh/ruff/issues/27502">#27502</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/22c7823c4e8bffcca97688d8438c9b567d6817d8"><code>22c7823</code></a> [ty] Enable (but downrank) auto-import completion suggestions from stub-only ...</li> <li><a href="https://github.com/astral-sh/ruff/commit/05160d507f05345a72db9c28ab4edf7c92334819"><code>05160d5</code></a> [ty] Diagnose invalid descriptor <code>__get__</code> calls (<a href="https://redirect.github.com/astral-sh/ruff/issues/27400">#27400</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/baea3d0dcec6d6f6d1659321940f3725771c5f45"><code>baea3d0</code></a> [ty] Expose strict analysis options in the playground (<a href="https://redirect.github.com/astral-sh/ruff/issues/27543">#27543</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/c88946ebeb92be6d276087f0d528cd6471df4ead"><code>c88946e</code></a> [ty] Bump ecosystem-analyzer for strict project settings (<a href="https://redirect.github.com/astral-sh/ruff/issues/27542">#27542</a>)</li> <li>Additional commits viewable in <a href="https://github.com/astral-sh/ruff/compare/0.15.22...0.16.2">compare view</a></li> </ul> </details> <br /> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
+35
-32
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -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())
|
||||
```
|
||||
|
||||
|
||||
+9
-7
@@ -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)
|
||||
```
|
||||
|
||||
+33
-44
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+1
-16
@@ -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}",
|
||||
|
||||
+12
-21
@@ -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
|
||||
|
||||
+21
-17
@@ -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
|
||||
|
||||
@@ -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())
|
||||
```
|
||||
|
||||
|
||||
+83
-54
@@ -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
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
+42
-37
@@ -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,
|
||||
|
||||
+4
-4
@@ -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,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-6
@@ -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
|
||||
```
|
||||
|
||||
|
||||
+15
-9
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
+12
-18
@@ -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:
|
||||
|
||||
+22
-18
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+29
-27
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user