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 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.
@@ -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
@@ -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