ai-token-tracker 0.1.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.
- ai_token_tracker-0.1.0/PKG-INFO +300 -0
- ai_token_tracker-0.1.0/README.md +265 -0
- ai_token_tracker-0.1.0/pyproject.toml +61 -0
- ai_token_tracker-0.1.0/setup.cfg +4 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/__init__.py +33 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/classifier.py +105 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/constants.py +32 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/envelope.py +67 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/ingestion_client.py +146 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/interception.py +524 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/interception_scope.py +53 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/options.py +43 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/py.typed +0 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/runtime.py +13 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/sdk_client.py +160 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker/types.py +89 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker.egg-info/PKG-INFO +300 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker.egg-info/SOURCES.txt +24 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker.egg-info/dependency_links.txt +1 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker.egg-info/requires.txt +15 -0
- ai_token_tracker-0.1.0/src/ai_token_tracker.egg-info/top_level.txt +1 -0
- ai_token_tracker-0.1.0/tests/test_ingestion_client_integration.py +75 -0
- ai_token_tracker-0.1.0/tests/test_interception.py +170 -0
- ai_token_tracker-0.1.0/tests/test_interception_scope.py +21 -0
- ai_token_tracker-0.1.0/tests/test_options_and_mapping.py +43 -0
- ai_token_tracker-0.1.0/tests/test_sdk_client.py +77 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai-token-tracker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for capturing LLM traffic and ingesting envelopes into Ai Token Tracker
|
|
5
|
+
Author: Ai Token Tracker
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ai-token-tracker/ai-token-tracker
|
|
8
|
+
Project-URL: Repository, https://github.com/ai-token-tracker/ai-token-tracker
|
|
9
|
+
Project-URL: Documentation, https://github.com/ai-token-tracker/ai-token-tracker/tree/main/sdks/python
|
|
10
|
+
Project-URL: Issues, https://github.com/ai-token-tracker/ai-token-tracker/issues
|
|
11
|
+
Keywords: llm,observability,openai,anthropic,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Provides-Extra: requests
|
|
25
|
+
Requires-Dist: requests>=2.31; extra == "requests"
|
|
26
|
+
Provides-Extra: httpx
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == "httpx"
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: requests>=2.31; extra == "all"
|
|
30
|
+
Requires-Dist: httpx>=0.27; extra == "all"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
33
|
+
Requires-Dist: requests>=2.31; extra == "dev"
|
|
34
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
35
|
+
|
|
36
|
+
# Ai Token Tracker Python SDK
|
|
37
|
+
|
|
38
|
+
`ai-token-tracker` captures outbound LLM traffic and posts ingest envelopes to Ai Token Tracker.
|
|
39
|
+
|
|
40
|
+
Feature parity targets:
|
|
41
|
+
1. Automatic HTTP interception (`requests`, `httpx` sync/async)
|
|
42
|
+
2. SDK wrapper integration (`begin_llm_call` scope)
|
|
43
|
+
3. Diagnostics fallback (metadata-only safety net)
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Python 3.10+
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install ai-token-tracker
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Optional extras:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install ai-token-tracker[requests]
|
|
59
|
+
pip install ai-token-tracker[httpx]
|
|
60
|
+
pip install ai-token-tracker[all]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Defaults
|
|
64
|
+
|
|
65
|
+
- API base URL: `http://localhost:8082` (internal, hardcoded)
|
|
66
|
+
- `enable_auto_interception=True`
|
|
67
|
+
- `enable_diagnostics_fallback=True`
|
|
68
|
+
- Ingest auth header (internal): `X-API-Key`
|
|
69
|
+
- Ingest path (internal): `/ingest`
|
|
70
|
+
|
|
71
|
+
## Quick Start (Plain Script)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from ai_token_tracker import (
|
|
75
|
+
AiTokenTrackerIngestionClient,
|
|
76
|
+
AiTokenTrackerOptions,
|
|
77
|
+
AiTokenTrackerSdkClient,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
ingestion = AiTokenTrackerIngestionClient(
|
|
81
|
+
AiTokenTrackerOptions(auth_token="atk_your_key")
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
sdk = AiTokenTrackerSdkClient(ingestion)
|
|
85
|
+
|
|
86
|
+
scope = sdk.begin_llm_call(
|
|
87
|
+
provider_hint="openai",
|
|
88
|
+
method="POST",
|
|
89
|
+
url="https://api.openai.com/v1/responses",
|
|
90
|
+
request={"model": "gpt-4.1-mini", "input": "hello"},
|
|
91
|
+
custom_filters={"workflow": "quickstart"},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
provider_response = {"id": "resp_123", "output": [{"type": "output_text", "text": "hi"}]}
|
|
96
|
+
scope.complete(provider_response, status_code=200)
|
|
97
|
+
except Exception as ex:
|
|
98
|
+
scope.fail(ex, status_code=500)
|
|
99
|
+
raise
|
|
100
|
+
finally:
|
|
101
|
+
ingestion.close()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Automatic HTTP Interception
|
|
105
|
+
|
|
106
|
+
Interception is explicit install + option-gated:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import requests
|
|
110
|
+
from ai_token_tracker import (
|
|
111
|
+
AiTokenTrackerIngestionClient,
|
|
112
|
+
AiTokenTrackerOptions,
|
|
113
|
+
install_http_interception,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
client = AiTokenTrackerIngestionClient(
|
|
117
|
+
AiTokenTrackerOptions(
|
|
118
|
+
auth_token="atk_your_key",
|
|
119
|
+
enable_auto_interception=True,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
install_http_interception(client)
|
|
124
|
+
|
|
125
|
+
# Classified request is captured and ingested automatically.
|
|
126
|
+
requests.post(
|
|
127
|
+
"https://api.openai.com/v1/responses",
|
|
128
|
+
headers={"Authorization": "Bearer sk_test"},
|
|
129
|
+
json={"model": "gpt-4.1-mini", "input": "hello"},
|
|
130
|
+
timeout=10,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
client.close() # unpatches interceptors + shuts down background worker
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Scoped Custom Filters For Automatic Interception
|
|
137
|
+
|
|
138
|
+
Use interception scope to attach custom filters without wrapper integration:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import requests
|
|
142
|
+
from ai_token_tracker import (
|
|
143
|
+
AiTokenTrackerIngestionClient,
|
|
144
|
+
AiTokenTrackerInterceptionScope,
|
|
145
|
+
AiTokenTrackerOptions,
|
|
146
|
+
install_http_interception,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
client = AiTokenTrackerIngestionClient(
|
|
150
|
+
AiTokenTrackerOptions(auth_token="atk_your_key", enable_auto_interception=True)
|
|
151
|
+
)
|
|
152
|
+
install_http_interception(client)
|
|
153
|
+
|
|
154
|
+
scope = AiTokenTrackerInterceptionScope.create({"workflow": "social_post_generation"})
|
|
155
|
+
scope.add_custom_filters({"job_id": "job-42", "customer_id": "cust-19"})
|
|
156
|
+
|
|
157
|
+
scope.open()
|
|
158
|
+
try:
|
|
159
|
+
requests.post(
|
|
160
|
+
"https://api.openai.com/v1/responses",
|
|
161
|
+
headers={"Authorization": "Bearer sk_test"},
|
|
162
|
+
json={"model": "gpt-4.1-mini", "input": "hello"},
|
|
163
|
+
timeout=10,
|
|
164
|
+
)
|
|
165
|
+
finally:
|
|
166
|
+
scope.close()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Always close scope in `finally` to avoid leaking filters to later calls.
|
|
170
|
+
|
|
171
|
+
## SDK Wrapper Example
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, AiTokenTrackerSdkClient
|
|
175
|
+
|
|
176
|
+
ingestion = AiTokenTrackerIngestionClient(AiTokenTrackerOptions(auth_token="atk_your_key"))
|
|
177
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
178
|
+
|
|
179
|
+
scope = tracker.begin_llm_call(
|
|
180
|
+
provider_hint="anthropic",
|
|
181
|
+
method="POST",
|
|
182
|
+
url="https://api.anthropic.com/v1/messages",
|
|
183
|
+
request={"model": "claude-sonnet-4-6", "max_tokens": 256, "messages": [{"role": "user", "content": "hello"}]},
|
|
184
|
+
custom_filters={"tenant": "acme", "job": "summarization"},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
scope.complete({"id": "msg_123"}, status_code=200)
|
|
188
|
+
ingestion.close()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Diagnostics Fallback Setup
|
|
192
|
+
|
|
193
|
+
Diagnostics fallback is best-effort metadata capture for uncovered traffic.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, install_http_interception
|
|
197
|
+
|
|
198
|
+
client = AiTokenTrackerIngestionClient(
|
|
199
|
+
AiTokenTrackerOptions(
|
|
200
|
+
auth_token="atk_your_key",
|
|
201
|
+
enable_diagnostics_fallback=True,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
install_http_interception(client)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Fallback behavior:
|
|
208
|
+
- captures method, url, status, headers
|
|
209
|
+
- does not guarantee request/response bodies
|
|
210
|
+
- intended for visibility and coverage-gap detection, not full fidelity auditing
|
|
211
|
+
|
|
212
|
+
## FastAPI Example
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from contextlib import asynccontextmanager
|
|
216
|
+
from fastapi import FastAPI
|
|
217
|
+
from ai_token_tracker import (
|
|
218
|
+
AiTokenTrackerIngestionClient,
|
|
219
|
+
AiTokenTrackerOptions,
|
|
220
|
+
AiTokenTrackerSdkClient,
|
|
221
|
+
install_http_interception,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
ingestion = AiTokenTrackerIngestionClient(
|
|
225
|
+
AiTokenTrackerOptions(auth_token="atk_your_key", enable_auto_interception=True)
|
|
226
|
+
)
|
|
227
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
228
|
+
|
|
229
|
+
@asynccontextmanager
|
|
230
|
+
async def lifespan(app: FastAPI):
|
|
231
|
+
install_http_interception(ingestion)
|
|
232
|
+
yield
|
|
233
|
+
ingestion.close()
|
|
234
|
+
|
|
235
|
+
app = FastAPI(lifespan=lifespan)
|
|
236
|
+
|
|
237
|
+
@app.get("/health")
|
|
238
|
+
def health() -> dict[str, str]:
|
|
239
|
+
return {"status": "ok"}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Background Worker / Job Pipeline Example
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, AiTokenTrackerSdkClient
|
|
246
|
+
|
|
247
|
+
ingestion = AiTokenTrackerIngestionClient(AiTokenTrackerOptions(auth_token="atk_your_key"))
|
|
248
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
249
|
+
|
|
250
|
+
def run_job(job_id: str, prompt: str) -> None:
|
|
251
|
+
scope = tracker.begin_llm_call(
|
|
252
|
+
provider_hint="openai",
|
|
253
|
+
method="POST",
|
|
254
|
+
url="https://api.openai.com/v1/responses",
|
|
255
|
+
request={"model": "gpt-4.1-mini", "input": prompt},
|
|
256
|
+
custom_filters={"job_id": job_id, "pipeline": "nightly"},
|
|
257
|
+
)
|
|
258
|
+
try:
|
|
259
|
+
response = {"id": f"resp_{job_id}", "output": [{"type": "output_text", "text": "done"}]}
|
|
260
|
+
scope.complete(response)
|
|
261
|
+
except Exception as ex:
|
|
262
|
+
scope.fail(ex)
|
|
263
|
+
raise
|
|
264
|
+
|
|
265
|
+
run_job("42", "summarize daily usage")
|
|
266
|
+
ingestion.close()
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Custom Filters Guidance
|
|
270
|
+
|
|
271
|
+
`custom_filters` is key/value metadata attached to each envelope:
|
|
272
|
+
- keep keys stable (`tenant`, `workflow`, `job_id`, `environment`)
|
|
273
|
+
- keep values low-cardinality when possible
|
|
274
|
+
- avoid putting secrets or full prompt bodies in filters
|
|
275
|
+
|
|
276
|
+
## Verification Checklist
|
|
277
|
+
|
|
278
|
+
1. Use SDK with valid `auth_token` (ingest URL is internal and fixed by SDK).
|
|
279
|
+
2. Create client with valid `auth_token`.
|
|
280
|
+
3. For interception, set option flag and call `install_http_interception(client)`.
|
|
281
|
+
4. Send known LLM request (`/v1/responses` or `/chat/completions`).
|
|
282
|
+
5. Confirm ingest API receives `POST /ingest` with `X-API-Key` and expected envelope JSON.
|
|
283
|
+
6. Confirm app flow still works if ingest endpoint is unavailable (tracking paths are non-throwing).
|
|
284
|
+
|
|
285
|
+
## Error Handling + Logging
|
|
286
|
+
|
|
287
|
+
- `track(...)` and scoped `complete(...)` / `fail(...)` are non-throwing for ingest failures.
|
|
288
|
+
- Warnings logged for ingestion failures and non-2xx ingest status.
|
|
289
|
+
- Debug logs used for interception/fallback internal capture issues.
|
|
290
|
+
|
|
291
|
+
## Public API Summary
|
|
292
|
+
|
|
293
|
+
- `AiTokenTrackerOptions(auth_token, enable_auto_interception=True, enable_diagnostics_fallback=True)`
|
|
294
|
+
- `AiTokenTrackerIngestionClient.track(...) -> TrackResult`
|
|
295
|
+
- `AiTokenTrackerIngestionClient.track_async(...) -> TrackResult`
|
|
296
|
+
- `AiTokenTrackerIngestionClient.close()`
|
|
297
|
+
- `AiTokenTrackerSdkClient.begin_llm_call(...) -> CallScope`
|
|
298
|
+
- `CallScope.complete(...)`, `CallScope.fail(...)`
|
|
299
|
+
- `CallScope.complete_async(...)`, `CallScope.fail_async(...)`
|
|
300
|
+
- `install_http_interception(client)`, `uninstall_http_interception(client)`
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Ai Token Tracker Python SDK
|
|
2
|
+
|
|
3
|
+
`ai-token-tracker` captures outbound LLM traffic and posts ingest envelopes to Ai Token Tracker.
|
|
4
|
+
|
|
5
|
+
Feature parity targets:
|
|
6
|
+
1. Automatic HTTP interception (`requests`, `httpx` sync/async)
|
|
7
|
+
2. SDK wrapper integration (`begin_llm_call` scope)
|
|
8
|
+
3. Diagnostics fallback (metadata-only safety net)
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Python 3.10+
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install ai-token-tracker
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Optional extras:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install ai-token-tracker[requests]
|
|
24
|
+
pip install ai-token-tracker[httpx]
|
|
25
|
+
pip install ai-token-tracker[all]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Defaults
|
|
29
|
+
|
|
30
|
+
- API base URL: `http://localhost:8082` (internal, hardcoded)
|
|
31
|
+
- `enable_auto_interception=True`
|
|
32
|
+
- `enable_diagnostics_fallback=True`
|
|
33
|
+
- Ingest auth header (internal): `X-API-Key`
|
|
34
|
+
- Ingest path (internal): `/ingest`
|
|
35
|
+
|
|
36
|
+
## Quick Start (Plain Script)
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from ai_token_tracker import (
|
|
40
|
+
AiTokenTrackerIngestionClient,
|
|
41
|
+
AiTokenTrackerOptions,
|
|
42
|
+
AiTokenTrackerSdkClient,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ingestion = AiTokenTrackerIngestionClient(
|
|
46
|
+
AiTokenTrackerOptions(auth_token="atk_your_key")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
sdk = AiTokenTrackerSdkClient(ingestion)
|
|
50
|
+
|
|
51
|
+
scope = sdk.begin_llm_call(
|
|
52
|
+
provider_hint="openai",
|
|
53
|
+
method="POST",
|
|
54
|
+
url="https://api.openai.com/v1/responses",
|
|
55
|
+
request={"model": "gpt-4.1-mini", "input": "hello"},
|
|
56
|
+
custom_filters={"workflow": "quickstart"},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
provider_response = {"id": "resp_123", "output": [{"type": "output_text", "text": "hi"}]}
|
|
61
|
+
scope.complete(provider_response, status_code=200)
|
|
62
|
+
except Exception as ex:
|
|
63
|
+
scope.fail(ex, status_code=500)
|
|
64
|
+
raise
|
|
65
|
+
finally:
|
|
66
|
+
ingestion.close()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Automatic HTTP Interception
|
|
70
|
+
|
|
71
|
+
Interception is explicit install + option-gated:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import requests
|
|
75
|
+
from ai_token_tracker import (
|
|
76
|
+
AiTokenTrackerIngestionClient,
|
|
77
|
+
AiTokenTrackerOptions,
|
|
78
|
+
install_http_interception,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
client = AiTokenTrackerIngestionClient(
|
|
82
|
+
AiTokenTrackerOptions(
|
|
83
|
+
auth_token="atk_your_key",
|
|
84
|
+
enable_auto_interception=True,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
install_http_interception(client)
|
|
89
|
+
|
|
90
|
+
# Classified request is captured and ingested automatically.
|
|
91
|
+
requests.post(
|
|
92
|
+
"https://api.openai.com/v1/responses",
|
|
93
|
+
headers={"Authorization": "Bearer sk_test"},
|
|
94
|
+
json={"model": "gpt-4.1-mini", "input": "hello"},
|
|
95
|
+
timeout=10,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
client.close() # unpatches interceptors + shuts down background worker
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Scoped Custom Filters For Automatic Interception
|
|
102
|
+
|
|
103
|
+
Use interception scope to attach custom filters without wrapper integration:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import requests
|
|
107
|
+
from ai_token_tracker import (
|
|
108
|
+
AiTokenTrackerIngestionClient,
|
|
109
|
+
AiTokenTrackerInterceptionScope,
|
|
110
|
+
AiTokenTrackerOptions,
|
|
111
|
+
install_http_interception,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
client = AiTokenTrackerIngestionClient(
|
|
115
|
+
AiTokenTrackerOptions(auth_token="atk_your_key", enable_auto_interception=True)
|
|
116
|
+
)
|
|
117
|
+
install_http_interception(client)
|
|
118
|
+
|
|
119
|
+
scope = AiTokenTrackerInterceptionScope.create({"workflow": "social_post_generation"})
|
|
120
|
+
scope.add_custom_filters({"job_id": "job-42", "customer_id": "cust-19"})
|
|
121
|
+
|
|
122
|
+
scope.open()
|
|
123
|
+
try:
|
|
124
|
+
requests.post(
|
|
125
|
+
"https://api.openai.com/v1/responses",
|
|
126
|
+
headers={"Authorization": "Bearer sk_test"},
|
|
127
|
+
json={"model": "gpt-4.1-mini", "input": "hello"},
|
|
128
|
+
timeout=10,
|
|
129
|
+
)
|
|
130
|
+
finally:
|
|
131
|
+
scope.close()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Always close scope in `finally` to avoid leaking filters to later calls.
|
|
135
|
+
|
|
136
|
+
## SDK Wrapper Example
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, AiTokenTrackerSdkClient
|
|
140
|
+
|
|
141
|
+
ingestion = AiTokenTrackerIngestionClient(AiTokenTrackerOptions(auth_token="atk_your_key"))
|
|
142
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
143
|
+
|
|
144
|
+
scope = tracker.begin_llm_call(
|
|
145
|
+
provider_hint="anthropic",
|
|
146
|
+
method="POST",
|
|
147
|
+
url="https://api.anthropic.com/v1/messages",
|
|
148
|
+
request={"model": "claude-sonnet-4-6", "max_tokens": 256, "messages": [{"role": "user", "content": "hello"}]},
|
|
149
|
+
custom_filters={"tenant": "acme", "job": "summarization"},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
scope.complete({"id": "msg_123"}, status_code=200)
|
|
153
|
+
ingestion.close()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Diagnostics Fallback Setup
|
|
157
|
+
|
|
158
|
+
Diagnostics fallback is best-effort metadata capture for uncovered traffic.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, install_http_interception
|
|
162
|
+
|
|
163
|
+
client = AiTokenTrackerIngestionClient(
|
|
164
|
+
AiTokenTrackerOptions(
|
|
165
|
+
auth_token="atk_your_key",
|
|
166
|
+
enable_diagnostics_fallback=True,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
install_http_interception(client)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Fallback behavior:
|
|
173
|
+
- captures method, url, status, headers
|
|
174
|
+
- does not guarantee request/response bodies
|
|
175
|
+
- intended for visibility and coverage-gap detection, not full fidelity auditing
|
|
176
|
+
|
|
177
|
+
## FastAPI Example
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from contextlib import asynccontextmanager
|
|
181
|
+
from fastapi import FastAPI
|
|
182
|
+
from ai_token_tracker import (
|
|
183
|
+
AiTokenTrackerIngestionClient,
|
|
184
|
+
AiTokenTrackerOptions,
|
|
185
|
+
AiTokenTrackerSdkClient,
|
|
186
|
+
install_http_interception,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
ingestion = AiTokenTrackerIngestionClient(
|
|
190
|
+
AiTokenTrackerOptions(auth_token="atk_your_key", enable_auto_interception=True)
|
|
191
|
+
)
|
|
192
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
193
|
+
|
|
194
|
+
@asynccontextmanager
|
|
195
|
+
async def lifespan(app: FastAPI):
|
|
196
|
+
install_http_interception(ingestion)
|
|
197
|
+
yield
|
|
198
|
+
ingestion.close()
|
|
199
|
+
|
|
200
|
+
app = FastAPI(lifespan=lifespan)
|
|
201
|
+
|
|
202
|
+
@app.get("/health")
|
|
203
|
+
def health() -> dict[str, str]:
|
|
204
|
+
return {"status": "ok"}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Background Worker / Job Pipeline Example
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from ai_token_tracker import AiTokenTrackerIngestionClient, AiTokenTrackerOptions, AiTokenTrackerSdkClient
|
|
211
|
+
|
|
212
|
+
ingestion = AiTokenTrackerIngestionClient(AiTokenTrackerOptions(auth_token="atk_your_key"))
|
|
213
|
+
tracker = AiTokenTrackerSdkClient(ingestion)
|
|
214
|
+
|
|
215
|
+
def run_job(job_id: str, prompt: str) -> None:
|
|
216
|
+
scope = tracker.begin_llm_call(
|
|
217
|
+
provider_hint="openai",
|
|
218
|
+
method="POST",
|
|
219
|
+
url="https://api.openai.com/v1/responses",
|
|
220
|
+
request={"model": "gpt-4.1-mini", "input": prompt},
|
|
221
|
+
custom_filters={"job_id": job_id, "pipeline": "nightly"},
|
|
222
|
+
)
|
|
223
|
+
try:
|
|
224
|
+
response = {"id": f"resp_{job_id}", "output": [{"type": "output_text", "text": "done"}]}
|
|
225
|
+
scope.complete(response)
|
|
226
|
+
except Exception as ex:
|
|
227
|
+
scope.fail(ex)
|
|
228
|
+
raise
|
|
229
|
+
|
|
230
|
+
run_job("42", "summarize daily usage")
|
|
231
|
+
ingestion.close()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Custom Filters Guidance
|
|
235
|
+
|
|
236
|
+
`custom_filters` is key/value metadata attached to each envelope:
|
|
237
|
+
- keep keys stable (`tenant`, `workflow`, `job_id`, `environment`)
|
|
238
|
+
- keep values low-cardinality when possible
|
|
239
|
+
- avoid putting secrets or full prompt bodies in filters
|
|
240
|
+
|
|
241
|
+
## Verification Checklist
|
|
242
|
+
|
|
243
|
+
1. Use SDK with valid `auth_token` (ingest URL is internal and fixed by SDK).
|
|
244
|
+
2. Create client with valid `auth_token`.
|
|
245
|
+
3. For interception, set option flag and call `install_http_interception(client)`.
|
|
246
|
+
4. Send known LLM request (`/v1/responses` or `/chat/completions`).
|
|
247
|
+
5. Confirm ingest API receives `POST /ingest` with `X-API-Key` and expected envelope JSON.
|
|
248
|
+
6. Confirm app flow still works if ingest endpoint is unavailable (tracking paths are non-throwing).
|
|
249
|
+
|
|
250
|
+
## Error Handling + Logging
|
|
251
|
+
|
|
252
|
+
- `track(...)` and scoped `complete(...)` / `fail(...)` are non-throwing for ingest failures.
|
|
253
|
+
- Warnings logged for ingestion failures and non-2xx ingest status.
|
|
254
|
+
- Debug logs used for interception/fallback internal capture issues.
|
|
255
|
+
|
|
256
|
+
## Public API Summary
|
|
257
|
+
|
|
258
|
+
- `AiTokenTrackerOptions(auth_token, enable_auto_interception=True, enable_diagnostics_fallback=True)`
|
|
259
|
+
- `AiTokenTrackerIngestionClient.track(...) -> TrackResult`
|
|
260
|
+
- `AiTokenTrackerIngestionClient.track_async(...) -> TrackResult`
|
|
261
|
+
- `AiTokenTrackerIngestionClient.close()`
|
|
262
|
+
- `AiTokenTrackerSdkClient.begin_llm_call(...) -> CallScope`
|
|
263
|
+
- `CallScope.complete(...)`, `CallScope.fail(...)`
|
|
264
|
+
- `CallScope.complete_async(...)`, `CallScope.fail_async(...)`
|
|
265
|
+
- `install_http_interception(client)`, `uninstall_http_interception(client)`
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai-token-tracker"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for capturing LLM traffic and ingesting envelopes into Ai Token Tracker"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ai Token Tracker" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["llm", "observability", "openai", "anthropic", "sdk"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Typing :: Typed"
|
|
27
|
+
]
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
requests = ["requests>=2.31"]
|
|
32
|
+
httpx = ["httpx>=0.27"]
|
|
33
|
+
all = [
|
|
34
|
+
"requests>=2.31",
|
|
35
|
+
"httpx>=0.27"
|
|
36
|
+
]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8.2",
|
|
39
|
+
"requests>=2.31",
|
|
40
|
+
"httpx>=0.27"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/ai-token-tracker/ai-token-tracker"
|
|
45
|
+
Repository = "https://github.com/ai-token-tracker/ai-token-tracker"
|
|
46
|
+
Documentation = "https://github.com/ai-token-tracker/ai-token-tracker/tree/main/sdks/python"
|
|
47
|
+
Issues = "https://github.com/ai-token-tracker/ai-token-tracker/issues"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
package-dir = { "" = "src" }
|
|
51
|
+
include-package-data = true
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["src"]
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.package-data]
|
|
57
|
+
ai_token_tracker = ["py.typed"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
61
|
+
addopts = "-ra"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Ai Token Tracker Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .classifier import DefaultLlmRequestClassifier
|
|
4
|
+
from .envelope import map_tracked_call_to_envelope
|
|
5
|
+
from .ingestion_client import AiTokenTrackerIngestionClient
|
|
6
|
+
from .interception_scope import AiTokenTrackerInterceptionScope
|
|
7
|
+
from .runtime import install_http_interception, uninstall_http_interception
|
|
8
|
+
from .sdk_client import AiTokenTrackerSdkClient, CallScope
|
|
9
|
+
from .types import (
|
|
10
|
+
AiTokenTrackerOptions,
|
|
11
|
+
IngestionEnvelope,
|
|
12
|
+
LlmRequestClassificationInput,
|
|
13
|
+
LlmRequestClassifier,
|
|
14
|
+
TrackResult,
|
|
15
|
+
TrackedLlmCall,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AiTokenTrackerIngestionClient",
|
|
20
|
+
"AiTokenTrackerOptions",
|
|
21
|
+
"AiTokenTrackerSdkClient",
|
|
22
|
+
"AiTokenTrackerInterceptionScope",
|
|
23
|
+
"CallScope",
|
|
24
|
+
"DefaultLlmRequestClassifier",
|
|
25
|
+
"IngestionEnvelope",
|
|
26
|
+
"LlmRequestClassificationInput",
|
|
27
|
+
"LlmRequestClassifier",
|
|
28
|
+
"TrackResult",
|
|
29
|
+
"TrackedLlmCall",
|
|
30
|
+
"install_http_interception",
|
|
31
|
+
"map_tracked_call_to_envelope",
|
|
32
|
+
"uninstall_http_interception",
|
|
33
|
+
]
|