pycontextfold 0.1.0__tar.gz → 0.1.2__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.
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/Cargo.lock +5 -5
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/Cargo.toml +6 -6
- pycontextfold-0.1.2/PKG-INFO +290 -0
- pycontextfold-0.1.2/README.md +230 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/pyproject.toml +34 -1
- pycontextfold-0.1.2/src/pycontextfold/bench/__init__.py +85 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/__main__.py +8 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/__init__.py +64 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/base.py +133 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/baseline.py +42 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/contextfold.py +95 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/openanchor.py +46 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pyagenthound.py +71 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pyapicheck.py +68 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pyinferencemanager.py +65 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pystreamai.py +51 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pystreammcp.py +85 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pystreampdf.py +91 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pytokencalc.py +82 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/pyvectorhound.py +63 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/adapters/registry.py +122 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/capabilities.py +124 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/chunking.py +187 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/cli.py +188 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/config.py +85 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/dataset.py +166 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/gates.py +164 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/metrics.py +176 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/models.py +350 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/report.py +320 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/runner.py +270 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/runs.py +215 -0
- pycontextfold-0.1.2/src/pycontextfold/bench/tokens.py +99 -0
- pycontextfold-0.1.0/PKG-INFO +0 -189
- pycontextfold-0.1.0/README.md +0 -156
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/LICENSE +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/Cargo.toml +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/analyzer.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/edit.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/engine.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/error.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/lang.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/lib.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/model.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/profile.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/rules.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/tokenizer.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/src/transform.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/tests/core_tests.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/tests/properties.proptest-regressions +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-core/tests/properties.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-parsers/Cargo.toml +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-parsers/src/langspec.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-parsers/src/lib.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-parsers/tests/parsing.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-py/Cargo.toml +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/crates/contextfold-py/src/lib.rs +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/src/pycontextfold/__init__.py +0 -0
- {pycontextfold-0.1.0 → pycontextfold-0.1.2}/src/pycontextfold/py.typed +0 -0
|
@@ -254,7 +254,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
|
254
254
|
|
|
255
255
|
[[package]]
|
|
256
256
|
name = "contextfold-bench"
|
|
257
|
-
version = "0.1.
|
|
257
|
+
version = "0.1.2"
|
|
258
258
|
dependencies = [
|
|
259
259
|
"contextfold-core",
|
|
260
260
|
"contextfold-parsers",
|
|
@@ -263,7 +263,7 @@ dependencies = [
|
|
|
263
263
|
|
|
264
264
|
[[package]]
|
|
265
265
|
name = "contextfold-cli"
|
|
266
|
-
version = "0.1.
|
|
266
|
+
version = "0.1.2"
|
|
267
267
|
dependencies = [
|
|
268
268
|
"anyhow",
|
|
269
269
|
"clap",
|
|
@@ -274,7 +274,7 @@ dependencies = [
|
|
|
274
274
|
|
|
275
275
|
[[package]]
|
|
276
276
|
name = "contextfold-core"
|
|
277
|
-
version = "0.1.
|
|
277
|
+
version = "0.1.2"
|
|
278
278
|
dependencies = [
|
|
279
279
|
"proptest",
|
|
280
280
|
"serde",
|
|
@@ -284,7 +284,7 @@ dependencies = [
|
|
|
284
284
|
|
|
285
285
|
[[package]]
|
|
286
286
|
name = "contextfold-parsers"
|
|
287
|
-
version = "0.1.
|
|
287
|
+
version = "0.1.2"
|
|
288
288
|
dependencies = [
|
|
289
289
|
"contextfold-core",
|
|
290
290
|
"serde",
|
|
@@ -299,7 +299,7 @@ dependencies = [
|
|
|
299
299
|
|
|
300
300
|
[[package]]
|
|
301
301
|
name = "contextfold-py"
|
|
302
|
-
version = "0.1.
|
|
302
|
+
version = "0.1.2"
|
|
303
303
|
dependencies = [
|
|
304
304
|
"contextfold-core",
|
|
305
305
|
"contextfold-parsers",
|
|
@@ -4,16 +4,16 @@ resolver = "2"
|
|
|
4
4
|
exclude = ["fuzz"]
|
|
5
5
|
|
|
6
6
|
[workspace.package]
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
edition = "2021"
|
|
9
|
-
rust-version = "1.
|
|
10
|
-
|
|
11
|
-
license = "Apache-2.0"
|
|
9
|
+
rust-version = "1.75"
|
|
10
|
+
license = "MIT"
|
|
12
11
|
repository = "https://github.com/Mullassery/PyContextFold"
|
|
12
|
+
authors = ["Georgi Mullassery"]
|
|
13
13
|
|
|
14
14
|
[workspace.dependencies]
|
|
15
|
-
contextfold-core = { path = "crates/contextfold-core", version = "0.1.
|
|
16
|
-
contextfold-parsers = { path = "crates/contextfold-parsers", version = "0.1.
|
|
15
|
+
contextfold-core = { path = "crates/contextfold-core", version = "0.1.2" }
|
|
16
|
+
contextfold-parsers = { path = "crates/contextfold-parsers", version = "0.1.2" }
|
|
17
17
|
|
|
18
18
|
serde = { version = "1.0", features = ["derive"] }
|
|
19
19
|
serde_json = "1.0"
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycontextfold
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Environment :: Console
|
|
6
|
+
Classifier: Intended Audience :: Developers
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: pyagenthound ; extra == 'agents'
|
|
18
|
+
Requires-Dist: pyapicheck ; extra == 'api'
|
|
19
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: maturin>=1.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.5.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pystreammcp ; extra == 'discovery'
|
|
23
|
+
Requires-Dist: pytokencalc ; extra == 'ecosystem'
|
|
24
|
+
Requires-Dist: pystreampdf ; extra == 'ecosystem'
|
|
25
|
+
Requires-Dist: pyagenthound ; extra == 'ecosystem'
|
|
26
|
+
Requires-Dist: pyapicheck ; extra == 'ecosystem'
|
|
27
|
+
Requires-Dist: pyvectorhound ; extra == 'ecosystem'
|
|
28
|
+
Requires-Dist: pyinferencemanager ; extra == 'ecosystem'
|
|
29
|
+
Requires-Dist: pystreamai ; extra == 'ecosystem'
|
|
30
|
+
Requires-Dist: openanchor ; extra == 'ecosystem'
|
|
31
|
+
Requires-Dist: pystreammcp ; extra == 'ecosystem'
|
|
32
|
+
Requires-Dist: pyinferencemanager ; extra == 'inference'
|
|
33
|
+
Requires-Dist: pystreamai ; extra == 'inference'
|
|
34
|
+
Requires-Dist: openanchor ; extra == 'middleware'
|
|
35
|
+
Requires-Dist: pystreampdf ; extra == 'pdf'
|
|
36
|
+
Requires-Dist: pyvectorhound ; extra == 'retrieval'
|
|
37
|
+
Requires-Dist: pytokencalc ; extra == 'tokens'
|
|
38
|
+
Provides-Extra: agents
|
|
39
|
+
Provides-Extra: api
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Provides-Extra: discovery
|
|
42
|
+
Provides-Extra: ecosystem
|
|
43
|
+
Provides-Extra: inference
|
|
44
|
+
Provides-Extra: middleware
|
|
45
|
+
Provides-Extra: pdf
|
|
46
|
+
Provides-Extra: retrieval
|
|
47
|
+
Provides-Extra: tokens
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Summary: Structure-aware context optimization for LLM consumption. Shrink source code, documentation, logs and structured text before sending them to an LLM. Typed Python API with a fast, deterministic Rust core, Tree-sitter parsing, conservative profiles, preservation policies, budget-aware folding and transformation reports.
|
|
50
|
+
Keywords: llm,context,compression,tree-sitter,token-budget,code-analysis,local-first,rust,pyo3,context-optimization,prompt-engineering,log-reduction,markdown
|
|
51
|
+
Author-email: Georgi Mammen Mullassery <mullassery@gmail.com>
|
|
52
|
+
License-Expression: Apache-2.0
|
|
53
|
+
Requires-Python: >=3.10
|
|
54
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
55
|
+
Project-URL: Documentation, https://github.com/Mullassery/PyContextFold/blob/main/README.md
|
|
56
|
+
Project-URL: Homepage, https://github.com/Mullassery/PyContextFold
|
|
57
|
+
Project-URL: Issues, https://github.com/Mullassery/PyContextFold/issues
|
|
58
|
+
Project-URL: Repository, https://github.com/Mullassery/PyContextFold.git
|
|
59
|
+
|
|
60
|
+
# PyContextFold
|
|
61
|
+
|
|
62
|
+
[](https://pypi.org/project/pycontextfold/)
|
|
63
|
+
[](https://pypi.org/project/pycontextfold/)
|
|
64
|
+
[](https://github.com/Mullassery/PyContextFold/actions/workflows/tests.yml)
|
|
65
|
+
[](LICENSE)
|
|
66
|
+
|
|
67
|
+
**A Python library for structure-aware context optimization for LLM consumption.**
|
|
68
|
+
|
|
69
|
+
PyContextFold shrinks source code, technical documentation, logs and structured
|
|
70
|
+
text before you send them to an LLM — cutting token cost while keeping every
|
|
71
|
+
transformation explicit, configurable and reportable. It is a local-first
|
|
72
|
+
`pip install` with a fast, deterministic Rust core underneath, and it never
|
|
73
|
+
sends your content anywhere.
|
|
74
|
+
|
|
75
|
+
It is a *transformation engine for content you already have* — not an inference
|
|
76
|
+
orchestrator, retrieval engine, token-counting library, PDF processor, or agent
|
|
77
|
+
framework.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install pycontextfold
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Requires Python ≥ 3.10 (the wheel is built `abi3-py310`). The core, the CLI and
|
|
86
|
+
the offline benchmark need nothing else. Optional ecosystem adapters activate
|
|
87
|
+
when their project is importable; install them individually, e.g.
|
|
88
|
+
`pip install "pycontextfold[tokens]"`, `[pdf]`, `[agents]`, or `[ecosystem]` for
|
|
89
|
+
all of them.
|
|
90
|
+
|
|
91
|
+
## Quick start
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import pycontextfold as pcf
|
|
95
|
+
|
|
96
|
+
result = pcf.fold(source, profile="safe", path="app.py")
|
|
97
|
+
print(result.output) # transformed text
|
|
98
|
+
print(result.report.detected_language) # "python"
|
|
99
|
+
print(result.report.reduction_ratio) # e.g. 0.19
|
|
100
|
+
print(result.report.syntax_valid) # True
|
|
101
|
+
print(result.report.to_dict()) # serialisable report
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Fold a file without ever modifying it:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
result = pcf.fold_file("app.py", profile="code-review")
|
|
108
|
+
open("app.folded.py", "w").write(result.output)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Introspection and full control:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
pcf.profiles() # ['budgeted', 'code-review', ...]
|
|
115
|
+
pcf.profile_info("safe") # {name, description, loss, ...}
|
|
116
|
+
pcf.supported_languages() # [...]
|
|
117
|
+
pcf.detect_language("fn main() {}", path="a.rs") # "rust"
|
|
118
|
+
|
|
119
|
+
# Typed request object with budgets, protection and a caller-supplied tokenizer
|
|
120
|
+
req = pcf.FoldRequest(
|
|
121
|
+
source,
|
|
122
|
+
profile="budgeted",
|
|
123
|
+
language="python",
|
|
124
|
+
budget_bytes=4096,
|
|
125
|
+
protected_ranges=[(0, 128)], # keep the license header
|
|
126
|
+
protected_line_markers=["contextfold:keep"],
|
|
127
|
+
)
|
|
128
|
+
res = pcf.Engine().fold_request(req)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Errors are typed — `UnknownProfileError`, `InvalidConfigError`,
|
|
132
|
+
`BudgetNotMetError` — all subclasses of `ContextFoldError`.
|
|
133
|
+
|
|
134
|
+
## What it owns (and what it deliberately does not)
|
|
135
|
+
|
|
136
|
+
**Owns:** content-aware parsing, structural transformations, redundancy
|
|
137
|
+
reduction, configurable preservation policies, budget-constrained folding,
|
|
138
|
+
validation of transformed output, and transformation reports.
|
|
139
|
+
|
|
140
|
+
**Does not own** (and never re-implements, imports, or depends on): provider
|
|
141
|
+
tokenization/cost (PyTokenCalc), context discovery/retrieval (PyStreamMCP),
|
|
142
|
+
inference orchestration (PyInferenceManager), PDF extraction (PyStreamPDF),
|
|
143
|
+
vector retrieval evaluation (PyVectorHound), agent tracing (PyAgentHound), or
|
|
144
|
+
OpenAnchor's token-intelligence middleware. Stable *interfaces* are exposed for
|
|
145
|
+
optional integration, but there are no cross-repository dependencies.
|
|
146
|
+
|
|
147
|
+
## Guarantees
|
|
148
|
+
|
|
149
|
+
- **Deterministic.** The same input, profile, engine version and registry
|
|
150
|
+
produce byte-identical output.
|
|
151
|
+
- **Conservative by default.** The `safe` profile never removes executable
|
|
152
|
+
statements, imports, conditions or error handling.
|
|
153
|
+
- **Honest about loss.** Lossy rules are labelled in the report. The engine
|
|
154
|
+
**never claims semantic equivalence** — only, optionally, syntactic validity.
|
|
155
|
+
- **Local-first.** No network access. Source content is never logged by default.
|
|
156
|
+
- **Non-destructive.** Inputs are never overwritten unless you explicitly opt in.
|
|
157
|
+
|
|
158
|
+
## Profiles
|
|
159
|
+
|
|
160
|
+
| Profile | Lossy | Summary |
|
|
161
|
+
|------------------|:-----:|---------|
|
|
162
|
+
| `safe` | no | Remove comments, collapse blank-line runs, trim trailing whitespace. Never removes code, imports, conditions or error handling. Docstrings preserved. |
|
|
163
|
+
| `code-review` | no | Preserve implementation logic **and** comments/docstrings; whitespace-only normalisation. |
|
|
164
|
+
| `interface-only` | yes | Keep imports, declarations, signatures and docstrings; replace function bodies with valid placeholders. |
|
|
165
|
+
| `documentation` | yes | Reduce redundant Markdown/prose; preserve headings, code fences, links and normative keywords. |
|
|
166
|
+
| `logs` | yes | Collapse consecutive duplicate log events, retaining configured counts. |
|
|
167
|
+
| `budgeted` | yes | Aggressive deterministic ordering to reach a size/token budget. |
|
|
168
|
+
|
|
169
|
+
Profiles are versioned (`PROFILE_SCHEMA_VERSION`, surfaced in every report).
|
|
170
|
+
See [`docs/PROFILES.md`](docs/PROFILES.md) for per-profile guarantees.
|
|
171
|
+
|
|
172
|
+
## Token budgets
|
|
173
|
+
|
|
174
|
+
PyContextFold accepts a **target budget** but does not ship a provider
|
|
175
|
+
tokenizer catalogue. Supply a tokenizer implementing `count(text) -> int`, or
|
|
176
|
+
use byte/character budgets (clearly reported as approximations, never as token
|
|
177
|
+
counts):
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
class MyTokenizer:
|
|
181
|
+
name = "my-tok"
|
|
182
|
+
def count(self, text: str) -> int:
|
|
183
|
+
...
|
|
184
|
+
|
|
185
|
+
req = pcf.FoldRequest(source, profile="budgeted",
|
|
186
|
+
budget_tokens=500, tokenizer=MyTokenizer())
|
|
187
|
+
res = pcf.Engine().fold_request(req)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
When a budget cannot be met under the configured loss policy, the report
|
|
191
|
+
contains `warnings: ["budget_not_met"]`; the engine does not silently drop code.
|
|
192
|
+
|
|
193
|
+
## Benchmarking (offline, dependency-light)
|
|
194
|
+
|
|
195
|
+
The `pycontextfold.bench` subpackage measures the trade-offs of folding: how much
|
|
196
|
+
is removed (size/character/token reduction) versus how much matters (ground-truth
|
|
197
|
+
preservation). It runs with nothing else installed and never touches the network.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from pycontextfold import bench
|
|
201
|
+
|
|
202
|
+
ds = bench.dataset.synthetic_dataset()
|
|
203
|
+
reg = bench.adapters.default_registry()
|
|
204
|
+
adapter = reg.get("contextfold")
|
|
205
|
+
|
|
206
|
+
for doc in ds:
|
|
207
|
+
result = adapter.optimize(bench.OptimizationRequest(doc, method="safe"))
|
|
208
|
+
metrics = bench.metrics.document_metrics(doc, result, ds.ground_truth_for(doc.id))
|
|
209
|
+
print(doc.id, {m.name: round(m.value, 3) for m in metrics if m.available})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Optional ecosystem adapters (PyTokenCalc, PyStreamPDF, PyVectorHound, ...) are
|
|
213
|
+
capability-declared and imported lazily; a requested-but-missing capability is
|
|
214
|
+
reported as *unavailable* rather than silently substituted. See
|
|
215
|
+
[`docs/ROADMAP.md`](docs/ROADMAP.md) and
|
|
216
|
+
[`docs/ECOSYSTEM_ASSESSMENT.md`](docs/ECOSYSTEM_ASSESSMENT.md).
|
|
217
|
+
|
|
218
|
+
Run a whole experiment and emit a single-file HTML report (no CDN, fully
|
|
219
|
+
offline):
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from pycontextfold import bench
|
|
223
|
+
|
|
224
|
+
run = bench.run_experiment(
|
|
225
|
+
bench.dataset.synthetic_dataset(),
|
|
226
|
+
bench.ExperimentConfig(name="demo", dataset="synthetic",
|
|
227
|
+
adapters=("baseline", "contextfold")),
|
|
228
|
+
)
|
|
229
|
+
bench.write_report(run, "report.html") # self-contained, deterministic
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
There is also a CLI (offline, stdlib only):
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
pycontextfold-bench adapters # list + availability
|
|
236
|
+
pycontextfold-bench run --config exp.json --report report.html
|
|
237
|
+
pycontextfold-bench run --config exp.json --gate # exit 1 on gate failure
|
|
238
|
+
pycontextfold-bench report run.json --out report.html
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Gates turn a run into a regression check — an aggregated metric below its
|
|
242
|
+
threshold, or an unavailable metric, fails the run.
|
|
243
|
+
|
|
244
|
+
## Command-line tool (optional)
|
|
245
|
+
|
|
246
|
+
Installing the package gives you the Python library. The repository also ships a
|
|
247
|
+
standalone `contextfold` CLI and a Rust library for non-Python use:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
cargo install --path crates/contextfold-cli
|
|
251
|
+
echo 'print("hi") # comment' | contextfold -p safe
|
|
252
|
+
contextfold app.py -p safe --report report.json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Architecture
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
src/pycontextfold Python package (public, typed API)
|
|
259
|
+
src/pycontextfold/bench offline benchmarking platform (adapters, runner, report, gates, CLI)
|
|
260
|
+
crates/
|
|
261
|
+
contextfold-core transformation pipeline (no Tree-sitter)
|
|
262
|
+
contextfold-parsers Tree-sitter integration + format detection
|
|
263
|
+
contextfold-cli standalone CLI
|
|
264
|
+
contextfold-py PyO3 bindings (module: pycontextfold._core)
|
|
265
|
+
tests/ Python + preservation/integration tests
|
|
266
|
+
tests/bench benchmarking platform tests
|
|
267
|
+
crates/*/tests Rust unit/integration tests
|
|
268
|
+
benches/ reproducible benchmarks
|
|
269
|
+
docs/ architecture, profiles, guarantees, limitations
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Python is the primary interface; the Rust core exists for speed, memory safety
|
|
273
|
+
and determinism. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
|
|
274
|
+
|
|
275
|
+
## Documentation
|
|
276
|
+
|
|
277
|
+
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
|
278
|
+
- [`docs/PROFILES.md`](docs/PROFILES.md)
|
|
279
|
+
- [`docs/GUARANTEES.md`](docs/GUARANTEES.md)
|
|
280
|
+
- [`docs/LIMITATIONS.md`](docs/LIMITATIONS.md)
|
|
281
|
+
- [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md)
|
|
282
|
+
- [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md)
|
|
283
|
+
- [`docs/BENCHMARKING.md`](docs/BENCHMARKING.md)
|
|
284
|
+
- [`docs/ECOSYSTEM_ASSESSMENT.md`](docs/ECOSYSTEM_ASSESSMENT.md)
|
|
285
|
+
- [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
Apache-2.0.
|
|
290
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# PyContextFold
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pycontextfold/)
|
|
4
|
+
[](https://pypi.org/project/pycontextfold/)
|
|
5
|
+
[](https://github.com/Mullassery/PyContextFold/actions/workflows/tests.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**A Python library for structure-aware context optimization for LLM consumption.**
|
|
9
|
+
|
|
10
|
+
PyContextFold shrinks source code, technical documentation, logs and structured
|
|
11
|
+
text before you send them to an LLM — cutting token cost while keeping every
|
|
12
|
+
transformation explicit, configurable and reportable. It is a local-first
|
|
13
|
+
`pip install` with a fast, deterministic Rust core underneath, and it never
|
|
14
|
+
sends your content anywhere.
|
|
15
|
+
|
|
16
|
+
It is a *transformation engine for content you already have* — not an inference
|
|
17
|
+
orchestrator, retrieval engine, token-counting library, PDF processor, or agent
|
|
18
|
+
framework.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install pycontextfold
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Python ≥ 3.10 (the wheel is built `abi3-py310`). The core, the CLI and
|
|
27
|
+
the offline benchmark need nothing else. Optional ecosystem adapters activate
|
|
28
|
+
when their project is importable; install them individually, e.g.
|
|
29
|
+
`pip install "pycontextfold[tokens]"`, `[pdf]`, `[agents]`, or `[ecosystem]` for
|
|
30
|
+
all of them.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import pycontextfold as pcf
|
|
36
|
+
|
|
37
|
+
result = pcf.fold(source, profile="safe", path="app.py")
|
|
38
|
+
print(result.output) # transformed text
|
|
39
|
+
print(result.report.detected_language) # "python"
|
|
40
|
+
print(result.report.reduction_ratio) # e.g. 0.19
|
|
41
|
+
print(result.report.syntax_valid) # True
|
|
42
|
+
print(result.report.to_dict()) # serialisable report
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Fold a file without ever modifying it:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
result = pcf.fold_file("app.py", profile="code-review")
|
|
49
|
+
open("app.folded.py", "w").write(result.output)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Introspection and full control:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
pcf.profiles() # ['budgeted', 'code-review', ...]
|
|
56
|
+
pcf.profile_info("safe") # {name, description, loss, ...}
|
|
57
|
+
pcf.supported_languages() # [...]
|
|
58
|
+
pcf.detect_language("fn main() {}", path="a.rs") # "rust"
|
|
59
|
+
|
|
60
|
+
# Typed request object with budgets, protection and a caller-supplied tokenizer
|
|
61
|
+
req = pcf.FoldRequest(
|
|
62
|
+
source,
|
|
63
|
+
profile="budgeted",
|
|
64
|
+
language="python",
|
|
65
|
+
budget_bytes=4096,
|
|
66
|
+
protected_ranges=[(0, 128)], # keep the license header
|
|
67
|
+
protected_line_markers=["contextfold:keep"],
|
|
68
|
+
)
|
|
69
|
+
res = pcf.Engine().fold_request(req)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Errors are typed — `UnknownProfileError`, `InvalidConfigError`,
|
|
73
|
+
`BudgetNotMetError` — all subclasses of `ContextFoldError`.
|
|
74
|
+
|
|
75
|
+
## What it owns (and what it deliberately does not)
|
|
76
|
+
|
|
77
|
+
**Owns:** content-aware parsing, structural transformations, redundancy
|
|
78
|
+
reduction, configurable preservation policies, budget-constrained folding,
|
|
79
|
+
validation of transformed output, and transformation reports.
|
|
80
|
+
|
|
81
|
+
**Does not own** (and never re-implements, imports, or depends on): provider
|
|
82
|
+
tokenization/cost (PyTokenCalc), context discovery/retrieval (PyStreamMCP),
|
|
83
|
+
inference orchestration (PyInferenceManager), PDF extraction (PyStreamPDF),
|
|
84
|
+
vector retrieval evaluation (PyVectorHound), agent tracing (PyAgentHound), or
|
|
85
|
+
OpenAnchor's token-intelligence middleware. Stable *interfaces* are exposed for
|
|
86
|
+
optional integration, but there are no cross-repository dependencies.
|
|
87
|
+
|
|
88
|
+
## Guarantees
|
|
89
|
+
|
|
90
|
+
- **Deterministic.** The same input, profile, engine version and registry
|
|
91
|
+
produce byte-identical output.
|
|
92
|
+
- **Conservative by default.** The `safe` profile never removes executable
|
|
93
|
+
statements, imports, conditions or error handling.
|
|
94
|
+
- **Honest about loss.** Lossy rules are labelled in the report. The engine
|
|
95
|
+
**never claims semantic equivalence** — only, optionally, syntactic validity.
|
|
96
|
+
- **Local-first.** No network access. Source content is never logged by default.
|
|
97
|
+
- **Non-destructive.** Inputs are never overwritten unless you explicitly opt in.
|
|
98
|
+
|
|
99
|
+
## Profiles
|
|
100
|
+
|
|
101
|
+
| Profile | Lossy | Summary |
|
|
102
|
+
|------------------|:-----:|---------|
|
|
103
|
+
| `safe` | no | Remove comments, collapse blank-line runs, trim trailing whitespace. Never removes code, imports, conditions or error handling. Docstrings preserved. |
|
|
104
|
+
| `code-review` | no | Preserve implementation logic **and** comments/docstrings; whitespace-only normalisation. |
|
|
105
|
+
| `interface-only` | yes | Keep imports, declarations, signatures and docstrings; replace function bodies with valid placeholders. |
|
|
106
|
+
| `documentation` | yes | Reduce redundant Markdown/prose; preserve headings, code fences, links and normative keywords. |
|
|
107
|
+
| `logs` | yes | Collapse consecutive duplicate log events, retaining configured counts. |
|
|
108
|
+
| `budgeted` | yes | Aggressive deterministic ordering to reach a size/token budget. |
|
|
109
|
+
|
|
110
|
+
Profiles are versioned (`PROFILE_SCHEMA_VERSION`, surfaced in every report).
|
|
111
|
+
See [`docs/PROFILES.md`](docs/PROFILES.md) for per-profile guarantees.
|
|
112
|
+
|
|
113
|
+
## Token budgets
|
|
114
|
+
|
|
115
|
+
PyContextFold accepts a **target budget** but does not ship a provider
|
|
116
|
+
tokenizer catalogue. Supply a tokenizer implementing `count(text) -> int`, or
|
|
117
|
+
use byte/character budgets (clearly reported as approximations, never as token
|
|
118
|
+
counts):
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
class MyTokenizer:
|
|
122
|
+
name = "my-tok"
|
|
123
|
+
def count(self, text: str) -> int:
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
req = pcf.FoldRequest(source, profile="budgeted",
|
|
127
|
+
budget_tokens=500, tokenizer=MyTokenizer())
|
|
128
|
+
res = pcf.Engine().fold_request(req)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
When a budget cannot be met under the configured loss policy, the report
|
|
132
|
+
contains `warnings: ["budget_not_met"]`; the engine does not silently drop code.
|
|
133
|
+
|
|
134
|
+
## Benchmarking (offline, dependency-light)
|
|
135
|
+
|
|
136
|
+
The `pycontextfold.bench` subpackage measures the trade-offs of folding: how much
|
|
137
|
+
is removed (size/character/token reduction) versus how much matters (ground-truth
|
|
138
|
+
preservation). It runs with nothing else installed and never touches the network.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from pycontextfold import bench
|
|
142
|
+
|
|
143
|
+
ds = bench.dataset.synthetic_dataset()
|
|
144
|
+
reg = bench.adapters.default_registry()
|
|
145
|
+
adapter = reg.get("contextfold")
|
|
146
|
+
|
|
147
|
+
for doc in ds:
|
|
148
|
+
result = adapter.optimize(bench.OptimizationRequest(doc, method="safe"))
|
|
149
|
+
metrics = bench.metrics.document_metrics(doc, result, ds.ground_truth_for(doc.id))
|
|
150
|
+
print(doc.id, {m.name: round(m.value, 3) for m in metrics if m.available})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Optional ecosystem adapters (PyTokenCalc, PyStreamPDF, PyVectorHound, ...) are
|
|
154
|
+
capability-declared and imported lazily; a requested-but-missing capability is
|
|
155
|
+
reported as *unavailable* rather than silently substituted. See
|
|
156
|
+
[`docs/ROADMAP.md`](docs/ROADMAP.md) and
|
|
157
|
+
[`docs/ECOSYSTEM_ASSESSMENT.md`](docs/ECOSYSTEM_ASSESSMENT.md).
|
|
158
|
+
|
|
159
|
+
Run a whole experiment and emit a single-file HTML report (no CDN, fully
|
|
160
|
+
offline):
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from pycontextfold import bench
|
|
164
|
+
|
|
165
|
+
run = bench.run_experiment(
|
|
166
|
+
bench.dataset.synthetic_dataset(),
|
|
167
|
+
bench.ExperimentConfig(name="demo", dataset="synthetic",
|
|
168
|
+
adapters=("baseline", "contextfold")),
|
|
169
|
+
)
|
|
170
|
+
bench.write_report(run, "report.html") # self-contained, deterministic
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
There is also a CLI (offline, stdlib only):
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pycontextfold-bench adapters # list + availability
|
|
177
|
+
pycontextfold-bench run --config exp.json --report report.html
|
|
178
|
+
pycontextfold-bench run --config exp.json --gate # exit 1 on gate failure
|
|
179
|
+
pycontextfold-bench report run.json --out report.html
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Gates turn a run into a regression check — an aggregated metric below its
|
|
183
|
+
threshold, or an unavailable metric, fails the run.
|
|
184
|
+
|
|
185
|
+
## Command-line tool (optional)
|
|
186
|
+
|
|
187
|
+
Installing the package gives you the Python library. The repository also ships a
|
|
188
|
+
standalone `contextfold` CLI and a Rust library for non-Python use:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
cargo install --path crates/contextfold-cli
|
|
192
|
+
echo 'print("hi") # comment' | contextfold -p safe
|
|
193
|
+
contextfold app.py -p safe --report report.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Architecture
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
src/pycontextfold Python package (public, typed API)
|
|
200
|
+
src/pycontextfold/bench offline benchmarking platform (adapters, runner, report, gates, CLI)
|
|
201
|
+
crates/
|
|
202
|
+
contextfold-core transformation pipeline (no Tree-sitter)
|
|
203
|
+
contextfold-parsers Tree-sitter integration + format detection
|
|
204
|
+
contextfold-cli standalone CLI
|
|
205
|
+
contextfold-py PyO3 bindings (module: pycontextfold._core)
|
|
206
|
+
tests/ Python + preservation/integration tests
|
|
207
|
+
tests/bench benchmarking platform tests
|
|
208
|
+
crates/*/tests Rust unit/integration tests
|
|
209
|
+
benches/ reproducible benchmarks
|
|
210
|
+
docs/ architecture, profiles, guarantees, limitations
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Python is the primary interface; the Rust core exists for speed, memory safety
|
|
214
|
+
and determinism. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
|
|
215
|
+
|
|
216
|
+
## Documentation
|
|
217
|
+
|
|
218
|
+
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
|
219
|
+
- [`docs/PROFILES.md`](docs/PROFILES.md)
|
|
220
|
+
- [`docs/GUARANTEES.md`](docs/GUARANTEES.md)
|
|
221
|
+
- [`docs/LIMITATIONS.md`](docs/LIMITATIONS.md)
|
|
222
|
+
- [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md)
|
|
223
|
+
- [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md)
|
|
224
|
+
- [`docs/BENCHMARKING.md`](docs/BENCHMARKING.md)
|
|
225
|
+
- [`docs/ECOSYSTEM_ASSESSMENT.md`](docs/ECOSYSTEM_ASSESSMENT.md)
|
|
226
|
+
- [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
Apache-2.0.
|
|
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pycontextfold"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "
|
|
8
|
+
description = "Structure-aware context optimization for LLM consumption. Shrink source code, documentation, logs and structured text before sending them to an LLM. Typed Python API with a fast, deterministic Rust core, Tree-sitter parsing, conservative profiles, preservation policies, budget-aware folding and transformation reports."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
|
|
@@ -33,6 +33,9 @@ classifiers = [
|
|
|
33
33
|
]
|
|
34
34
|
dependencies = []
|
|
35
35
|
|
|
36
|
+
[project.scripts]
|
|
37
|
+
pycontextfold-bench = "pycontextfold.bench.cli:main"
|
|
38
|
+
|
|
36
39
|
[project.urls]
|
|
37
40
|
Homepage = "https://github.com/Mullassery/PyContextFold"
|
|
38
41
|
Documentation = "https://github.com/Mullassery/PyContextFold/blob/main/README.md"
|
|
@@ -41,6 +44,29 @@ Issues = "https://github.com/Mullassery/PyContextFold/issues"
|
|
|
41
44
|
|
|
42
45
|
[project.optional-dependencies]
|
|
43
46
|
dev = ["pytest>=7.0", "maturin>=1.0", "ruff>=0.5.0"]
|
|
47
|
+
# Optional ecosystem extras: each maps to a sibling project and activates one or
|
|
48
|
+
# more benchmark adapters. All are optional — the core and the offline benchmark
|
|
49
|
+
# run with none of them installed. Install the ones whose projects are reachable
|
|
50
|
+
# from your package index.
|
|
51
|
+
tokens = ["pytokencalc"]
|
|
52
|
+
pdf = ["pystreampdf"]
|
|
53
|
+
agents = ["pyagenthound"]
|
|
54
|
+
api = ["pyapicheck"]
|
|
55
|
+
retrieval = ["pyvectorhound"]
|
|
56
|
+
inference = ["pyinferencemanager", "pystreamai"]
|
|
57
|
+
middleware = ["openanchor"]
|
|
58
|
+
discovery = ["pystreammcp"]
|
|
59
|
+
ecosystem = [
|
|
60
|
+
"pytokencalc",
|
|
61
|
+
"pystreampdf",
|
|
62
|
+
"pyagenthound",
|
|
63
|
+
"pyapicheck",
|
|
64
|
+
"pyvectorhound",
|
|
65
|
+
"pyinferencemanager",
|
|
66
|
+
"pystreamai",
|
|
67
|
+
"openanchor",
|
|
68
|
+
"pystreammcp",
|
|
69
|
+
]
|
|
44
70
|
|
|
45
71
|
[tool.maturin]
|
|
46
72
|
python-source = "src"
|
|
@@ -50,3 +76,10 @@ include = ["LICENSE"]
|
|
|
50
76
|
|
|
51
77
|
[tool.pytest.ini_options]
|
|
52
78
|
testpaths = ["tests"]
|
|
79
|
+
markers = [
|
|
80
|
+
"optional: requires a real (optional) ecosystem package to be installed",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
target-version = "py310"
|
|
85
|
+
extend-exclude = ["tests/fixtures"]
|