ctxintel 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctxintel-0.1.0/LICENSE +21 -0
- ctxintel-0.1.0/PKG-INFO +210 -0
- ctxintel-0.1.0/README.md +177 -0
- ctxintel-0.1.0/ctxintel/__init__.py +16 -0
- ctxintel-0.1.0/ctxintel/compressor.py +122 -0
- ctxintel-0.1.0/ctxintel/data/patterns.yaml +183 -0
- ctxintel-0.1.0/ctxintel/extractor.py +411 -0
- ctxintel-0.1.0/ctxintel/memory.py +205 -0
- ctxintel-0.1.0/ctxintel/models.py +65 -0
- ctxintel-0.1.0/ctxintel/optimizer.py +123 -0
- ctxintel-0.1.0/ctxintel/pipeline.py +159 -0
- ctxintel-0.1.0/ctxintel/presets.py +101 -0
- ctxintel-0.1.0/ctxintel/ranker.py +169 -0
- ctxintel-0.1.0/ctxintel.egg-info/PKG-INFO +210 -0
- ctxintel-0.1.0/ctxintel.egg-info/SOURCES.txt +24 -0
- ctxintel-0.1.0/ctxintel.egg-info/dependency_links.txt +1 -0
- ctxintel-0.1.0/ctxintel.egg-info/requires.txt +17 -0
- ctxintel-0.1.0/ctxintel.egg-info/top_level.txt +1 -0
- ctxintel-0.1.0/pyproject.toml +48 -0
- ctxintel-0.1.0/setup.cfg +4 -0
- ctxintel-0.1.0/tests/test_compressor.py +71 -0
- ctxintel-0.1.0/tests/test_extractor.py +82 -0
- ctxintel-0.1.0/tests/test_memory.py +84 -0
- ctxintel-0.1.0/tests/test_optimizer.py +58 -0
- ctxintel-0.1.0/tests/test_pipeline.py +77 -0
- ctxintel-0.1.0/tests/test_ranker.py +67 -0
ctxintel-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Usman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ctxintel-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctxintel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Context Operating System for LLM Applications โ local, zero-cost context intelligence
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/nccsuzzi/ctxintel
|
|
7
|
+
Project-URL: Issues, https://github.com/nccsuzzi/ctxintel/issues
|
|
8
|
+
Keywords: llm,context,compression,nlp,ai,memory
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: scikit-learn>=1.3
|
|
19
|
+
Requires-Dist: nltk>=3.8
|
|
20
|
+
Requires-Dist: sumy>=0.11
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Requires-Dist: tiktoken>=0.5
|
|
23
|
+
Provides-Extra: spacy
|
|
24
|
+
Requires-Dist: spacy>=3.7; extra == "spacy"
|
|
25
|
+
Provides-Extra: embeddings
|
|
26
|
+
Requires-Dist: sentence-transformers>=2.2; extra == "embeddings"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
30
|
+
Requires-Dist: black; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# ctxintel โ Context Operating System for LLM Applications
|
|
35
|
+
|
|
36
|
+
**ctxintel** is a fully local, zero-cost context intelligence engine for LLM applications. Instead of blindly summarizing or truncating conversations, it runs a structured 6-stage deterministic pipeline that scores, extracts, remembers, compresses, and optimizes your context window โ all without a single external AI API call.
|
|
37
|
+
|
|
38
|
+
> ๐ **Fully offline** ยท ๐ฏ **Deterministic & debuggable** ยท ๐ **Data-driven rules** ยท โก **Drop-in for any LLM framework**
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ctxintel
|
|
46
|
+
|
|
47
|
+
# Optional: enable NER and dependency parsing
|
|
48
|
+
pip install spacy
|
|
49
|
+
python -m spacy download en_core_web_sm
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from ctxintel import ContextIntel
|
|
56
|
+
|
|
57
|
+
sdk = ContextIntel(preset="coding_assistant")
|
|
58
|
+
|
|
59
|
+
messages = [
|
|
60
|
+
{"role": "user", "content": "Hi, I'm John. Building a REST API."},
|
|
61
|
+
{"role": "assistant", "content": "What stack are you using?"},
|
|
62
|
+
{"role": "user", "content": "FastAPI and Python. Deploying on AWS."},
|
|
63
|
+
{"role": "user", "content": "Don't use synchronous code."},
|
|
64
|
+
{"role": "user", "content": "ok"},
|
|
65
|
+
{"role": "user", "content": "cool"},
|
|
66
|
+
{"role": "user", "content": "Now add JWT authentication."},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
result = sdk.process(messages)
|
|
70
|
+
print(f"Tokens: {result.original_token_count} โ {result.token_count}")
|
|
71
|
+
print(f"Memories: {result.extracted_memories}")
|
|
72
|
+
print(sdk.memory_summary())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Raw Messages
|
|
79
|
+
โ
|
|
80
|
+
โผ
|
|
81
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
82
|
+
โ 1. RANKING โ TF-IDF + recency + signal patterns + semantic uniqueness
|
|
83
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
84
|
+
โผ
|
|
85
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
86
|
+
โ 2. EXTRACTION โ patterns.yaml rules + spaCy NER + dependency parsing
|
|
87
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
88
|
+
โผ
|
|
89
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
90
|
+
โ 3. MEMORY โ Per-memory scoring + frequency tracking + JSON persistence
|
|
91
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
92
|
+
โผ
|
|
93
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
94
|
+
โ 4. COMPRESSION โ Extractive summarization via sumy LSA (zero AI calls)
|
|
95
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
96
|
+
โผ
|
|
97
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
98
|
+
โ 5. OPTIMIZATIONโ Fit within token budget via tiktoken
|
|
99
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
100
|
+
โผ
|
|
101
|
+
Final Context
|
|
102
|
+
(minimal tokens, maximum relevance)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Presets
|
|
106
|
+
|
|
107
|
+
| Preset | Use Case | Token Budget | Threshold |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `coding_assistant` | Code generation & programming | 8,000 | 0.35 |
|
|
110
|
+
| `customer_support` | Support bots & helpdesk | 6,000 | 0.40 |
|
|
111
|
+
| `ai_tutor` | Educational assistants | 10,000 | 0.30 |
|
|
112
|
+
| `agent_system` | Autonomous agents | 12,000 | 0.45 |
|
|
113
|
+
| `general` | General chat apps | 8,000 | 0.40 |
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from ctxintel import list_presets
|
|
117
|
+
print(list_presets())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Extending Patterns
|
|
121
|
+
|
|
122
|
+
The core innovation of ctxintel is its data-driven rule engine at `ctxintel/data/patterns.yaml`. You can add custom categories without changing any code:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# Add to the categories section in patterns.yaml
|
|
126
|
+
categories:
|
|
127
|
+
database:
|
|
128
|
+
patterns:
|
|
129
|
+
- "using {X} database"
|
|
130
|
+
- "store data in {X}"
|
|
131
|
+
- "migrate to {X}"
|
|
132
|
+
keywords:
|
|
133
|
+
- postgresql
|
|
134
|
+
- mysql
|
|
135
|
+
- mongodb
|
|
136
|
+
- redis
|
|
137
|
+
priority: high
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
No code changes needed โ ctxintel loads the YAML at runtime.
|
|
141
|
+
|
|
142
|
+
## Why Not Just Summarize?
|
|
143
|
+
|
|
144
|
+
| Approach | What You Lose |
|
|
145
|
+
|---|---|
|
|
146
|
+
| **Truncation** | Early context, user preferences, constraints |
|
|
147
|
+
| **Blind summarization** | Structured facts, decisions, priorities |
|
|
148
|
+
| **ctxintel** | Nothing important โ it scores, extracts, and remembers |
|
|
149
|
+
|
|
150
|
+
ctxintel doesn't just shorten your context. It **understands** what matters: user preferences are preserved, tasks are tracked, decisions are remembered, and filler is compressed โ all deterministically.
|
|
151
|
+
|
|
152
|
+
## Zero API Calls
|
|
153
|
+
|
|
154
|
+
> โ ๏ธ **ctxintel makes zero external API calls. Ever.**
|
|
155
|
+
>
|
|
156
|
+
> No OpenAI. No Anthropic. No Cohere. No network requests.
|
|
157
|
+
> Everything runs locally using scikit-learn, sumy, spaCy, and tiktoken.
|
|
158
|
+
> Your conversations never leave your machine.
|
|
159
|
+
|
|
160
|
+
## API Reference
|
|
161
|
+
|
|
162
|
+
### `ContextIntel`
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
sdk = ContextIntel(
|
|
166
|
+
preset="coding_assistant", # or None for manual config
|
|
167
|
+
preserve=["task", "constraint"],
|
|
168
|
+
threshold=0.4,
|
|
169
|
+
token_budget=8000,
|
|
170
|
+
memory_path=".ctxintel_memory.json",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Full pipeline
|
|
174
|
+
result = sdk.process(messages)
|
|
175
|
+
|
|
176
|
+
# Incremental workflow
|
|
177
|
+
sdk.add_message("user", "Hello!")
|
|
178
|
+
sdk.add_message("assistant", "Hi there!")
|
|
179
|
+
result = sdk.flush()
|
|
180
|
+
|
|
181
|
+
# Utilities
|
|
182
|
+
sdk.preview_compression(messages)
|
|
183
|
+
sdk.memory_summary()
|
|
184
|
+
sdk.reset_memory()
|
|
185
|
+
sdk.supported_categories()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `ContextResult`
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
result.messages # Final optimized messages
|
|
192
|
+
result.memories # Extracted memories
|
|
193
|
+
result.token_count # Final token count
|
|
194
|
+
result.original_token_count # Original token count
|
|
195
|
+
result.compression_ratio # Reduction ratio (0.0โ1.0)
|
|
196
|
+
result.extracted_memories # Number of memories found
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Roadmap
|
|
200
|
+
|
|
201
|
+
- [ ] Sentence-transformers reranker (opt-in)
|
|
202
|
+
- [ ] CLI: `ctxintel compress chat.json --budget 4000`
|
|
203
|
+
- [ ] Streaming message support
|
|
204
|
+
- [ ] Custom patterns.yaml path support
|
|
205
|
+
- [ ] LangChain / LlamaIndex integration wrappers
|
|
206
|
+
- [ ] Multi-conversation memory aggregation
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT
|
ctxintel-0.1.0/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ctxintel โ Context Operating System for LLM Applications
|
|
2
|
+
|
|
3
|
+
**ctxintel** is a fully local, zero-cost context intelligence engine for LLM applications. Instead of blindly summarizing or truncating conversations, it runs a structured 6-stage deterministic pipeline that scores, extracts, remembers, compresses, and optimizes your context window โ all without a single external AI API call.
|
|
4
|
+
|
|
5
|
+
> ๐ **Fully offline** ยท ๐ฏ **Deterministic & debuggable** ยท ๐ **Data-driven rules** ยท โก **Drop-in for any LLM framework**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install ctxintel
|
|
13
|
+
|
|
14
|
+
# Optional: enable NER and dependency parsing
|
|
15
|
+
pip install spacy
|
|
16
|
+
python -m spacy download en_core_web_sm
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from ctxintel import ContextIntel
|
|
23
|
+
|
|
24
|
+
sdk = ContextIntel(preset="coding_assistant")
|
|
25
|
+
|
|
26
|
+
messages = [
|
|
27
|
+
{"role": "user", "content": "Hi, I'm John. Building a REST API."},
|
|
28
|
+
{"role": "assistant", "content": "What stack are you using?"},
|
|
29
|
+
{"role": "user", "content": "FastAPI and Python. Deploying on AWS."},
|
|
30
|
+
{"role": "user", "content": "Don't use synchronous code."},
|
|
31
|
+
{"role": "user", "content": "ok"},
|
|
32
|
+
{"role": "user", "content": "cool"},
|
|
33
|
+
{"role": "user", "content": "Now add JWT authentication."},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
result = sdk.process(messages)
|
|
37
|
+
print(f"Tokens: {result.original_token_count} โ {result.token_count}")
|
|
38
|
+
print(f"Memories: {result.extracted_memories}")
|
|
39
|
+
print(sdk.memory_summary())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Raw Messages
|
|
46
|
+
โ
|
|
47
|
+
โผ
|
|
48
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
49
|
+
โ 1. RANKING โ TF-IDF + recency + signal patterns + semantic uniqueness
|
|
50
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
51
|
+
โผ
|
|
52
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
53
|
+
โ 2. EXTRACTION โ patterns.yaml rules + spaCy NER + dependency parsing
|
|
54
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
55
|
+
โผ
|
|
56
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
57
|
+
โ 3. MEMORY โ Per-memory scoring + frequency tracking + JSON persistence
|
|
58
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
59
|
+
โผ
|
|
60
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
61
|
+
โ 4. COMPRESSION โ Extractive summarization via sumy LSA (zero AI calls)
|
|
62
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
63
|
+
โผ
|
|
64
|
+
โโโโโโโโโโโโโโโโโโโ
|
|
65
|
+
โ 5. OPTIMIZATIONโ Fit within token budget via tiktoken
|
|
66
|
+
โโโโโโโโโโฌโโโโโโโโโ
|
|
67
|
+
โผ
|
|
68
|
+
Final Context
|
|
69
|
+
(minimal tokens, maximum relevance)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Presets
|
|
73
|
+
|
|
74
|
+
| Preset | Use Case | Token Budget | Threshold |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| `coding_assistant` | Code generation & programming | 8,000 | 0.35 |
|
|
77
|
+
| `customer_support` | Support bots & helpdesk | 6,000 | 0.40 |
|
|
78
|
+
| `ai_tutor` | Educational assistants | 10,000 | 0.30 |
|
|
79
|
+
| `agent_system` | Autonomous agents | 12,000 | 0.45 |
|
|
80
|
+
| `general` | General chat apps | 8,000 | 0.40 |
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from ctxintel import list_presets
|
|
84
|
+
print(list_presets())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Extending Patterns
|
|
88
|
+
|
|
89
|
+
The core innovation of ctxintel is its data-driven rule engine at `ctxintel/data/patterns.yaml`. You can add custom categories without changing any code:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
# Add to the categories section in patterns.yaml
|
|
93
|
+
categories:
|
|
94
|
+
database:
|
|
95
|
+
patterns:
|
|
96
|
+
- "using {X} database"
|
|
97
|
+
- "store data in {X}"
|
|
98
|
+
- "migrate to {X}"
|
|
99
|
+
keywords:
|
|
100
|
+
- postgresql
|
|
101
|
+
- mysql
|
|
102
|
+
- mongodb
|
|
103
|
+
- redis
|
|
104
|
+
priority: high
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
No code changes needed โ ctxintel loads the YAML at runtime.
|
|
108
|
+
|
|
109
|
+
## Why Not Just Summarize?
|
|
110
|
+
|
|
111
|
+
| Approach | What You Lose |
|
|
112
|
+
|---|---|
|
|
113
|
+
| **Truncation** | Early context, user preferences, constraints |
|
|
114
|
+
| **Blind summarization** | Structured facts, decisions, priorities |
|
|
115
|
+
| **ctxintel** | Nothing important โ it scores, extracts, and remembers |
|
|
116
|
+
|
|
117
|
+
ctxintel doesn't just shorten your context. It **understands** what matters: user preferences are preserved, tasks are tracked, decisions are remembered, and filler is compressed โ all deterministically.
|
|
118
|
+
|
|
119
|
+
## Zero API Calls
|
|
120
|
+
|
|
121
|
+
> โ ๏ธ **ctxintel makes zero external API calls. Ever.**
|
|
122
|
+
>
|
|
123
|
+
> No OpenAI. No Anthropic. No Cohere. No network requests.
|
|
124
|
+
> Everything runs locally using scikit-learn, sumy, spaCy, and tiktoken.
|
|
125
|
+
> Your conversations never leave your machine.
|
|
126
|
+
|
|
127
|
+
## API Reference
|
|
128
|
+
|
|
129
|
+
### `ContextIntel`
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
sdk = ContextIntel(
|
|
133
|
+
preset="coding_assistant", # or None for manual config
|
|
134
|
+
preserve=["task", "constraint"],
|
|
135
|
+
threshold=0.4,
|
|
136
|
+
token_budget=8000,
|
|
137
|
+
memory_path=".ctxintel_memory.json",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Full pipeline
|
|
141
|
+
result = sdk.process(messages)
|
|
142
|
+
|
|
143
|
+
# Incremental workflow
|
|
144
|
+
sdk.add_message("user", "Hello!")
|
|
145
|
+
sdk.add_message("assistant", "Hi there!")
|
|
146
|
+
result = sdk.flush()
|
|
147
|
+
|
|
148
|
+
# Utilities
|
|
149
|
+
sdk.preview_compression(messages)
|
|
150
|
+
sdk.memory_summary()
|
|
151
|
+
sdk.reset_memory()
|
|
152
|
+
sdk.supported_categories()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `ContextResult`
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
result.messages # Final optimized messages
|
|
159
|
+
result.memories # Extracted memories
|
|
160
|
+
result.token_count # Final token count
|
|
161
|
+
result.original_token_count # Original token count
|
|
162
|
+
result.compression_ratio # Reduction ratio (0.0โ1.0)
|
|
163
|
+
result.extracted_memories # Number of memories found
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Roadmap
|
|
167
|
+
|
|
168
|
+
- [ ] Sentence-transformers reranker (opt-in)
|
|
169
|
+
- [ ] CLI: `ctxintel compress chat.json --budget 4000`
|
|
170
|
+
- [ ] Streaming message support
|
|
171
|
+
- [ ] Custom patterns.yaml path support
|
|
172
|
+
- [ ] LangChain / LlamaIndex integration wrappers
|
|
173
|
+
- [ ] Multi-conversation memory aggregation
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""ctxintel โ Context Operating System for LLM Applications."""
|
|
2
|
+
|
|
3
|
+
from ctxintel.pipeline import ContextIntel
|
|
4
|
+
from ctxintel.models import Message, Memory, ContextResult
|
|
5
|
+
from ctxintel.presets import PRESETS, load_preset, list_presets
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ContextIntel",
|
|
10
|
+
"Message",
|
|
11
|
+
"Memory",
|
|
12
|
+
"ContextResult",
|
|
13
|
+
"PRESETS",
|
|
14
|
+
"load_preset",
|
|
15
|
+
"list_presets",
|
|
16
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Message compressor using extractive summarization (sumy LSA) with fallback."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from ctxintel.models import Message
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from sumy.parsers.plaintext import PlaintextParser
|
|
10
|
+
from sumy.nlp.tokenizers import Tokenizer
|
|
11
|
+
from sumy.summarizers.lsa import LsaSummarizer
|
|
12
|
+
from sumy.nlp.stemmers import Stemmer
|
|
13
|
+
_HAS_SUMY = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
_HAS_SUMY = False
|
|
16
|
+
warnings.warn(
|
|
17
|
+
"sumy not found. Compression will use a simpler sentence-based fallback. "
|
|
18
|
+
"Install with: pip install sumy",
|
|
19
|
+
stacklevel=2,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Compressor:
|
|
24
|
+
"""Compresses low-importance messages into a summary while preserving critical ones.
|
|
25
|
+
|
|
26
|
+
Uses sumy's LSA summarizer for extractive summarization (zero AI API calls).
|
|
27
|
+
Falls back to a simple first-N-sentences approach if sumy is unavailable.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def compress(
|
|
31
|
+
self,
|
|
32
|
+
messages: List[Message],
|
|
33
|
+
preserve: Optional[List[str]] = None,
|
|
34
|
+
threshold: float = 0.4,
|
|
35
|
+
) -> List[Message]:
|
|
36
|
+
"""Compress messages below the importance threshold into a summary.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
messages: Ranked list of Message objects.
|
|
40
|
+
preserve: Category names to preserve (unused โ preserved flag is on Message).
|
|
41
|
+
threshold: Messages below this importance score get compressed.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of messages with low-importance ones replaced by a summary.
|
|
45
|
+
"""
|
|
46
|
+
keep_msgs = [m for m in messages if m.importance >= threshold or m.preserved]
|
|
47
|
+
compress_msgs = [m for m in messages if m.importance < threshold and not m.preserved]
|
|
48
|
+
|
|
49
|
+
if not compress_msgs:
|
|
50
|
+
return keep_msgs
|
|
51
|
+
|
|
52
|
+
combined_text = " ".join(m.content for m in compress_msgs)
|
|
53
|
+
|
|
54
|
+
# If the compressed content is very short, just drop it โ
|
|
55
|
+
# a summary message would be larger than the original filler
|
|
56
|
+
if len(combined_text) < 50:
|
|
57
|
+
return keep_msgs
|
|
58
|
+
|
|
59
|
+
summary = self._summarize(combined_text, len(compress_msgs))
|
|
60
|
+
|
|
61
|
+
# Only inject summary if it's actually shorter than the combined original
|
|
62
|
+
if len(summary) >= len(combined_text):
|
|
63
|
+
return keep_msgs
|
|
64
|
+
|
|
65
|
+
summary_msg = Message(
|
|
66
|
+
role="system",
|
|
67
|
+
content=f"[Earlier context summary]: {summary}",
|
|
68
|
+
importance=0.85,
|
|
69
|
+
preserved=True,
|
|
70
|
+
)
|
|
71
|
+
return [summary_msg] + keep_msgs
|
|
72
|
+
|
|
73
|
+
def estimate_reduction(self, messages: List[Message], threshold: float = 0.4) -> Dict:
|
|
74
|
+
"""Preview compression stats without actually compressing.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
messages: Ranked list of Message objects.
|
|
78
|
+
threshold: Importance threshold for compression.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict with total_messages, will_keep, will_compress, estimated_reduction_pct.
|
|
82
|
+
"""
|
|
83
|
+
keep = sum(1 for m in messages if m.importance >= threshold or m.preserved)
|
|
84
|
+
compress = sum(1 for m in messages if m.importance < threshold and not m.preserved)
|
|
85
|
+
total = len(messages)
|
|
86
|
+
return {
|
|
87
|
+
"total_messages": total,
|
|
88
|
+
"will_keep": keep,
|
|
89
|
+
"will_compress": compress,
|
|
90
|
+
"estimated_reduction_pct": round(compress / max(total, 1) * 100, 1),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def _summarize(self, text: str, msg_count: int) -> str:
|
|
94
|
+
"""Summarize text using sumy LSA or a sentence-based fallback."""
|
|
95
|
+
if _HAS_SUMY:
|
|
96
|
+
return self._summarize_lsa(text, msg_count)
|
|
97
|
+
return self._summarize_fallback(text)
|
|
98
|
+
|
|
99
|
+
def _summarize_lsa(self, text: str, msg_count: int) -> str:
|
|
100
|
+
"""Use sumy's LSA summarizer for extractive summarization."""
|
|
101
|
+
try:
|
|
102
|
+
sentence_count = max(3, min(7, msg_count // 3))
|
|
103
|
+
parser = PlaintextParser.from_string(text, Tokenizer("english"))
|
|
104
|
+
stemmer = Stemmer("english")
|
|
105
|
+
summarizer = LsaSummarizer(stemmer)
|
|
106
|
+
sentences = summarizer(parser.document, sentence_count)
|
|
107
|
+
summary = " ".join(str(s) for s in sentences)
|
|
108
|
+
if not summary.strip():
|
|
109
|
+
return self._summarize_fallback(text)
|
|
110
|
+
return summary
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
warnings.warn(f"sumy LSA failed ({exc}). Using fallback.", stacklevel=2)
|
|
113
|
+
return self._summarize_fallback(text)
|
|
114
|
+
|
|
115
|
+
def _summarize_fallback(self, text: str) -> str:
|
|
116
|
+
"""Simple fallback: take the first 5 sentences."""
|
|
117
|
+
sentences = text.split(". ")
|
|
118
|
+
selected = sentences[:5]
|
|
119
|
+
result = ". ".join(selected)
|
|
120
|
+
if not result.endswith("."):
|
|
121
|
+
result += "."
|
|
122
|
+
return result
|