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 &quot;irm
https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.ps1
| iex&quot;
</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="5b48a04097"><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="1b9e5fc483"><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="c4e86fc039"><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="17a00de2e2"><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="6ea296b969"><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="2fc445f005"><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="22c7823c4e"><code>22c7823</code></a>
[ty] Enable (but downrank) auto-import completion suggestions from
stub-only ...</li>
<li><a
href="05160d507f"><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="baea3d0dce"><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="c88946ebeb"><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:
dependabot[bot] 2026-08-14 16:38:08 -05:00 committed by GitHub
parent bbe901319d
commit ff17961cd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 526 additions and 519 deletions

View file

@ -27,7 +27,7 @@ repos:
# unconditionally, so installing hooks is not required for enforcement. # unconditionally, so installing hooks is not required for enforcement.
args: [--assume-in-merge] args: [--assume-in-merge]
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.22 rev: v0.16.2
hooks: hooks:
- id: ruff - id: ruff
args: [--fix] args: [--fix]

View file

@ -33,7 +33,11 @@ assert report.passed
suite = ( suite = (
Headroom.Suite("phase-1") Headroom.Suite("phase-1")
.Add(Headroom.WithOpenAI().named("openai-cache").WithCompression(mode="cache")) .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) suite.write_manifest_bundle("headroom-testing-bundle.json", provider="openai", port_start=19000)

View file

@ -277,7 +277,7 @@ dev = [
"pytest>=7.0.0", "pytest>=7.0.0",
"pytest-cov>=4.0.0", "pytest-cov>=4.0.0",
"pytest-asyncio>=0.21.0", "pytest-asyncio>=0.21.0",
"ruff==0.15.22", "ruff==0.16.2",
"mypy>=1.0.0", "mypy>=1.0.0",
"pre-commit>=3.0.0", "pre-commit>=3.0.0",
"openai>=1.0.0", "openai>=1.0.0",

236
uv.lock generated
View file

@ -280,12 +280,12 @@ name = "any-llm-sdk"
version = "1.12.1" version = "1.12.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ dependencies = [
{ name = "httpx" }, { name = "httpx", marker = "python_full_version >= '3.11'" },
{ name = "openai" }, { name = "openai", marker = "python_full_version >= '3.11'" },
{ name = "openresponses-types" }, { name = "openresponses-types", marker = "python_full_version >= '3.11'" },
{ name = "pydantic" }, { name = "pydantic", marker = "python_full_version >= '3.11'" },
{ name = "rich" }, { name = "rich", marker = "python_full_version >= '3.11'" },
{ name = "typing-extensions" }, { 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" } 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 = [ wheels = [
@ -811,7 +811,7 @@ name = "coloredlogs"
version = "15.0.1" version = "15.0.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ wheels = [
@ -823,7 +823,7 @@ name = "colorlog"
version = "6.10.1" version = "6.10.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ wheels = [
@ -1045,7 +1045,7 @@ name = "cuda-bindings"
version = "13.3.1" version = "13.3.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = [ 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" }, { 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] [package.optional-dependencies]
cublas = [ cublas = [
{ name = "nvidia-cublas", 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 = "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')" },
] ]
cudart = [ 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 = [ cufft = [
{ name = "nvidia-cufft", 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 = "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')" },
] ]
cufile = [ 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 = [ 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 = [ 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 = [ cusolver = [
{ name = "nvidia-cublas", 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 = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, { 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 = "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 = "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')" },
] ]
cusparse = [ cusparse = [
{ name = "nvidia-cusparse", 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 = "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')" },
] ]
nvjitlink = [ 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 = [ 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 = [ 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]] [[package]]
@ -1220,7 +1220,7 @@ name = "exceptiongroup"
version = "1.3.1" version = "1.3.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ wheels = [
@ -1637,7 +1637,7 @@ name = "gunicorn"
version = "26.0.0" version = "26.0.0"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ 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 = "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 = "respx", marker = "extra == 'dev'", specifier = ">=0.20.0" },
{ name = "rich", specifier = ">=13.0.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 = "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 = "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" }, { 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" version = "10.0"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ wheels = [
@ -2690,18 +2690,18 @@ name = "litellm"
version = "1.88.1" version = "1.88.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp", marker = "python_full_version < '3.14'" },
{ name = "click" }, { name = "click", marker = "python_full_version < '3.14'" },
{ name = "fastuuid" }, { name = "fastuuid", marker = "python_full_version < '3.14'" },
{ name = "httpx" }, { name = "httpx", marker = "python_full_version < '3.14'" },
{ name = "importlib-metadata" }, { name = "importlib-metadata", marker = "python_full_version < '3.14'" },
{ name = "jinja2" }, { name = "jinja2", marker = "python_full_version < '3.14'" },
{ name = "jsonschema" }, { name = "jsonschema", marker = "python_full_version < '3.14'" },
{ name = "openai" }, { name = "openai", marker = "python_full_version < '3.14'" },
{ name = "pydantic" }, { name = "pydantic", marker = "python_full_version < '3.14'" },
{ name = "python-dotenv" }, { name = "python-dotenv", marker = "python_full_version < '3.14'" },
{ name = "tiktoken" }, { name = "tiktoken", marker = "python_full_version < '3.14'" },
{ name = "tokenizers" }, { 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" } 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 = [ wheels = [
@ -3583,7 +3583,7 @@ name = "nvidia-cublas"
version = "13.1.1.3" version = "13.1.1.3"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = [ 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" }, { 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" version = "9.20.0.48"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = [ 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" }, { 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" version = "12.0.0.61"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = [ 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" }, { 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" version = "12.0.4.66"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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')" },
{ name = "nvidia-cusparse" }, { 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" }, { 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 = [ 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" }, { 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" version = "12.6.3.3"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = [ 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" }, { 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" version = "2.3.0"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ dependencies = [
{ name = "antlr4-python3-runtime" }, { name = "antlr4-python3-runtime", marker = "python_full_version >= '3.13'" },
{ name = "pyyaml" }, { 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" } 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 = [ wheels = [
@ -3773,12 +3773,12 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
dependencies = [ dependencies = [
{ name = "coloredlogs" }, { name = "coloredlogs", marker = "python_full_version < '3.11'" },
{ name = "flatbuffers" }, { name = "flatbuffers", marker = "python_full_version < '3.11'" },
{ 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'" },
{ name = "packaging" }, { name = "packaging", marker = "python_full_version < '3.11'" },
{ name = "protobuf" }, { name = "protobuf", marker = "python_full_version < '3.11'" },
{ name = "sympy" }, { name = "sympy", marker = "python_full_version < '3.11'" },
] ]
wheels = [ 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" }, { 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'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
] ]
dependencies = [ dependencies = [
{ name = "flatbuffers" }, { name = "flatbuffers", marker = "python_full_version >= '3.11'" },
{ 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'" },
{ name = "packaging" }, { name = "packaging", marker = "python_full_version >= '3.11'" },
{ name = "protobuf" }, { name = "protobuf", marker = "python_full_version >= '3.11'" },
] ]
wheels = [ 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" }, { 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" version = "2.3.0.post1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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" } 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 = [ wheels = [
@ -4154,10 +4154,10 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
dependencies = [ 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'" },
{ name = "python-dateutil" }, { name = "python-dateutil", marker = "python_full_version < '3.11'" },
{ name = "pytz" }, { name = "pytz", marker = "python_full_version < '3.11'" },
{ name = "tzdata" }, { 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" } 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 = [ wheels = [
@ -4229,9 +4229,9 @@ resolution-markers = [
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
] ]
dependencies = [ 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'" },
{ name = "python-dateutil" }, { name = "python-dateutil", marker = "python_full_version >= '3.11'" },
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, { 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" } 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 = [ wheels = [
@ -5393,17 +5393,17 @@ name = "rapidocr"
version = "3.8.1" version = "3.8.1"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ dependencies = [
{ name = "colorlog" }, { name = "colorlog", marker = "python_full_version >= '3.13'" },
{ 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.13'" },
{ name = "omegaconf" }, { name = "omegaconf", marker = "python_full_version >= '3.13'" },
{ name = "opencv-python" }, { name = "opencv-python", marker = "python_full_version >= '3.13'" },
{ name = "pillow" }, { name = "pillow", marker = "python_full_version >= '3.13'" },
{ name = "pyclipper" }, { name = "pyclipper", marker = "python_full_version >= '3.13'" },
{ name = "pyyaml" }, { name = "pyyaml", marker = "python_full_version >= '3.13'" },
{ name = "requests" }, { name = "requests", marker = "python_full_version >= '3.13'" },
{ name = "shapely" }, { name = "shapely", marker = "python_full_version >= '3.13'" },
{ name = "six" }, { name = "six", marker = "python_full_version >= '3.13'" },
{ name = "tqdm" }, { name = "tqdm", marker = "python_full_version >= '3.13'" },
] ]
wheels = [ 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" }, { 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" version = "1.4.4"
source = { registry = "https://pypi.org/simple/" } source = { registry = "https://pypi.org/simple/" }
dependencies = [ 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 = "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 = "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 = "opencv-python", marker = "python_full_version < '3.13'" },
{ name = "pillow" }, { name = "pillow", marker = "python_full_version < '3.13'" },
{ name = "pyclipper" }, { name = "pyclipper", marker = "python_full_version < '3.13'" },
{ name = "pyyaml" }, { name = "pyyaml", marker = "python_full_version < '3.13'" },
{ name = "shapely" }, { name = "shapely", marker = "python_full_version < '3.13'" },
{ name = "six" }, { name = "six", marker = "python_full_version < '3.13'" },
{ name = "tqdm" }, { name = "tqdm", marker = "python_full_version < '3.13'" },
] ]
wheels = [ 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" }, { 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]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.22" version = "0.16.2"
source = { registry = "https://pypi.org/simple/" } 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 = [ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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]] [[package]]
@ -5821,10 +5821,10 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
dependencies = [ dependencies = [
{ name = "joblib" }, { name = "joblib", marker = "python_full_version < '3.11'" },
{ 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'" },
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple/" } }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version < '3.11'" },
{ name = "threadpoolctl" }, { 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" } 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 = [ wheels = [
@ -5879,10 +5879,10 @@ resolution-markers = [
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
] ]
dependencies = [ dependencies = [
{ name = "joblib" }, { name = "joblib", marker = "python_full_version >= '3.11'" },
{ 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'" },
{ name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple/" } }, { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple/" }, marker = "python_full_version >= '3.11'" },
{ name = "threadpoolctl" }, { 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" } 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 = [ wheels = [
@ -5932,7 +5932,7 @@ resolution-markers = [
"python_full_version < '3.11'", "python_full_version < '3.11'",
] ]
dependencies = [ 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" } 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 = [ wheels = [
@ -6002,7 +6002,7 @@ resolution-markers = [
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
] ]
dependencies = [ 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" } 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 = [ wheels = [

View file

@ -226,8 +226,8 @@ analysis = {
"data": [ "data": [
{"ts": 45, "cpu": 92}, # Keep the spike! {"ts": 45, "cpu": 92}, # Keep the spike!
{"ts": 46, "cpu": 95}, {"ts": 46, "cpu": 95},
... ...,
] ],
} }
``` ```
@ -397,11 +397,9 @@ def analyze_field(key, items):
"unique_ratio": len(set(values)) / len(values), "unique_ratio": len(set(values)) / len(values),
# 0.0 = all same (constant) # 0.0 = all same (constant)
# 1.0 = all different (unique IDs) # 1.0 = all different (unique IDs)
"variance": statistics.variance(values), # For numbers "variance": statistics.variance(values), # For numbers
# Low = stable # Low = stable
# High = changing # High = changing
"change_points": detect_spikes(values), "change_points": detect_spikes(values),
# Indices where value jumps significantly # Indices where value jumps significantly
} }
@ -502,14 +500,14 @@ When SmartCrusher compresses, the original content is stored for on-demand retri
```python ```python
@dataclass @dataclass
class CompressionEntry: class CompressionEntry:
hash: str # 16-char SHA256 for retrieval hash: str # 16-char SHA256 for retrieval
original_content: str # Full JSON before compression original_content: str # Full JSON before compression
compressed_content: str # Compressed JSON compressed_content: str # Compressed JSON
original_item_count: int original_item_count: int
compressed_item_count: int compressed_item_count: int
tool_name: str | None # For feedback tracking tool_name: str | None # For feedback tracking
created_at: float created_at: float
ttl: int = 300 # 5 minute default ttl: int = 300 # 5 minute default
``` ```
**Features:** **Features:**
@ -625,12 +623,12 @@ The feedback system learns from retrieval patterns to improve future compression
@dataclass @dataclass
class ToolPattern: class ToolPattern:
tool_name: str tool_name: str
total_compressions: int # Times we compressed this tool total_compressions: int # Times we compressed this tool
total_retrievals: int # Times LLM asked for more total_retrievals: int # Times LLM asked for more
full_retrievals: int # Retrieved everything (all retrievals — hash-only) full_retrievals: int # Retrieved everything (all retrievals — hash-only)
search_retrievals: int # Legacy; always 0 (retrieval is hash-only, no search) search_retrievals: int # Legacy; always 0 (retrieval is hash-only, no search)
common_queries: dict[str, int] # Legacy query-pattern frequency (no longer populated) common_queries: dict[str, int] # Legacy query-pattern frequency (no longer populated)
queried_fields: dict[str, int] # Legacy queried-field frequency (no longer populated) queried_fields: dict[str, int] # Legacy queried-field frequency (no longer populated)
``` ```
**Key Metrics:** **Key Metrics:**
@ -644,12 +642,12 @@ class ToolPattern:
```python ```python
@dataclass @dataclass
class CompressionHints: class CompressionHints:
max_items: int = 15 # Target item count max_items: int = 15 # Target item count
suggested_items: int | None # Calculated optimal suggested_items: int | None # Calculated optimal
skip_compression: bool # Don't compress at all skip_compression: bool # Don't compress at all
preserve_fields: list[str] # Always keep these fields preserve_fields: list[str] # Always keep these fields
aggressiveness: float # 0.0 = aggressive, 1.0 = conservative aggressiveness: float # 0.0 = aggressive, 1.0 = conservative
reason: str # Explanation reason: str # Explanation
``` ```
**Feedback-Driven Adjustment:** **Feedback-Driven Adjustment:**
@ -733,24 +731,26 @@ if self.config.use_feedback_hints and tool_name:
```python ```python
@dataclass @dataclass
class CCRToolCall: class CCRToolCall:
tool_call_id: str # For matching response tool_call_id: str # For matching response
hash_key: str # CCR hash to retrieve hash_key: str # CCR hash to retrieve
@dataclass @dataclass
class CCRToolResult: class CCRToolResult:
tool_call_id: str tool_call_id: str
content: str # Retrieved data as JSON content: str # Retrieved data as JSON
success: bool success: bool
items_retrieved: int items_retrieved: int
class CCRResponseHandler: class CCRResponseHandler:
async def handle_response( async def handle_response(
self, self,
response: dict, # Initial LLM response response: dict, # Initial LLM response
messages: list, # Conversation history messages: list, # Conversation history
tools: list, # Tool definitions tools: list, # Tool definitions
api_call_fn: Callable, # Function to make API calls api_call_fn: Callable, # Function to make API calls
provider: str, # "anthropic" or "openai" provider: str, # "anthropic" or "openai"
) -> dict: ) -> dict:
"""Handle CCR tool calls until final response.""" """Handle CCR tool calls until final response."""
``` ```
@ -762,11 +762,14 @@ The handler also supports streaming responses via `StreamingCCRHandler`:
```python ```python
class StreamingCCRBuffer: class StreamingCCRBuffer:
"""Buffers streaming chunks to detect CCR tool calls.""" """Buffers streaming chunks to detect CCR tool calls."""
chunks: list[bytes] chunks: list[bytes]
detected_ccr: bool detected_ccr: bool
class StreamingCCRHandler: class StreamingCCRHandler:
"""Handles CCR in streaming responses.""" """Handles CCR in streaming responses."""
async def process_stream(self, stream, messages, tools, api_call_fn): async def process_stream(self, stream, messages, tools, api_call_fn):
"""Yields chunks, switching to buffered mode if CCR detected.""" """Yields chunks, switching to buffered mode if CCR detected."""
``` ```
@ -848,11 +851,11 @@ The tracker uses simple but effective heuristics:
@dataclass @dataclass
class ContextTrackerConfig: class ContextTrackerConfig:
enabled: bool = True enabled: bool = True
max_tracked_contexts: int = 100 # LRU eviction max_tracked_contexts: int = 100 # LRU eviction
relevance_threshold: float = 0.3 # Min score to recommend relevance_threshold: float = 0.3 # Min score to recommend
max_context_age_seconds: float = 300 # 5 minutes max_context_age_seconds: float = 300 # 5 minutes
proactive_expansion: bool = True proactive_expansion: bool = True
max_proactive_expansions: int = 2 # Per query max_proactive_expansions: int = 2 # Per query
``` ```
--- ---

View file

@ -167,6 +167,7 @@ Full async support for high-throughput applications:
import asyncio import asyncio
from headroom.integrations.agno import HeadroomAgnoModel from headroom.integrations.agno import HeadroomAgnoModel
async def process_async(): async def process_async():
model = HeadroomAgnoModel(OpenAIChat(id="gpt-4o")) model = HeadroomAgnoModel(OpenAIChat(id="gpt-4o"))
@ -179,6 +180,7 @@ async def process_async():
print(f"\nTokens saved: {model.total_tokens_saved}") print(f"\nTokens saved: {model.total_tokens_saved}")
asyncio.run(process_async()) asyncio.run(process_async())
``` ```

View file

@ -101,9 +101,9 @@ config = CacheAlignerConfig(
from headroom import RelevanceScorerConfig from headroom import RelevanceScorerConfig
config = RelevanceScorerConfig( config = RelevanceScorerConfig(
scorer_type="bm25", # "bm25", "embedding", or "hybrid" scorer_type="bm25", # "bm25", "embedding", or "hybrid"
embedding_model=None, # Model name for embedding scorer embedding_model=None, # Model name for embedding scorer
hybrid_alpha=0.5, # Weight for hybrid scoring hybrid_alpha=0.5, # Weight for hybrid scoring
) )
``` ```
@ -286,10 +286,12 @@ result = aligner.align(messages)
```python ```python
from headroom import TransformPipeline from headroom import TransformPipeline
pipeline = TransformPipeline([ pipeline = TransformPipeline(
SmartCrusher(), [
CacheAligner(), SmartCrusher(),
]) CacheAligner(),
]
)
result = pipeline.transform(messages) result = pipeline.transform(messages)
``` ```

View file

@ -79,21 +79,18 @@ from headroom.compression import UniversalCompressorConfig
config = UniversalCompressorConfig( config = UniversalCompressorConfig(
# Detection # Detection
use_magika=True, # Use ML-based detection (requires magika) use_magika=True, # Use ML-based detection (requires magika)
# Compression # Compression
# (Note: the legacy `use_llmlingua` flag was retired with the # (Note: the legacy `use_llmlingua` flag was retired with the
# LLMLingua-2 integration. The optional ML compressor is now Kompress, # LLMLingua-2 integration. The optional ML compressor is now Kompress,
# installed via `headroom-ai[ml]` and configured separately.) # installed via `headroom-ai[ml]` and configured separately.)
compression_ratio_target=0.3, # Keep 30% of content (70% reduction) 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 # Structure preservation
use_entropy_preservation=True, # Preserve high-entropy tokens use_entropy_preservation=True, # Preserve high-entropy tokens
entropy_threshold=0.85, # Entropy threshold for preservation entropy_threshold=0.85, # Entropy threshold for preservation
# CCR # 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 from headroom.compression.handlers.json_handler import JSONStructureHandler
handler = JSONStructureHandler( handler = JSONStructureHandler(
preserve_short_values=True, # Keep values < 20 chars preserve_short_values=True, # Keep values < 20 chars
short_value_threshold=20, # Threshold for "short" short_value_threshold=20, # Threshold for "short"
preserve_high_entropy=True, # Keep UUIDs, hashes preserve_high_entropy=True, # Keep UUIDs, hashes
entropy_threshold=0.85, # Entropy threshold entropy_threshold=0.85, # Entropy threshold
max_array_items_full=3, # Keep first N array items full max_array_items_full=3, # Keep first N array items full
max_number_digits=10, # Preserve numbers up to N digits max_number_digits=10, # Preserve numbers up to N digits
) )
``` ```
@ -141,18 +138,10 @@ handler = JSONStructureHandler(
```python ```python
# Before # 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) # 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 ### 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 from headroom.compression.handlers.code_handler import CodeStructureHandler
handler = CodeStructureHandler( handler = CodeStructureHandler(
preserve_comments=False, # Preserve comments as structural preserve_comments=False, # Preserve comments as structural
use_tree_sitter=True, # Use tree-sitter for parsing use_tree_sitter=True, # Use tree-sitter for parsing
default_language="python", # Default when detection fails default_language="python", # Default when detection fails
) )
``` ```
@ -226,24 +215,24 @@ from headroom.compression import compress
result = compress(content) result = compress(content)
# Access result fields # Access result fields
print(result.compressed) # Compressed content print(result.compressed) # Compressed content
print(result.original) # Original content print(result.original) # Original content
print(result.compression_ratio) # e.g., 0.35 (35% of original size) print(result.compression_ratio) # e.g., 0.35 (35% of original size)
print(result.tokens_before) # Estimated tokens before print(result.tokens_before) # Estimated tokens before
print(result.tokens_after) # Estimated tokens after print(result.tokens_after) # Estimated tokens after
print(result.tokens_saved) # tokens_before - tokens_after print(result.tokens_saved) # tokens_before - tokens_after
print(result.savings_percentage) # e.g., 65.0 (65% savings) print(result.savings_percentage) # e.g., 65.0 (65% savings)
# Detection info # Detection info
print(result.content_type) # ContentType.JSON, CODE, etc. print(result.content_type) # ContentType.JSON, CODE, etc.
print(result.detection_confidence) # 0.0-1.0 print(result.detection_confidence) # 0.0-1.0
# Structure info # Structure info
print(result.handler_used) # "json", "code", etc. print(result.handler_used) # "json", "code", etc.
print(result.preservation_ratio) # Fraction preserved as structure print(result.preservation_ratio) # Fraction preserved as structure
# CCR info # 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 = [ contents = [
'{"users": [...]}', '{"users": [...]}',
'def hello(): pass', "def hello(): pass",
'Plain text content', "Plain text content",
] ]
results = compressor.compress_batch(contents) results = compressor.compress_batch(contents)
@ -393,11 +382,11 @@ json_content = """
result = compressor.compress(json_content) result = compressor.compress(json_content)
print(f"Type: {result.content_type}") # ContentType.JSON print(f"Type: {result.content_type}") # ContentType.JSON
print(f"Handler: {result.handler_used}") # json print(f"Handler: {result.handler_used}") # json
print(f"Saved: {result.savings_percentage:.0f}%") # ~60% print(f"Saved: {result.savings_percentage:.0f}%") # ~60%
print(f"Structure: {result.preservation_ratio:.0%} preserved") # ~40% 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
``` ```
--- ---

View file

@ -31,22 +31,17 @@ from openai import OpenAI
client = HeadroomClient( client = HeadroomClient(
original_client=OpenAI(), original_client=OpenAI(),
provider=OpenAIProvider(), provider=OpenAIProvider(),
# Mode: "audit" (observe only) or "optimize" (apply transforms) # Mode: "audit" (observe only) or "optimize" (apply transforms)
default_mode="optimize", default_mode="optimize",
# Enable provider-specific cache optimization # Enable provider-specific cache optimization
enable_cache_optimizer=True, enable_cache_optimizer=True,
# Enable query-level semantic caching # Enable query-level semantic caching
enable_semantic_cache=False, enable_semantic_cache=False,
# Override default context limits per model # Override default context limits per model
model_context_limits={ model_context_limits={
"gpt-4o": 128000, "gpt-4o": 128000,
"gpt-4o-mini": 128000, "gpt-4o-mini": 128000,
}, },
# Database location (defaults to temp directory) # Database location (defaults to temp directory)
# store_url="sqlite:////absolute/path/to/headroom.db", # store_url="sqlite:////absolute/path/to/headroom.db",
) )
@ -136,20 +131,14 @@ Override configuration for specific requests:
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[...], messages=[...],
# Override mode for this request # Override mode for this request
headroom_mode="audit", headroom_mode="audit",
# Reserve more tokens for output # Reserve more tokens for output
headroom_output_buffer_tokens=8000, headroom_output_buffer_tokens=8000,
# Keep last N turns (don't compress) # Keep last N turns (don't compress)
headroom_keep_turns=5, headroom_keep_turns=5,
# Skip compression for specific tools # Skip compression for specific tools
headroom_tool_profiles={ headroom_tool_profiles={"important_tool": {"skip_compression": True}},
"important_tool": {"skip_compression": True}
}
) )
``` ```
@ -186,13 +175,10 @@ from headroom.transforms import SmartCrusherConfig
config = SmartCrusherConfig( config = SmartCrusherConfig(
# Maximum items to keep after compression # Maximum items to keep after compression
max_items_after_crush=15, max_items_after_crush=15,
# Minimum tokens before applying compression # Minimum tokens before applying compression
min_tokens_to_crush=200, min_tokens_to_crush=200,
# Relevance scoring tier: "bm25" (fast) or "embedding" (accurate) # Relevance scoring tier: "bm25" (fast) or "embedding" (accurate)
relevance_tier="bm25", relevance_tier="bm25",
# Always keep items with these field values # Always keep items with these field values
preserve_fields=["error", "warning", "failure"], preserve_fields=["error", "warning", "failure"],
) )
@ -208,7 +194,6 @@ from headroom.transforms import CacheAlignerConfig
config = CacheAlignerConfig( config = CacheAlignerConfig(
# Enable/disable cache alignment # Enable/disable cache alignment
enabled=True, enabled=True,
# Patterns to extract from system prompt # Patterns to extract from system prompt
dynamic_patterns=[ dynamic_patterns=[
r"Today is \w+ \d+, \d{4}", r"Today is \w+ \d+, \d{4}",

View file

@ -6,12 +6,12 @@ Headroom provides explicit exceptions for debugging, with a safety guarantee tha
```python ```python
from headroom import ( from headroom import (
HeadroomError, # Base class - catch all Headroom errors HeadroomError, # Base class - catch all Headroom errors
ConfigurationError, # Invalid configuration ConfigurationError, # Invalid configuration
ProviderError, # Provider issues (unknown model, etc.) ProviderError, # Provider issues (unknown model, etc.)
StorageError, # Database/storage failures StorageError, # Database/storage failures
CompressionError, # Compression failures (rare) CompressionError, # Compression failures (rare)
ValidationError, # Setup validation failures ValidationError, # Setup validation failures
) )
``` ```
@ -75,10 +75,7 @@ Raised for provider-specific issues.
# - Token counting failure # - Token counting failure
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(model="unknown-model-xyz", messages=[...])
model="unknown-model-xyz",
messages=[...]
)
except ProviderError as e: except ProviderError as e:
print(f"Provider error: {e}") print(f"Provider error: {e}")
print(f"Provider: {e.details.get('provider')}") print(f"Provider: {e.details.get('provider')}")
@ -122,10 +119,7 @@ Raised when setup validation fails.
```python ```python
result = client.validate_setup() result = client.validate_setup()
if not result["valid"]: if not result["valid"]:
raise ValidationError( raise ValidationError("Setup validation failed", details={"issues": result["issues"]})
"Setup validation failed",
details={"issues": result["issues"]}
)
``` ```
## Safety Guarantee ## Safety Guarantee
@ -136,16 +130,11 @@ This is a core design principle. Your LLM calls never fail due to Headroom:
```python ```python
# Even if SmartCrusher encounters unexpected data: # Even if SmartCrusher encounters unexpected data:
messages = [ messages = [{"role": "tool", "content": "malformed json {{{"}]
{"role": "tool", "content": "malformed json {{{"}
]
# This will NOT raise an exception # This will NOT raise an exception
# Instead, the malformed content passes through unchanged # Instead, the malformed content passes through unchanged
response = client.chat.completions.create( response = client.chat.completions.create(model="gpt-4o", messages=messages)
model="gpt-4o",
messages=messages
)
``` ```
## Logging Errors ## Logging Errors
@ -154,6 +143,7 @@ Enable logging to see error details:
```python ```python
import logging import logging
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
# Now you'll see warnings when compression is skipped: # Now you'll see warnings when compression is skipped:
@ -232,6 +222,7 @@ response = client.chat.completions.create(...)
```python ```python
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
# Shows detailed transform decisions # Shows detailed transform decisions

View file

@ -61,13 +61,15 @@ client = HeadroomClient(provider="openai")
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[{ messages=[
"role": "user", {
"content": [ "role": "user",
{"type": "text", "text": "What animal is this?"}, "content": [
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}} {"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) # Image automatically compressed with detail="low" (87% savings)
``` ```
@ -106,8 +108,8 @@ from headroom.image import ImageCompressor
compressor = ImageCompressor( compressor = ImageCompressor(
model_id="chopratejas/technique-router", # HuggingFace model model_id="chopratejas/technique-router", # HuggingFace model
use_siglip=True, # Enable image analysis use_siglip=True, # Enable image analysis
device="cuda", # Use GPU if available device="cuda", # Use GPU if available
) )
``` ```
@ -229,9 +231,11 @@ The HuggingFace model downloads on first use:
```python ```python
# Force a specific cache directory # Force a specific cache directory
import os import os
os.environ["HF_HOME"] = "/path/to/cache" os.environ["HF_HOME"] = "/path/to/cache"
from headroom.image import ImageCompressor from headroom.image import ImageCompressor
compressor = ImageCompressor() compressor = ImageCompressor()
``` ```
@ -290,10 +294,10 @@ class ImageCompressor:
```python ```python
@dataclass @dataclass
class CompressionResult: class CompressionResult:
technique: Technique # full_low, preserve, crop, transcode technique: Technique # full_low, preserve, crop, transcode
original_tokens: int # Estimated tokens before original_tokens: int # Estimated tokens before
compressed_tokens: int # Estimated tokens after compressed_tokens: int # Estimated tokens after
confidence: float # Router confidence (0-1) confidence: float # Router confidence (0-1)
@property @property
def savings_percent(self) -> float: def savings_percent(self) -> float:
@ -304,10 +308,10 @@ class CompressionResult:
```python ```python
class Technique(Enum): class Technique(Enum):
FULL_LOW = "full_low" # 87% savings FULL_LOW = "full_low" # 87% savings
PRESERVE = "preserve" # 0% savings PRESERVE = "preserve" # 0% savings
CROP = "crop" # 50-90% savings CROP = "crop" # 50-90% savings
TRANSCODE = "transcode" # 99% savings TRANSCODE = "transcode" # 99% savings
``` ```
## See Also ## See Also

View file

@ -92,22 +92,26 @@ import httpx
from headroom import compress from headroom import compress
compressed = compress(messages, model="claude-sonnet-4-5-20250929") compressed = compress(messages, model="claude-sonnet-4-5-20250929")
httpx.post("https://api.anthropic.com/v1/messages", json={ httpx.post(
"model": "claude-sonnet-4-5-20250929", "https://api.anthropic.com/v1/messages",
"messages": compressed.messages, json={
}, headers={"X-Api-Key": api_key, "anthropic-version": "2023-06-01"}) "model": "claude-sonnet-4-5-20250929",
"messages": compressed.messages,
},
headers={"X-Api-Key": api_key, "anthropic-version": "2023-06-01"},
)
``` ```
### What compress() returns ### What compress() returns
```python ```python
result = compress(messages, model="gpt-4o") result = compress(messages, model="gpt-4o")
result.messages # list[dict] — compressed messages, same format as input result.messages # list[dict] — compressed messages, same format as input
result.tokens_before # int — original token count result.tokens_before # int — original token count
result.tokens_after # int — compressed token count result.tokens_after # int — compressed token count
result.tokens_saved # int — tokens removed result.tokens_saved # int — tokens removed
result.compression_ratio # float — 0.0 (no savings) to 1.0 (100% 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 # LiteLLM proxy
from litellm.proxy.proxy_server import app from litellm.proxy.proxy_server import app
app.add_middleware(CompressionMiddleware) app.add_middleware(CompressionMiddleware)
``` ```
@ -309,6 +314,7 @@ Customize compression behavior without modifying Headroom's code:
```python ```python
from headroom import compress, CompressionHooks, CompressContext from headroom import compress, CompressionHooks, CompressContext
class MyHooks(CompressionHooks): class MyHooks(CompressionHooks):
def pre_compress(self, messages, ctx): def pre_compress(self, messages, ctx):
# Modify messages before compression (dedup, filter, inject) # Modify messages before compression (dedup, filter, inject)
@ -323,6 +329,7 @@ class MyHooks(CompressionHooks):
# Observe results (logging, analytics, learning) # Observe results (logging, analytics, learning)
print(f"Saved {event.tokens_saved} tokens") print(f"Saved {event.tokens_saved} tokens")
result = compress(messages, model="gpt-4o", hooks=MyHooks()) result = compress(messages, model="gpt-4o", hooks=MyHooks())
``` ```

View file

@ -91,11 +91,13 @@ Works seamlessly with LangChain tool calling:
```python ```python
from langchain_core.tools import tool from langchain_core.tools import tool
@tool @tool
def search(query: str) -> str: def search(query: str) -> str:
"""Search the web.""" """Search the web."""
return {"results": [...]} # Large JSON response return {"results": [...]} # Large JSON response
llm_with_tools = llm.bind_tools([search]) llm_with_tools = llm.bind_tools([search])
response = llm_with_tools.invoke("Search for Python tutorials") response = llm_with_tools.invoke("Search for Python tutorials")
# Tool outputs are automatically compressed in subsequent turns # Tool outputs are automatically compressed in subsequent turns
@ -117,7 +119,7 @@ base_history = ChatMessageHistory()
compressed_history = HeadroomChatMessageHistory( compressed_history = HeadroomChatMessageHistory(
base_history, base_history,
compress_threshold_tokens=4000, # Compress when over 4K tokens 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 # 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) # Wrap with Headroom compression (keep best for precision)
compressor = HeadroomDocumentCompressor( compressor = HeadroomDocumentCompressor(
max_documents=10, # Keep top 10 max_documents=10, # Keep top 10
min_relevance=0.3, # Minimum relevance score min_relevance=0.3, # Minimum relevance score
prefer_diverse=True, # MMR-style diversity prefer_diverse=True, # MMR-style diversity
) )
retriever = ContextualCompressionRetriever( retriever = ContextualCompressionRetriever(
@ -179,18 +181,21 @@ from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.tools import tool from langchain_core.tools import tool
from headroom.integrations import wrap_tools_with_headroom from headroom.integrations import wrap_tools_with_headroom
@tool @tool
def search_database(query: str) -> str: def search_database(query: str) -> str:
"""Search the database.""" """Search the database."""
# Returns 1000 results as JSON # Returns 1000 results as JSON
return json.dumps({"results": [...], "total": 1000}) return json.dumps({"results": [...], "total": 1000})
@tool @tool
def fetch_logs(service: str) -> str: def fetch_logs(service: str) -> str:
"""Fetch service logs.""" """Fetch service logs."""
# Returns 500 log entries # Returns 500 log entries
return json.dumps({"logs": [...]}) return json.dumps({"logs": [...]})
# Wrap tools with compression # Wrap tools with compression
tools = [search_database, fetch_logs] tools = [search_database, fetch_logs]
wrapped_tools = wrap_tools_with_headroom( wrapped_tools = wrap_tools_with_headroom(
@ -296,26 +301,33 @@ from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent from langgraph.prebuilt import create_react_agent
from headroom.integrations import HeadroomChatModel, wrap_tools_with_headroom from headroom.integrations import HeadroomChatModel, wrap_tools_with_headroom
# Define tools that return large outputs # Define tools that return large outputs
@tool @tool
def search_web(query: str) -> str: def search_web(query: str) -> str:
"""Search the web for information.""" """Search the web for information."""
# Simulating large search results # Simulating large search results
return json.dumps({ return json.dumps(
"results": [ {
{"title": f"Result {i}", "snippet": "..." * 100, "url": f"https://..."} "results": [
for i in range(100) {"title": f"Result {i}", "snippet": "..." * 100, "url": f"https://..."}
], for i in range(100)
"total": 1000, ],
}) "total": 1000,
}
)
@tool @tool
def query_database(sql: str) -> str: def query_database(sql: str) -> str:
"""Execute SQL query.""" """Execute SQL query."""
return json.dumps({ return json.dumps(
"rows": [{"id": i, "data": "..." * 50} for i in range(500)], {
"total": 500, "rows": [{"id": i, "data": "..." * 50} for i in range(500)],
}) "total": 500,
}
)
# Wrap model with Headroom # Wrap model with Headroom
llm = HeadroomChatModel(ChatOpenAI(model="gpt-4o")) 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) agent = create_react_agent(llm, tools)
# Run - tool outputs are automatically compressed between iterations # Run - tool outputs are automatically compressed between iterations
result = agent.invoke({ result = agent.invoke(
"messages": [("user", "Find all users who signed up last week and their activity")] {"messages": [("user", "Find all users who signed up last week and their activity")]}
}) )
# Check savings # Check savings
print(f"Tokens saved: {llm.get_metrics()['tokens_saved']}") 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 langgraph.graph import StateGraph, MessagesState, START, END
from headroom.integrations.langchain import create_compress_tool_messages_node from headroom.integrations.langchain import create_compress_tool_messages_node
# Define your agent and tools nodes # Define your agent and tools nodes
def agent_node(state: MessagesState): def agent_node(state: MessagesState):
llm = ChatOpenAI(model="gpt-4o") llm = ChatOpenAI(model="gpt-4o")
response = llm.invoke(state["messages"]) response = llm.invoke(state["messages"])
return {"messages": [response]} return {"messages": [response]}
def tools_node(state: MessagesState): def tools_node(state: MessagesState):
# Your tool execution logic here # Your tool execution logic here
... ...
# Build the graph with a compression step # Build the graph with a compression step
graph = StateGraph(MessagesState) graph = StateGraph(MessagesState)
graph.add_node("agent", agent_node) graph.add_node("agent", agent_node)
graph.add_node("tools", tools_node) graph.add_node("tools", tools_node)
graph.add_node("compress", create_compress_tool_messages_node( graph.add_node(
min_tokens_to_compress=100, # Only compress outputs > ~100 tokens "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) # Wire: tools -> compress -> agent (instead of tools -> agent directly)
graph.add_edge(START, "agent") graph.add_edge(START, "agent")
@ -411,9 +429,9 @@ base_retriever = vectorstore.as_retriever(search_kwargs={"k": 50})
# Headroom compressor for precision # Headroom compressor for precision
compressor = HeadroomDocumentCompressor( compressor = HeadroomDocumentCompressor(
max_documents=5, # Keep only top 5 max_documents=5, # Keep only top 5
min_relevance=0.4, # Must be 40%+ relevant min_relevance=0.4, # Must be 40%+ relevant
prefer_diverse=True, # Avoid redundant docs prefer_diverse=True, # Avoid redundant docs
) )
# Combine into compression retriever # Combine into compression retriever
@ -461,7 +479,7 @@ base_history = ChatMessageHistory()
compressed_history = HeadroomChatMessageHistory( compressed_history = HeadroomChatMessageHistory(
base_history, base_history,
compress_threshold_tokens=8000, # Compress when over 8K 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( memory = ConversationBufferMemory(
@ -500,30 +518,45 @@ from headroom.integrations import (
reset_tool_metrics, reset_tool_metrics,
) )
@tool @tool
def search_arxiv(query: str) -> str: def search_arxiv(query: str) -> str:
"""Search arXiv for papers.""" """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 @tool
def search_github(query: str) -> str: def search_github(query: str) -> str:
"""Search GitHub repositories.""" """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 @tool
def fetch_documentation(url: str) -> str: def fetch_documentation(url: str) -> str:
"""Fetch documentation from URL.""" """Fetch documentation from URL."""
return "..." * 5000 # Large doc content return "..." * 5000 # Large doc content
# Wrap everything # Wrap everything
llm = HeadroomChatModel(ChatOpenAI(model="gpt-4o")) llm = HeadroomChatModel(ChatOpenAI(model="gpt-4o"))
tools = wrap_tools_with_headroom([search_arxiv, search_github, fetch_documentation]) tools = wrap_tools_with_headroom([search_arxiv, search_github, fetch_documentation])
prompt = ChatPromptTemplate.from_messages([ prompt = ChatPromptTemplate.from_messages(
("system", "You are a research assistant. Use tools to gather information."), [
("human", "{input}"), ("system", "You are a research assistant. Use tools to gather information."),
("placeholder", "{agent_scratchpad}"), ("human", "{input}"),
]) ("placeholder", "{agent_scratchpad}"),
]
)
agent = create_openai_tools_agent(llm, tools, prompt) agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True) executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
@ -532,9 +565,11 @@ executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
reset_tool_metrics() reset_tool_metrics()
# Run complex research task # Run complex research task
result = executor.invoke({ result = executor.invoke(
"input": "Research the latest advances in LLM context compression and find relevant GitHub projects" {
}) "input": "Research the latest advances in LLM context compression and find relevant GitHub projects"
}
)
# Check per-tool metrics # Check per-tool metrics
metrics = get_tool_metrics().get_summary() metrics = get_tool_metrics().get_summary()
@ -550,9 +585,9 @@ print(f"Per-tool breakdown: {metrics['by_tool']}")
```python ```python
HeadroomChatModel( HeadroomChatModel(
wrapped_model, # Any LangChain BaseChatModel wrapped_model, # Any LangChain BaseChatModel
headroom_config=HeadroomConfig(), # Headroom configuration 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 ```python
HeadroomChatMessageHistory( HeadroomChatMessageHistory(
base_history, # Any BaseChatMessageHistory base_history, # Any BaseChatMessageHistory
compress_threshold_tokens=4000, # Token threshold for compression compress_threshold_tokens=4000, # Token threshold for compression
keep_recent_turns=5, # Minimum turns to preserve keep_recent_turns=5, # Minimum turns to preserve
model="gpt-4o", # Model for token counting model="gpt-4o", # Model for token counting
) )
``` ```
@ -571,9 +606,9 @@ HeadroomChatMessageHistory(
```python ```python
HeadroomDocumentCompressor( HeadroomDocumentCompressor(
max_documents=10, # Maximum docs to return max_documents=10, # Maximum docs to return
min_relevance=0.0, # Minimum relevance score (0-1) min_relevance=0.0, # Minimum relevance score (0-1)
prefer_diverse=False, # Use MMR for diversity prefer_diverse=False, # Use MMR for diversity
) )
``` ```
@ -581,9 +616,9 @@ HeadroomDocumentCompressor(
```python ```python
wrap_tools_with_headroom( wrap_tools_with_headroom(
tools, # List of LangChain tools tools, # List of LangChain tools
min_chars_to_compress=1000, # Minimum output size min_chars_to_compress=1000, # Minimum output size
smart_crusher_config=None, # SmartCrusher configuration smart_crusher_config=None, # SmartCrusher configuration
) )
``` ```
@ -595,27 +630,21 @@ wrap_tools_with_headroom(
from headroom.integrations import ( from headroom.integrations import (
# Chat Model # Chat Model
HeadroomChatModel, HeadroomChatModel,
# Memory # Memory
HeadroomChatMessageHistory, HeadroomChatMessageHistory,
# Retrievers # Retrievers
HeadroomDocumentCompressor, HeadroomDocumentCompressor,
# Agents # Agents
HeadroomToolWrapper, HeadroomToolWrapper,
wrap_tools_with_headroom, wrap_tools_with_headroom,
get_tool_metrics, get_tool_metrics,
reset_tool_metrics, reset_tool_metrics,
# Streaming # Streaming
StreamingMetricsTracker, StreamingMetricsTracker,
StreamingMetricsCallback, StreamingMetricsCallback,
track_streaming_response, track_streaming_response,
# LangSmith # LangSmith
HeadroomLangSmithCallbackHandler, HeadroomLangSmithCallbackHandler,
# Provider Detection # Provider Detection
detect_provider, detect_provider,
get_headroom_provider, get_headroom_provider,
@ -660,7 +689,7 @@ Check that your message count exceeds the threshold:
history = HeadroomChatMessageHistory( history = HeadroomChatMessageHistory(
base_history, base_history,
compress_threshold_tokens=1000, # Lower threshold compress_threshold_tokens=1000, # Lower threshold
keep_recent_turns=2, # Fewer preserved turns keep_recent_turns=2, # Fewer preserved turns
) )
``` ```

View file

@ -159,15 +159,13 @@ client = with_memory(OpenAI(), user_id="alice")
# Use exactly like normal # Use exactly like normal
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o", messages=[{"role": "user", "content": "I prefer Python for backend work"}]
messages=[{"role": "user", "content": "I prefer Python for backend work"}]
) )
# Memory extracted INLINE - zero extra latency # Memory extracted INLINE - zero extra latency
# Later, in a new conversation... # Later, in a new conversation...
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o", messages=[{"role": "user", "content": "What language should I use?"}]
messages=[{"role": "user", "content": "What language should I use?"}]
) )
# → Response uses the Python preference from memory # → Response uses the Python preference from memory
``` ```
@ -228,7 +226,7 @@ client1 = with_memory(
) )
response = client1.chat.completions.create( response = client1.chat.completions.create(
model="gpt-4o", 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) # Memory stored at USER level (persists across sessions)
@ -239,8 +237,7 @@ client2 = with_memory(
session_id="afternoon-session", # Different session session_id="afternoon-session", # Different session
) )
response = client2.chat.completions.create( response = client2.chat.completions.create(
model="gpt-4o", model="gpt-4o", messages=[{"role": "user", "content": "What language for my new microservice?"}]
messages=[{"role": "user", "content": "What language for my new microservice?"}]
) )
# → Recalls Go preference from morning session! # → Recalls Go preference from morning session!
``` ```
@ -270,17 +267,21 @@ new = await memory.supersede(
) )
# Query current state (excludes superseded) # Query current state (excludes superseded)
current = await memory.query(MemoryFilter( current = await memory.query(
user_id="alice", MemoryFilter(
include_superseded=False, # Default user_id="alice",
)) include_superseded=False, # Default
)
)
# → Returns only "User now works at Anthropic" # → Returns only "User now works at Anthropic"
# Query full history (includes superseded) # Query full history (includes superseded)
history = await memory.query(MemoryFilter( history = await memory.query(
user_id="alice", MemoryFilter(
include_superseded=True, user_id="alice",
)) include_superseded=True,
)
)
# → Returns both memories with validity timestamps # → Returns both memories with validity timestamps
# Get the chain # Get the chain
@ -362,6 +363,7 @@ from headroom.memory import (
) )
from headroom.memory.ports import MemoryFilter, VectorFilter from headroom.memory.ports import MemoryFilter, VectorFilter
async def main(): async def main():
# Create with custom configuration # Create with custom configuration
config = MemoryConfig( config = MemoryConfig(
@ -402,17 +404,20 @@ async def main():
) )
# Query with filters # Query with filters
memories = await memory.query(MemoryFilter( memories = await memory.query(
user_id="alice", MemoryFilter(
categories=[MemoryCategory.PREFERENCE, MemoryCategory.FACT], user_id="alice",
min_importance=0.7, categories=[MemoryCategory.PREFERENCE, MemoryCategory.FACT],
limit=10, min_importance=0.7,
)) limit=10,
)
)
# Convenience methods # Convenience methods
await memory.remember("Likes coffee", user_id="alice", importance=0.6) await memory.remember("Likes coffee", user_id="alice", importance=0.6)
relevant = await memory.recall("beverage preferences", user_id="alice") relevant = await memory.recall("beverage preferences", user_id="alice")
asyncio.run(main()) asyncio.run(main())
``` ```
@ -482,13 +487,13 @@ Apple GPU instead of the default ONNX CPU embedder. Notes:
```python ```python
config = MemoryConfig( config = MemoryConfig(
db_path="memory.db", # SQLite database path db_path="memory.db", # SQLite database path
vector_dimension=384, # Must match embedder output vector_dimension=384, # Must match embedder output
hnsw_ef_construction=200, # HNSW index quality (higher = better, slower) hnsw_ef_construction=200, # HNSW index quality (higher = better, slower)
hnsw_m=16, # HNSW connections per node hnsw_m=16, # HNSW connections per node
hnsw_ef_search=50, # HNSW search quality hnsw_ef_search=50, # HNSW search quality
cache_enabled=True, # Enable LRU cache cache_enabled=True, # Enable LRU cache
cache_max_size=1000, # Max cached memories cache_max_size=1000, # Max cached memories
) )
``` ```
@ -499,7 +504,7 @@ client = with_memory(
OpenAI(), OpenAI(),
user_id="alice", user_id="alice",
db_path="memory.db", db_path="memory.db",
top_k=5, # Memories to inject per request top_k=5, # Memories to inject per request
session_id="optional-session", session_id="optional-session",
agent_id="optional-agent", agent_id="optional-agent",
embedder_backend=EmbedderBackend.LOCAL, embedder_backend=EmbedderBackend.LOCAL,
@ -659,6 +664,7 @@ client = with_memory(
# Groq # Groq
from groq import Groq from groq import Groq
client = with_memory(Groq(), user_id="alice") client = with_memory(Groq(), user_id="alice")
# Any OpenAI-compatible client # Any OpenAI-compatible client
@ -678,10 +684,12 @@ client = with_memory(OpenAI(), user_id="developer_jane")
# Conversation 1: User shares context # Conversation 1: User shares context
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[{ messages=[
"role": "user", {
"content": "I'm a Python developer at a fintech startup. We use PostgreSQL and FastAPI." "role": "user",
}] "content": "I'm a Python developer at a fintech startup. We use PostgreSQL and FastAPI.",
}
],
) )
# Memories extracted: # Memories extracted:
# - [FACT] Python developer at fintech startup # - [FACT] Python developer at fintech startup
@ -691,10 +699,7 @@ response = client.chat.completions.create(
# Conversation 2 (new session): User asks question # Conversation 2 (new session): User asks question
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[{ messages=[{"role": "user", "content": "What database should I use for my new project?"}],
"role": "user",
"content": "What database should I use for my new project?"
}]
) )
# Response references PostgreSQL preference from memory: # Response references PostgreSQL preference from memory:
# → "Given your experience with PostgreSQL at your fintech company, # → "Given your experience with PostgreSQL at your fintech company,

View file

@ -336,19 +336,19 @@ print(stats)
"tokens_saved_total": 15000, "tokens_saved_total": 15000,
"tokens_output_total": 8000, "tokens_output_total": 8000,
"cache_hits": 3, "cache_hits": 3,
"compression_ratio_avg": 0.70 "compression_ratio_avg": 0.70,
}, },
"config": { "config": {
"mode": "optimize", "mode": "optimize",
"provider": "openai", "provider": "openai",
"cache_optimizer_enabled": True, "cache_optimizer_enabled": True,
"semantic_cache_enabled": False "semantic_cache_enabled": False,
}, },
"transforms": { "transforms": {
"smart_crusher_enabled": True, "smart_crusher_enabled": True,
"cache_aligner_enabled": True, "cache_aligner_enabled": True,
"rolling_window_enabled": True "rolling_window_enabled": True,
} },
} }
``` ```

View file

@ -72,17 +72,18 @@ Different data patterns need different position importance:
```python ```python
class AnchorStrategy(Enum): class AnchorStrategy(Enum):
FRONT_HEAVY = "front_heavy" # Search results: top items matter most FRONT_HEAVY = "front_heavy" # Search results: top items matter most
BACK_HEAVY = "back_heavy" # Logs: recent items matter most BACK_HEAVY = "back_heavy" # Logs: recent items matter most
BALANCED = "balanced" # Time series: both ends matter BALANCED = "balanced" # Time series: both ends matter
MIDDLE_AWARE = "middle_aware" # Database: order might be arbitrary MIDDLE_AWARE = "middle_aware" # Database: order might be arbitrary
def get_anchor_strategy(pattern: DataPattern) -> AnchorStrategy: def get_anchor_strategy(pattern: DataPattern) -> AnchorStrategy:
return { return {
DataPattern.SEARCH_RESULTS: AnchorStrategy.FRONT_HEAVY, # Top N by score DataPattern.SEARCH_RESULTS: AnchorStrategy.FRONT_HEAVY, # Top N by score
DataPattern.LOGS: AnchorStrategy.BACK_HEAVY, # Recency matters DataPattern.LOGS: AnchorStrategy.BACK_HEAVY, # Recency matters
DataPattern.TIME_SERIES: AnchorStrategy.BALANCED, # Both ends for trend DataPattern.TIME_SERIES: AnchorStrategy.BALANCED, # Both ends for trend
DataPattern.GENERIC: AnchorStrategy.MIDDLE_AWARE, # Don't assume order DataPattern.GENERIC: AnchorStrategy.MIDDLE_AWARE, # Don't assume order
}.get(pattern, AnchorStrategy.BALANCED) }.get(pattern, AnchorStrategy.BALANCED)
``` ```
@ -150,7 +151,7 @@ def select_informative_anchors(
items: list[dict], items: list[dict],
region: str, # "front", "back", "middle" region: str, # "front", "back", "middle"
slots: int, slots: int,
all_items_hash: set[str] all_items_hash: set[str],
) -> list[int]: ) -> list[int]:
"""Select most informative items from a region.""" """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(): for field, value in item.items():
field_values = [i.get(field) for i in all_items if field in i] field_values = [i.get(field) for i in all_items if field in i]
value_frequency = field_values.count(value) / len(field_values) 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 # 2. Structural uniqueness - different fields than typical
typical_fields = get_typical_fields(all_items) typical_fields = get_typical_fields(all_items)
@ -221,6 +222,7 @@ Track which positions users actually retrieve and learn from it:
@dataclass @dataclass
class PositionRetrievalPattern: class PositionRetrievalPattern:
"""Learned position importance from retrieval data.""" """Learned position importance from retrieval data."""
tool_name: str tool_name: str
total_compressions: int total_compressions: int
position_retrievals: dict[str, int] # "front_10%", "middle", "back_10%" position_retrievals: dict[str, int] # "front_10%", "middle", "back_10%"
@ -231,21 +233,13 @@ class PositionRetrievalPattern:
if total == 0: if total == 0:
return {"front": 0.5, "middle": 0.0, "back": 0.5} return {"front": 0.5, "middle": 0.0, "back": 0.5}
return { return {position: count / total for position, count in self.position_retrievals.items()}
position: count / total
for position, count in self.position_retrievals.items()
}
class TOINPositionLearning: class TOINPositionLearning:
"""Learn position importance from retrieval patterns.""" """Learn position importance from retrieval patterns."""
def record_retrieval( def record_retrieval(self, tool_name: str, original_size: int, retrieved_indices: list[int]):
self,
tool_name: str,
original_size: int,
retrieved_indices: list[int]
):
"""Record which positions were retrieved.""" """Record which positions were retrieved."""
for idx in retrieved_indices: for idx in retrieved_indices:
position = self._classify_position(idx, original_size) position = self._classify_position(idx, original_size)
@ -287,9 +281,7 @@ For large arrays, sample strategically from middle:
```python ```python
def stratified_middle_sample( def stratified_middle_sample(
items: list[dict], items: list[dict], num_samples: int, analysis: ArrayAnalysis
num_samples: int,
analysis: ArrayAnalysis
) -> list[int]: ) -> list[int]:
"""Sample middle positions using stratified approach.""" """Sample middle positions using stratified approach."""
@ -310,9 +302,7 @@ def stratified_middle_sample(
if analysis.numeric_fields: if analysis.numeric_fields:
variance_scores = calculate_position_variance(items, analysis.numeric_fields) variance_scores = calculate_position_variance(items, analysis.numeric_fields)
sorted_by_variance = sorted( sorted_by_variance = sorted(
middle_items, middle_items, key=lambda i: variance_scores.get(i, 0), reverse=True
key=lambda i: variance_scores.get(i, 0),
reverse=True
) )
return sorted(sorted_by_variance[:num_samples]) return sorted(sorted_by_variance[:num_samples])
@ -383,11 +373,7 @@ class TestAdversarialPositions:
items[42]["name"] = "target_item" items[42]["name"] = "target_item"
items[42]["description"] = "This is what user asked about" items[42]["description"] = "This is what user asked about"
result = smart_crusher.crush( result = smart_crusher.crush(items, max_items=10, query="find target_item")
items,
max_items=10,
query="find target_item"
)
# Query-matched item MUST be preserved # Query-matched item MUST be preserved
assert any("target_item" in item.get("name", "") for item in result) assert any("target_item" in item.get("name", "") for item in result)
@ -399,12 +385,15 @@ class TestAdversarialPositions:
class TestSizeAdaptation: class TestSizeAdaptation:
"""Test that anchor allocation scales with array size.""" """Test that anchor allocation scales with array size."""
@pytest.mark.parametrize("size,expected_min_anchors", [ @pytest.mark.parametrize(
(20, 3), # Small array: at least 3 anchors "size,expected_min_anchors",
(100, 4), # Medium array: at least 4 anchors [
(500, 5), # Large array: at least 5 anchors (20, 3), # Small array: at least 3 anchors
(2000, 6), # Very large: at least 6 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): def test_anchor_count_scales(self, size, expected_min_anchors):
"""Anchor count should increase with array size.""" """Anchor count should increase with array size."""
items = [{"id": i, "value": i * 10} for i in range(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% # Count items from first 10% and last 10%
anchor_count = sum( anchor_count = sum(
1 for item in result 1 for item in result if item["id"] < size * 0.1 or item["id"] > size * 0.9
if item["id"] < size * 0.1 or item["id"] > size * 0.9
) )
assert anchor_count >= expected_min_anchors assert anchor_count >= expected_min_anchors
@ -454,10 +442,7 @@ class TestPatternAwareAnchoring:
def test_search_results_front_heavy(self): def test_search_results_front_heavy(self):
"""Search results should preserve more from front.""" """Search results should preserve more from front."""
items = [ items = [{"title": f"Result {i}", "score": 1.0 - (i * 0.01)} for i in range(100)]
{"title": f"Result {i}", "score": 1.0 - (i * 0.01)}
for i in range(100)
]
result = smart_crusher.crush(items, max_items=10) result = smart_crusher.crush(items, max_items=10)
@ -485,10 +470,7 @@ class TestPatternAwareAnchoring:
def test_time_series_balanced(self): def test_time_series_balanced(self):
"""Time series should have balanced front/back.""" """Time series should have balanced front/back."""
items = [ items = [{"timestamp": f"2024-01-01T{i:02d}:00:00", "value": 100 + i} for i in range(24)]
{"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) result = smart_crusher.crush(items, max_items=8)
@ -510,11 +492,7 @@ class TestQueryAwareAnchoring:
"""'Latest' in query should preserve more recent items.""" """'Latest' in query should preserve more recent items."""
items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)] items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)]
result = smart_crusher.crush( result = smart_crusher.crush(items, max_items=8, query="Show me the latest entries")
items,
max_items=8,
query="Show me the latest entries"
)
ids = [item["id"] for item in result] ids = [item["id"] for item in result]
recent_count = sum(1 for id in ids if id > 20) recent_count = sum(1 for id in ids if id > 20)
@ -525,11 +503,7 @@ class TestQueryAwareAnchoring:
"""'First' in query should preserve earlier items.""" """'First' in query should preserve earlier items."""
items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)] items = [{"id": i, "created": f"2024-01-{i:02d}"} for i in range(1, 31)]
result = smart_crusher.crush( result = smart_crusher.crush(items, max_items=8, query="Show me the first entries")
items,
max_items=8,
query="Show me the first entries"
)
ids = [item["id"] for item in result] ids = [item["id"] for item in result]
early_count = sum(1 for id in ids if id < 10) 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.""" """Query for specific ID should find it regardless of position."""
items = [{"id": f"item_{i:04d}", "value": i} for i in range(1000)] items = [{"id": f"item_{i:04d}", "value": i} for i in range(1000)]
result = smart_crusher.crush( result = smart_crusher.crush(items, max_items=10, query="Find item_0567")
items,
max_items=10,
query="Find item_0567"
)
assert any(item["id"] == "item_0567" for item in result) assert any(item["id"] == "item_0567" for item in result)
``` ```
@ -574,8 +544,7 @@ class TestCoverageMetrics:
def test_category_coverage(self): def test_category_coverage(self):
"""Preserved items should represent all categories.""" """Preserved items should represent all categories."""
items = [ items = [
{"category": cat, "id": i} {"category": cat, "id": i} for i, cat in enumerate(["A"] * 30 + ["B"] * 30 + ["C"] * 40)
for i, cat in enumerate(["A"] * 30 + ["B"] * 30 + ["C"] * 40)
] ]
result = smart_crusher.crush(items, max_items=10) result = smart_crusher.crush(items, max_items=10)
@ -608,10 +577,7 @@ class TestRetrievalSimulation:
def test_retrieval_hit_rate_random_queries(self): def test_retrieval_hit_rate_random_queries(self):
"""Measure how often preserved items satisfy random queries.""" """Measure how often preserved items satisfy random queries."""
items = [ items = [{"id": i, "name": f"Item {i}", "category": f"cat_{i % 5}"} for i in range(100)]
{"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 = smart_crusher.crush(items, max_items=15)
compressed_ids = {item["id"] for item in compressed} compressed_ids = {item["id"] for item in compressed}
@ -636,9 +602,9 @@ class TestRetrievalSimulation:
# Weight queries toward front (30%), back (30%), anomalies (40%) # Weight queries toward front (30%), back (30%), anomalies (40%)
hits = 0 hits = 0
queries = ( queries = (
list(range(10)) * 3 + # Front queries list(range(10)) * 3 # Front queries
list(range(90, 100)) * 3 + # Back queries + list(range(90, 100)) * 3 # Back queries
[50] * 4 # Middle anomaly queries + [50] * 4 # Middle anomaly queries
) )
for target_id in queries: for target_id in queries:
@ -718,12 +684,12 @@ class AnchorConfig:
time_series_balance: float = 0.5 time_series_balance: float = 0.5
# Query keyword detection # Query keyword detection
recency_keywords: list[str] = field(default_factory=lambda: [ recency_keywords: list[str] = field(
"latest", "recent", "last", "newest", "current" default_factory=lambda: ["latest", "recent", "last", "newest", "current"]
]) )
historical_keywords: list[str] = field(default_factory=lambda: [ historical_keywords: list[str] = field(
"first", "oldest", "earliest", "original", "initial" default_factory=lambda: ["first", "oldest", "earliest", "original", "initial"]
]) )
# Information density selection # Information density selection
use_information_density: bool = True use_information_density: bool = True

View file

@ -323,13 +323,18 @@ The provider caches the bytes you *forwarded*, which compression already changed
```python ```python
forwarded = [] forwarded = []
def next_turn(new_messages): def next_turn(new_messages):
r = requests.post(f"{proxy}/v1/compress", json={ r = requests.post(
"messages": forwarded + new_messages, f"{proxy}/v1/compress",
"model": "claude-sonnet-4-6", json={
"config": {"frozen_message_count": len(forwarded)}, "messages": forwarded + new_messages,
}).json() "model": "claude-sonnet-4-6",
forwarded[:] = r["messages"] # next turn's frozen prefix "config": {"frozen_message_count": len(forwarded)},
},
).json()
forwarded[:] = r["messages"] # next turn's frozen prefix
return forwarded return forwarded
``` ```

View file

@ -149,19 +149,21 @@ messages = [
{ {
"role": "assistant", "role": "assistant",
"content": None, "content": None,
"tool_calls": [{ "tool_calls": [
"id": "call_1", {
"type": "function", "id": "call_1",
"function": {"name": "search", "arguments": '{"q": "python"}'}, "type": "function",
}], "function": {"name": "search", "arguments": '{"q": "python"}'},
}
],
}, },
{ {
"role": "tool", "role": "tool",
"tool_call_id": "call_1", "tool_call_id": "call_1",
# This is where Headroom shines - compressing large outputs # This is where Headroom shines - compressing large outputs
"content": json.dumps({ "content": json.dumps(
"results": [{"title": f"Result {i}", "score": 100-i} for i in range(500)] {"results": [{"title": f"Result {i}", "score": 100 - i} for i in range(500)]}
}), ),
}, },
{"role": "user", "content": "What are the top 3 results?"}, {"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 before: {plan.tokens_before}")
print(f"Tokens after: {plan.tokens_after}") 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"Transforms: {plan.transforms}")
print(f"Estimated savings: {plan.estimated_savings}") print(f"Estimated savings: {plan.estimated_savings}")
``` ```
@ -227,6 +231,7 @@ print(response.content[0].text)
```python ```python
import logging import logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
# Now you'll see: # Now you'll see:
@ -347,6 +352,7 @@ print(stats["config"]["mode"]) # Should be "optimize"
# 2. Enable logging to see what's happening # 2. Enable logging to see what's happening
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
``` ```

View file

@ -45,30 +45,26 @@ messages = [
{ {
"role": "assistant", "role": "assistant",
"content": None, "content": None,
"tool_calls": [{ "tool_calls": [
"id": "call_123", {
"type": "function", "id": "call_123",
"function": {"name": "search", "arguments": '{"q": "python"}'}, "type": "function",
}], "function": {"name": "search", "arguments": '{"q": "python"}'},
}
],
}, },
{ {
"role": "tool", "role": "tool",
"tool_call_id": "call_123", "tool_call_id": "call_123",
"content": json.dumps({ "content": json.dumps(
"results": [ {"results": [{"title": f"Tutorial {i}", "score": 100 - i} for i in range(500)]}
{"title": f"Tutorial {i}", "score": 100-i} ),
for i in range(500)
]
}),
}, },
{"role": "user", "content": "What are the top 3?"}, {"role": "user", "content": "What are the top 3?"},
] ]
# Headroom compresses 500 results to ~15, keeping highest-scoring items # Headroom compresses 500 results to ~15, keeping highest-scoring items
response = client.chat.completions.create( response = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
model="gpt-4o-mini",
messages=messages
)
# Check savings # Check savings
stats = client.get_stats() stats = client.get_stats()
@ -187,13 +183,10 @@ print(f"Transforms: {plan.transforms}")
response = client.chat.completions.create( response = client.chat.completions.create(
model="gpt-4o", model="gpt-4o",
messages=[...], messages=[...],
# Override mode for this request # Override mode for this request
headroom_mode="audit", headroom_mode="audit",
# Reserve more tokens for output # Reserve more tokens for output
headroom_output_buffer_tokens=8000, headroom_output_buffer_tokens=8000,
# Keep last N turns # Keep last N turns
headroom_keep_turns=5, headroom_keep_turns=5,
) )
@ -203,6 +196,7 @@ response = client.chat.completions.create(
```python ```python
import logging import logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
# Now you'll see: # Now you'll see:

View file

@ -28,10 +28,10 @@ Store content under a key. Compresses automatically using Headroom's full pipeli
```python ```python
entry = ctx.put("findings", big_json_output, agent="researcher") entry = ctx.put("findings", big_json_output, agent="researcher")
entry.original_tokens # 20,000 entry.original_tokens # 20,000
entry.compressed_tokens # 4,000 entry.compressed_tokens # 4,000
entry.savings_percent # 80.0 entry.savings_percent # 80.0
entry.transforms # ["router:json:0.20"] entry.transforms # ["router:json:0.20"]
``` ```
### `get(key, *, full=False)` ### `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`. Retrieve content. Returns compressed version by default, original with `full=True`.
```python ```python
compressed = ctx.get("findings") # 4K tokens compressed = ctx.get("findings") # 4K tokens
original = ctx.get("findings", full=True) # 20K tokens original = ctx.get("findings", full=True) # 20K tokens
missing = ctx.get("nonexistent") # None missing = ctx.get("nonexistent") # None
``` ```
### `get_entry(key)` ### `get_entry(key)`
@ -50,13 +50,13 @@ Get the full `ContextEntry` with metadata.
```python ```python
entry = ctx.get_entry("findings") entry = ctx.get_entry("findings")
entry.key # "findings" entry.key # "findings"
entry.agent # "researcher" entry.agent # "researcher"
entry.original_tokens # 20000 entry.original_tokens # 20000
entry.compressed_tokens # 4000 entry.compressed_tokens # 4000
entry.savings_percent # 80.0 entry.savings_percent # 80.0
entry.timestamp # 1710000000.0 entry.timestamp # 1710000000.0
entry.transforms # ["router:json:0.20"] entry.transforms # ["router:json:0.20"]
``` ```
### `keys()` ### `keys()`
@ -69,11 +69,11 @@ Aggregated stats across all entries.
```python ```python
stats = ctx.stats() stats = ctx.stats()
stats.entries # 3 stats.entries # 3
stats.total_original_tokens # 60000 stats.total_original_tokens # 60000
stats.total_compressed_tokens # 12000 stats.total_compressed_tokens # 12000
stats.total_tokens_saved # 48000 stats.total_tokens_saved # 48000
stats.savings_percent # 80.0 stats.savings_percent # 80.0
``` ```
### `clear()` ### `clear()`
@ -85,8 +85,8 @@ Remove all entries.
```python ```python
ctx = SharedContext( ctx = SharedContext(
model="claude-sonnet-4-5-20250929", # For token counting model="claude-sonnet-4-5-20250929", # For token counting
ttl=3600, # 1 hour (default) ttl=3600, # 1 hour (default)
max_entries=100, # Evicts oldest when full max_entries=100, # Evicts oldest when full
) )
``` ```
@ -113,11 +113,13 @@ from headroom import SharedContext
ctx = SharedContext() ctx = SharedContext()
def researcher_node(state): def researcher_node(state):
result = do_research() result = do_research()
ctx.put("research", result) ctx.put("research", result)
return {"research_summary": ctx.get("research")} return {"research_summary": ctx.get("research")}
def coder_node(state): def coder_node(state):
# Compressed summary in state, full details on demand # Compressed summary in state, full details on demand
full = ctx.get("research", full=True) full = ctx.get("research", full=True)
@ -131,6 +133,7 @@ from headroom import SharedContext
ctx = SharedContext() ctx = SharedContext()
def compress_handoff(messages): def compress_handoff(messages):
for msg in messages: for msg in messages:
if len(msg.content) > 1000: if len(msg.content) > 1000:
@ -138,6 +141,7 @@ def compress_handoff(messages):
msg.content = ctx.get(msg.id) msg.content = ctx.get(msg.id)
return messages return messages
handoff(agent=coder, input_filter=compress_handoff) handoff(agent=coder, input_filter=compress_handoff)
``` ```

View file

@ -114,11 +114,13 @@ HeadroomStrandsModel supports Strands' structured output feature:
```python ```python
from pydantic import BaseModel from pydantic import BaseModel
class Analysis(BaseModel): class Analysis(BaseModel):
severity: str severity: str
root_cause: str root_cause: str
recommendation: str recommendation: str
result = optimized.structured_output(Analysis, messages) result = optimized.structured_output(Analysis, messages)
``` ```

View file

@ -136,10 +136,14 @@ elif detection.content_type == ContentType.PLAIN_TEXT:
```python ```python
from headroom.transforms import ( from headroom.transforms import (
detect_content_type, ContentType, detect_content_type,
SearchCompressor, LogCompressor, TextCompressor ContentType,
SearchCompressor,
LogCompressor,
TextCompressor,
) )
def compress_tool_output(content: str, context: str = "") -> str: def compress_tool_output(content: str, context: str = "") -> str:
"""Application-level compression with explicit control.""" """Application-level compression with explicit control."""
detection = detect_content_type(content) detection = detect_content_type(content)
@ -166,7 +170,7 @@ Each compressor accepts configuration options:
from headroom.transforms import SearchCompressor, SearchCompressorConfig from headroom.transforms import SearchCompressor, SearchCompressorConfig
config = 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 preserve_file_diversity=True, # Ensure different files represented
relevance_threshold=0.3, # Minimum relevance score to keep relevance_threshold=0.3, # Minimum relevance score to keep
) )

View file

@ -22,13 +22,13 @@ SmartCrusher analyzes JSON arrays and selectively keeps important items:
from headroom import SmartCrusherConfig from headroom import SmartCrusherConfig
config = SmartCrusherConfig( config = SmartCrusherConfig(
min_tokens_to_crush=200, # Only compress if > 200 tokens min_tokens_to_crush=200, # Only compress if > 200 tokens
max_items_after_crush=50, # Keep at most 50 items max_items_after_crush=50, # Keep at most 50 items
keep_first=3, # Always keep first 3 items keep_first=3, # Always keep first 3 items
keep_last=2, # Always keep last 2 items keep_last=2, # Always keep last 2 items
relevance_threshold=0.3, # Keep items with relevance > 0.3 relevance_threshold=0.3, # Keep items with relevance > 0.3
anomaly_std_threshold=2.0, # Keep items > 2 std dev from mean anomaly_std_threshold=2.0, # Keep items > 2 std dev from mean
preserve_errors=True, # Always keep error items preserve_errors=True, # Always keep error items
) )
``` ```
@ -94,9 +94,9 @@ result = aligner.align(messages)
from headroom import CacheAlignerConfig from headroom import CacheAlignerConfig
config = CacheAlignerConfig( config = CacheAlignerConfig(
extract_dates=True, # Move dates to dynamic section extract_dates=True, # Move dates to dynamic section
normalize_whitespace=True, # Consistent spacing normalize_whitespace=True, # Consistent spacing
stable_prefix_min_tokens=100, # Min prefix size for alignment 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 from headroom.transforms import CodeAwareCompressor, CodeCompressorConfig, DocstringMode
config = CodeCompressorConfig( config = CodeCompressorConfig(
preserve_imports=True, # Always keep imports preserve_imports=True, # Always keep imports
preserve_signatures=True, # Always keep function signatures preserve_signatures=True, # Always keep function signatures
preserve_type_annotations=True, # Keep type hints preserve_type_annotations=True, # Keep type hints
preserve_error_handlers=True, # Keep try/except blocks preserve_error_handlers=True, # Keep try/except blocks
preserve_decorators=True, # Keep decorators preserve_decorators=True, # Keep decorators
docstring_mode=DocstringMode.FIRST_LINE, # FULL, FIRST_LINE, REMOVE docstring_mode=DocstringMode.FIRST_LINE, # FULL, FIRST_LINE, REMOVE
target_compression_rate=0.2, # Keep 20% of tokens target_compression_rate=0.2, # Keep 20% of tokens
max_body_lines=5, # Lines to keep per function body max_body_lines=5, # Lines to keep per function body
min_tokens_for_compression=100, # Skip small content min_tokens_for_compression=100, # Skip small content
language_hint=None, # Auto-detect if None language_hint=None, # Auto-detect if None
) )
compressor = CodeAwareCompressor(config) compressor = CodeAwareCompressor(config)
@ -263,10 +263,10 @@ ContentRouter analyzes content and selects the best compression strategy:
from headroom.transforms import ContentRouter, ContentRouterConfig, CompressionStrategy from headroom.transforms import ContentRouter, ContentRouterConfig, CompressionStrategy
config = ContentRouterConfig( config = ContentRouterConfig(
min_section_tokens=100, # Minimum tokens to compress min_section_tokens=100, # Minimum tokens to compress
enable_code_aware=True, # Use CodeAwareCompressor for code enable_code_aware=True, # Use CodeAwareCompressor for code
enable_search_compression=True, # Use SearchCompressor for grep output enable_search_compression=True, # Use SearchCompressor for grep output
enable_log_compression=True, # Use LogCompressor for logs enable_log_compression=True, # Use LogCompressor for logs
default_strategy=CompressionStrategy.TEXT, # Fallback strategy default_strategy=CompressionStrategy.TEXT, # Fallback strategy
) )
@ -331,10 +331,12 @@ Combine transforms for optimal results.
```python ```python
from headroom import TransformPipeline, SmartCrusher, CacheAligner from headroom import TransformPipeline, SmartCrusher, CacheAligner
pipeline = TransformPipeline([ pipeline = TransformPipeline(
SmartCrusher(), # First: compress tool outputs [
CacheAligner(), # Then: stabilize prefix SmartCrusher(), # First: compress tool outputs
]) CacheAligner(), # Then: stabilize prefix
]
)
result = pipeline.transform(messages) result = pipeline.transform(messages)
print(f"Saved {result.tokens_saved} tokens") print(f"Saved {result.tokens_saved} tokens")

View file

@ -224,6 +224,7 @@ client = HeadroomClient(
# 2. For temp directory storage # 2. For temp directory storage
import tempfile import tempfile
import os import os
db_path = os.path.join(tempfile.gettempdir(), "headroom.db") db_path = os.path.join(tempfile.gettempdir(), "headroom.db")
client = HeadroomClient( client = HeadroomClient(
original_client=OpenAI(), original_client=OpenAI(),
@ -287,6 +288,7 @@ pip install --upgrade headroom-ai
```python ```python
# Check available imports # Check available imports
import headroom import headroom
print(dir(headroom)) print(dir(headroom))
# Common imports: # Common imports:
@ -397,6 +399,7 @@ print(f"Waste signals: {plan.waste_signals}")
# See the actual optimized messages # See the actual optimized messages
import json import json
print(json.dumps(plan.messages_optimized, indent=2)) print(json.dumps(plan.messages_optimized, indent=2))
``` ```