0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
175 lines
6.9 KiB
Python
175 lines
6.9 KiB
Python
"""Prompt-conditioned relevance split for KEEP/DROP compression decisions.
|
|
|
|
Segments tool output into coherent records, scores each against the request's
|
|
*information need* (user prompt + the triggering tool call's args) using the
|
|
existing :class:`~headroom.relevance.RelevanceScorer` (BM25 / bge-small
|
|
embeddings / hybrid), and partitions the content into ordered KEEP/DROP runs.
|
|
|
|
The split is **mode-agnostic**: this module decides *what* is worth keeping
|
|
verbatim vs. what is a low-value tail; the caller applies the disposition. In
|
|
lossless (no-CCR) mode the KEEP runs stay byte-verbatim and the DROP tail is
|
|
Kompressed marker-free; in CCR mode the same DROP tail can be dropped with a
|
|
retrieval marker. Nothing here emits markers or calls a compressor.
|
|
|
|
Segmentation is boundary-aware, not line-based: blank lines delimit records,
|
|
indented continuation lines stay attached to their parent (so stack traces and
|
|
pretty-printed blobs are scored as one unit), and dense blank-free streams
|
|
(grep, tight logs) are packed into small fixed windows. The partition is
|
|
lossless -- ``"".join(segment(content)) == content`` -- so KEEP runs
|
|
reconstruct the original bytes exactly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from headroom.relevance import RelevanceScorer
|
|
|
|
__all__ = ["adaptive_threshold", "build_relevance_query", "plan_relevance_split", "segment"]
|
|
|
|
|
|
def build_relevance_query(user_query: str, tool_name: str = "", tool_args: str = "") -> str:
|
|
"""Compose the information-need query for relevance scoring.
|
|
|
|
The user's prompt is the high-level intent; the triggering tool call's args
|
|
(a grep pattern, a read path, a search query) are the *precise*, per-output
|
|
ask and usually the sharpest signal. Both are included so the lexical (BM25)
|
|
half locks onto exact tokens (e.g. the grep pattern) while the semantic half
|
|
tracks the intent.
|
|
"""
|
|
parts: list[str] = []
|
|
q = (user_query or "").strip()
|
|
if q:
|
|
parts.append(q)
|
|
call = " ".join(p for p in ((tool_name or "").strip(), (tool_args or "").strip()) if p)
|
|
if call:
|
|
parts.append(call)
|
|
return "\n".join(parts)
|
|
|
|
|
|
def segment(content: str, *, window: int = 8, max_chars: int = 1200) -> list[str]:
|
|
"""Partition ``content`` into coherent records.
|
|
|
|
Lossless partition: ``"".join(segment(content)) == content``. Blank lines
|
|
delimit records; oversized or dense blank-free blocks are packed into
|
|
windows of at most ``window`` lines / ``max_chars`` chars, with indented
|
|
continuation lines held to their window so multi-line units aren't cut.
|
|
"""
|
|
lines = content.splitlines(keepends=True)
|
|
if len(lines) <= 1:
|
|
return [content] if content else []
|
|
|
|
# Pass 1: blank-line-delimited blocks (paragraphs / record gaps).
|
|
blocks: list[list[str]] = []
|
|
cur: list[str] = []
|
|
for ln in lines:
|
|
cur.append(ln)
|
|
if ln.strip() == "":
|
|
blocks.append(cur)
|
|
cur = []
|
|
if cur:
|
|
blocks.append(cur)
|
|
|
|
# Pass 2: pack/window each block. Dense blank-free streams (grep, tight
|
|
# logs) become fixed windows; indented continuation lines stay attached to
|
|
# their window so stack traces / pretty JSON aren't split mid-unit.
|
|
segments: list[str] = []
|
|
for block in blocks:
|
|
if len(block) <= window and sum(len(x) for x in block) <= max_chars:
|
|
segments.append("".join(block))
|
|
continue
|
|
i = 0
|
|
n = len(block)
|
|
while i < n:
|
|
j = min(i + window, n)
|
|
while j < n and block[j][:1] in (" ", "\t"):
|
|
j += 1 # don't cut off an indented continuation run
|
|
segments.append("".join(block[i:j]))
|
|
i = j
|
|
return segments
|
|
|
|
|
|
def _otsu_threshold(values: list[float]) -> float:
|
|
"""Otsu's method: the cut between two classes that maximizes between-class
|
|
variance. Parameter-free — the candidate cuts are the data's own values, so
|
|
there's no bin size or magic constant. Returns the midpoint of the winning
|
|
adjacent pair.
|
|
"""
|
|
xs = sorted(values)
|
|
n = len(xs)
|
|
total = sum(xs)
|
|
w0 = 0.0
|
|
sum0 = 0.0
|
|
best_t = xs[0]
|
|
best_var = -1.0
|
|
for i in range(n - 1):
|
|
w0 += 1
|
|
sum0 += xs[i]
|
|
w1 = n - w0
|
|
m0 = sum0 / w0
|
|
m1 = (total - sum0) / w1
|
|
between = w0 * w1 * (m0 - m1) ** 2
|
|
if between > best_var:
|
|
best_var = between
|
|
best_t = (xs[i] + xs[i + 1]) / 2.0
|
|
return best_t
|
|
|
|
|
|
def adaptive_threshold(values: list[float], floor: float) -> float:
|
|
"""Data-driven KEEP/DROP cut for one output's relevance scores.
|
|
|
|
The operative cut is the natural relevant/irrelevant break (Otsu) *for this
|
|
output and query* — so it moves with the score distribution instead of a
|
|
fixed constant. It's floored by ``floor`` so records below the absolute
|
|
minimum relevance are never kept verbatim (an all-irrelevant output drops
|
|
entirely). When every record scores the same there's no break to find, so
|
|
the floor decides (all-in or all-out).
|
|
"""
|
|
if len({round(v, 9) for v in values}) < 2:
|
|
return floor
|
|
return max(_otsu_threshold(values), floor)
|
|
|
|
|
|
def plan_relevance_split(
|
|
content: str,
|
|
query: str,
|
|
scorer: RelevanceScorer,
|
|
*,
|
|
threshold: float,
|
|
adaptive: bool = True,
|
|
window: int = 8,
|
|
max_chars: int = 1200,
|
|
max_records: int | None = None,
|
|
) -> list[tuple[bool, str]]:
|
|
"""Split ``content`` into ordered ``(keep, text)`` runs by relevance to ``query``.
|
|
|
|
A record is KEEP when its relevance score clears the cut. With
|
|
``adaptive`` (default), the cut is the natural relevant/irrelevant break in
|
|
*this* output's score distribution (Otsu), floored by ``threshold`` — so it
|
|
adapts to the content instead of being a fixed constant. With
|
|
``adaptive=False`` the cut is ``threshold`` exactly. Either way *which*
|
|
records clear it is entirely prompt-driven, so the KEEP fraction ranges
|
|
from 0% to 100% with the content, not a fixed quota. Consecutive
|
|
same-disposition records are merged into runs (order preserved) so the
|
|
caller applies one disposition per run. Returns a single KEEP run -- i.e.
|
|
no split -- when the query is empty, the content is a single record, or it
|
|
segments into more than ``max_records`` records (a latency guard).
|
|
"""
|
|
if not query.strip():
|
|
return [(True, content)]
|
|
segs = segment(content, window=window, max_chars=max_chars)
|
|
if len(segs) < 2 or (max_records and len(segs) > max_records):
|
|
return [(True, content)]
|
|
|
|
scores = scorer.score_batch(segs, query)
|
|
cut = adaptive_threshold([s.score for s in scores], threshold) if adaptive else threshold
|
|
runs: list[tuple[bool, str]] = []
|
|
for seg, sc in zip(segs, scores):
|
|
keep = sc.score >= cut
|
|
if runs and runs[-1][0] == keep:
|
|
runs[-1] = (keep, runs[-1][1] + seg)
|
|
else:
|
|
runs.append((keep, seg))
|
|
return runs
|