tokenbiryani 0.2.0__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 (51) hide show
  1. tokenbiryani/__init__.py +3 -0
  2. tokenbiryani/api/__init__.py +0 -0
  3. tokenbiryani/api/app.py +583 -0
  4. tokenbiryani/api/asgi.py +32 -0
  5. tokenbiryani/cli.py +1045 -0
  6. tokenbiryani/config.py +532 -0
  7. tokenbiryani/core/__init__.py +0 -0
  8. tokenbiryani/core/account.py +258 -0
  9. tokenbiryani/core/batch.py +135 -0
  10. tokenbiryani/core/breaker.py +53 -0
  11. tokenbiryani/core/cacheadvice.py +239 -0
  12. tokenbiryani/core/diagnostics.py +131 -0
  13. tokenbiryani/core/estimator.py +180 -0
  14. tokenbiryani/core/gateway.py +2395 -0
  15. tokenbiryani/core/handoff.py +87 -0
  16. tokenbiryani/core/keys.py +199 -0
  17. tokenbiryani/core/limits.py +440 -0
  18. tokenbiryani/core/oauth.py +222 -0
  19. tokenbiryani/core/pacing.py +320 -0
  20. tokenbiryani/core/queue.py +132 -0
  21. tokenbiryani/core/router.py +323 -0
  22. tokenbiryani/core/secrets.py +114 -0
  23. tokenbiryani/core/session.py +117 -0
  24. tokenbiryani/dashboard/__init__.py +56 -0
  25. tokenbiryani/dashboard/console.css +610 -0
  26. tokenbiryani/dashboard/console.html +3250 -0
  27. tokenbiryani/observability/__init__.py +0 -0
  28. tokenbiryani/observability/events.py +171 -0
  29. tokenbiryani/observability/usage.py +226 -0
  30. tokenbiryani/prices.yaml +77 -0
  31. tokenbiryani/providers/__init__.py +0 -0
  32. tokenbiryani/providers/anthropic_api.py +118 -0
  33. tokenbiryani/providers/base.py +173 -0
  34. tokenbiryani/providers/bedrock.py +182 -0
  35. tokenbiryani/providers/oauth.py +165 -0
  36. tokenbiryani/providers/oauth_credentials.py +293 -0
  37. tokenbiryani/providers/translate.py +35 -0
  38. tokenbiryani/providers/vertex.py +144 -0
  39. tokenbiryani/proxy/__init__.py +0 -0
  40. tokenbiryani/proxy/errors.py +169 -0
  41. tokenbiryani/proxy/sse.py +98 -0
  42. tokenbiryani/store/__init__.py +0 -0
  43. tokenbiryani/store/base.py +150 -0
  44. tokenbiryani/store/memory.py +149 -0
  45. tokenbiryani/store/redis_store.py +222 -0
  46. tokenbiryani/store/sqlite.py +336 -0
  47. tokenbiryani-0.2.0.dist-info/METADATA +697 -0
  48. tokenbiryani-0.2.0.dist-info/RECORD +51 -0
  49. tokenbiryani-0.2.0.dist-info/WHEEL +4 -0
  50. tokenbiryani-0.2.0.dist-info/entry_points.txt +2 -0
  51. tokenbiryani-0.2.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,336 @@
1
+ """SQLite state store: one file, no server, survives a restart.
2
+
3
+ The default for anyone running a single instance who wants a spend cap to mean
4
+ something after `systemctl restart`. sqlite3 is synchronous, so every statement runs
5
+ on the loop's executor.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ import sqlite3
14
+ import threading
15
+ import time
16
+ from typing import Any, Callable, Dict, List, Optional
17
+
18
+ from ..observability.usage import USAGE_FIELDS
19
+ from .base import StateStore
20
+
21
+ SCHEMA = """
22
+ CREATE TABLE IF NOT EXISTS affinity (
23
+ session_key TEXT PRIMARY KEY,
24
+ account_id TEXT NOT NULL,
25
+ expires_at REAL NOT NULL
26
+ );
27
+ CREATE INDEX IF NOT EXISTS affinity_expiry ON affinity (expires_at);
28
+
29
+ CREATE TABLE IF NOT EXISTS spend_ledger (
30
+ scope TEXT NOT NULL,
31
+ name TEXT NOT NULL,
32
+ at REAL NOT NULL,
33
+ amount REAL NOT NULL
34
+ );
35
+ CREATE INDEX IF NOT EXISTS spend_lookup ON spend_ledger (scope, name, at);
36
+
37
+ CREATE TABLE IF NOT EXISTS managed_keys (
38
+ name TEXT PRIMARY KEY,
39
+ record TEXT NOT NULL
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS managed_accounts (
43
+ id TEXT PRIMARY KEY,
44
+ record TEXT NOT NULL
45
+ );
46
+
47
+ CREATE TABLE IF NOT EXISTS settings (
48
+ name TEXT PRIMARY KEY,
49
+ value TEXT NOT NULL
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS usage_events (
53
+ at REAL NOT NULL,
54
+ account_id TEXT NOT NULL,
55
+ key_name TEXT NOT NULL,
56
+ model TEXT NOT NULL,
57
+ status INTEGER NOT NULL,
58
+ input_tokens INTEGER NOT NULL,
59
+ output_tokens INTEGER NOT NULL,
60
+ cache_read_tokens INTEGER NOT NULL,
61
+ cache_creation_tokens INTEGER NOT NULL,
62
+ cost_usd REAL NOT NULL,
63
+ latency REAL NOT NULL,
64
+ affinity_broken INTEGER NOT NULL,
65
+ via TEXT NOT NULL,
66
+ error TEXT NOT NULL
67
+ );
68
+ CREATE INDEX IF NOT EXISTS usage_at ON usage_events (at);
69
+ CREATE INDEX IF NOT EXISTS usage_account ON usage_events (account_id, at);
70
+
71
+ CREATE TABLE IF NOT EXISTS key_requests (
72
+ key_name TEXT NOT NULL,
73
+ at REAL NOT NULL
74
+ );
75
+ CREATE INDEX IF NOT EXISTS key_requests_lookup ON key_requests (key_name, at);
76
+ """
77
+
78
+ #: Ledger rows older than this are pruned. Comfortably past any sane spend window.
79
+ LEDGER_RETENTION_SECONDS = 90 * 24 * 3600
80
+
81
+
82
+ class SqliteStateStore(StateStore):
83
+ def __init__(self, path: str) -> None:
84
+ self.path = path
85
+ self._lock = threading.Lock()
86
+ self._connection: Optional[sqlite3.Connection] = None
87
+
88
+ # ---- plumbing ------------------------------------------------------------
89
+
90
+ def _connect(self) -> sqlite3.Connection:
91
+ if self._connection is None:
92
+ directory = os.path.dirname(os.path.abspath(self.path))
93
+ if directory and not os.path.isdir(directory):
94
+ os.makedirs(directory, exist_ok=True)
95
+ self._connection = sqlite3.connect(self.path, check_same_thread=False)
96
+ self._connection.execute("PRAGMA journal_mode=WAL")
97
+ self._connection.execute("PRAGMA synchronous=NORMAL")
98
+ self._connection.executescript(SCHEMA)
99
+ self._connection.commit()
100
+ return self._connection
101
+
102
+ async def _run(self, fn: Callable[..., Any], *args: Any) -> Any:
103
+ loop = asyncio.get_event_loop()
104
+ return await loop.run_in_executor(None, lambda: self._locked(fn, *args))
105
+
106
+ def _locked(self, fn: Callable[..., Any], *args: Any) -> Any:
107
+ with self._lock:
108
+ return fn(self._connect(), *args)
109
+
110
+ async def startup(self) -> None:
111
+ await self._run(lambda conn: conn.execute("SELECT 1").fetchone())
112
+ await self._run(self._prune)
113
+
114
+ async def close(self) -> None:
115
+ with self._lock:
116
+ if self._connection is not None:
117
+ self._connection.close()
118
+ self._connection = None
119
+
120
+ @staticmethod
121
+ def _prune(conn: sqlite3.Connection) -> None:
122
+ now = time.time()
123
+ conn.execute("DELETE FROM affinity WHERE expires_at < ?", (now,))
124
+ conn.execute(
125
+ "DELETE FROM spend_ledger WHERE at < ?", (now - LEDGER_RETENTION_SECONDS,)
126
+ )
127
+ conn.execute("DELETE FROM key_requests WHERE at < ?", (now - 3600.0,))
128
+ conn.execute(
129
+ "DELETE FROM usage_events WHERE at < ?", (now - LEDGER_RETENTION_SECONDS,)
130
+ )
131
+ conn.commit()
132
+
133
+ # ---- interface -----------------------------------------------------------
134
+
135
+ async def get_affinity(self, session_key: str) -> Optional[str]:
136
+ def query(conn: sqlite3.Connection) -> Optional[str]:
137
+ row = conn.execute(
138
+ "SELECT account_id, expires_at FROM affinity WHERE session_key = ?",
139
+ (session_key,),
140
+ ).fetchone()
141
+ if row is None:
142
+ return None
143
+ if row[1] < time.time():
144
+ conn.execute("DELETE FROM affinity WHERE session_key = ?", (session_key,))
145
+ conn.commit()
146
+ return None
147
+ return row[0]
148
+
149
+ return await self._run(query)
150
+
151
+ async def set_affinity(self, session_key: str, account_id: str, ttl: float) -> None:
152
+ def write(conn: sqlite3.Connection) -> None:
153
+ conn.execute(
154
+ "INSERT INTO affinity (session_key, account_id, expires_at) "
155
+ "VALUES (?, ?, ?) ON CONFLICT(session_key) DO UPDATE SET "
156
+ "account_id = excluded.account_id, expires_at = excluded.expires_at",
157
+ (session_key, account_id, time.time() + ttl),
158
+ )
159
+ conn.commit()
160
+
161
+ await self._run(write)
162
+
163
+ async def clear_affinity(self, session_key: str) -> None:
164
+ def write(conn: sqlite3.Connection) -> None:
165
+ conn.execute("DELETE FROM affinity WHERE session_key = ?", (session_key,))
166
+ conn.commit()
167
+
168
+ await self._run(write)
169
+
170
+ async def clear_affinity_for_account(self, account_id: str) -> int:
171
+ def write(conn: sqlite3.Connection) -> int:
172
+ cursor = conn.execute(
173
+ "DELETE FROM affinity WHERE account_id = ?", (account_id,)
174
+ )
175
+ conn.commit()
176
+ return int(cursor.rowcount or 0)
177
+
178
+ return await self._run(write)
179
+
180
+ async def record_key_request(self, key_name: str, now: float) -> int:
181
+ def write(conn: sqlite3.Connection) -> int:
182
+ conn.execute(
183
+ "INSERT INTO key_requests (key_name, at) VALUES (?, ?)", (key_name, now)
184
+ )
185
+ conn.execute(
186
+ "DELETE FROM key_requests WHERE key_name = ? AND at < ?",
187
+ (key_name, now - 60.0),
188
+ )
189
+ conn.commit()
190
+ row = conn.execute(
191
+ "SELECT COUNT(*) FROM key_requests WHERE key_name = ? AND at >= ?",
192
+ (key_name, now - 60.0),
193
+ ).fetchone()
194
+ return int(row[0]) if row else 0
195
+
196
+ return await self._run(write)
197
+
198
+ async def add_spend(self, scope: str, name: str, amount: float) -> None:
199
+ def write(conn: sqlite3.Connection) -> None:
200
+ conn.execute(
201
+ "INSERT INTO spend_ledger (scope, name, at, amount) VALUES (?, ?, ?, ?)",
202
+ (scope, name, time.time(), amount),
203
+ )
204
+ conn.commit()
205
+
206
+ await self._run(write)
207
+
208
+ async def get_spend(self, scope: str, name: str, window_seconds: float) -> float:
209
+ def query(conn: sqlite3.Connection) -> float:
210
+ row = conn.execute(
211
+ "SELECT COALESCE(SUM(amount), 0) FROM spend_ledger "
212
+ "WHERE scope = ? AND name = ? AND at >= ?",
213
+ (scope, name, time.time() - window_seconds),
214
+ ).fetchone()
215
+ return float(row[0]) if row else 0.0
216
+
217
+ return await self._run(query)
218
+
219
+ async def record_usage(self, sample: Dict[str, Any]) -> None:
220
+ columns = ", ".join(USAGE_FIELDS)
221
+ placeholders = ", ".join("?" for _ in USAGE_FIELDS)
222
+ values = tuple(sample.get(field) for field in USAGE_FIELDS)
223
+
224
+ def write(conn: sqlite3.Connection) -> None:
225
+ conn.execute(
226
+ f"INSERT INTO usage_events ({columns}) VALUES ({placeholders})", values
227
+ )
228
+ conn.commit()
229
+
230
+ await self._run(write)
231
+
232
+ async def usage_rows(
233
+ self, since: float, until: float, account_id: Optional[str] = None
234
+ ) -> List[Dict[str, Any]]:
235
+ columns = ", ".join(USAGE_FIELDS)
236
+
237
+ def query(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
238
+ if account_id is None:
239
+ sql = f"SELECT {columns} FROM usage_events WHERE at >= ? AND at < ?"
240
+ params: tuple = (since, until)
241
+ else:
242
+ sql = (
243
+ f"SELECT {columns} FROM usage_events "
244
+ "WHERE account_id = ? AND at >= ? AND at < ?"
245
+ )
246
+ params = (account_id, since, until)
247
+ rows = conn.execute(sql + " ORDER BY at", params).fetchall()
248
+ return [dict(zip(USAGE_FIELDS, row)) for row in rows]
249
+
250
+ return await self._run(query)
251
+
252
+ async def put_key(self, record: Dict[str, Any]) -> None:
253
+ def write(conn: sqlite3.Connection) -> None:
254
+ conn.execute(
255
+ "INSERT INTO managed_keys (name, record) VALUES (?, ?) "
256
+ "ON CONFLICT(name) DO UPDATE SET record = excluded.record",
257
+ (str(record["name"]), json.dumps(record)),
258
+ )
259
+ conn.commit()
260
+
261
+ await self._run(write)
262
+
263
+ async def delete_key(self, name: str) -> bool:
264
+ def write(conn: sqlite3.Connection) -> bool:
265
+ cursor = conn.execute("DELETE FROM managed_keys WHERE name = ?", (name,))
266
+ conn.commit()
267
+ return cursor.rowcount > 0
268
+
269
+ return await self._run(write)
270
+
271
+ async def list_keys(self) -> List[Dict[str, Any]]:
272
+ def query(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
273
+ rows = conn.execute("SELECT record FROM managed_keys").fetchall()
274
+ return [json.loads(row[0]) for row in rows]
275
+
276
+ return await self._run(query)
277
+
278
+ async def put_account(self, record: Dict[str, Any]) -> None:
279
+ def write(conn: sqlite3.Connection) -> None:
280
+ conn.execute(
281
+ "INSERT INTO managed_accounts (id, record) VALUES (?, ?) "
282
+ "ON CONFLICT(id) DO UPDATE SET record = excluded.record",
283
+ (str(record["id"]), json.dumps(record)),
284
+ )
285
+ conn.commit()
286
+
287
+ await self._run(write)
288
+
289
+ async def delete_account(self, account_id: str) -> bool:
290
+ def write(conn: sqlite3.Connection) -> bool:
291
+ cursor = conn.execute("DELETE FROM managed_accounts WHERE id = ?", (account_id,))
292
+ conn.commit()
293
+ return cursor.rowcount > 0
294
+
295
+ return await self._run(write)
296
+
297
+ async def list_accounts(self) -> List[Dict[str, Any]]:
298
+ def query(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
299
+ rows = conn.execute("SELECT record FROM managed_accounts ORDER BY id").fetchall()
300
+ return [json.loads(row[0]) for row in rows]
301
+
302
+ return await self._run(query)
303
+
304
+ async def put_setting(self, name: str, value: Any) -> None:
305
+ def write(conn: sqlite3.Connection) -> None:
306
+ conn.execute(
307
+ "INSERT INTO settings (name, value) VALUES (?, ?) "
308
+ "ON CONFLICT(name) DO UPDATE SET value = excluded.value",
309
+ (str(name), json.dumps(value)),
310
+ )
311
+ conn.commit()
312
+
313
+ await self._run(write)
314
+
315
+ async def get_settings(self) -> Dict[str, Any]:
316
+ def query(conn: sqlite3.Connection) -> Dict[str, Any]:
317
+ out: Dict[str, Any] = {}
318
+ for name, value in conn.execute("SELECT name, value FROM settings"):
319
+ try:
320
+ out[name] = json.loads(value)
321
+ except ValueError:
322
+ continue
323
+ return out
324
+
325
+ return await self._run(query)
326
+
327
+ async def spend_by_scope(self, scope: str, window_seconds: float) -> Dict[str, float]:
328
+ def query(conn: sqlite3.Connection) -> Dict[str, float]:
329
+ rows = conn.execute(
330
+ "SELECT name, COALESCE(SUM(amount), 0) FROM spend_ledger "
331
+ "WHERE scope = ? AND at >= ? GROUP BY name",
332
+ (scope, time.time() - window_seconds),
333
+ ).fetchall()
334
+ return {row[0]: float(row[1]) for row in rows}
335
+
336
+ return await self._run(query)