driftlock 0.2.0__tar.gz

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 (47) hide show
  1. driftlock-0.2.0/LICENSE +21 -0
  2. driftlock-0.2.0/PKG-INFO +537 -0
  3. driftlock-0.2.0/README.md +506 -0
  4. driftlock-0.2.0/driftlock/__init__.py +72 -0
  5. driftlock-0.2.0/driftlock/alerts.py +170 -0
  6. driftlock-0.2.0/driftlock/anthropic_client.py +537 -0
  7. driftlock-0.2.0/driftlock/cache.py +162 -0
  8. driftlock-0.2.0/driftlock/cli.py +363 -0
  9. driftlock-0.2.0/driftlock/client.py +610 -0
  10. driftlock-0.2.0/driftlock/config.py +30 -0
  11. driftlock-0.2.0/driftlock/context.py +44 -0
  12. driftlock-0.2.0/driftlock/drift.py +97 -0
  13. driftlock-0.2.0/driftlock/logger.py +105 -0
  14. driftlock-0.2.0/driftlock/metrics.py +84 -0
  15. driftlock-0.2.0/driftlock/optimization.py +252 -0
  16. driftlock-0.2.0/driftlock/policy.py +341 -0
  17. driftlock-0.2.0/driftlock/pricing.py +91 -0
  18. driftlock-0.2.0/driftlock/providers/__init__.py +5 -0
  19. driftlock-0.2.0/driftlock/providers/anthropic_provider.py +29 -0
  20. driftlock-0.2.0/driftlock/providers/base.py +20 -0
  21. driftlock-0.2.0/driftlock/providers/openai_provider.py +26 -0
  22. driftlock-0.2.0/driftlock/storage.py +418 -0
  23. driftlock-0.2.0/driftlock/streaming.py +164 -0
  24. driftlock-0.2.0/driftlock/tokenizer.py +66 -0
  25. driftlock-0.2.0/driftlock.egg-info/PKG-INFO +537 -0
  26. driftlock-0.2.0/driftlock.egg-info/SOURCES.txt +45 -0
  27. driftlock-0.2.0/driftlock.egg-info/dependency_links.txt +1 -0
  28. driftlock-0.2.0/driftlock.egg-info/entry_points.txt +2 -0
  29. driftlock-0.2.0/driftlock.egg-info/requires.txt +15 -0
  30. driftlock-0.2.0/driftlock.egg-info/top_level.txt +1 -0
  31. driftlock-0.2.0/pyproject.toml +52 -0
  32. driftlock-0.2.0/setup.cfg +4 -0
  33. driftlock-0.2.0/tests/test_alerts.py +88 -0
  34. driftlock-0.2.0/tests/test_async.py +103 -0
  35. driftlock-0.2.0/tests/test_cache.py +372 -0
  36. driftlock-0.2.0/tests/test_client.py +217 -0
  37. driftlock-0.2.0/tests/test_demo.py +313 -0
  38. driftlock-0.2.0/tests/test_drift.py +109 -0
  39. driftlock-0.2.0/tests/test_forecast.py +72 -0
  40. driftlock-0.2.0/tests/test_optimization.py +332 -0
  41. driftlock-0.2.0/tests/test_per_user_budget.py +105 -0
  42. driftlock-0.2.0/tests/test_policy.py +129 -0
  43. driftlock-0.2.0/tests/test_pricing.py +47 -0
  44. driftlock-0.2.0/tests/test_providers.py +67 -0
  45. driftlock-0.2.0/tests/test_storage.py +58 -0
  46. driftlock-0.2.0/tests/test_streaming.py +108 -0
  47. driftlock-0.2.0/tests/test_velocity.py +117 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Driftlock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,537 @@
1
+ Metadata-Version: 2.4
2
+ Name: driftlock
3
+ Version: 0.2.0
4
+ Summary: LLM cost governance and control layer — supports OpenAI, Anthropic, and more
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/your-org/driftlock
7
+ Project-URL: Repository, https://github.com/your-org/driftlock
8
+ Project-URL: Bug Tracker, https://github.com/your-org/driftlock/issues
9
+ Keywords: openai,anthropic,llm,cost,policy,governance,middleware
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: openai>=1.0.0
19
+ Provides-Extra: anthropic
20
+ Requires-Dist: anthropic>=0.30.0; extra == "anthropic"
21
+ Provides-Extra: fastapi
22
+ Requires-Dist: fastapi>=0.110.0; extra == "fastapi"
23
+ Requires-Dist: uvicorn[standard]>=0.29.0; extra == "fastapi"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
27
+ Requires-Dist: pytest-mock>=3.14; extra == "dev"
28
+ Requires-Dist: httpx>=0.27; extra == "dev"
29
+ Requires-Dist: ruff>=0.4; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Driftlock
33
+
34
+ **LLM cost governance and control layer for Python.**
35
+
36
+ Driftlock sits between your application and LLM providers (OpenAI, Anthropic). It enforces cost policies, detects runaway spending, tracks per-call telemetry, and prevents budget overruns — with a drop-in API wrapper and zero changes to existing call sites.
37
+
38
+ ---
39
+
40
+ ## Install
41
+
42
+ Not yet on PyPI. Install directly from source:
43
+
44
+ ```bash
45
+ git clone https://github.com/your-org/driftlock
46
+ cd driftlock
47
+ pip install -e .
48
+ ```
49
+
50
+ With Anthropic support:
51
+
52
+ ```bash
53
+ pip install -e ".[anthropic]"
54
+ ```
55
+
56
+ With FastAPI support:
57
+
58
+ ```bash
59
+ pip install -e ".[fastapi]"
60
+ ```
61
+
62
+ Requires Python ≥ 3.11.
63
+
64
+ ---
65
+
66
+ ## Try It Now (no API key needed)
67
+
68
+ ```bash
69
+ git clone https://github.com/your-org/driftlock && cd driftlock
70
+ pip install -e .
71
+ python examples/demo.py
72
+ ```
73
+
74
+ This runs a fully-mocked demo in-process — no API key, no network calls, no cost. It exercises every major feature: tracking, optimization, budget guardrails, cache, and context tags.
75
+
76
+ ---
77
+
78
+ ## 60-Second Quickstart (real API)
79
+
80
+ ```bash
81
+ export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY=sk-ant-...
82
+ driftlock demo
83
+ ```
84
+
85
+ Driftlock makes one cheap request (`gpt-4o-mini` or `claude-haiku-4-5`) under a default policy and prints a receipt:
86
+
87
+ ```
88
+ Driftlock demo — provider=openai model=gpt-4o-mini
89
+
90
+ ┌─ Receipt ──────────────────────────────────────────────┐
91
+ │ provider : openai │
92
+ │ model : gpt-4o-mini │
93
+ │ tokens : 23 (15 in / 8 out) │
94
+ │ cost : $0.000007 │
95
+ │ latency : 412 ms │
96
+ │ db : ./driftlock.sqlite │
97
+ └────────────────────────────────────────────────────────┘
98
+
99
+ Next steps:
100
+ driftlock stats # aggregate cost + token totals
101
+ driftlock recent # last 20 calls
102
+ driftlock forecast # projected monthly spend
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Basic Integration — OpenAI
108
+
109
+ ```python
110
+ from driftlock import DriftlockClient
111
+
112
+ # Replace openai.OpenAI() with DriftlockClient().
113
+ # All other arguments are forwarded to the OpenAI client unchanged.
114
+ client = DriftlockClient(api_key="sk-...")
115
+
116
+ response = client.chat.completions.create(
117
+ model="gpt-4o-mini",
118
+ messages=[{"role": "user", "content": "Hello!"}],
119
+ )
120
+ ```
121
+
122
+ Every call is logged, costed, and saved to a local SQLite file.
123
+
124
+ ```json
125
+ {
126
+ "level": "INFO",
127
+ "logger": "driftlock",
128
+ "message": "model=gpt-4o-mini | tokens=157 | latency=421ms | cost=$0.000033",
129
+ "metrics": {
130
+ "timestamp": "2025-03-01T12:00:00+00:00",
131
+ "model": "gpt-4o-mini",
132
+ "prompt_tokens": 120,
133
+ "completion_tokens": 37,
134
+ "total_tokens": 157,
135
+ "latency_ms": 421.3,
136
+ "estimated_cost_usd": 0.0000330
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### Async
142
+
143
+ ```python
144
+ response = await client.chat.completions.acreate(
145
+ model="gpt-4o-mini",
146
+ messages=[{"role": "user", "content": "Hello!"}],
147
+ )
148
+ ```
149
+
150
+ ### Streaming
151
+
152
+ ```python
153
+ for chunk in client.chat.completions.create(
154
+ model="gpt-4o-mini",
155
+ messages=[{"role": "user", "content": "Tell me a story."}],
156
+ stream=True,
157
+ ):
158
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
159
+ # Metrics are logged and saved when the stream closes.
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Basic Integration — Anthropic
165
+
166
+ Requires `pip install -e ".[anthropic]"`.
167
+
168
+ ```python
169
+ from driftlock import AnthropicDriftlockClient
170
+
171
+ client = AnthropicDriftlockClient(api_key="sk-ant-...")
172
+
173
+ response = client.messages.create(
174
+ model="claude-3-5-sonnet-20241022",
175
+ max_tokens=1024,
176
+ messages=[{"role": "user", "content": "Hello!"}],
177
+ )
178
+ ```
179
+
180
+ `max_tokens` is required by Anthropic. The `system` parameter is a top-level kwarg, not a message role — same as the native SDK.
181
+
182
+ ---
183
+
184
+ ## Labelling Calls
185
+
186
+ Use `_dl_endpoint` and `_dl_labels` to annotate individual calls. These are stripped before the request reaches the provider.
187
+
188
+ ```python
189
+ response = client.chat.completions.create(
190
+ model="gpt-4o-mini",
191
+ messages=[...],
192
+ _dl_endpoint="summarise_article", # logical function name
193
+ _dl_labels={"user_id": "u_123", "team": "growth"},
194
+ )
195
+ ```
196
+
197
+ `user_id` and `team_id` in labels are indexed in SQLite for fast per-user queries.
198
+
199
+ ---
200
+
201
+ ## Ambient Tagging
202
+
203
+ Attach labels to all calls within a scope without modifying every call site — useful in middleware:
204
+
205
+ ```python
206
+ import driftlock
207
+
208
+ with driftlock.tag(request_id="req_abc", user_id="u_42", feature="chat"):
209
+ response = client.chat.completions.create(...)
210
+ ```
211
+
212
+ Tags from nested `driftlock.tag()` blocks merge; inner values override outer ones. Per-call `_dl_labels` always wins.
213
+
214
+ ---
215
+
216
+ ## Configuration
217
+
218
+ ```python
219
+ from driftlock import DriftlockClient, DriftlockConfig
220
+
221
+ config = DriftlockConfig(
222
+ log_json=True, # JSON logs (default). False = human-readable.
223
+ log_level="INFO",
224
+ storage_backend="sqlite", # "sqlite" | "none"
225
+ db_path="driftlock.sqlite",
226
+ prompt_token_warning_threshold=4000, # Warn if prompt > N tokens.
227
+ cost_warning_threshold=0.10, # Warn if a single call costs > $X.
228
+ default_labels={"env": "prod"}, # Attached to every tracked call.
229
+ )
230
+
231
+ client = DriftlockClient(api_key="sk-...", config=config)
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Policy Engine
237
+
238
+ The policy engine enforces governance rules before every API call. Rules are evaluated in order; the first block raises `PolicyViolationError`.
239
+
240
+ ```python
241
+ from driftlock import (
242
+ DriftlockClient,
243
+ PolicyEngine,
244
+ MonthlyBudgetRule,
245
+ MaxCostPerRequestRule,
246
+ VelocityLimitRule,
247
+ CostVelocityRule,
248
+ PerUserBudgetRule,
249
+ ForecastBudgetRule,
250
+ RestrictModelRule,
251
+ TagBasedModelDowngradeRule,
252
+ PolicyViolationError,
253
+ )
254
+
255
+ policy = PolicyEngine(rules=[
256
+ MonthlyBudgetRule(max_usd=100.0), # Block at $100/month workspace
257
+ MaxCostPerRequestRule(max_usd=0.10), # Block single calls > $0.10
258
+ VelocityLimitRule(max_requests=60, window_seconds=60), # 60 req/min circuit breaker
259
+ ])
260
+
261
+ client = DriftlockClient(api_key="sk-...", policy=policy)
262
+
263
+ try:
264
+ response = client.chat.completions.create(...)
265
+ except PolicyViolationError as e:
266
+ print(f"Blocked by {e.rule_name}: {e.decision.metadata}")
267
+ ```
268
+
269
+ ### Available Rules
270
+
271
+ | Rule | What it does |
272
+ |---|---|
273
+ | `MonthlyBudgetRule(max_usd, scope="workspace"\|"user")` | Block once monthly spend cap is reached |
274
+ | `MaxCostPerRequestRule(max_usd)` | Block a single call if estimated cost exceeds the limit |
275
+ | `PerUserBudgetRule(user_budgets, default_max_usd)` | Per-user monthly caps from a dict |
276
+ | `ForecastBudgetRule(monthly_budget_usd, lookback_days=7)` | Block when projected 30-day spend will exceed budget |
277
+ | `VelocityLimitRule(max_requests, window_seconds, scope="workspace"\|"user")` | Circuit breaker on request rate |
278
+ | `CostVelocityRule(max_cost_usd, window_seconds)` | Circuit breaker on spend rate (e.g. $5/hour) |
279
+ | `RestrictModelRule(disallowed_models, condition=None)` | Block calls to specific models |
280
+ | `TagBasedModelDowngradeRule(condition, downgrade_to)` | Silently swap model based on labels |
281
+
282
+ ### Per-User Budgets
283
+
284
+ ```python
285
+ policy = PolicyEngine(rules=[
286
+ PerUserBudgetRule(
287
+ user_budgets={"power_user": 20.0, "free_tier": 1.0},
288
+ default_max_usd=5.0, # applied to any user_id not in the dict
289
+ ),
290
+ ])
291
+ # user_id is read from _dl_labels={"user_id": "..."} or ambient tags
292
+ ```
293
+
294
+ ### Forecast-Based Blocking
295
+
296
+ ```python
297
+ policy = PolicyEngine(rules=[
298
+ ForecastBudgetRule(monthly_budget_usd=50.0, lookback_days=7),
299
+ ])
300
+ # Blocks before the budget is actually exhausted — proactive, not reactive
301
+ ```
302
+
303
+ ### Model Governance
304
+
305
+ ```python
306
+ policy = PolicyEngine(rules=[
307
+ # Block GPT-4o on free plan users
308
+ RestrictModelRule(
309
+ disallowed_models={"gpt-4o", "gpt-4"},
310
+ condition=lambda ctx: ctx["labels"].get("plan") == "free",
311
+ ),
312
+ # Auto-downgrade free users to mini
313
+ TagBasedModelDowngradeRule(
314
+ condition=lambda ctx: ctx["labels"].get("plan") == "free",
315
+ downgrade_to="gpt-4o-mini",
316
+ ),
317
+ ])
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Alerts
323
+
324
+ Fire-and-forget notifications when policies trip or cost thresholds are crossed.
325
+
326
+ ```python
327
+ from driftlock import DriftlockConfig, WebhookAlertChannel, SlackAlertChannel, LogAlertChannel
328
+
329
+ config = DriftlockConfig(
330
+ alert_channels=[
331
+ SlackAlertChannel(webhook_url="https://hooks.slack.com/services/..."),
332
+ WebhookAlertChannel(url="https://example.com/hooks/driftlock"),
333
+ LogAlertChannel(), # logs to Python logging at WARNING level
334
+ ]
335
+ )
336
+ ```
337
+
338
+ Alert events: `policy_block`, `cost_warning`, `budget_threshold`, `velocity_trip`.
339
+
340
+ Delivery failures are logged at WARNING level and never propagate to the caller.
341
+
342
+ ---
343
+
344
+ ## Cost Reduction Engine
345
+
346
+ Enable the optimization pipeline to automatically trim prompts, cap output, and fall back to cheaper models:
347
+
348
+ ```python
349
+ from driftlock import DriftlockClient, OptimizationConfig
350
+
351
+ client = DriftlockClient(
352
+ api_key="sk-...",
353
+ optimization=OptimizationConfig(
354
+ max_prompt_tokens=3000, # trim history if prompt exceeds this
355
+ keep_last_n_messages=10, # always keep the N most recent turns
356
+ always_keep_system=True, # never drop the system message
357
+ default_max_output_tokens=512, # cap output when caller omits max_tokens
358
+ max_cost_per_request_usd=0.05, # abort if estimated cost > $0.05
359
+ budget_exceeded_action="fallback",
360
+ fallback_model="gpt-4o-mini",
361
+ ),
362
+ )
363
+ ```
364
+
365
+ Every call logs an `optimization` block showing tokens and cost saved:
366
+
367
+ ```json
368
+ {
369
+ "optimization": {
370
+ "original_prompt_tokens": 3840,
371
+ "optimized_prompt_tokens": 142,
372
+ "tokens_saved": 3698,
373
+ "cost_saved_usd": 0.0005547,
374
+ "optimizations_applied": ["prompt_trim", "output_cap"],
375
+ "quality_risk": true
376
+ }
377
+ }
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Response Cache
383
+
384
+ Exact in-memory cache (LRU + TTL). Returns stored responses for identical requests without hitting the API:
385
+
386
+ ```python
387
+ from driftlock import DriftlockClient, CacheConfig
388
+
389
+ client = DriftlockClient(
390
+ api_key="sk-...",
391
+ cache=CacheConfig(
392
+ ttl_seconds=600, # entries expire after 10 minutes
393
+ max_entries=500, # LRU eviction above this
394
+ ),
395
+ )
396
+ ```
397
+
398
+ Cache hits report `cost=$0.00` and record tokens and dollars saved. Streaming responses are never cached.
399
+
400
+ ```python
401
+ client.cache_stats()
402
+ # {"enabled": True, "size": 12, "hits": 48, "misses": 14, "hit_rate": 0.7742}
403
+ ```
404
+
405
+ ---
406
+
407
+ ## Reading Metrics
408
+
409
+ ```python
410
+ # Aggregate stats (all time)
411
+ client.stats()
412
+ # {'calls': 42, 'total_tokens': 18500, 'total_cost_usd': 0.003245, ...}
413
+
414
+ # Filter by endpoint, model, or time window
415
+ client.stats(endpoint="summarise_article")
416
+ client.stats(model="gpt-4o")
417
+ client.stats(since="2025-03-01T00:00:00+00:00")
418
+
419
+ # Recent calls
420
+ client.recent_calls(limit=10)
421
+
422
+ # Projected monthly spend
423
+ client.forecast(lookback_days=7)
424
+ # {'daily_avg_usd': 0.0004, 'projected_monthly_usd': 0.012, ...}
425
+
426
+ # Prompt drift detection (detect template changes by endpoint)
427
+ client.prompt_drift(endpoint="summarise_article")
428
+ ```
429
+
430
+ ---
431
+
432
+ ## CLI
433
+
434
+ Inspect telemetry without writing code:
435
+
436
+ ```bash
437
+ driftlock stats # aggregate totals
438
+ driftlock stats --since 7d # last 7 days
439
+ driftlock stats --endpoint summarise # filter by endpoint
440
+ driftlock recent --limit 20 # last 20 calls
441
+ driftlock forecast --lookback 7 # projected monthly spend
442
+ driftlock top-endpoints --since 7d # most expensive endpoints
443
+ driftlock top-users --since 30d # per-user spend
444
+ driftlock models # spend by model
445
+ driftlock drift summarise_article # prompt change history
446
+ driftlock --db /path/to/other.db stats # point at a different db
447
+ ```
448
+
449
+ Set `DRIFTLOCK_DB_PATH` to override the default `driftlock.sqlite` path.
450
+
451
+ ---
452
+
453
+ ## Environment Variables
454
+
455
+ | Variable | Default | Effect |
456
+ |---|---|---|
457
+ | `DRIFTLOCK_ENABLED` | `true` | Set to `false` to pass through all calls with zero overhead |
458
+ | `DRIFTLOCK_TRACK_ONLY` | `false` | Track metrics but skip optimization and policy enforcement |
459
+ | `DRIFTLOCK_DB_PATH` | `driftlock.sqlite` | Override the SQLite file path for CLI commands |
460
+
461
+ ---
462
+
463
+ ## FastAPI Example
464
+
465
+ See [examples/fastapi_app.py](examples/fastapi_app.py) for a full integration with middleware tagging, optimization, and cache.
466
+
467
+ ```bash
468
+ OPENAI_API_KEY=sk-... uvicorn examples.fastapi_app:app --reload
469
+ ```
470
+
471
+ ---
472
+
473
+ ## Project Structure
474
+
475
+ ```
476
+ driftlock/
477
+ ├── __init__.py # Public API
478
+ ├── client.py # DriftlockClient (OpenAI wrapper, sync + async)
479
+ ├── anthropic_client.py # AnthropicDriftlockClient (opt-in)
480
+ ├── config.py # DriftlockConfig
481
+ ├── policy.py # PolicyEngine + all rules
482
+ ├── alerts.py # AlertChannel, Webhook/Slack/Log implementations
483
+ ├── metrics.py # CallMetrics dataclass
484
+ ├── pricing.py # OpenAI + Anthropic pricing table
485
+ ├── storage.py # SQLiteStorage + NoopStorage (auto-migrating)
486
+ ├── optimization.py # OptimizationPipeline, OptimizationConfig
487
+ ├── cache.py # ResponseCache (LRU+TTL), CacheConfig
488
+ ├── streaming.py # StreamingInterceptor (deferred metrics)
489
+ ├── drift.py # Prompt hash + drift detection
490
+ ├── cli.py # driftlock CLI entry point
491
+ ├── context.py # driftlock.tag() context manager
492
+ ├── logger.py # Structured JSON logger
493
+ ├── tokenizer.py # tiktoken + char fallback
494
+ └── providers/ # NormalizedUsage, OpenAIProvider, AnthropicProvider
495
+
496
+ examples/
497
+ ├── basic_usage.py
498
+ ├── fastapi_app.py
499
+ └── dashboard_app.py
500
+
501
+ tests/ # 131 tests
502
+ ```
503
+
504
+ ---
505
+
506
+ ## Roadmap
507
+
508
+ | Feature | Status |
509
+ |---|---|
510
+ | OpenAI chat wrapper (sync + async) | ✅ |
511
+ | Anthropic Messages wrapper (sync + async) | ✅ |
512
+ | Token tracking + cost estimation | ✅ |
513
+ | Latency measurement | ✅ |
514
+ | SQLite storage (auto-migrating) | ✅ |
515
+ | Structured JSON logging | ✅ |
516
+ | Policy engine (budget, velocity, model) | ✅ |
517
+ | Per-user / per-team budget caps | ✅ |
518
+ | Forecast-based budget blocking | ✅ |
519
+ | Velocity + cost circuit breakers | ✅ |
520
+ | Prompt optimization pipeline | ✅ |
521
+ | Exact in-memory response cache | ✅ |
522
+ | Streaming support | ✅ |
523
+ | Prompt drift detection | ✅ |
524
+ | Alert channels (Slack, Webhook, Log) | ✅ |
525
+ | Ambient tagging context manager | ✅ |
526
+ | CLI (stats, forecast, drift, top-users) | ✅ |
527
+ | OpenTelemetry export | Planned |
528
+ | Redis cache backend | Planned |
529
+ | Semantic (embedding-based) cache | Planned |
530
+ | Gemini adapter | Planned |
531
+ | PyPI release | Planned |
532
+
533
+ ---
534
+
535
+ ## License
536
+
537
+ MIT