contextpress 0.5.7__tar.gz → 0.5.8__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.
- {contextpress-0.5.7 → contextpress-0.5.8}/AUDIT.md +11 -2
- {contextpress-0.5.7 → contextpress-0.5.8}/CHANGELOG.md +8 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/PKG-INFO +1 -1
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/__init__.py +1 -1
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/normalizer.py +1 -1
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/filler.py +20 -2
- {contextpress-0.5.7 → contextpress-0.5.8}/pyproject.toml +1 -1
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_filler.py +17 -0
- contextpress-0.5.8/tests/test_v058.py +55 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/.gitignore +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/AGENTS.md +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/CITATION.cff +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/CONTRIBUTING.md +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/LICENSE +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/NOTICE +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/README.md +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/_bootstrap.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/compression.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/core.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/llm/__init__.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/llm/_helpers.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/llm/adapters.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/llm/base.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/models.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/pipeline.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/profiles.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/py.typed +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/registry.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/stats.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/__init__.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/base.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/budget.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/recency.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/repetition.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/strategies/resolution.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/text_sim.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/contextpress/warnings_capture.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/agent_pipeline.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/benchmark_presets.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/dry_run_preview.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/estimate_and_stats.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/llm_tier_claude.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/llm_tier_gemini.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/llm_tier_ollama.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/llm_tier_openai.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/examples/pick_preset.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/__init__.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/01_filler_heavy.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/02_resolution_thread.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/03_repetition.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/04_long_history.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/05_agent_tools.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/06_rag_chunks.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/07_short_stable.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/08_mixed_ack_resolution.json +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/fixtures/chats/README.md +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_budget.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_fixture_chats.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_llm_helpers.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_models.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_normalizer.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_pipeline.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_recency.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_repetition.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_resolution.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_stats.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v03.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v04.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v05.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v051.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v052.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v053.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v054.py +0 -0
- {contextpress-0.5.7 → contextpress-0.5.8}/tests/test_v056.py +0 -0
|
@@ -26,7 +26,7 @@ Profiles: `chat`, `rag_doc`, `agent`. Tier 1 deps: nltk, scikit-learn, sumy, tik
|
|
|
26
26
|
|----|----------|---------|--------|
|
|
27
27
|
| C1 | Medium | Invariant 1 says system turns are untouched; `BudgetStrategy` truncates system as last resort (`budget.py`) | Document in contract (Phase 2) or change behavior later |
|
|
28
28
|
| C2 | Medium | LLM dedup failures in `Pipeline._run_llm_tier` fall back silently (no warning); summarize path warns | Add warning (Phase 2) |
|
|
29
|
-
| C3 |
|
|
29
|
+
| C3 | Low | Filler drops assistant-only ack phrases like `"sounds good"` before resolution (`filler.py` / `resolution.py`) | **By design** — fixture `08_*` + Layer-A still collapses; documented |
|
|
30
30
|
| C4 | Low | `Turn.importance` / `Turn.resolved` set by resolution but unused by pipeline | Leave; optional cleanup later |
|
|
31
31
|
|
|
32
32
|
### Duplication / maintainability (Phase 2)
|
|
@@ -72,7 +72,16 @@ Profiles: `chat`, `rag_doc`, `agent`. Tier 1 deps: nltk, scikit-learn, sumy, tik
|
|
|
72
72
|
| 1 Analysis | **0.5.5** | This document + backlog — shipped |
|
|
73
73
|
| 2 Refactor | **0.5.6** | R1–R3, C1 doc, C2 warning (no semantic stage changes) — shipped |
|
|
74
74
|
| 3 Fixtures | **0.5.7** | Offline chat samples + smoke/invariant tests — shipped |
|
|
75
|
-
| 4 Fixes | **0.5.8** | Bugs proven by fixtures + T1–T5 regression tests |
|
|
75
|
+
| 4 Fixes | **0.5.8** | Bugs proven by fixtures + T1–T5 regression tests — shipped |
|
|
76
|
+
|
|
77
|
+
### Phase 4 outcomes (0.5.8)
|
|
78
|
+
|
|
79
|
+
| Fix | Detail |
|
|
80
|
+
|-----|--------|
|
|
81
|
+
| Filler punctuation | Cleanup orphan commas / leading punctuation after phrase removal |
|
|
82
|
+
| User filler-only | User turns are not dropped when filler removal empties them |
|
|
83
|
+
| `messages=None` | Raises ``TypeError`` (was silently empty) |
|
|
84
|
+
| Regression tests | Extra keys, rag_doc skips resolution, resolution fixture, ``compress_many`` immutability |
|
|
76
85
|
|
|
77
86
|
## Invariants checklist (from `pipeline.py`)
|
|
78
87
|
|
|
@@ -4,6 +4,14 @@ All notable changes to `contextpress` are recorded here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.8] - 2026-07-26
|
|
8
|
+
|
|
9
|
+
- **Stabilization Phase 4** — fixture-driven fixes:
|
|
10
|
+
- Filler stage cleans orphan commas / leading punctuation after removals.
|
|
11
|
+
- User turns are never dropped when filler removal would empty them.
|
|
12
|
+
- ``compress(None)`` / ``normalize_messages(None)`` raise ``TypeError``.
|
|
13
|
+
- Regression tests for rag_doc (no resolution), resolution fixture, extra dict keys, batch immutability.
|
|
14
|
+
|
|
7
15
|
## [0.5.7] - 2026-07-26
|
|
8
16
|
|
|
9
17
|
- **Stabilization Phase 3** — offline chat fixtures under `tests/fixtures/chats/` (8 synthetic threads).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contextpress
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.8
|
|
4
4
|
Summary: Deterministic context compression for LLM chat, RAG, and agent pipelines
|
|
5
5
|
Project-URL: Homepage, https://github.com/Taha-azizi/contextpress
|
|
6
6
|
Project-URL: Documentation, https://github.com/Taha-azizi/contextpress#readme
|
|
@@ -133,7 +133,7 @@ def normalize_messages(
|
|
|
133
133
|
ctx: dict[str, Any] = {"format": "unknown", "extras": []}
|
|
134
134
|
|
|
135
135
|
if messages is None:
|
|
136
|
-
|
|
136
|
+
raise TypeError("contextpress: messages must not be None")
|
|
137
137
|
|
|
138
138
|
# LangChain-style objects (list of objects with .content)
|
|
139
139
|
if (
|
|
@@ -98,13 +98,28 @@ _FILLER_RE = _build_filler_pattern()
|
|
|
98
98
|
_ACTUALLY_NO = re.compile(r"^\s*actually\s*,\s*no\b", re.IGNORECASE)
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def _cleanup_after_filler(text: str) -> str:
|
|
102
|
+
"""Repair punctuation left behind after filler phrase removal."""
|
|
103
|
+
s = re.sub(r"\s+", " ", text)
|
|
104
|
+
s = re.sub(r"\s*,\s*(?:,\s*)+", ", ", s)
|
|
105
|
+
s = re.sub(r"\s*;\s*(?:;\s*)+", "; ", s)
|
|
106
|
+
s = re.sub(r"\s+([,.;:!?])", r"\1", s)
|
|
107
|
+
s = re.sub(r"^[\s,;:.]+", "", s)
|
|
108
|
+
s = re.sub(r"[,;]\s*$", "", s)
|
|
109
|
+
s = s.strip()
|
|
110
|
+
if s and s[0].islower():
|
|
111
|
+
s = s[0].upper() + s[1:]
|
|
112
|
+
return s
|
|
113
|
+
|
|
114
|
+
|
|
101
115
|
def _remove_fillers_text(text: str) -> str:
|
|
102
116
|
if _ACTUALLY_NO.match(text):
|
|
103
117
|
return text
|
|
104
118
|
# do not strip "actually" in "actually, no"
|
|
105
119
|
s = _FILLER_RE.sub("", text)
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
if s == text:
|
|
121
|
+
return text
|
|
122
|
+
return _cleanup_after_filler(s)
|
|
108
123
|
|
|
109
124
|
|
|
110
125
|
def _is_acknowledgement_only(text: str) -> bool:
|
|
@@ -152,6 +167,9 @@ class FillerStrategy(BaseStrategy):
|
|
|
152
167
|
|
|
153
168
|
new_text = _remove_fillers_text(text)
|
|
154
169
|
if not new_text.strip():
|
|
170
|
+
# Never drop user turns entirely (filler-only user messages stay).
|
|
171
|
+
if turn.role == "user":
|
|
172
|
+
new_turns.append(copy.deepcopy(turn))
|
|
155
173
|
continue
|
|
156
174
|
|
|
157
175
|
if new_text != text:
|
|
@@ -21,6 +21,23 @@ def test_filler_sentence_start():
|
|
|
21
21
|
t = Turn(role="assistant", content="Basically, we use Postgres.")
|
|
22
22
|
out = _run()([t]).turns[0]
|
|
23
23
|
assert "basically" not in out.content.lower()
|
|
24
|
+
assert not out.content.startswith(",")
|
|
25
|
+
assert out.content.startswith("We")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_filler_cleans_orphan_commas():
|
|
29
|
+
t = Turn(role="assistant", content="Yeah, you know, you can add indexes.")
|
|
30
|
+
out = _run()([t]).turns[0]
|
|
31
|
+
assert "you know" not in out.content.lower()
|
|
32
|
+
assert ", ," not in out.content
|
|
33
|
+
assert "Yeah, you can" in out.content
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_filler_only_user_not_dropped():
|
|
37
|
+
t = Turn(role="user", content="Basically.")
|
|
38
|
+
out = _run()([t]).turns
|
|
39
|
+
assert len(out) == 1
|
|
40
|
+
assert out[0].role == "user"
|
|
24
41
|
|
|
25
42
|
|
|
26
43
|
def test_acknowledgement_assistant_dropped():
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from contextpress import ContextManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_messages_none_raises():
|
|
11
|
+
cm = ContextManager(type="chat")
|
|
12
|
+
with pytest.raises(TypeError, match="must not be None"):
|
|
13
|
+
cm.compress(None)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_extra_dict_keys_preserved():
|
|
17
|
+
cm = ContextManager(type="chat")
|
|
18
|
+
messages = [{"role": "user", "content": "hello basically", "name": "alice", "custom": 123}]
|
|
19
|
+
out = cm.compress(messages, token_budget=None)
|
|
20
|
+
assert out[0]["name"] == "alice"
|
|
21
|
+
assert out[0]["custom"] == 123
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_rag_doc_skips_resolution_stage():
|
|
25
|
+
data = json.loads(
|
|
26
|
+
(Path(__file__).parent / "fixtures" / "chats" / "06_rag_chunks.json").read_text(
|
|
27
|
+
encoding="utf-8"
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
cm = ContextManager(type="rag_doc", compression="high")
|
|
31
|
+
result = cm.compress(data["messages"], token_budget=None, return_stats=True)
|
|
32
|
+
assert "resolution" not in result.stats.stages_run
|
|
33
|
+
assert not any("RESOLVED" in str(m.get("content", "")) for m in result.messages)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_resolution_fixture_collapses():
|
|
37
|
+
data = json.loads(
|
|
38
|
+
(Path(__file__).parent / "fixtures" / "chats" / "02_resolution_thread.json").read_text(
|
|
39
|
+
encoding="utf-8"
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
cm = ContextManager(type="chat", compression="high")
|
|
43
|
+
result = cm.compress(data["messages"], token_budget=None, return_stats=True)
|
|
44
|
+
assert any("RESOLVED" in str(m.get("content", "")) for m in result.messages)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_compress_many_immutability():
|
|
48
|
+
batches = [
|
|
49
|
+
[{"role": "user", "content": "hello basically"}],
|
|
50
|
+
[{"role": "user", "content": "thanks basically"}],
|
|
51
|
+
]
|
|
52
|
+
original = copy.deepcopy(batches)
|
|
53
|
+
cm = ContextManager(type="chat")
|
|
54
|
+
cm.compress_many(batches, token_budget=200)
|
|
55
|
+
assert batches == original
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|