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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
State backend abstraction.
|
|
3
|
+
|
|
4
|
+
InMemoryBackend — development only, dies on restart
|
|
5
|
+
RedisBackend — production, survives restarts, works across workers
|
|
6
|
+
|
|
7
|
+
Set TOKENMIZER_STATE_BACKEND=redis and TOKENMIZER_REDIS_URL=redis://...
|
|
8
|
+
to enable Redis in production.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StateBackend(ABC):
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def get(self, key: str) -> Optional[Any]: ...
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
|
|
26
|
+
"""Returns True if the write succeeded, False otherwise.
|
|
27
|
+
|
|
28
|
+
FIXED: previously returned None unconditionally (success or
|
|
29
|
+
failure looked identical to every caller). The one real caller —
|
|
30
|
+
`_set_context_used()` in api/app.py, which tracks how many tokens
|
|
31
|
+
of context a session has used — had no way to know its write was
|
|
32
|
+
dropped. A dropped write under-counts context usage, which means
|
|
33
|
+
the auto-checkpoint trigger_at_percent threshold could be silently
|
|
34
|
+
missed: the proxy would think a session is using less context than
|
|
35
|
+
it actually is, and the safety-net checkpoint that's supposed to
|
|
36
|
+
fire before the context window overflows simply... wouldn't.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def delete(self, key: str) -> bool:
|
|
42
|
+
"""Returns True if the delete succeeded (or key didn't exist),
|
|
43
|
+
False if the backend call itself failed."""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def keys(self, prefix: str) -> list[str]: ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InMemoryBackend(StateBackend):
|
|
51
|
+
"""
|
|
52
|
+
Development backend. NOT suitable for production or multi-worker deployments.
|
|
53
|
+
State is lost on process restart.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self._store: dict[str, Any] = {}
|
|
58
|
+
logger.warning(
|
|
59
|
+
"InMemoryBackend: session state will be lost on restart. "
|
|
60
|
+
"Set TOKENMIZER_STATE_BACKEND=redis for production."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def get(self, key: str) -> Optional[Any]:
|
|
64
|
+
return self._store.get(key)
|
|
65
|
+
|
|
66
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
|
|
67
|
+
self._store[key] = value # TTL not enforced in-memory
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def delete(self, key: str) -> bool:
|
|
71
|
+
self._store.pop(key, None)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def keys(self, prefix: str) -> list[str]:
|
|
75
|
+
return [k for k in self._store if k.startswith(prefix)]
|
|
76
|
+
|
|
77
|
+
def __len__(self) -> int:
|
|
78
|
+
return len(self._store)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RedisBackend(StateBackend):
|
|
82
|
+
"""
|
|
83
|
+
Production backend. Survives restarts, works across uvicorn workers.
|
|
84
|
+
Requires: pip install redis
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, url: str = "redis://localhost:6379/0"):
|
|
88
|
+
try:
|
|
89
|
+
import redis
|
|
90
|
+
self._r = redis.from_url(url, decode_responses=True, socket_connect_timeout=5)
|
|
91
|
+
self._r.ping()
|
|
92
|
+
logger.info(f"Redis backend connected: {url}")
|
|
93
|
+
except ImportError:
|
|
94
|
+
raise ImportError("pip install redis — required for Redis state backend")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise ConnectionError(f"Redis connection failed ({url}): {e}") from e
|
|
97
|
+
|
|
98
|
+
def get(self, key: str) -> Optional[Any]:
|
|
99
|
+
try:
|
|
100
|
+
val = self._r.get(key)
|
|
101
|
+
return json.loads(val) if val is not None else None
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning(f"Redis GET failed for {key}: {e}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
|
|
107
|
+
try:
|
|
108
|
+
serialized = json.dumps(value, default=str)
|
|
109
|
+
if ttl:
|
|
110
|
+
self._r.setex(key, ttl, serialized)
|
|
111
|
+
else:
|
|
112
|
+
self._r.set(key, serialized)
|
|
113
|
+
return True
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(f"Redis SET failed for {key} — write was DROPPED: {e}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def delete(self, key: str) -> bool:
|
|
119
|
+
try:
|
|
120
|
+
self._r.delete(key)
|
|
121
|
+
return True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.warning(f"Redis DELETE failed for {key}: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def keys(self, prefix: str) -> list[str]:
|
|
127
|
+
try:
|
|
128
|
+
return list(self._r.scan_iter(f"{prefix}*"))
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.warning(f"Redis KEYS failed for prefix {prefix}: {e}")
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_state_backend(backend_type: str = "memory", redis_url: str = "redis://localhost:6379/0") -> StateBackend:
|
|
135
|
+
if backend_type == "redis":
|
|
136
|
+
return RedisBackend(url=redis_url)
|
|
137
|
+
return InMemoryBackend()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified storage interface.
|
|
3
|
+
tokenmizer/storage/__init__.py
|
|
4
|
+
|
|
5
|
+
Your friend correctly identified that graph, checkpoint, and state all have
|
|
6
|
+
different persistence approaches with no shared interface.
|
|
7
|
+
|
|
8
|
+
This module defines a single StorageBackend protocol that all of them satisfy,
|
|
9
|
+
making the storage layer consistent and testable.
|
|
10
|
+
|
|
11
|
+
Current implementations:
|
|
12
|
+
GraphMemory → SQLite (tokenmizer/graph_memory/graph.py)
|
|
13
|
+
CheckpointManager → SQLite (tokenmizer/checkpoints/manager.py)
|
|
14
|
+
StateBackend → Redis or InMemory (tokenmizer/state/backend.py)
|
|
15
|
+
|
|
16
|
+
All three now implement the same conceptual interface:
|
|
17
|
+
get(key) → value | None
|
|
18
|
+
set(key, value, ttl?)
|
|
19
|
+
delete(key)
|
|
20
|
+
keys(prefix) → list[str]
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from abc import ABC, abstractmethod
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StorageBackend(ABC):
|
|
29
|
+
"""
|
|
30
|
+
Minimal key-value storage interface.
|
|
31
|
+
All TokenMizer persistence layers implement this protocol.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get(self, key: str) -> Optional[Any]:
|
|
36
|
+
"""Return value for key, or None if not found."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
41
|
+
"""Store value at key. Optional TTL in seconds."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def delete(self, key: str) -> None:
|
|
46
|
+
"""Remove key. No-op if not found."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def keys(self, prefix: str = "") -> list[str]:
|
|
51
|
+
"""Return all keys matching prefix."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def exists(self, key: str) -> bool:
|
|
55
|
+
"""Convenience: check if key exists."""
|
|
56
|
+
return self.get(key) is not None
|