powerailabs-squeeze 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .uv/
8
+ .ruff_cache/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .idea/
14
+ .vscode/
15
+ .DS_Store
16
+ *.log
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: powerailabs-squeeze
3
+ Version: 0.1.0
4
+ Summary: Compress: shrink verbose context (JSON/logs/prose) 60-90% โ€” reversibly. compress() returns a handle; expand() restores the original.
5
+ Author: Raghav Mishra
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: powerailabs-core<0.2,>=0.1
9
+ Description-Content-Type: text/markdown
10
+
11
+ # powerailabs-squeeze
12
+
13
+ Shrink verbose context โ€” JSON, logs, prose โ€” without throwing anything away. Compression returns
14
+ a *handle*; the original is always restorable. Content-aware: each type gets a purpose-built,
15
+ deterministic compressor (no LLM).
16
+
17
+ **80% smaller, 100% reversible.**
18
+
19
+ ![status](https://img.shields.io/badge/status-building-yellow) ![license](https://img.shields.io/badge/license-MIT-blue)
20
+
21
+ ๐Ÿšง building (v0) ยท `pip install powerailabs-squeeze` ยท `from powerailabs.squeeze import compress`
22
+
23
+ ```python
24
+ from powerailabs.squeeze import compress
25
+
26
+ small, handle = compress(huge_json, kind="auto") # detect + route (JSON/logs/prose)
27
+ small, handle = compress(logs, kind="logs", target_tokens=400) # compress to a budget
28
+ original = handle.expand() # restore on demand, byte-for-byte
29
+ ```
30
+
31
+ **Inbound** โ€” usually you don't call it directly; `contextkit` does, when a block is marked
32
+ `evict="compress"` (`pip install powerailabs-contextkit[squeeze]`). It satisfies core's
33
+ `Compressor` protocol by shape, so contextkit never imports squeeze. Call it directly to shrink a
34
+ single known-huge blob (e.g. a 50k-token tool response) before it ever enters the window.
35
+ Reversibility comes from a content-addressed store that keeps each original keyed by hash โ€”
36
+ structural compressors (JSON folding, log dedup) are deterministic; prose is extractive.
37
+
38
+ See [`docs/squeeze.md`](../../docs/squeeze.md). *Part of the PowerAI Labs stack โ€” github.com/PowerAI-Labs/powerailabs.*
@@ -0,0 +1,28 @@
1
+ # powerailabs-squeeze
2
+
3
+ Shrink verbose context โ€” JSON, logs, prose โ€” without throwing anything away. Compression returns
4
+ a *handle*; the original is always restorable. Content-aware: each type gets a purpose-built,
5
+ deterministic compressor (no LLM).
6
+
7
+ **80% smaller, 100% reversible.**
8
+
9
+ ![status](https://img.shields.io/badge/status-building-yellow) ![license](https://img.shields.io/badge/license-MIT-blue)
10
+
11
+ ๐Ÿšง building (v0) ยท `pip install powerailabs-squeeze` ยท `from powerailabs.squeeze import compress`
12
+
13
+ ```python
14
+ from powerailabs.squeeze import compress
15
+
16
+ small, handle = compress(huge_json, kind="auto") # detect + route (JSON/logs/prose)
17
+ small, handle = compress(logs, kind="logs", target_tokens=400) # compress to a budget
18
+ original = handle.expand() # restore on demand, byte-for-byte
19
+ ```
20
+
21
+ **Inbound** โ€” usually you don't call it directly; `contextkit` does, when a block is marked
22
+ `evict="compress"` (`pip install powerailabs-contextkit[squeeze]`). It satisfies core's
23
+ `Compressor` protocol by shape, so contextkit never imports squeeze. Call it directly to shrink a
24
+ single known-huge blob (e.g. a 50k-token tool response) before it ever enters the window.
25
+ Reversibility comes from a content-addressed store that keeps each original keyed by hash โ€”
26
+ structural compressors (JSON folding, log dedup) are deterministic; prose is extractive.
27
+
28
+ See [`docs/squeeze.md`](../../docs/squeeze.md). *Part of the PowerAI Labs stack โ€” github.com/PowerAI-Labs/powerailabs.*
@@ -0,0 +1,16 @@
1
+ [project]
2
+ name = "powerailabs-squeeze"
3
+ version = "0.1.0"
4
+ description = "Compress: shrink verbose context (JSON/logs/prose) 60-90% โ€” reversibly. compress() returns a handle; expand() restores the original."
5
+ requires-python = ">=3.11"
6
+ license = "MIT"
7
+ authors = [{ name = "Raghav Mishra" }]
8
+ readme = "README.md"
9
+ dependencies = ["powerailabs-core>=0.1,<0.2"]
10
+
11
+ [build-system]
12
+ requires = ["hatchling"]
13
+ build-backend = "hatchling.build"
14
+
15
+ [tool.hatch.build.targets.wheel]
16
+ packages = ["src/powerailabs"] # contributes powerailabs/squeeze only โ€” NEVER add src/powerailabs/__init__.py
@@ -0,0 +1,238 @@
1
+ """powerailabs.squeeze โ€” content-aware, reversible context compression.
2
+
3
+ Shrink verbose context without throwing anything away: :func:`compress` returns ``(small, handle)``
4
+ and ``handle.expand()`` restores the original on demand. Content is routed by type โ€” JSON, logs,
5
+ and prose each get a purpose-built, deterministic compressor (no LLM). Reversibility is guaranteed
6
+ by a **content-addressed store** (CCR): every original is kept keyed by its hash, deduped across
7
+ calls, so ``expand()`` is always exact no matter how hard we squeeze.
8
+
9
+ Satisfies ``powerailabs.core.protocols.Compressor`` by shape, so ``contextkit`` uses it for
10
+ ``Block(evict="compress")`` via the ``contextkit[squeeze]`` extra โ€” without importing this package.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import re
18
+ import uuid
19
+ from dataclasses import dataclass, field
20
+ from typing import Any
21
+
22
+ from powerailabs.core import tokens
23
+
24
+ __all__ = ["compress", "decompress", "detect", "Handle", "SqueezeCompressor"]
25
+
26
+ # Content-addressed store: sha256(original) -> original. Deduped; the basis of reversibility.
27
+ _STORE: dict[str, str] = {}
28
+
29
+
30
+ @dataclass
31
+ class Handle:
32
+ """Restore handle for a compression. ``expand()`` returns the exact original. (docs ยง5)"""
33
+
34
+ id: str
35
+ kind: str
36
+ original_ref: str # CCR key into the local content store
37
+ restore_map: dict = field(default_factory=dict)
38
+
39
+ def expand(self) -> str:
40
+ """Return the original content, byte-for-byte."""
41
+ return _STORE[self.original_ref]
42
+
43
+
44
+ def _store(original: str) -> str:
45
+ key = hashlib.sha256(original.encode("utf-8")).hexdigest()
46
+ _STORE.setdefault(key, original)
47
+ return key
48
+
49
+
50
+ # --------------------------------------------------------------------------- detection
51
+
52
+ _TS = re.compile(r"\b\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b")
53
+ _UUID = re.compile(r"\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b")
54
+ _LEVEL = re.compile(r"\b(?:DEBUG|INFO|WARN|WARNING|ERROR|CRITICAL|TRACE|FATAL)\b")
55
+
56
+
57
+ def detect(content: str) -> str:
58
+ """Detect the content kind: ``"json"`` | ``"logs"`` | ``"prose"``. docs/squeeze.md ยง4."""
59
+ s = content.strip()
60
+ if not s:
61
+ return "prose"
62
+ if s[0] in "{[":
63
+ try:
64
+ json.loads(s)
65
+ return "json"
66
+ except (ValueError, TypeError):
67
+ pass
68
+ if _looks_like_logs(s):
69
+ return "logs"
70
+ return "prose"
71
+
72
+
73
+ def _looks_like_logs(s: str) -> bool:
74
+ lines = [ln for ln in s.splitlines() if ln.strip()]
75
+ if len(lines) < 3:
76
+ return False
77
+ hits = sum(1 for ln in lines if _TS.search(ln) or _LEVEL.search(ln))
78
+ return hits >= len(lines) * 0.5
79
+
80
+
81
+ # --------------------------------------------------------------------------- public API
82
+
83
+
84
+ def compress(
85
+ content: Any,
86
+ kind: str = "auto",
87
+ target_tokens: int | None = None,
88
+ model: str = "gpt-4o",
89
+ ) -> tuple[str, Handle]:
90
+ """Compress ``content`` and return ``(small, handle)``. ``handle.expand()`` restores it.
91
+
92
+ Args:
93
+ content: A string, or a JSON-serializable object (dict/list).
94
+ kind: ``"auto"`` (detect) or one of ``"json"`` | ``"logs"`` | ``"prose"``.
95
+ target_tokens: If given, compress *to* this budget (best effort, never exceeds it).
96
+ model: Model id used for token counting.
97
+ """
98
+ if isinstance(content, str):
99
+ original = content
100
+ else:
101
+ original = json.dumps(content, ensure_ascii=False, separators=(",", ":"))
102
+ if kind == "auto":
103
+ kind = "json"
104
+
105
+ if kind == "auto":
106
+ kind = detect(original)
107
+
108
+ if kind == "json":
109
+ small, restore_map = _compress_json(original, target_tokens, model)
110
+ elif kind == "logs":
111
+ small, restore_map = _compress_logs(original, target_tokens, model)
112
+ else:
113
+ small, restore_map = _compress_prose(original, target_tokens, model)
114
+
115
+ handle = Handle(
116
+ id=uuid.uuid4().hex,
117
+ kind=kind,
118
+ original_ref=_store(original),
119
+ restore_map=restore_map,
120
+ )
121
+ return small, handle
122
+
123
+
124
+ def decompress(handle: Handle) -> str:
125
+ """Restore the original content for a handle (same as ``handle.expand()``)."""
126
+ return handle.expand()
127
+
128
+
129
+ class SqueezeCompressor:
130
+ """Object form satisfying ``core.protocols.Compressor`` (delegates to :func:`compress`)."""
131
+
132
+ def compress(
133
+ self,
134
+ content: Any,
135
+ *,
136
+ target_tokens: int | None = None,
137
+ model: str | None = None,
138
+ kind: str = "auto",
139
+ ) -> tuple[str, Handle]:
140
+ return compress(content, kind=kind, target_tokens=target_tokens, model=model or "gpt-4o")
141
+
142
+
143
+ # --------------------------------------------------------------------------- compressors
144
+
145
+
146
+ def _strip_nulls(obj: Any) -> Any:
147
+ if isinstance(obj, dict):
148
+ return {k: _strip_nulls(v) for k, v in obj.items() if v is not None}
149
+ if isinstance(obj, list):
150
+ return [_strip_nulls(v) for v in obj]
151
+ return obj
152
+
153
+
154
+ def _compress_json(text: str, target_tokens: int | None, model: str) -> tuple[str, dict]:
155
+ """Lossless-ish: minify whitespace and drop null-valued keys. Original kept in the CCR store."""
156
+ try:
157
+ obj = json.loads(text)
158
+ except (ValueError, TypeError):
159
+ return _compress_prose(text, target_tokens, model)
160
+ small = json.dumps(_strip_nulls(obj), ensure_ascii=False, separators=(",", ":"))
161
+ if target_tokens is not None and tokens.count(small, model) > target_tokens:
162
+ small = _truncate_to_tokens(small, target_tokens, model)
163
+ return small, {"technique": "minify+dropnulls"}
164
+
165
+
166
+ def _compress_logs(text: str, target_tokens: int | None, model: str) -> tuple[str, dict]:
167
+ """Normalize volatile fields (timestamps/UUIDs) then dedup repeated lines into ``(ร—N)``."""
168
+ counts: dict[str, int] = {}
169
+ order: list[str] = []
170
+ for line in text.splitlines():
171
+ norm = _UUID.sub("<uuid>", _TS.sub("<ts>", line))
172
+ if norm not in counts:
173
+ order.append(norm)
174
+ counts[norm] = counts.get(norm, 0) + 1
175
+
176
+ if target_tokens is not None:
177
+ order.sort(key=lambda ln: counts[ln], reverse=True) # keep the noisiest patterns first
178
+
179
+ out: list[str] = []
180
+ for norm in order:
181
+ rendered = f"{norm} (ร—{counts[norm]})" if counts[norm] > 1 else norm
182
+ if target_tokens is not None:
183
+ candidate = "\n".join([*out, rendered])
184
+ if tokens.count(candidate, model) > target_tokens:
185
+ break
186
+ out.append(rendered)
187
+ return "\n".join(out), {"technique": "normalize+dedup", "patterns": len(order)}
188
+
189
+
190
+ _SENT = re.compile(r"(?<=[.!?])\s+")
191
+ _STOP = frozenset(
192
+ "the a an and or but of to in on for with is are was were be been it this that as at by".split()
193
+ )
194
+
195
+
196
+ def _compress_prose(text: str, target_tokens: int | None, model: str) -> tuple[str, dict]:
197
+ """Extractive: rank sentences by keyword density, keep the top ones in original order."""
198
+ sentences = [s for s in _SENT.split(text.strip()) if s.strip()]
199
+ if len(sentences) <= 1:
200
+ return text, {"technique": "extractive", "kept": len(sentences)}
201
+
202
+ freq: dict[str, int] = {}
203
+ for word in re.findall(r"[a-zA-Z']+", text.lower()):
204
+ if word not in _STOP:
205
+ freq[word] = freq.get(word, 0) + 1
206
+
207
+ def score(sentence: str) -> float:
208
+ words = re.findall(r"[a-zA-Z']+", sentence.lower())
209
+ if not words:
210
+ return 0.0
211
+ return sum(freq.get(w, 0) for w in words) / len(words)
212
+
213
+ ranked = sorted(range(len(sentences)), key=lambda i: score(sentences[i]), reverse=True)
214
+
215
+ if target_tokens is not None:
216
+ keep: set[int] = set()
217
+ for i in ranked:
218
+ trial = " ".join(sentences[j] for j in sorted(keep | {i}))
219
+ if tokens.count(trial, model) > target_tokens and keep:
220
+ break
221
+ keep.add(i)
222
+ else:
223
+ keep = set(ranked[: max(1, len(sentences) // 2)]) # default: keep the top half
224
+
225
+ small = " ".join(sentences[i] for i in sorted(keep))
226
+ return small, {"technique": "extractive", "kept": len(keep), "of": len(sentences)}
227
+
228
+
229
+ def _truncate_to_tokens(text: str, target: int, model: str) -> str:
230
+ if target <= 0:
231
+ return ""
232
+ if tokens.count(text, model) <= target:
233
+ return text
234
+ ratio = max(1, len(text)) / max(1, tokens.count(text, model))
235
+ cut = text[: int(target * ratio)]
236
+ while cut and tokens.count(cut, model) > target:
237
+ cut = cut[: int(len(cut) * 0.9)]
238
+ return cut
@@ -0,0 +1,96 @@
1
+ """Compression is content-aware, deterministic, and 100% reversible. No network."""
2
+
3
+ import json
4
+
5
+ import pytest
6
+ from powerailabs.core import protocols, tokens
7
+ from powerailabs.squeeze import SqueezeCompressor, compress, decompress, detect
8
+
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def _heuristic_tokens(monkeypatch):
12
+ monkeypatch.setattr(tokens, "_tiktoken_encoding", lambda model: None)
13
+ yield
14
+
15
+
16
+ def test_detect():
17
+ assert detect('{"a": 1}') == "json"
18
+ assert detect("[1, 2, 3]") == "json"
19
+ logs = "\n".join(f"2026-06-01T00:00:0{i} INFO started worker" for i in range(5))
20
+ assert detect(logs) == "logs"
21
+ assert detect("The cat sat on the mat. It was a sunny day.") == "prose"
22
+
23
+
24
+ def test_json_compression_is_smaller_and_reversible():
25
+ obj = {"name": "alice", "age": 30, "note": None, "tags": ["x", "y"], "extra": None}
26
+ pretty = json.dumps(obj, indent=4)
27
+ small, handle = compress(pretty, kind="auto")
28
+ assert detect(pretty) == "json"
29
+ assert len(small) < len(pretty) # whitespace + nulls gone
30
+ assert "note" not in small and "null" not in small # nulls dropped
31
+ assert handle.expand() == pretty # exact original restored
32
+
33
+
34
+ def test_logs_dedup_collapses_repeats():
35
+ logs = "\n".join(["2026-06-01T00:00:00Z INFO retry attempt"] * 20)
36
+ small, handle = compress(logs, kind="logs")
37
+ assert "(ร—20)" in small
38
+ assert tokens.count(small, "gpt-4o") < tokens.count(logs, "gpt-4o")
39
+ assert handle.expand() == logs
40
+
41
+
42
+ def test_prose_extractive_hits_target_tokens():
43
+ text = (
44
+ "Refunds are processed within five business days. "
45
+ "The weather today is mild and pleasant. "
46
+ "Customers must contact support to request a refund. "
47
+ "Our office cat is named Mittens. "
48
+ "Refund eligibility depends on the purchase date."
49
+ )
50
+ target = 20
51
+ small, handle = compress(text, kind="prose", target_tokens=target)
52
+ assert tokens.count(small, "gpt-4o") <= target
53
+ assert len(small) < len(text)
54
+ assert handle.expand() == text # original always restorable
55
+
56
+
57
+ def test_object_input_serialized_and_restored():
58
+ small, handle = compress({"k": "v", "n": None}, kind="auto")
59
+ assert "null" not in small
60
+ restored = json.loads(handle.expand())
61
+ assert restored == {"k": "v", "n": None}
62
+
63
+
64
+ def test_decompress_matches_expand():
65
+ small, handle = compress("hello world. goodbye world.", kind="prose")
66
+ assert decompress(handle) == handle.expand()
67
+
68
+
69
+ def test_ccr_store_dedupes_identical_originals():
70
+ from powerailabs.squeeze import _STORE
71
+
72
+ before = len(_STORE)
73
+ _, h1 = compress("identical content here", kind="prose")
74
+ _, h2 = compress("identical content here", kind="prose")
75
+ assert h1.original_ref == h2.original_ref # same hash key
76
+ assert len(_STORE) == before + 1 # stored once
77
+
78
+
79
+ def test_satisfies_core_compressor_protocol():
80
+ assert isinstance(SqueezeCompressor(), protocols.Compressor)
81
+ small, handle = SqueezeCompressor().compress("a. b. c. d.", target_tokens=5, model="gpt-4o")
82
+ assert isinstance(handle.expand(), str)
83
+
84
+
85
+ def test_contextkit_compress_routes_through_squeeze():
86
+ # End-to-end: contextkit discovers squeeze by shape via the [squeeze] extra (both installed).
87
+ from powerailabs.contextkit import Block, Context
88
+
89
+ ctx = Context(budget_tokens=30, model="gpt-4o")
90
+ ctx.add(Block("keep me", priority=10, role="system"))
91
+ big = " ".join(f"Sentence number {i} about refunds and billing." for i in range(20))
92
+ ctx.add(Block(big, priority=1, role="user", evict="compress"))
93
+ ctx.assemble()
94
+ decision = next(d for d in ctx.report().decisions if d.role == "user")
95
+ assert decision.action == "compressed"
96
+ assert decision.tokens_after < decision.tokens_before