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