contextpress 0.5.0__tar.gz → 0.5.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.
Files changed (55) hide show
  1. {contextpress-0.5.0 → contextpress-0.5.2}/CHANGELOG.md +10 -0
  2. {contextpress-0.5.0 → contextpress-0.5.2}/PKG-INFO +29 -4
  3. {contextpress-0.5.0 → contextpress-0.5.2}/README.md +28 -3
  4. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/__init__.py +1 -1
  5. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/__init__.py +2 -0
  6. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/adapters.py +100 -14
  7. contextpress-0.5.2/examples/llm_tier_claude.py +50 -0
  8. contextpress-0.5.2/examples/llm_tier_gemini.py +50 -0
  9. {contextpress-0.5.0 → contextpress-0.5.2}/examples/llm_tier_openai.py +1 -3
  10. {contextpress-0.5.0 → contextpress-0.5.2}/pyproject.toml +1 -1
  11. contextpress-0.5.2/tests/test_v051.py +50 -0
  12. contextpress-0.5.2/tests/test_v052.py +32 -0
  13. {contextpress-0.5.0 → contextpress-0.5.2}/.gitignore +0 -0
  14. {contextpress-0.5.0 → contextpress-0.5.2}/CITATION.cff +0 -0
  15. {contextpress-0.5.0 → contextpress-0.5.2}/CONTRIBUTING.md +0 -0
  16. {contextpress-0.5.0 → contextpress-0.5.2}/LICENSE +0 -0
  17. {contextpress-0.5.0 → contextpress-0.5.2}/NOTICE +0 -0
  18. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/_bootstrap.py +0 -0
  19. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/compression.py +0 -0
  20. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/core.py +0 -0
  21. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/_helpers.py +0 -0
  22. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/base.py +0 -0
  23. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/models.py +0 -0
  24. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/normalizer.py +0 -0
  25. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/pipeline.py +0 -0
  26. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/profiles.py +0 -0
  27. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/py.typed +0 -0
  28. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/registry.py +0 -0
  29. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/stats.py +0 -0
  30. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/__init__.py +0 -0
  31. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/base.py +0 -0
  32. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/budget.py +0 -0
  33. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/filler.py +0 -0
  34. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/recency.py +0 -0
  35. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/repetition.py +0 -0
  36. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/resolution.py +0 -0
  37. {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/warnings_capture.py +0 -0
  38. {contextpress-0.5.0 → contextpress-0.5.2}/examples/benchmark_presets.py +0 -0
  39. {contextpress-0.5.0 → contextpress-0.5.2}/examples/dry_run_preview.py +0 -0
  40. {contextpress-0.5.0 → contextpress-0.5.2}/examples/estimate_and_stats.py +0 -0
  41. {contextpress-0.5.0 → contextpress-0.5.2}/examples/llm_tier_ollama.py +0 -0
  42. {contextpress-0.5.0 → contextpress-0.5.2}/tests/__init__.py +0 -0
  43. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_budget.py +0 -0
  44. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_filler.py +0 -0
  45. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_llm_helpers.py +0 -0
  46. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_models.py +0 -0
  47. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_normalizer.py +0 -0
  48. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_pipeline.py +0 -0
  49. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_recency.py +0 -0
  50. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_repetition.py +0 -0
  51. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_resolution.py +0 -0
  52. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_stats.py +0 -0
  53. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_v03.py +0 -0
  54. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_v04.py +0 -0
  55. {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_v05.py +0 -0
@@ -4,6 +4,16 @@ 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.2] - 2026-07-22
8
+
9
+ - **`OpenAIBackend`** — optional `api_key` / env (`OPENAI_API_KEY`); no manual client required.
10
+
11
+ ## [0.5.1] - 2026-07-22
12
+
13
+ - **`ClaudeBackend`** — shortcut for Anthropic Claude (`ANTHROPIC_API_KEY` or `api_key=`).
14
+ - **`GeminiBackend`** — optional `model_name` + `api_key` / env (`GOOGLE_API_KEY`, `GEMINI_API_KEY`).
15
+ - Examples: `examples/llm_tier_claude.py`, `examples/llm_tier_gemini.py`.
16
+
7
17
  ## [0.5.0] - 2026-07-22
8
18
 
9
19
  - **`compress_async()`** — async wrapper around ``compress()`` for asyncio apps.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextpress
3
- Version: 0.5.0
3
+ Version: 0.5.2
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
@@ -401,7 +401,7 @@ ContextManager(type="agent")
401
401
  | **Techniques** | Rules, TF–IDF, cosine similarity, NLTK, Sumy extractive summarization, tiktoken | Your provider’s chat/completions API (you supply the client) |
402
402
  | **API key** | None | Required for your chosen provider (OpenAI, Anthropic, …) |
403
403
  | **Determinism** | Deterministic for a fixed input and settings | Non-deterministic (model sampling) |
404
- | **How to enable** | Default: `ContextManager()` runs Tier 1 only | Pass `llm_backend=` (`OpenAIBackend`, `AnthropicBackend`, `GeminiBackend`, **`OllamaBackend`**, or custom `LLMBackend`) |
404
+ | **How to enable** | Default: `ContextManager()` runs Tier 1 only | Pass `llm_backend=` (`OpenAIBackend`, **`ClaudeBackend`**, `GeminiBackend`, `OllamaBackend`, or custom `LLMBackend`) |
405
405
 
406
406
  **Note:** `ContextManager(model="gpt-4")` is only for **tiktoken** encoding when counting tokens in the **budget** stage. It does **not** call that model unless you also pass **`llm_backend`**.
407
407
 
@@ -459,11 +459,10 @@ Optional constructor knobs: **`llm_min_input_chars`**, **`llm_max_summary_tokens
459
459
  **Install SDKs** (not bundled): `pip install openai`, `anthropic`, and/or **`ollama`** (for local Ollama), or `pip install "contextpress[llm]"` from this repo’s `pyproject.toml` to pull all optional LLM clients.
460
460
 
461
461
  ```python
462
- from openai import OpenAI
463
462
  from contextpress import ContextManager
464
463
  from contextpress.llm.adapters import OpenAIBackend
465
464
 
466
- backend = OpenAIBackend(client=OpenAI(), model="gpt-4o-mini")
465
+ backend = OpenAIBackend(model="gpt-4o-mini") # uses OPENAI_API_KEY
467
466
  cm = ContextManager(
468
467
  type="chat",
469
468
  llm_backend=backend,
@@ -494,6 +493,32 @@ out = cm.compress(messages, token_budget=4000)
494
493
 
495
494
  Runnable script: [`examples/llm_tier_ollama.py`](examples/llm_tier_ollama.py).
496
495
 
496
+ **Claude (Anthropic)** — `pip install anthropic`, set `ANTHROPIC_API_KEY`:
497
+
498
+ ```python
499
+ from contextpress import ContextManager
500
+ from contextpress.llm.adapters import ClaudeBackend
501
+
502
+ backend = ClaudeBackend(model="claude-haiku-4-5")
503
+ cm = ContextManager(type="chat", llm_backend=backend, llm_min_input_chars=500)
504
+ out = cm.compress(messages, token_budget=4000)
505
+ ```
506
+
507
+ Runnable script: [`examples/llm_tier_claude.py`](examples/llm_tier_claude.py).
508
+
509
+ **Gemini (Google)** — `pip install google-generativeai`, set `GOOGLE_API_KEY`:
510
+
511
+ ```python
512
+ from contextpress import ContextManager
513
+ from contextpress.llm.adapters import GeminiBackend
514
+
515
+ backend = GeminiBackend(model_name="gemini-2.0-flash")
516
+ cm = ContextManager(type="chat", llm_backend=backend, llm_min_input_chars=500)
517
+ out = cm.compress(messages, token_budget=4000)
518
+ ```
519
+
520
+ Runnable script: [`examples/llm_tier_gemini.py`](examples/llm_tier_gemini.py).
521
+
497
522
  ```bash
498
523
  pip install ollama
499
524
  ollama pull llama3.2
@@ -160,7 +160,7 @@ ContextManager(type="agent")
160
160
  | **Techniques** | Rules, TF–IDF, cosine similarity, NLTK, Sumy extractive summarization, tiktoken | Your provider’s chat/completions API (you supply the client) |
161
161
  | **API key** | None | Required for your chosen provider (OpenAI, Anthropic, …) |
162
162
  | **Determinism** | Deterministic for a fixed input and settings | Non-deterministic (model sampling) |
163
- | **How to enable** | Default: `ContextManager()` runs Tier 1 only | Pass `llm_backend=` (`OpenAIBackend`, `AnthropicBackend`, `GeminiBackend`, **`OllamaBackend`**, or custom `LLMBackend`) |
163
+ | **How to enable** | Default: `ContextManager()` runs Tier 1 only | Pass `llm_backend=` (`OpenAIBackend`, **`ClaudeBackend`**, `GeminiBackend`, `OllamaBackend`, or custom `LLMBackend`) |
164
164
 
165
165
  **Note:** `ContextManager(model="gpt-4")` is only for **tiktoken** encoding when counting tokens in the **budget** stage. It does **not** call that model unless you also pass **`llm_backend`**.
166
166
 
@@ -218,11 +218,10 @@ Optional constructor knobs: **`llm_min_input_chars`**, **`llm_max_summary_tokens
218
218
  **Install SDKs** (not bundled): `pip install openai`, `anthropic`, and/or **`ollama`** (for local Ollama), or `pip install "contextpress[llm]"` from this repo’s `pyproject.toml` to pull all optional LLM clients.
219
219
 
220
220
  ```python
221
- from openai import OpenAI
222
221
  from contextpress import ContextManager
223
222
  from contextpress.llm.adapters import OpenAIBackend
224
223
 
225
- backend = OpenAIBackend(client=OpenAI(), model="gpt-4o-mini")
224
+ backend = OpenAIBackend(model="gpt-4o-mini") # uses OPENAI_API_KEY
226
225
  cm = ContextManager(
227
226
  type="chat",
228
227
  llm_backend=backend,
@@ -253,6 +252,32 @@ out = cm.compress(messages, token_budget=4000)
253
252
 
254
253
  Runnable script: [`examples/llm_tier_ollama.py`](examples/llm_tier_ollama.py).
255
254
 
255
+ **Claude (Anthropic)** — `pip install anthropic`, set `ANTHROPIC_API_KEY`:
256
+
257
+ ```python
258
+ from contextpress import ContextManager
259
+ from contextpress.llm.adapters import ClaudeBackend
260
+
261
+ backend = ClaudeBackend(model="claude-haiku-4-5")
262
+ cm = ContextManager(type="chat", llm_backend=backend, llm_min_input_chars=500)
263
+ out = cm.compress(messages, token_budget=4000)
264
+ ```
265
+
266
+ Runnable script: [`examples/llm_tier_claude.py`](examples/llm_tier_claude.py).
267
+
268
+ **Gemini (Google)** — `pip install google-generativeai`, set `GOOGLE_API_KEY`:
269
+
270
+ ```python
271
+ from contextpress import ContextManager
272
+ from contextpress.llm.adapters import GeminiBackend
273
+
274
+ backend = GeminiBackend(model_name="gemini-2.0-flash")
275
+ cm = ContextManager(type="chat", llm_backend=backend, llm_min_input_chars=500)
276
+ out = cm.compress(messages, token_budget=4000)
277
+ ```
278
+
279
+ Runnable script: [`examples/llm_tier_gemini.py`](examples/llm_tier_gemini.py).
280
+
256
281
  ```bash
257
282
  pip install ollama
258
283
  ollama pull llama3.2
@@ -16,7 +16,7 @@ __all__ = [
16
16
  "CompressionResult",
17
17
  "CompressionStats",
18
18
  ]
19
- __version__ = "0.5.0"
19
+ __version__ = "0.5.2"
20
20
 
21
21
 
22
22
  def __getattr__(name: str) -> Any:
@@ -1,5 +1,6 @@
1
1
  from contextpress.llm.adapters import (
2
2
  AnthropicBackend,
3
+ ClaudeBackend,
3
4
  GeminiBackend,
4
5
  OllamaBackend,
5
6
  OpenAIBackend,
@@ -12,6 +13,7 @@ __all__ = [
12
13
  "OpenAIBackend",
13
14
  "OpenAICompatibleBackend",
14
15
  "AnthropicBackend",
16
+ "ClaudeBackend",
15
17
  "GeminiBackend",
16
18
  "OllamaBackend",
17
19
  ]
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  import warnings
4
5
  from typing import Any
5
6
 
@@ -26,20 +27,45 @@ def _ollama_response_text(resp: Any) -> str:
26
27
 
27
28
  class OpenAIBackend(LLMBackend):
28
29
  """
29
- Adapter for OpenAI-compatible APIs.
30
- Requires: pip install openai
31
- User must pass their own client instance.
30
+ Adapter for **OpenAI** chat APIs.
31
+
32
+ Requires: ``pip install openai``
33
+
34
+ Usage (API key from env ``OPENAI_API_KEY`` or ``api_key=``)::
32
35
 
33
- Usage:
34
- from openai import OpenAI
35
36
  from contextpress.llm.adapters import OpenAIBackend
36
37
 
37
- backend = OpenAIBackend(client=OpenAI(), model="gpt-4o-mini")
38
- cm = ContextManager(type="chat", llm_backend=backend)
38
+ backend = OpenAIBackend(model=\"gpt-4o-mini\")
39
+ cm = ContextManager(type=\"chat\", llm_backend=backend)
40
+
41
+ Or pass your own client::
42
+
43
+ from openai import OpenAI
44
+
45
+ backend = OpenAIBackend(client=OpenAI(), model=\"gpt-4o-mini\")
39
46
  """
40
47
 
41
- def __init__(self, client: Any, model: str = "gpt-4o-mini"):
42
- self.client = client
48
+ def __init__(
49
+ self,
50
+ client: Any | None = None,
51
+ model: str = "gpt-4o-mini",
52
+ *,
53
+ api_key: str | None = None,
54
+ ):
55
+ if client is not None:
56
+ self.client = client
57
+ self.model = model
58
+ return
59
+ try:
60
+ from openai import OpenAI
61
+ except ImportError as exc:
62
+ raise ImportError(
63
+ "OpenAIBackend requires the 'openai' package. " "Install with: pip install openai"
64
+ ) from exc
65
+ key = api_key or os.environ.get("OPENAI_API_KEY")
66
+ if not key:
67
+ raise ValueError("Set OPENAI_API_KEY or pass api_key= for OpenAIBackend")
68
+ self.client = OpenAI(api_key=key)
43
69
  self.model = model
44
70
 
45
71
  def summarize(self, text: str, max_tokens: int) -> str:
@@ -135,6 +161,43 @@ class AnthropicBackend(LLMBackend):
135
161
  return list(range(len(turns)))
136
162
 
137
163
 
164
+ class ClaudeBackend(AnthropicBackend):
165
+ """
166
+ Convenience adapter for **Anthropic Claude** (wraps ``AnthropicBackend``).
167
+
168
+ Requires: ``pip install anthropic``
169
+
170
+ Usage::
171
+
172
+ from contextpress.llm.adapters import ClaudeBackend
173
+
174
+ backend = ClaudeBackend(model=\"claude-haiku-4-5\") # uses ANTHROPIC_API_KEY
175
+ cm = ContextManager(type=\"chat\", llm_backend=backend)
176
+ """
177
+
178
+ def __init__(
179
+ self,
180
+ model: str = "claude-haiku-4-5",
181
+ *,
182
+ api_key: str | None = None,
183
+ client: Any | None = None,
184
+ ):
185
+ if client is not None:
186
+ super().__init__(client=client, model=model)
187
+ return
188
+ try:
189
+ import anthropic
190
+ except ImportError as exc:
191
+ raise ImportError(
192
+ "ClaudeBackend requires the 'anthropic' package. "
193
+ "Install with: pip install anthropic"
194
+ ) from exc
195
+ key = api_key or os.environ.get("ANTHROPIC_API_KEY")
196
+ if not key:
197
+ raise ValueError("Set ANTHROPIC_API_KEY or pass api_key= for ClaudeBackend")
198
+ super().__init__(client=anthropic.Anthropic(api_key=key), model=model)
199
+
200
+
138
201
  class OllamaBackend(LLMBackend):
139
202
  """
140
203
  Adapter for **Ollama** (local or remote) using the official ``ollama`` Python library.
@@ -270,18 +333,41 @@ class GeminiBackend(LLMBackend):
270
333
 
271
334
  Requires: ``pip install google-generativeai``
272
335
 
273
- Usage::
336
+ Usage (API key from env ``GOOGLE_API_KEY`` or ``GEMINI_API_KEY``)::
274
337
 
275
- import google.generativeai as genai
276
338
  from contextpress.llm.adapters import GeminiBackend
277
339
 
340
+ backend = GeminiBackend(model_name=\"gemini-2.0-flash\")
341
+ cm = ContextManager(type=\"chat\", llm_backend=backend)
342
+
343
+ Or pass a preconfigured model instance::
344
+
345
+ import google.generativeai as genai
278
346
  genai.configure(api_key=\"...\")
279
347
  backend = GeminiBackend(model=genai.GenerativeModel(\"gemini-2.0-flash\"))
280
- cm = ContextManager(type=\"chat\", llm_backend=backend)
281
348
  """
282
349
 
283
- def __init__(self, model: Any):
284
- self.model = model
350
+ def __init__(
351
+ self,
352
+ model: Any | None = None,
353
+ *,
354
+ model_name: str = "gemini-2.0-flash",
355
+ api_key: str | None = None,
356
+ ):
357
+ if model is not None:
358
+ self.model = model
359
+ return
360
+ try:
361
+ import google.generativeai as genai
362
+ except ImportError as exc:
363
+ raise ImportError(
364
+ "GeminiBackend requires 'google-generativeai'. "
365
+ "Install with: pip install google-generativeai"
366
+ ) from exc
367
+ key = api_key or os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
368
+ if key:
369
+ genai.configure(api_key=key)
370
+ self.model = genai.GenerativeModel(model_name)
285
371
 
286
372
  def _generate(self, prompt: str, *, max_tokens: int) -> str:
287
373
  resp = self.model.generate_content(
@@ -0,0 +1,50 @@
1
+ """
2
+ Tier 2 compression with Anthropic Claude.
3
+
4
+ Requires ``pip install anthropic`` and ``ANTHROPIC_API_KEY``.
5
+
6
+ Run:
7
+ set ANTHROPIC_API_KEY=sk-ant-... # Windows
8
+ python examples/llm_tier_claude.py
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+
16
+ from contextpress import ContextManager
17
+ from contextpress.llm.adapters import ClaudeBackend
18
+
19
+
20
+ def main() -> None:
21
+ if not os.environ.get("ANTHROPIC_API_KEY"):
22
+ print("Set ANTHROPIC_API_KEY to run this example.", file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+ messages = [
26
+ {"role": "system", "content": "You are a concise assistant."},
27
+ {"role": "user", "content": "What is Python? " + ("Explain briefly. " * 200)},
28
+ {"role": "assistant", "content": "Python is a programming language. " * 150},
29
+ {"role": "user", "content": "And numpy? " * 80},
30
+ ]
31
+
32
+ backend = ClaudeBackend(model="claude-haiku-4-5")
33
+ cm = ContextManager(
34
+ type="chat",
35
+ llm_backend=backend,
36
+ compression="medium",
37
+ llm_min_input_chars=500,
38
+ llm_max_summary_tokens=512,
39
+ )
40
+
41
+ out = cm.compress(messages, token_budget=2000)
42
+ print("turns in:", len(messages), "out:", len(out))
43
+ for i, m in enumerate(out):
44
+ c = m["content"]
45
+ preview = (c[:200] + "…") if isinstance(c, str) and len(c) > 200 else c
46
+ print(i, m["role"] + ":", preview)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -0,0 +1,50 @@
1
+ """
2
+ Tier 2 compression with Google Gemini.
3
+
4
+ Requires ``pip install google-generativeai`` and ``GOOGLE_API_KEY``.
5
+
6
+ Run:
7
+ set GOOGLE_API_KEY=... # or GEMINI_API_KEY
8
+ python examples/llm_tier_gemini.py
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+
16
+ from contextpress import ContextManager
17
+ from contextpress.llm.adapters import GeminiBackend
18
+
19
+
20
+ def main() -> None:
21
+ if not (os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")):
22
+ print("Set GOOGLE_API_KEY or GEMINI_API_KEY to run this example.", file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+ messages = [
26
+ {"role": "system", "content": "You are a concise assistant."},
27
+ {"role": "user", "content": "What is Python? " + ("Explain briefly. " * 200)},
28
+ {"role": "assistant", "content": "Python is a programming language. " * 150},
29
+ {"role": "user", "content": "And numpy? " * 80},
30
+ ]
31
+
32
+ backend = GeminiBackend(model_name="gemini-2.0-flash")
33
+ cm = ContextManager(
34
+ type="chat",
35
+ llm_backend=backend,
36
+ compression="medium",
37
+ llm_min_input_chars=500,
38
+ llm_max_summary_tokens=512,
39
+ )
40
+
41
+ out = cm.compress(messages, token_budget=2000)
42
+ print("turns in:", len(messages), "out:", len(out))
43
+ for i, m in enumerate(out):
44
+ c = m["content"]
45
+ preview = (c[:200] + "…") if isinstance(c, str) and len(c) > 200 else c
46
+ print(i, m["role"] + ":", preview)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -17,8 +17,6 @@ from __future__ import annotations
17
17
  import os
18
18
  import sys
19
19
 
20
- from openai import OpenAI
21
-
22
20
  from contextpress import ContextManager
23
21
  from contextpress.llm.adapters import OpenAIBackend
24
22
 
@@ -35,7 +33,7 @@ def main() -> None:
35
33
  {"role": "user", "content": "And numpy? " * 80},
36
34
  ]
37
35
 
38
- backend = OpenAIBackend(client=OpenAI(), model="gpt-4o-mini")
36
+ backend = OpenAIBackend(model="gpt-4o-mini")
39
37
  cm = ContextManager(
40
38
  type="chat",
41
39
  llm_backend=backend,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "contextpress"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "Deterministic context compression for LLM chat, RAG, and agent pipelines"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1,50 @@
1
+ import os
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pytest
5
+
6
+ from contextpress.llm.adapters import ClaudeBackend, GeminiBackend
7
+
8
+
9
+ def test_claude_backend_with_client():
10
+ client = MagicMock()
11
+ backend = ClaudeBackend(model="claude-haiku-4-5", client=client)
12
+ assert backend.client is client
13
+ assert backend.model == "claude-haiku-4-5"
14
+
15
+
16
+ def test_claude_backend_requires_api_key():
17
+ mock_anthropic = MagicMock()
18
+ with patch.dict(os.environ, {}, clear=True):
19
+ with patch.dict("sys.modules", {"anthropic": mock_anthropic}):
20
+ with pytest.raises(ValueError, match="ANTHROPIC_API_KEY"):
21
+ ClaudeBackend()
22
+
23
+
24
+ def test_claude_backend_from_api_key():
25
+ mock_module = MagicMock()
26
+ mock_client = MagicMock()
27
+ mock_module.Anthropic.return_value = mock_client
28
+ with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-test"}):
29
+ with patch.dict("sys.modules", {"anthropic": mock_module}):
30
+ backend = ClaudeBackend(model="claude-haiku-4-5")
31
+ assert backend.client is mock_client
32
+
33
+
34
+ def test_gemini_backend_with_model_instance():
35
+ model = MagicMock()
36
+ backend = GeminiBackend(model=model)
37
+ assert backend.model is model
38
+
39
+
40
+ def test_gemini_backend_from_model_name():
41
+ mock_genai = MagicMock()
42
+ mock_model = MagicMock()
43
+ mock_genai.GenerativeModel.return_value = mock_model
44
+ fake_google = MagicMock()
45
+ fake_google.generativeai = mock_genai
46
+ with patch.dict(os.environ, {"GOOGLE_API_KEY": "key-test"}):
47
+ with patch.dict("sys.modules", {"google": fake_google, "google.generativeai": mock_genai}):
48
+ backend = GeminiBackend(model_name="gemini-2.0-flash")
49
+ assert backend.model is mock_model
50
+ mock_genai.configure.assert_called_once_with(api_key="key-test")
@@ -0,0 +1,32 @@
1
+ import os
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pytest
5
+
6
+ from contextpress.llm.adapters import OpenAIBackend
7
+
8
+
9
+ def test_openai_backend_with_client():
10
+ client = MagicMock()
11
+ backend = OpenAIBackend(client=client, model="gpt-4o-mini")
12
+ assert backend.client is client
13
+ assert backend.model == "gpt-4o-mini"
14
+
15
+
16
+ def test_openai_backend_requires_api_key():
17
+ mock_openai = MagicMock()
18
+ with patch.dict(os.environ, {}, clear=True):
19
+ with patch.dict("sys.modules", {"openai": mock_openai}):
20
+ with pytest.raises(ValueError, match="OPENAI_API_KEY"):
21
+ OpenAIBackend()
22
+
23
+
24
+ def test_openai_backend_from_api_key():
25
+ mock_module = MagicMock()
26
+ mock_client = MagicMock()
27
+ mock_module.OpenAI.return_value = mock_client
28
+ with patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test"}):
29
+ with patch.dict("sys.modules", {"openai": mock_module}):
30
+ backend = OpenAIBackend(model="gpt-4o-mini")
31
+ assert backend.client is mock_client
32
+ mock_module.OpenAI.assert_called_once_with(api_key="sk-test")
File without changes
File without changes
File without changes
File without changes