llmapi-tracker 1.0.6__tar.gz → 1.0.8__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.
- llmapi_tracker-1.0.8/PKG-INFO +294 -0
- llmapi_tracker-1.0.8/README.md +262 -0
- llmapi_tracker-1.0.8/backend/app/launcher.py +44 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/schemas.py +11 -2
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/usage_service.py +6 -1
- llmapi_tracker-1.0.6/backend/app/static/assets/index-jlcpybVb.js → llmapi_tracker-1.0.8/backend/app/static/assets/index-M74b_d8l.js +1 -1
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/static/index.html +1 -1
- llmapi_tracker-1.0.8/llmapi_tracker.egg-info/PKG-INFO +294 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/SOURCES.txt +2 -1
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/pyproject.toml +8 -6
- llmapi_tracker-1.0.8/tests/test_launcher.py +13 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_routes.py +18 -0
- llmapi_tracker-1.0.6/PKG-INFO +0 -234
- llmapi_tracker-1.0.6/README.md +0 -202
- llmapi_tracker-1.0.6/backend/app/launcher.py +0 -21
- llmapi_tracker-1.0.6/llmapi_tracker.egg-info/PKG-INFO +0 -234
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/LICENSE +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/database.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/frontend.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/main.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/models.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/paths.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/analytics.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/models.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/pricing.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/projects.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/providers.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/usage.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/cost_service.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/static/assets/index-3YVy8dav.css +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/dependency_links.txt +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/entry_points.txt +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/requires.txt +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/top_level.txt +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/client.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/models.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/pricing.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/__init__.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/gemini.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/openai.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/py.typed +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/tracker.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/transport.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/setup.cfg +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_analytics_routes.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_cost_service.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_paths.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_provider_model_routes.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_tracker.py +0 -0
- {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_usage_service.py +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llmapi-tracker
|
|
3
|
+
Version: 1.0.8
|
|
4
|
+
Summary: Local-first API usage and cost tracking platform
|
|
5
|
+
Author: API Tracker
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Lns9777/API-Usage-Tracker
|
|
8
|
+
Project-URL: Repository, https://github.com/Lns9777/API-Usage-Tracker
|
|
9
|
+
Project-URL: Issues, https://github.com/Lns9777/API-Usage-Tracker/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: requests>=2.32
|
|
14
|
+
Requires-Dist: fastapi>=0.115
|
|
15
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
16
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
17
|
+
Requires-Dist: pydantic>=2.8
|
|
18
|
+
Provides-Extra: openai
|
|
19
|
+
Requires-Dist: openai>=1.40; extra == "openai"
|
|
20
|
+
Provides-Extra: gemini
|
|
21
|
+
Requires-Dist: google-genai>=0.7; extra == "gemini"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: openai>=1.40; extra == "all"
|
|
24
|
+
Requires-Dist: google-genai>=0.7; extra == "all"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
29
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
30
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
# 📊 llmapi-tracker
|
|
36
|
+
|
|
37
|
+
**A local-first API usage and cost observability platform for LLM applications**
|
|
38
|
+
|
|
39
|
+
[](#license)
|
|
40
|
+
[](#prerequisites)
|
|
41
|
+
[](#prerequisites)
|
|
42
|
+
[](#what-api-tracker-is)
|
|
43
|
+
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Table of Contents
|
|
49
|
+
|
|
50
|
+
- [What API Tracker Is](#what-api-tracker-is)
|
|
51
|
+
- [Features](#features)
|
|
52
|
+
- [Installation](#installation)
|
|
53
|
+
- [Prerequisites](#prerequisites)
|
|
54
|
+
- [Local Development Install](#local-development-install)
|
|
55
|
+
- [Package Install](#package-install)
|
|
56
|
+
- [Starting the Application](#starting-the-application)
|
|
57
|
+
- [SDK Usage](#sdk-usage)
|
|
58
|
+
- [Database Location](#database-location)
|
|
59
|
+
- [Configuration](#configuration)
|
|
60
|
+
- [Development Setup](#development-setup)
|
|
61
|
+
- [Contributing](#contributing)
|
|
62
|
+
- [License](#license)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## What API Tracker Is
|
|
67
|
+
|
|
68
|
+
`llmapi-tracker` is a **local-first API usage and cost observability platform** for LLM applications. It records request counts, token usage, latency, errors, and versioned pricing for provider models such as OpenAI and Gemini.
|
|
69
|
+
|
|
70
|
+
The project is designed to keep data local by default. It stores usage and pricing in a local SQLite database and **does not persist prompts, responses, or raw API keys** in the application database.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
| Category | Capability |
|
|
77
|
+
|---|---|
|
|
78
|
+
| 🖥️ Backend | Local FastAPI backend with SQLite storage |
|
|
79
|
+
| 💰 Pricing | Versioned pricing records for historical cost tracking |
|
|
80
|
+
| 📈 Usage | Capture for input, output, thinking, cached, and total tokens |
|
|
81
|
+
| 🧮 Cost | Request-level and analytics-level cost calculation |
|
|
82
|
+
| 📊 Dashboard | React + Vite dashboard for usage, analytics, providers, models, and pricing |
|
|
83
|
+
| 🔌 SDKs | Wrappers for OpenAI and Gemini |
|
|
84
|
+
| 🚀 Launcher | Packaged launcher command that starts the local app and opens the browser |
|
|
85
|
+
| 🧪 Quality | Open-source repository with tests, CI, and release automation |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
|
|
91
|
+
### Prerequisites
|
|
92
|
+
|
|
93
|
+
- Python **3.10** or newer
|
|
94
|
+
- Node.js **20** or newer for frontend development
|
|
95
|
+
|
|
96
|
+
### Local Development Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python -m venv .venv
|
|
100
|
+
.venv\Scripts\activate
|
|
101
|
+
pip install -r requirements.txt
|
|
102
|
+
cd frontend
|
|
103
|
+
npm install
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Package Install
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install llmapi-tracker
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Optional provider SDK support:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install "llmapi-tracker[openai]"
|
|
116
|
+
pip install "llmapi-tracker[gemini]"
|
|
117
|
+
pip install "llmapi-tracker[all]"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Starting the Application
|
|
123
|
+
|
|
124
|
+
### Backend Only
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uvicorn backend.app.main:app --reload
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Frontend Only
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd frontend
|
|
134
|
+
npm run dev
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Packaged App
|
|
138
|
+
|
|
139
|
+
After the frontend assets are built and packaged into the backend static directory:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
api-tracker
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> The launcher starts the backend server and opens the browser to the local dashboard.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## SDK Usage
|
|
150
|
+
|
|
151
|
+
### Initialize the SDK
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
import os
|
|
155
|
+
from api_tracker import APITracker
|
|
156
|
+
|
|
157
|
+
tracker = APITracker(
|
|
158
|
+
project="Pharmacy-AI",
|
|
159
|
+
backend_url=os.getenv("TRACKER_BACKEND_URL", "http://localhost:8000"),
|
|
160
|
+
openai_api_key=os.getenv("OPENAI_API_KEY"),
|
|
161
|
+
gemini_api_key=os.getenv("GEMINI_API_KEY"),
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### OpenAI
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
response = tracker.openai.chat(
|
|
169
|
+
model="gpt-5",
|
|
170
|
+
messages=[{"role": "user", "content": "Analyze this prescription"}],
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Gemini
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
response = tracker.gemini.generate(
|
|
178
|
+
model="gemini-2.5-flash",
|
|
179
|
+
contents="Analyze this prescription",
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
> The SDK reads usage metadata from the provider response and sends it to the local backend automatically.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Database Location
|
|
188
|
+
|
|
189
|
+
By default, the SQLite database is stored in a stable user data directory so it survives uninstall and reinstall.
|
|
190
|
+
|
|
191
|
+
The path is resolved in this order:
|
|
192
|
+
|
|
193
|
+
1. `API_TRACKER_DB_PATH` if set
|
|
194
|
+
2. `DATABASE_URL` if set
|
|
195
|
+
3. A user-data location based on the operating system
|
|
196
|
+
|
|
197
|
+
**Typical defaults**
|
|
198
|
+
|
|
199
|
+
| OS | Path |
|
|
200
|
+
|---|---|
|
|
201
|
+
| Windows | `%LOCALAPPDATA%\APITracker\api_tracker.db` |
|
|
202
|
+
| macOS / Linux | `~/.local/share/APITracker/api_tracker.db` |
|
|
203
|
+
|
|
204
|
+
> If the preferred user-data directory cannot be created, the app falls back to a writable local `.api-tracker-data/` directory in the current workspace.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
Environment variables:
|
|
211
|
+
|
|
212
|
+
| Variable | Description |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `DATABASE_URL` | Overrides the database connection string |
|
|
215
|
+
| `API_TRACKER_DATA_DIR` | Sets the user-data root used for SQLite storage |
|
|
216
|
+
| `API_TRACKER_DB_PATH` | Points directly to a specific SQLite file |
|
|
217
|
+
| `API_TRACKER_HOST` | Host the backend binds to (default `127.0.0.1`) |
|
|
218
|
+
| `API_TRACKER_PORT` | Port the backend binds to (default `8000`) |
|
|
219
|
+
| `API_TRACKER_OPEN_BROWSER` | Set to `false` to disable browser auto-open for the launcher |
|
|
220
|
+
| `OPENAI_API_KEY` | API key used by the OpenAI SDK wrapper |
|
|
221
|
+
| `GEMINI_API_KEY` | API key used by the Gemini SDK wrapper |
|
|
222
|
+
| `TRACKER_BACKEND_URL` | Backend URL used by the SDK client |
|
|
223
|
+
| `VITE_API_BASE_URL` | Backend URL used by the frontend dashboard |
|
|
224
|
+
|
|
225
|
+
Example `.env`:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
DATABASE_URL=sqlite:///./api_tracker.db
|
|
229
|
+
API_TRACKER_DATA_DIR=
|
|
230
|
+
API_TRACKER_DB_PATH=
|
|
231
|
+
API_TRACKER_HOST=127.0.0.1
|
|
232
|
+
API_TRACKER_PORT=8000
|
|
233
|
+
API_TRACKER_OPEN_BROWSER=true
|
|
234
|
+
OPENAI_API_KEY=
|
|
235
|
+
GEMINI_API_KEY=
|
|
236
|
+
TRACKER_BACKEND_URL=http://localhost:8000
|
|
237
|
+
VITE_API_BASE_URL=http://localhost:8000
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Development Setup
|
|
243
|
+
|
|
244
|
+
### Backend
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python -m compileall backend sdk tests
|
|
248
|
+
pytest -q
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Frontend
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
cd frontend
|
|
255
|
+
npm run build
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Packaging Assets
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
cd frontend
|
|
262
|
+
npm run build
|
|
263
|
+
cd ..
|
|
264
|
+
python scripts/package_frontend_assets.py
|
|
265
|
+
python scripts/verify_frontend_assets.py
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Contributing
|
|
271
|
+
|
|
272
|
+
`llmapi-tracker` is intended to be open source and contributor friendly.
|
|
273
|
+
|
|
274
|
+
**Contribution files**
|
|
275
|
+
|
|
276
|
+
- [`CONTRIBUTING.md`](L:/Personal/APITracker/CONTRIBUTING.md)
|
|
277
|
+
- [`CODE_OF_CONDUCT.md`](L:/Personal/APITracker/CODE_OF_CONDUCT.md)
|
|
278
|
+
- [`SECURITY.md`](L:/Personal/APITracker/SECURITY.md)
|
|
279
|
+
- [`CHANGELOG.md`](L:/Personal/APITracker/CHANGELOG.md)
|
|
280
|
+
- [`LICENSE`](L:/Personal/APITracker/LICENSE)
|
|
281
|
+
|
|
282
|
+
**Contribution expectations**
|
|
283
|
+
|
|
284
|
+
- ✅ Open changes through pull requests
|
|
285
|
+
- ✅ Require review before merge
|
|
286
|
+
- ✅ Keep the app local-first
|
|
287
|
+
- 🚫 Avoid storing prompts, responses, or raw API keys
|
|
288
|
+
- ✅ Add tests for behavior changes
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
`llmapi-tracker` is released under the **MIT License**. See [`LICENSE`](L:/Personal/APITracker/LICENSE).
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 📊 llmapi-tracker
|
|
4
|
+
|
|
5
|
+
**A local-first API usage and cost observability platform for LLM applications**
|
|
6
|
+
|
|
7
|
+
[](#license)
|
|
8
|
+
[](#prerequisites)
|
|
9
|
+
[](#prerequisites)
|
|
10
|
+
[](#what-api-tracker-is)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [What API Tracker Is](#what-api-tracker-is)
|
|
19
|
+
- [Features](#features)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Prerequisites](#prerequisites)
|
|
22
|
+
- [Local Development Install](#local-development-install)
|
|
23
|
+
- [Package Install](#package-install)
|
|
24
|
+
- [Starting the Application](#starting-the-application)
|
|
25
|
+
- [SDK Usage](#sdk-usage)
|
|
26
|
+
- [Database Location](#database-location)
|
|
27
|
+
- [Configuration](#configuration)
|
|
28
|
+
- [Development Setup](#development-setup)
|
|
29
|
+
- [Contributing](#contributing)
|
|
30
|
+
- [License](#license)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What API Tracker Is
|
|
35
|
+
|
|
36
|
+
`llmapi-tracker` is a **local-first API usage and cost observability platform** for LLM applications. It records request counts, token usage, latency, errors, and versioned pricing for provider models such as OpenAI and Gemini.
|
|
37
|
+
|
|
38
|
+
The project is designed to keep data local by default. It stores usage and pricing in a local SQLite database and **does not persist prompts, responses, or raw API keys** in the application database.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
| Category | Capability |
|
|
45
|
+
|---|---|
|
|
46
|
+
| 🖥️ Backend | Local FastAPI backend with SQLite storage |
|
|
47
|
+
| 💰 Pricing | Versioned pricing records for historical cost tracking |
|
|
48
|
+
| 📈 Usage | Capture for input, output, thinking, cached, and total tokens |
|
|
49
|
+
| 🧮 Cost | Request-level and analytics-level cost calculation |
|
|
50
|
+
| 📊 Dashboard | React + Vite dashboard for usage, analytics, providers, models, and pricing |
|
|
51
|
+
| 🔌 SDKs | Wrappers for OpenAI and Gemini |
|
|
52
|
+
| 🚀 Launcher | Packaged launcher command that starts the local app and opens the browser |
|
|
53
|
+
| 🧪 Quality | Open-source repository with tests, CI, and release automation |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
### Prerequisites
|
|
60
|
+
|
|
61
|
+
- Python **3.10** or newer
|
|
62
|
+
- Node.js **20** or newer for frontend development
|
|
63
|
+
|
|
64
|
+
### Local Development Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m venv .venv
|
|
68
|
+
.venv\Scripts\activate
|
|
69
|
+
pip install -r requirements.txt
|
|
70
|
+
cd frontend
|
|
71
|
+
npm install
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Package Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install llmapi-tracker
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Optional provider SDK support:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install "llmapi-tracker[openai]"
|
|
84
|
+
pip install "llmapi-tracker[gemini]"
|
|
85
|
+
pip install "llmapi-tracker[all]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Starting the Application
|
|
91
|
+
|
|
92
|
+
### Backend Only
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uvicorn backend.app.main:app --reload
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Frontend Only
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cd frontend
|
|
102
|
+
npm run dev
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Packaged App
|
|
106
|
+
|
|
107
|
+
After the frontend assets are built and packaged into the backend static directory:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
api-tracker
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> The launcher starts the backend server and opens the browser to the local dashboard.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## SDK Usage
|
|
118
|
+
|
|
119
|
+
### Initialize the SDK
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import os
|
|
123
|
+
from api_tracker import APITracker
|
|
124
|
+
|
|
125
|
+
tracker = APITracker(
|
|
126
|
+
project="Pharmacy-AI",
|
|
127
|
+
backend_url=os.getenv("TRACKER_BACKEND_URL", "http://localhost:8000"),
|
|
128
|
+
openai_api_key=os.getenv("OPENAI_API_KEY"),
|
|
129
|
+
gemini_api_key=os.getenv("GEMINI_API_KEY"),
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### OpenAI
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
response = tracker.openai.chat(
|
|
137
|
+
model="gpt-5",
|
|
138
|
+
messages=[{"role": "user", "content": "Analyze this prescription"}],
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Gemini
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
response = tracker.gemini.generate(
|
|
146
|
+
model="gemini-2.5-flash",
|
|
147
|
+
contents="Analyze this prescription",
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> The SDK reads usage metadata from the provider response and sends it to the local backend automatically.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Database Location
|
|
156
|
+
|
|
157
|
+
By default, the SQLite database is stored in a stable user data directory so it survives uninstall and reinstall.
|
|
158
|
+
|
|
159
|
+
The path is resolved in this order:
|
|
160
|
+
|
|
161
|
+
1. `API_TRACKER_DB_PATH` if set
|
|
162
|
+
2. `DATABASE_URL` if set
|
|
163
|
+
3. A user-data location based on the operating system
|
|
164
|
+
|
|
165
|
+
**Typical defaults**
|
|
166
|
+
|
|
167
|
+
| OS | Path |
|
|
168
|
+
|---|---|
|
|
169
|
+
| Windows | `%LOCALAPPDATA%\APITracker\api_tracker.db` |
|
|
170
|
+
| macOS / Linux | `~/.local/share/APITracker/api_tracker.db` |
|
|
171
|
+
|
|
172
|
+
> If the preferred user-data directory cannot be created, the app falls back to a writable local `.api-tracker-data/` directory in the current workspace.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
Environment variables:
|
|
179
|
+
|
|
180
|
+
| Variable | Description |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `DATABASE_URL` | Overrides the database connection string |
|
|
183
|
+
| `API_TRACKER_DATA_DIR` | Sets the user-data root used for SQLite storage |
|
|
184
|
+
| `API_TRACKER_DB_PATH` | Points directly to a specific SQLite file |
|
|
185
|
+
| `API_TRACKER_HOST` | Host the backend binds to (default `127.0.0.1`) |
|
|
186
|
+
| `API_TRACKER_PORT` | Port the backend binds to (default `8000`) |
|
|
187
|
+
| `API_TRACKER_OPEN_BROWSER` | Set to `false` to disable browser auto-open for the launcher |
|
|
188
|
+
| `OPENAI_API_KEY` | API key used by the OpenAI SDK wrapper |
|
|
189
|
+
| `GEMINI_API_KEY` | API key used by the Gemini SDK wrapper |
|
|
190
|
+
| `TRACKER_BACKEND_URL` | Backend URL used by the SDK client |
|
|
191
|
+
| `VITE_API_BASE_URL` | Backend URL used by the frontend dashboard |
|
|
192
|
+
|
|
193
|
+
Example `.env`:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
DATABASE_URL=sqlite:///./api_tracker.db
|
|
197
|
+
API_TRACKER_DATA_DIR=
|
|
198
|
+
API_TRACKER_DB_PATH=
|
|
199
|
+
API_TRACKER_HOST=127.0.0.1
|
|
200
|
+
API_TRACKER_PORT=8000
|
|
201
|
+
API_TRACKER_OPEN_BROWSER=true
|
|
202
|
+
OPENAI_API_KEY=
|
|
203
|
+
GEMINI_API_KEY=
|
|
204
|
+
TRACKER_BACKEND_URL=http://localhost:8000
|
|
205
|
+
VITE_API_BASE_URL=http://localhost:8000
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Development Setup
|
|
211
|
+
|
|
212
|
+
### Backend
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
python -m compileall backend sdk tests
|
|
216
|
+
pytest -q
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Frontend
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cd frontend
|
|
223
|
+
npm run build
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Packaging Assets
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
cd frontend
|
|
230
|
+
npm run build
|
|
231
|
+
cd ..
|
|
232
|
+
python scripts/package_frontend_assets.py
|
|
233
|
+
python scripts/verify_frontend_assets.py
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Contributing
|
|
239
|
+
|
|
240
|
+
`llmapi-tracker` is intended to be open source and contributor friendly.
|
|
241
|
+
|
|
242
|
+
**Contribution files**
|
|
243
|
+
|
|
244
|
+
- [`CONTRIBUTING.md`](L:/Personal/APITracker/CONTRIBUTING.md)
|
|
245
|
+
- [`CODE_OF_CONDUCT.md`](L:/Personal/APITracker/CODE_OF_CONDUCT.md)
|
|
246
|
+
- [`SECURITY.md`](L:/Personal/APITracker/SECURITY.md)
|
|
247
|
+
- [`CHANGELOG.md`](L:/Personal/APITracker/CHANGELOG.md)
|
|
248
|
+
- [`LICENSE`](L:/Personal/APITracker/LICENSE)
|
|
249
|
+
|
|
250
|
+
**Contribution expectations**
|
|
251
|
+
|
|
252
|
+
- ✅ Open changes through pull requests
|
|
253
|
+
- ✅ Require review before merge
|
|
254
|
+
- ✅ Keep the app local-first
|
|
255
|
+
- 🚫 Avoid storing prompts, responses, or raw API keys
|
|
256
|
+
- ✅ Add tests for behavior changes
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
`llmapi-tracker` is released under the **MIT License**. See [`LICENSE`](L:/Personal/APITracker/LICENSE).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import threading
|
|
6
|
+
import webbrowser
|
|
7
|
+
|
|
8
|
+
import uvicorn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def find_available_port(host: str, preferred_port: int = 8000) -> int:
|
|
12
|
+
"""Prefer the standard port, then choose the first available port."""
|
|
13
|
+
candidates = (
|
|
14
|
+
[preferred_port]
|
|
15
|
+
if preferred_port == 0
|
|
16
|
+
else range(preferred_port, preferred_port + 100)
|
|
17
|
+
)
|
|
18
|
+
for port in candidates:
|
|
19
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
20
|
+
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
21
|
+
try:
|
|
22
|
+
probe.bind((host, port))
|
|
23
|
+
except OSError:
|
|
24
|
+
continue
|
|
25
|
+
return probe.getsockname()[1]
|
|
26
|
+
raise RuntimeError(
|
|
27
|
+
f"No available port found between {preferred_port} and {preferred_port + 99}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def launch_app(*_args: str) -> None:
|
|
32
|
+
host = os.getenv("API_TRACKER_HOST", "127.0.0.1")
|
|
33
|
+
requested_port = int(os.getenv("API_TRACKER_PORT", "8000"))
|
|
34
|
+
port = find_available_port(host, requested_port)
|
|
35
|
+
open_browser = os.getenv("API_TRACKER_OPEN_BROWSER", "true").lower() not in {
|
|
36
|
+
"0",
|
|
37
|
+
"false",
|
|
38
|
+
"no",
|
|
39
|
+
}
|
|
40
|
+
print(f"API Tracker dashboard: http://{host}:{port}")
|
|
41
|
+
if open_browser:
|
|
42
|
+
url = f"http://{host}:{port}"
|
|
43
|
+
threading.Timer(1.0, lambda: webbrowser.open(url)).start()
|
|
44
|
+
uvicorn.run("backend.app.main:app", host=host, port=port, reload=False)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class ProjectBase(BaseModel):
|
|
@@ -84,6 +84,8 @@ class UsageDataIn(BaseModel):
|
|
|
84
84
|
model: str
|
|
85
85
|
internal_request_id: str
|
|
86
86
|
provider_request_id: str | None = None
|
|
87
|
+
# Timestamp of the provider request. If omitted, the ingestion time is used.
|
|
88
|
+
timestamp: datetime | None = None
|
|
87
89
|
input_tokens: int = 0
|
|
88
90
|
output_tokens: int = 0
|
|
89
91
|
thinking_tokens: int = 0
|
|
@@ -128,3 +130,10 @@ class UsageResponse(BaseModel):
|
|
|
128
130
|
metadata_json: dict[str, Any]
|
|
129
131
|
capture_content: bool
|
|
130
132
|
model_config = ConfigDict(from_attributes=True)
|
|
133
|
+
|
|
134
|
+
@field_serializer("timestamp")
|
|
135
|
+
def serialize_timestamp(self, value: datetime) -> str:
|
|
136
|
+
"""Always expose stored timestamps as explicit UTC ISO-8601 values."""
|
|
137
|
+
if value.tzinfo is None:
|
|
138
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
139
|
+
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
@@ -76,8 +76,13 @@ def create_usage(db: Session, payload: dict[str, Any]) -> ApiUsage:
|
|
|
76
76
|
project = _get_or_create_project(db, payload["project"])
|
|
77
77
|
provider = _get_or_create_provider(db, payload["provider"])
|
|
78
78
|
model = _get_or_create_model(db, provider.id, payload["model"])
|
|
79
|
-
# timestamp = payload.get("timestamp") or datetime.utcnow()
|
|
80
79
|
timestamp = payload.get("timestamp") or datetime.now(timezone.utc)
|
|
80
|
+
# SQLite does not preserve timezone metadata. Normalize incoming values
|
|
81
|
+
# before using them for pricing and persistence so all records mean UTC.
|
|
82
|
+
if timestamp.tzinfo is None:
|
|
83
|
+
timestamp = timestamp.replace(tzinfo=timezone.utc)
|
|
84
|
+
else:
|
|
85
|
+
timestamp = timestamp.astimezone(timezone.utc)
|
|
81
86
|
pricing = get_applicable_pricing(db, model.id, timestamp)
|
|
82
87
|
snapshot = PricingSnapshot(
|
|
83
88
|
input_price_per_1m=getattr(pricing, "input_price_per_1m", 0.0),
|