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.
Files changed (50) hide show
  1. tokenmizer/__init__.py +21 -0
  2. tokenmizer/agents/__init__.py +0 -0
  3. tokenmizer/analytics/__init__.py +0 -0
  4. tokenmizer/analytics/engine.py +188 -0
  5. tokenmizer/api/__init__.py +0 -0
  6. tokenmizer/api/app.py +958 -0
  7. tokenmizer/api/rate_limiter.py +110 -0
  8. tokenmizer/checkpoints/__init__.py +0 -0
  9. tokenmizer/checkpoints/manager.py +383 -0
  10. tokenmizer/cli.py +153 -0
  11. tokenmizer/compression/__init__.py +0 -0
  12. tokenmizer/compression/engine.py +669 -0
  13. tokenmizer/compression/output_trimmer.py +95 -0
  14. tokenmizer/compression/window.py +104 -0
  15. tokenmizer/config/__init__.py +0 -0
  16. tokenmizer/config/settings.py +170 -0
  17. tokenmizer/core/__init__.py +0 -0
  18. tokenmizer/core/dto.py +196 -0
  19. tokenmizer/core/errors.py +35 -0
  20. tokenmizer/core/tokenizer.py +96 -0
  21. tokenmizer/dashboard/__init__.py +0 -0
  22. tokenmizer/dashboard/page.py +267 -0
  23. tokenmizer/filters/__init__.py +0 -0
  24. tokenmizer/filters/file_intelligence.py +960 -0
  25. tokenmizer/graph_memory/__init__.py +0 -0
  26. tokenmizer/graph_memory/decision_tracker.py +225 -0
  27. tokenmizer/graph_memory/graph.py +1287 -0
  28. tokenmizer/graph_memory/helpers.py +121 -0
  29. tokenmizer/graph_memory/hybrid_extractor.py +703 -0
  30. tokenmizer/graph_memory/types.py +134 -0
  31. tokenmizer/graph_memory/validator.py +304 -0
  32. tokenmizer/graph_memory/visualization.py +228 -0
  33. tokenmizer/mcp/__init__.py +0 -0
  34. tokenmizer/mcp/server.py +368 -0
  35. tokenmizer/providers/__init__.py +0 -0
  36. tokenmizer/providers/providers.py +456 -0
  37. tokenmizer/security/__init__.py +0 -0
  38. tokenmizer/security/auth.py +95 -0
  39. tokenmizer/security/middleware.py +138 -0
  40. tokenmizer/security/redaction.py +126 -0
  41. tokenmizer/semantic_cache/__init__.py +0 -0
  42. tokenmizer/semantic_cache/cache.py +383 -0
  43. tokenmizer/state/__init__.py +0 -0
  44. tokenmizer/state/backend.py +137 -0
  45. tokenmizer/storage/__init__.py +56 -0
  46. tokenmizer-0.2.4.dist-info/METADATA +529 -0
  47. tokenmizer-0.2.4.dist-info/RECORD +50 -0
  48. tokenmizer-0.2.4.dist-info/WHEEL +4 -0
  49. tokenmizer-0.2.4.dist-info/entry_points.txt +2 -0
  50. 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