tokenmizer 0.2.4__py3-none-any.whl
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.
- tokenmizer/__init__.py +21 -0
- tokenmizer/agents/__init__.py +0 -0
- tokenmizer/analytics/__init__.py +0 -0
- tokenmizer/analytics/engine.py +188 -0
- tokenmizer/api/__init__.py +0 -0
- tokenmizer/api/app.py +958 -0
- tokenmizer/api/rate_limiter.py +110 -0
- tokenmizer/checkpoints/__init__.py +0 -0
- tokenmizer/checkpoints/manager.py +383 -0
- tokenmizer/cli.py +153 -0
- tokenmizer/compression/__init__.py +0 -0
- tokenmizer/compression/engine.py +669 -0
- tokenmizer/compression/output_trimmer.py +95 -0
- tokenmizer/compression/window.py +104 -0
- tokenmizer/config/__init__.py +0 -0
- tokenmizer/config/settings.py +170 -0
- tokenmizer/core/__init__.py +0 -0
- tokenmizer/core/dto.py +196 -0
- tokenmizer/core/errors.py +35 -0
- tokenmizer/core/tokenizer.py +96 -0
- tokenmizer/dashboard/__init__.py +0 -0
- tokenmizer/dashboard/page.py +267 -0
- tokenmizer/filters/__init__.py +0 -0
- tokenmizer/filters/file_intelligence.py +960 -0
- tokenmizer/graph_memory/__init__.py +0 -0
- tokenmizer/graph_memory/decision_tracker.py +225 -0
- tokenmizer/graph_memory/graph.py +1287 -0
- tokenmizer/graph_memory/helpers.py +121 -0
- tokenmizer/graph_memory/hybrid_extractor.py +703 -0
- tokenmizer/graph_memory/types.py +134 -0
- tokenmizer/graph_memory/validator.py +304 -0
- tokenmizer/graph_memory/visualization.py +228 -0
- tokenmizer/mcp/__init__.py +0 -0
- tokenmizer/mcp/server.py +368 -0
- tokenmizer/providers/__init__.py +0 -0
- tokenmizer/providers/providers.py +456 -0
- tokenmizer/security/__init__.py +0 -0
- tokenmizer/security/auth.py +95 -0
- tokenmizer/security/middleware.py +138 -0
- tokenmizer/security/redaction.py +126 -0
- tokenmizer/semantic_cache/__init__.py +0 -0
- tokenmizer/semantic_cache/cache.py +383 -0
- tokenmizer/state/__init__.py +0 -0
- tokenmizer/state/backend.py +137 -0
- tokenmizer/storage/__init__.py +56 -0
- tokenmizer-0.2.4.dist-info/METADATA +529 -0
- tokenmizer-0.2.4.dist-info/RECORD +50 -0
- tokenmizer-0.2.4.dist-info/WHEEL +4 -0
- tokenmizer-0.2.4.dist-info/entry_points.txt +2 -0
- tokenmizer-0.2.4.dist-info/licenses/LICENSE +21 -0
tokenmizer/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""TokenMizer — Never lose your AI context again."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.4"
|
|
4
|
+
__all__ = ["GraphMemory", "CheckpointManager", "get_settings"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def __getattr__(name):
|
|
8
|
+
"""Lazy imports — avoids pydantic being required at import time for tests."""
|
|
9
|
+
if name == "get_settings":
|
|
10
|
+
from tokenmizer.config.settings import get_settings
|
|
11
|
+
return get_settings
|
|
12
|
+
if name == "Settings":
|
|
13
|
+
from tokenmizer.config.settings import Settings
|
|
14
|
+
return Settings
|
|
15
|
+
if name == "GraphMemory":
|
|
16
|
+
from tokenmizer.graph_memory.graph import GraphMemory
|
|
17
|
+
return GraphMemory
|
|
18
|
+
if name == "CheckpointManager":
|
|
19
|
+
from tokenmizer.checkpoints.manager import CheckpointManager
|
|
20
|
+
return CheckpointManager
|
|
21
|
+
raise AttributeError(f"module 'tokenmizer' has no attribute {name!r}")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Analytics engine — daily/weekly/monthly rollups."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Dict, List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PeriodStats:
|
|
12
|
+
requests: int = 0
|
|
13
|
+
tokens_saved: int = 0
|
|
14
|
+
tokens_used: int = 0
|
|
15
|
+
cost_saved: float = 0.0
|
|
16
|
+
cost_actual: float = 0.0
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def savings_pct(self) -> float:
|
|
20
|
+
total = self.tokens_saved + self.tokens_used
|
|
21
|
+
return (self.tokens_saved / total * 100) if total > 0 else 0.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_COST_PER_1K = {
|
|
25
|
+
"claude": 0.003, "anthropic": 0.003,
|
|
26
|
+
"openai": 0.005, "gpt": 0.005,
|
|
27
|
+
"gemini": 0.001,
|
|
28
|
+
"deepseek": 0.0014,
|
|
29
|
+
"mistral": 0.002,
|
|
30
|
+
"default": 0.003,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cost(tokens: int, provider: str) -> float:
|
|
35
|
+
rate = _COST_PER_1K.get(provider.lower(), _COST_PER_1K["default"])
|
|
36
|
+
return (tokens / 1000) * rate
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class AnalyticsRecord:
|
|
41
|
+
timestamp: float
|
|
42
|
+
session_id: str
|
|
43
|
+
provider: str
|
|
44
|
+
model: str
|
|
45
|
+
input_tokens_original: int
|
|
46
|
+
input_tokens_sent: int
|
|
47
|
+
output_tokens: int
|
|
48
|
+
tokens_saved: int
|
|
49
|
+
latency_ms: float
|
|
50
|
+
cache_hit: bool
|
|
51
|
+
layer_savings: Dict[str, int] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AnalyticsEngine:
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self._records: List[AnalyticsRecord] = []
|
|
58
|
+
self._by_provider: Dict[str, List[AnalyticsRecord]] = defaultdict(list)
|
|
59
|
+
# FIXED: previously, silent failures (checkpoint save, graph
|
|
60
|
+
# eviction persist, Redis write, AND background LLM extraction
|
|
61
|
+
# errors) were caught, logged at low severity, and otherwise
|
|
62
|
+
# invisible — no way to know in production whether data loss or
|
|
63
|
+
# feature degradation was happening without grepping logs. This
|
|
64
|
+
# counter makes "how many times did something silently fail this
|
|
65
|
+
# session" a queryable number via /api/stats instead of a fact
|
|
66
|
+
# buried in a log line. Dict key is `persist_failures` for API
|
|
67
|
+
# stability even though it now covers a slightly broader category
|
|
68
|
+
# than literal persistence (see record_silent_failure docstring).
|
|
69
|
+
self._persist_failures: Dict[str, int] = defaultdict(int)
|
|
70
|
+
|
|
71
|
+
def record_silent_failure(self, source: str) -> None:
|
|
72
|
+
"""Track a failure that would otherwise be invisible outside debug
|
|
73
|
+
logs — persistence (checkpoint save, graph eviction, Redis write)
|
|
74
|
+
AND non-persistence failures like background LLM extraction
|
|
75
|
+
errors. The common thread: all of these used to fail silently
|
|
76
|
+
with zero visibility outside of logs nobody watches by default.
|
|
77
|
+
Call this from every place that catches such an exception — it
|
|
78
|
+
costs one dict increment and turns 'silent forever' into 'visible
|
|
79
|
+
in /api/stats'."""
|
|
80
|
+
self._persist_failures[source] += 1
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def persist_failures(self) -> Dict[str, int]:
|
|
84
|
+
return dict(self._persist_failures)
|
|
85
|
+
|
|
86
|
+
def record(
|
|
87
|
+
self,
|
|
88
|
+
session_id: str,
|
|
89
|
+
provider: str,
|
|
90
|
+
model: str,
|
|
91
|
+
input_tokens_original: int,
|
|
92
|
+
input_tokens_sent: int,
|
|
93
|
+
output_tokens: int,
|
|
94
|
+
tokens_saved: int,
|
|
95
|
+
latency_ms: float,
|
|
96
|
+
cache_hit: bool,
|
|
97
|
+
layer_savings: Dict[str, int] | None = None,
|
|
98
|
+
) -> None:
|
|
99
|
+
r = AnalyticsRecord(
|
|
100
|
+
timestamp=time.time(),
|
|
101
|
+
session_id=session_id,
|
|
102
|
+
provider=provider,
|
|
103
|
+
model=model,
|
|
104
|
+
input_tokens_original=input_tokens_original,
|
|
105
|
+
input_tokens_sent=input_tokens_sent,
|
|
106
|
+
output_tokens=output_tokens,
|
|
107
|
+
tokens_saved=tokens_saved,
|
|
108
|
+
latency_ms=latency_ms,
|
|
109
|
+
cache_hit=cache_hit,
|
|
110
|
+
layer_savings=layer_savings or {},
|
|
111
|
+
)
|
|
112
|
+
self._records.append(r)
|
|
113
|
+
self._by_provider[provider].append(r)
|
|
114
|
+
|
|
115
|
+
def _period_stats(self, cutoff: float) -> PeriodStats:
|
|
116
|
+
stats = PeriodStats()
|
|
117
|
+
for r in self._records:
|
|
118
|
+
if r.timestamp < cutoff:
|
|
119
|
+
continue
|
|
120
|
+
stats.requests += 1
|
|
121
|
+
stats.tokens_saved += r.tokens_saved
|
|
122
|
+
stats.tokens_used += r.input_tokens_sent + r.output_tokens
|
|
123
|
+
stats.cost_saved += _cost(r.tokens_saved, r.provider)
|
|
124
|
+
stats.cost_actual += _cost(r.input_tokens_sent + r.output_tokens, r.provider)
|
|
125
|
+
return stats
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def daily(self) -> PeriodStats:
|
|
129
|
+
return self._period_stats(time.time() - 86_400)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def weekly(self) -> PeriodStats:
|
|
133
|
+
return self._period_stats(time.time() - 7 * 86_400)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def monthly(self) -> PeriodStats:
|
|
137
|
+
return self._period_stats(time.time() - 30 * 86_400)
|
|
138
|
+
|
|
139
|
+
def layer_breakdown(self) -> Dict[str, int]:
|
|
140
|
+
totals: Dict[str, int] = defaultdict(int)
|
|
141
|
+
for r in self._records:
|
|
142
|
+
for layer, saved in r.layer_savings.items():
|
|
143
|
+
totals[layer] += saved
|
|
144
|
+
return dict(totals)
|
|
145
|
+
|
|
146
|
+
def generate_suggestions(self) -> list[str]:
|
|
147
|
+
suggestions = []
|
|
148
|
+
d = self.daily
|
|
149
|
+
if d.requests == 0:
|
|
150
|
+
suggestions.append("No requests yet — start sending requests to see analytics.")
|
|
151
|
+
return suggestions
|
|
152
|
+
if d.tokens_saved == 0:
|
|
153
|
+
suggestions.append("Enable compression in tokenmizer.yaml to start saving tokens.")
|
|
154
|
+
breakdown = self.layer_breakdown()
|
|
155
|
+
if breakdown.get("cache", 0) == 0:
|
|
156
|
+
suggestions.append("Semantic cache has no hits yet — similar queries will be cached automatically.")
|
|
157
|
+
return suggestions
|
|
158
|
+
|
|
159
|
+
def summary(self) -> dict:
|
|
160
|
+
d, w, m = self.daily, self.weekly, self.monthly
|
|
161
|
+
return {
|
|
162
|
+
"total_requests": len(self._records),
|
|
163
|
+
"daily": {
|
|
164
|
+
"requests": d.requests,
|
|
165
|
+
"tokens_saved": d.tokens_saved,
|
|
166
|
+
"savings_pct": round(d.savings_pct, 1),
|
|
167
|
+
"cost_saved_usd": round(d.cost_saved, 4),
|
|
168
|
+
},
|
|
169
|
+
"weekly": {
|
|
170
|
+
"requests": w.requests,
|
|
171
|
+
"tokens_saved": w.tokens_saved,
|
|
172
|
+
"savings_pct": round(w.savings_pct, 1),
|
|
173
|
+
"cost_saved_usd": round(w.cost_saved, 4),
|
|
174
|
+
},
|
|
175
|
+
"monthly": {
|
|
176
|
+
"requests": m.requests,
|
|
177
|
+
"tokens_saved": m.tokens_saved,
|
|
178
|
+
"savings_pct": round(m.savings_pct, 1),
|
|
179
|
+
"cost_saved_usd": round(m.cost_saved, 4),
|
|
180
|
+
},
|
|
181
|
+
"layer_breakdown": self.layer_breakdown(),
|
|
182
|
+
"by_provider": {p: len(recs) for p, recs in self._by_provider.items()},
|
|
183
|
+
"suggestions": self.generate_suggestions(),
|
|
184
|
+
# FIXED: persistence failures (checkpoint/graph/redis writes that
|
|
185
|
+
# silently failed) are now visible here instead of only in logs.
|
|
186
|
+
# Non-zero values mean data was lost — investigate immediately.
|
|
187
|
+
"persist_failures": self.persist_failures,
|
|
188
|
+
}
|
|
File without changes
|