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.
- {contextpress-0.5.0 → contextpress-0.5.2}/CHANGELOG.md +10 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/PKG-INFO +29 -4
- {contextpress-0.5.0 → contextpress-0.5.2}/README.md +28 -3
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/__init__.py +1 -1
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/__init__.py +2 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/adapters.py +100 -14
- contextpress-0.5.2/examples/llm_tier_claude.py +50 -0
- contextpress-0.5.2/examples/llm_tier_gemini.py +50 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/examples/llm_tier_openai.py +1 -3
- {contextpress-0.5.0 → contextpress-0.5.2}/pyproject.toml +1 -1
- contextpress-0.5.2/tests/test_v051.py +50 -0
- contextpress-0.5.2/tests/test_v052.py +32 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/.gitignore +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/CITATION.cff +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/CONTRIBUTING.md +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/LICENSE +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/NOTICE +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/_bootstrap.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/compression.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/core.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/_helpers.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/llm/base.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/models.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/normalizer.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/pipeline.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/profiles.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/py.typed +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/registry.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/stats.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/__init__.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/base.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/budget.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/filler.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/recency.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/repetition.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/strategies/resolution.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/contextpress/warnings_capture.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/examples/benchmark_presets.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/examples/dry_run_preview.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/examples/estimate_and_stats.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/examples/llm_tier_ollama.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/__init__.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_budget.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_filler.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_llm_helpers.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_models.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_normalizer.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_pipeline.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_recency.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_repetition.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_resolution.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_stats.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_v03.py +0 -0
- {contextpress-0.5.0 → contextpress-0.5.2}/tests/test_v04.py +0 -0
- {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.
|
|
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`,
|
|
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(
|
|
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`,
|
|
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(
|
|
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
|
|
@@ -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
|
|
30
|
-
|
|
31
|
-
|
|
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(
|
|
38
|
-
cm = ContextManager(type
|
|
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__(
|
|
42
|
-
self
|
|
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__(
|
|
284
|
-
self
|
|
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(
|
|
36
|
+
backend = OpenAIBackend(model="gpt-4o-mini")
|
|
39
37
|
cm = ContextManager(
|
|
40
38
|
type="chat",
|
|
41
39
|
llm_backend=backend,
|
|
@@ -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
|
|
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
|