llmtrack-sdk 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.
- llmtrack_sdk-0.1.0/.gitignore +8 -0
- llmtrack_sdk-0.1.0/LICENSE +21 -0
- llmtrack_sdk-0.1.0/PKG-INFO +201 -0
- llmtrack_sdk-0.1.0/README.md +179 -0
- llmtrack_sdk-0.1.0/integration.py +67 -0
- llmtrack_sdk-0.1.0/pyproject.toml +29 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/__init__.py +4 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.github/workflows/python.yml +34 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.gitignore +66 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.gitlab-ci.yml +31 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.openapi-generator/FILES +68 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.openapi-generator/VERSION +1 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.openapi-generator-ignore +23 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/.travis.yml +17 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/README.md +147 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/AuthErrorResponse.md +29 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/DefaultApi.md +101 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/DuplicateResponse.md +30 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/FreePlanQuotaResponse.md +31 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/FreePlanQuotaResponseQuota.md +33 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/IngestLlmRequest200Response.md +37 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/IngestRequest.md +44 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/IngestSuccess.md +37 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/InternalErrorResponse.md +29 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/InvalidPayloadResponse.md +31 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/PaidPlanInactiveResponse.md +32 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/PaymentRequiredResponse.md +33 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/SourceTriple.md +31 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/UsageLimitResponse.md +29 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/docs/VisibilityContext.md +31 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/git_push.sh +54 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/__init__.py +76 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/api/__init__.py +5 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/api/default_api.py +341 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/api_client.py +833 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/api_response.py +21 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/configuration.py +683 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/exceptions.py +218 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/__init__.py +30 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/auth_error_response.py +95 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/duplicate_response.py +104 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/free_plan_quota_response.py +123 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/free_plan_quota_response_quota.py +111 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/ingest_llm_request200_response.py +137 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/ingest_request.py +200 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/ingest_success.py +162 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/internal_error_response.py +95 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/invalid_payload_response.py +113 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/paid_plan_inactive_response.py +115 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/payment_required_response.py +151 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/source_triple.py +107 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/usage_limit_response.py +95 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/models/visibility_context.py +107 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/py.typed +0 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/llmtrack_generated/rest.py +334 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/pyproject.toml +94 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/requirements.txt +4 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/setup.cfg +2 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/setup.py +47 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/__init__.py +0 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_auth_error_response.py +52 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_default_api.py +38 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_duplicate_response.py +54 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_free_plan_quota_response.py +66 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_free_plan_quota_response_quota.py +60 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_ingest_llm_request200_response.py +90 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_ingest_request.py +67 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_ingest_success.py +90 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_internal_error_response.py +52 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_invalid_payload_response.py +56 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_paid_plan_inactive_response.py +58 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_payment_required_response.py +70 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_source_triple.py +56 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_usage_limit_response.py +52 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test/test_visibility_context.py +72 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/test-requirements.txt +6 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/_generated/tox.ini +9 -0
- llmtrack_sdk-0.1.0/src/llmtrack_sdk/client.py +147 -0
- llmtrack_sdk-0.1.0/tests/test_client.py +27 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LLMtrack
|
|
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,201 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: llmtrack-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Server-side SDK for tracking LLM token usage, costs, and observability data in LLMtrack
|
|
5
|
+
Project-URL: Homepage, https://llm-track.com
|
|
6
|
+
Project-URL: Documentation, https://llm-track.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/othmanemer9-cloud/llmtrack-sdks
|
|
8
|
+
Project-URL: Issues, https://github.com/othmanemer9-cloud/llmtrack-sdks/issues
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,anthropic,cost,cost-tracking,llm,observability,openai,tokens
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Requires-Dist: httpx<1,>=0.27
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: build>=1; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# llmtrack-sdk (Python)
|
|
24
|
+
|
|
25
|
+
LLMtrack records server-side LLM token usage, cost, and request context so teams can understand AI usage in one dashboard.
|
|
26
|
+
|
|
27
|
+
> [!WARNING]
|
|
28
|
+
> **Server-side only.** Never put an LLMtrack key in browser, mobile, desktop-client, or other public code. Load it from a server-side environment variable only.
|
|
29
|
+
|
|
30
|
+
The PyPI distribution is named `llmtrack-sdk`, while Python imports use `llmtrack_sdk`: distribution names can contain hyphens, but import identifiers cannot, and the distinct name also avoids the unrelated `llmtrack` distribution.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pip install llmtrack-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
Create an ingestion key in LLMtrack, save it at `~/.config/llmtrack/api-key`, and run:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
export LLMTRACK_API_KEY="$(cat ~/.config/llmtrack/api-key)"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import os
|
|
48
|
+
from llmtrack_sdk import LLMtrack
|
|
49
|
+
tracker = LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
50
|
+
tracker.track(provider="openai", model="gpt-5.6-sol", prompt_tokens=241,
|
|
51
|
+
completion_tokens=86, reasoning_tokens=32, feature="support-chat")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Provider examples
|
|
55
|
+
|
|
56
|
+
Both examples call `track()` immediately after the provider completion and read counts from the official response objects.
|
|
57
|
+
|
|
58
|
+
### OpenAI
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import os
|
|
62
|
+
from openai import OpenAI
|
|
63
|
+
from llmtrack_sdk import LLMtrack
|
|
64
|
+
openai, tracker = OpenAI(), LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
65
|
+
response = openai.chat.completions.create(model="gpt-5.6-sol", messages=[{"role": "user", "content": "Explain why the sky is blue in two sentences."}])
|
|
66
|
+
usage = response.usage
|
|
67
|
+
tracker.track(provider="openai", model=response.model, prompt_tokens=usage.prompt_tokens, completion_tokens=usage.completion_tokens,
|
|
68
|
+
reasoning_tokens=usage.completion_tokens_details.reasoning_tokens if usage.completion_tokens_details else 0, feature="science-explainer")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Anthropic
|
|
72
|
+
|
|
73
|
+
Anthropic includes thinking tokens in `output_tokens` rather than exposing a separate reasoning-token count, so do not duplicate them in `reasoning_tokens`.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import os
|
|
77
|
+
from anthropic import Anthropic
|
|
78
|
+
from llmtrack_sdk import LLMtrack
|
|
79
|
+
anthropic, tracker = Anthropic(), LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
80
|
+
response = anthropic.messages.create(model="claude-opus-5", max_tokens=1024, messages=[{"role": "user", "content": "Summarize the benefits of typed APIs."}])
|
|
81
|
+
tracker.track(provider="anthropic", model=response.model, prompt_tokens=response.usage.input_tokens,
|
|
82
|
+
completion_tokens=response.usage.output_tokens, feature="document-summary")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Fire-and-forget and awaited usage
|
|
86
|
+
|
|
87
|
+
| Method | Behavior |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `track(**event)` | Synchronous fire-and-forget entry point: starts a daemon delivery thread, returns `None` immediately, **never blocks and never raises**. Errors go to `on_error`. |
|
|
90
|
+
| `await track_sync(**event)` | Async awaited entry point: returns the API dictionary and **raises `LLMtrackError`** on failure. Despite its compatibility name, it must be awaited. |
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
result = await tracker.track_sync(provider="openai", model="gpt-5.6-sol", prompt_tokens=241,
|
|
94
|
+
completion_tokens=86, reasoning_tokens=32, feature="support-chat")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Set `enabled=False` in tests and local development to make both methods no-ops:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
tracker = LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"], enabled=False)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Constructor options
|
|
104
|
+
|
|
105
|
+
| Name | Type | Default | Description |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| `api_key` | `str` | required | Server-side LLMtrack ingestion key. |
|
|
108
|
+
| `base_url` | `str` | `https://llm-track.com` | API origin; primarily useful with a test server. |
|
|
109
|
+
| `environment` | `str` | `production` | Environment applied when an event does not override it. |
|
|
110
|
+
| `on_error` | `Callable[[LLMtrackError], None] \| None` | logs one `[llmtrack]` warning | Receives `track()` background errors; callback exceptions are contained. |
|
|
111
|
+
| `on_warning` | `Callable[[LLMtrackWarning], None] \| None` | logs one `[llmtrack]` warning | Receives visibility and pricing warnings, once per distinct warning per process. |
|
|
112
|
+
| `enabled` | `bool` | `True` | When `False`, both tracking methods do nothing. |
|
|
113
|
+
| `timeout_ms` | `int` | `5000` | Timeout in milliseconds for each request attempt. |
|
|
114
|
+
| `max_retries` | `int` | `3` | Maximum attempts, including the first request. |
|
|
115
|
+
|
|
116
|
+
## Event fields
|
|
117
|
+
|
|
118
|
+
The wrapper intentionally accepts the following snake-case subset of the `IngestRequest` contract. `total_tokens`, `cached_input_tokens`, and `cache_write_tokens` exist in the wire contract but are not arguments in the current Python public API; do not pass them to this SDK.
|
|
119
|
+
|
|
120
|
+
| Name | Type | Requirement | Description |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| `provider` | `str` | required | Provider name, such as `openai` or `anthropic`. |
|
|
123
|
+
| `model` | `str` | required | Provider model name, such as `gpt-5.6-sol`. |
|
|
124
|
+
| `prompt_tokens` | `int` | required | Non-negative integer input-token count. |
|
|
125
|
+
| `completion_tokens` | `int` | required | Non-negative integer output-token count. |
|
|
126
|
+
| `total_tokens` | `int \| None` | not exposed | Optional explicit total in `IngestRequest`; the current wrapper relies on the server-computed total. |
|
|
127
|
+
| `reasoning_tokens` | `int \| None` | optional | Non-negative reasoning-token count when separately reported. |
|
|
128
|
+
| `cached_input_tokens` | `int \| None` | not exposed | Optional cached-input count in `IngestRequest`; not an argument in the current Python public API. |
|
|
129
|
+
| `cache_write_tokens` | `int \| None` | not exposed | Optional cache-write count in `IngestRequest`; not an argument in the current Python public API. |
|
|
130
|
+
| `latency_ms` | `int \| None` | optional | End-to-end latency in milliseconds. |
|
|
131
|
+
| `status` | `str \| None` | optional | One of `success`, `error`, `timeout`, or `cancelled`. |
|
|
132
|
+
| `feature` | `str \| None` | optional | Product feature, such as `support-chat`; defaults server-side to `unknown`. |
|
|
133
|
+
| `customer_id` | `str \| None` | optional | Your stable customer identifier. |
|
|
134
|
+
| `customer_name` | `str \| None` | optional | Your customer display name. |
|
|
135
|
+
| `environment` | `str \| None` | optional | Overrides the constructor environment for this event. |
|
|
136
|
+
| `metadata` | `dict[str, Any] \| None` | optional | JSON object containing request context; maximum serialized size is 8 KiB (8192 bytes). |
|
|
137
|
+
| `idempotency_key` | `str \| None` | optional | SDK-only header option used to deduplicate the call, not an event-body field. |
|
|
138
|
+
|
|
139
|
+
The client validates `prompt_tokens`, `completion_tokens`, and `reasoning_tokens` as non-negative integers and rejects metadata whose compact UTF-8 JSON serialization exceeds 8192 bytes. The server validates the remaining contract constraints.
|
|
140
|
+
|
|
141
|
+
## Delivery, retries, and idempotency
|
|
142
|
+
|
|
143
|
+
Each call automatically receives a UUID v4 idempotency key. That key stays stable across retries, preventing double-counting when a stored response is lost. Supply your own stable key to deduplicate separate calls:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
tracker.track(provider="openai", model="gpt-5.6-sol", prompt_tokens=241, completion_tokens=86,
|
|
147
|
+
reasoning_tokens=32, feature="support-chat", idempotency_key="9ea0c2ec-7b98-4bc3-9802-20ae6a468a35")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The SDK retries only network failures, timeouts, and HTTP 5xx responses. It never retries HTTP 4xx responses.
|
|
151
|
+
|
|
152
|
+
## Errors
|
|
153
|
+
|
|
154
|
+
| Code | Meaning | Fix |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `INVALID_API_KEY` | The key is missing, unknown, or belongs to a missing workspace. | Check `LLMTRACK_API_KEY` and create or copy a valid ingestion key. |
|
|
157
|
+
| `REVOKED_API_KEY` | The key was revoked. | Replace it with an active key. |
|
|
158
|
+
| `INACTIVE_API_KEY` | The key was deactivated. | Reactivate it or replace it. |
|
|
159
|
+
| `INVALID_PAYLOAD` | A field failed local or server validation. | Correct the named field; check integer token counts and metadata size. |
|
|
160
|
+
| `QUOTA_EXCEEDED` | The event allowance or pay-per-event credits are exhausted. | Wait for reset, add credits, or upgrade. |
|
|
161
|
+
| `PLAN_INACTIVE` | Billing status prevents ingestion. | Restore billing and reactivate the plan. |
|
|
162
|
+
| `NETWORK_ERROR` | Network/timeout retries were exhausted, or an unclassified HTTP/server failure occurred. | Check connectivity, `base_url`, timeout, and service availability. |
|
|
163
|
+
|
|
164
|
+
`track_sync()` raises these errors. `track()` sends them to `on_error` and never raises.
|
|
165
|
+
|
|
166
|
+
## Warnings and free-plan key binding
|
|
167
|
+
|
|
168
|
+
| Response | Meaning |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `dashboard_visible: false` | The event was accepted and billed/consumed quota, but is hidden because its source does not match the free-key binding. |
|
|
171
|
+
| `pricing_status: unknown_model` | No active pricing matched the provider/model, so cost is `0`; token usage is still recorded. |
|
|
172
|
+
|
|
173
|
+
**The most common reason events do not appear:** free-plan keys bind to one **provider/model/feature triple**. Mismatched events are accepted and billed (or consume quota) but hidden. Match all three values to the binding shown in LLMtrack. `NOT_DASHBOARD_VISIBLE` includes the bound and submitted triples.
|
|
174
|
+
|
|
175
|
+
## Troubleshooting
|
|
176
|
+
|
|
177
|
+
### Events are not appearing
|
|
178
|
+
|
|
179
|
+
Check warnings for `NOT_DASHBOARD_VISIBLE` and compare provider, model, and feature with the key binding. A short-lived process can exit before the daemon thread finishes; use `await track_sync()` in scripts and jobs that must confirm delivery.
|
|
180
|
+
|
|
181
|
+
### Cost shows 0
|
|
182
|
+
|
|
183
|
+
Check for `UNKNOWN_MODEL` or `pricing_status: unknown_model`. Use the exact `response.model` returned by the provider so active pricing can match it.
|
|
184
|
+
|
|
185
|
+
### The key is rejected
|
|
186
|
+
|
|
187
|
+
Inspect `LLMtrackError.code`: replace invalid/revoked keys, reactivate inactive keys, and confirm the environment variable is available to the server process. Use `await track_sync()` to inspect the complete exception.
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
Async tests use `pytest-asyncio` (included in the `test` and `dev` extras):
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
pip install -e 'packages/python[test]'
|
|
195
|
+
pytest packages/python/tests
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## More information
|
|
199
|
+
|
|
200
|
+
- [LLMtrack documentation](https://llm-track.com/docs)
|
|
201
|
+
- [MIT License](./LICENSE)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# llmtrack-sdk (Python)
|
|
2
|
+
|
|
3
|
+
LLMtrack records server-side LLM token usage, cost, and request context so teams can understand AI usage in one dashboard.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Server-side only.** Never put an LLMtrack key in browser, mobile, desktop-client, or other public code. Load it from a server-side environment variable only.
|
|
7
|
+
|
|
8
|
+
The PyPI distribution is named `llmtrack-sdk`, while Python imports use `llmtrack_sdk`: distribution names can contain hyphens, but import identifiers cannot, and the distinct name also avoids the unrelated `llmtrack` distribution.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install llmtrack-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
Create an ingestion key in LLMtrack, save it at `~/.config/llmtrack/api-key`, and run:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
export LLMTRACK_API_KEY="$(cat ~/.config/llmtrack/api-key)"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import os
|
|
26
|
+
from llmtrack_sdk import LLMtrack
|
|
27
|
+
tracker = LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
28
|
+
tracker.track(provider="openai", model="gpt-5.6-sol", prompt_tokens=241,
|
|
29
|
+
completion_tokens=86, reasoning_tokens=32, feature="support-chat")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Provider examples
|
|
33
|
+
|
|
34
|
+
Both examples call `track()` immediately after the provider completion and read counts from the official response objects.
|
|
35
|
+
|
|
36
|
+
### OpenAI
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import os
|
|
40
|
+
from openai import OpenAI
|
|
41
|
+
from llmtrack_sdk import LLMtrack
|
|
42
|
+
openai, tracker = OpenAI(), LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
43
|
+
response = openai.chat.completions.create(model="gpt-5.6-sol", messages=[{"role": "user", "content": "Explain why the sky is blue in two sentences."}])
|
|
44
|
+
usage = response.usage
|
|
45
|
+
tracker.track(provider="openai", model=response.model, prompt_tokens=usage.prompt_tokens, completion_tokens=usage.completion_tokens,
|
|
46
|
+
reasoning_tokens=usage.completion_tokens_details.reasoning_tokens if usage.completion_tokens_details else 0, feature="science-explainer")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Anthropic
|
|
50
|
+
|
|
51
|
+
Anthropic includes thinking tokens in `output_tokens` rather than exposing a separate reasoning-token count, so do not duplicate them in `reasoning_tokens`.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import os
|
|
55
|
+
from anthropic import Anthropic
|
|
56
|
+
from llmtrack_sdk import LLMtrack
|
|
57
|
+
anthropic, tracker = Anthropic(), LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"])
|
|
58
|
+
response = anthropic.messages.create(model="claude-opus-5", max_tokens=1024, messages=[{"role": "user", "content": "Summarize the benefits of typed APIs."}])
|
|
59
|
+
tracker.track(provider="anthropic", model=response.model, prompt_tokens=response.usage.input_tokens,
|
|
60
|
+
completion_tokens=response.usage.output_tokens, feature="document-summary")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Fire-and-forget and awaited usage
|
|
64
|
+
|
|
65
|
+
| Method | Behavior |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `track(**event)` | Synchronous fire-and-forget entry point: starts a daemon delivery thread, returns `None` immediately, **never blocks and never raises**. Errors go to `on_error`. |
|
|
68
|
+
| `await track_sync(**event)` | Async awaited entry point: returns the API dictionary and **raises `LLMtrackError`** on failure. Despite its compatibility name, it must be awaited. |
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
result = await tracker.track_sync(provider="openai", model="gpt-5.6-sol", prompt_tokens=241,
|
|
72
|
+
completion_tokens=86, reasoning_tokens=32, feature="support-chat")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Set `enabled=False` in tests and local development to make both methods no-ops:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
tracker = LLMtrack(api_key=os.environ["LLMTRACK_API_KEY"], enabled=False)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Constructor options
|
|
82
|
+
|
|
83
|
+
| Name | Type | Default | Description |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `api_key` | `str` | required | Server-side LLMtrack ingestion key. |
|
|
86
|
+
| `base_url` | `str` | `https://llm-track.com` | API origin; primarily useful with a test server. |
|
|
87
|
+
| `environment` | `str` | `production` | Environment applied when an event does not override it. |
|
|
88
|
+
| `on_error` | `Callable[[LLMtrackError], None] \| None` | logs one `[llmtrack]` warning | Receives `track()` background errors; callback exceptions are contained. |
|
|
89
|
+
| `on_warning` | `Callable[[LLMtrackWarning], None] \| None` | logs one `[llmtrack]` warning | Receives visibility and pricing warnings, once per distinct warning per process. |
|
|
90
|
+
| `enabled` | `bool` | `True` | When `False`, both tracking methods do nothing. |
|
|
91
|
+
| `timeout_ms` | `int` | `5000` | Timeout in milliseconds for each request attempt. |
|
|
92
|
+
| `max_retries` | `int` | `3` | Maximum attempts, including the first request. |
|
|
93
|
+
|
|
94
|
+
## Event fields
|
|
95
|
+
|
|
96
|
+
The wrapper intentionally accepts the following snake-case subset of the `IngestRequest` contract. `total_tokens`, `cached_input_tokens`, and `cache_write_tokens` exist in the wire contract but are not arguments in the current Python public API; do not pass them to this SDK.
|
|
97
|
+
|
|
98
|
+
| Name | Type | Requirement | Description |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `provider` | `str` | required | Provider name, such as `openai` or `anthropic`. |
|
|
101
|
+
| `model` | `str` | required | Provider model name, such as `gpt-5.6-sol`. |
|
|
102
|
+
| `prompt_tokens` | `int` | required | Non-negative integer input-token count. |
|
|
103
|
+
| `completion_tokens` | `int` | required | Non-negative integer output-token count. |
|
|
104
|
+
| `total_tokens` | `int \| None` | not exposed | Optional explicit total in `IngestRequest`; the current wrapper relies on the server-computed total. |
|
|
105
|
+
| `reasoning_tokens` | `int \| None` | optional | Non-negative reasoning-token count when separately reported. |
|
|
106
|
+
| `cached_input_tokens` | `int \| None` | not exposed | Optional cached-input count in `IngestRequest`; not an argument in the current Python public API. |
|
|
107
|
+
| `cache_write_tokens` | `int \| None` | not exposed | Optional cache-write count in `IngestRequest`; not an argument in the current Python public API. |
|
|
108
|
+
| `latency_ms` | `int \| None` | optional | End-to-end latency in milliseconds. |
|
|
109
|
+
| `status` | `str \| None` | optional | One of `success`, `error`, `timeout`, or `cancelled`. |
|
|
110
|
+
| `feature` | `str \| None` | optional | Product feature, such as `support-chat`; defaults server-side to `unknown`. |
|
|
111
|
+
| `customer_id` | `str \| None` | optional | Your stable customer identifier. |
|
|
112
|
+
| `customer_name` | `str \| None` | optional | Your customer display name. |
|
|
113
|
+
| `environment` | `str \| None` | optional | Overrides the constructor environment for this event. |
|
|
114
|
+
| `metadata` | `dict[str, Any] \| None` | optional | JSON object containing request context; maximum serialized size is 8 KiB (8192 bytes). |
|
|
115
|
+
| `idempotency_key` | `str \| None` | optional | SDK-only header option used to deduplicate the call, not an event-body field. |
|
|
116
|
+
|
|
117
|
+
The client validates `prompt_tokens`, `completion_tokens`, and `reasoning_tokens` as non-negative integers and rejects metadata whose compact UTF-8 JSON serialization exceeds 8192 bytes. The server validates the remaining contract constraints.
|
|
118
|
+
|
|
119
|
+
## Delivery, retries, and idempotency
|
|
120
|
+
|
|
121
|
+
Each call automatically receives a UUID v4 idempotency key. That key stays stable across retries, preventing double-counting when a stored response is lost. Supply your own stable key to deduplicate separate calls:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
tracker.track(provider="openai", model="gpt-5.6-sol", prompt_tokens=241, completion_tokens=86,
|
|
125
|
+
reasoning_tokens=32, feature="support-chat", idempotency_key="9ea0c2ec-7b98-4bc3-9802-20ae6a468a35")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The SDK retries only network failures, timeouts, and HTTP 5xx responses. It never retries HTTP 4xx responses.
|
|
129
|
+
|
|
130
|
+
## Errors
|
|
131
|
+
|
|
132
|
+
| Code | Meaning | Fix |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `INVALID_API_KEY` | The key is missing, unknown, or belongs to a missing workspace. | Check `LLMTRACK_API_KEY` and create or copy a valid ingestion key. |
|
|
135
|
+
| `REVOKED_API_KEY` | The key was revoked. | Replace it with an active key. |
|
|
136
|
+
| `INACTIVE_API_KEY` | The key was deactivated. | Reactivate it or replace it. |
|
|
137
|
+
| `INVALID_PAYLOAD` | A field failed local or server validation. | Correct the named field; check integer token counts and metadata size. |
|
|
138
|
+
| `QUOTA_EXCEEDED` | The event allowance or pay-per-event credits are exhausted. | Wait for reset, add credits, or upgrade. |
|
|
139
|
+
| `PLAN_INACTIVE` | Billing status prevents ingestion. | Restore billing and reactivate the plan. |
|
|
140
|
+
| `NETWORK_ERROR` | Network/timeout retries were exhausted, or an unclassified HTTP/server failure occurred. | Check connectivity, `base_url`, timeout, and service availability. |
|
|
141
|
+
|
|
142
|
+
`track_sync()` raises these errors. `track()` sends them to `on_error` and never raises.
|
|
143
|
+
|
|
144
|
+
## Warnings and free-plan key binding
|
|
145
|
+
|
|
146
|
+
| Response | Meaning |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `dashboard_visible: false` | The event was accepted and billed/consumed quota, but is hidden because its source does not match the free-key binding. |
|
|
149
|
+
| `pricing_status: unknown_model` | No active pricing matched the provider/model, so cost is `0`; token usage is still recorded. |
|
|
150
|
+
|
|
151
|
+
**The most common reason events do not appear:** free-plan keys bind to one **provider/model/feature triple**. Mismatched events are accepted and billed (or consume quota) but hidden. Match all three values to the binding shown in LLMtrack. `NOT_DASHBOARD_VISIBLE` includes the bound and submitted triples.
|
|
152
|
+
|
|
153
|
+
## Troubleshooting
|
|
154
|
+
|
|
155
|
+
### Events are not appearing
|
|
156
|
+
|
|
157
|
+
Check warnings for `NOT_DASHBOARD_VISIBLE` and compare provider, model, and feature with the key binding. A short-lived process can exit before the daemon thread finishes; use `await track_sync()` in scripts and jobs that must confirm delivery.
|
|
158
|
+
|
|
159
|
+
### Cost shows 0
|
|
160
|
+
|
|
161
|
+
Check for `UNKNOWN_MODEL` or `pricing_status: unknown_model`. Use the exact `response.model` returned by the provider so active pricing can match it.
|
|
162
|
+
|
|
163
|
+
### The key is rejected
|
|
164
|
+
|
|
165
|
+
Inspect `LLMtrackError.code`: replace invalid/revoked keys, reactivate inactive keys, and confirm the environment variable is available to the server process. Use `await track_sync()` to inspect the complete exception.
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
Async tests use `pytest-asyncio` (included in the `test` and `dev` extras):
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
pip install -e 'packages/python[test]'
|
|
173
|
+
pytest packages/python/tests
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## More information
|
|
177
|
+
|
|
178
|
+
- [LLMtrack documentation](https://llm-track.com/docs)
|
|
179
|
+
- [MIT License](./LICENSE)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Opt-in live checks. Requires a dedicated LLMTRACK_API_KEY."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from llmtrack_sdk import LLMtrack, LLMtrackError
|
|
7
|
+
|
|
8
|
+
API_KEY = os.environ.get("LLMTRACK_API_KEY")
|
|
9
|
+
if not API_KEY:
|
|
10
|
+
raise SystemExit("FAIL setup: LLMTRACK_API_KEY is required")
|
|
11
|
+
EVENT = dict(provider="openai", model="gpt-4o-mini", prompt_tokens=2, completion_tokens=1,
|
|
12
|
+
reasoning_tokens=1, feature="sdk-integration")
|
|
13
|
+
failures = 0
|
|
14
|
+
|
|
15
|
+
async def check(name, operation):
|
|
16
|
+
global failures
|
|
17
|
+
try:
|
|
18
|
+
await operation()
|
|
19
|
+
print(f"PASS {name}")
|
|
20
|
+
except Exception as exc:
|
|
21
|
+
failures += 1
|
|
22
|
+
print(f"FAIL {name}: {exc}")
|
|
23
|
+
|
|
24
|
+
async def callback_error(client):
|
|
25
|
+
loop = asyncio.get_running_loop()
|
|
26
|
+
done = loop.create_future()
|
|
27
|
+
def receive(error):
|
|
28
|
+
loop.call_soon_threadsafe(lambda: None if done.done() else done.set_result(error))
|
|
29
|
+
client.on_error = receive
|
|
30
|
+
client.track(**EVENT)
|
|
31
|
+
return await asyncio.wait_for(done, 7)
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
client = LLMtrack(api_key=API_KEY)
|
|
35
|
+
await check("happy path with reasoning tokens", lambda: client.track_sync(**EVENT))
|
|
36
|
+
async def invalid():
|
|
37
|
+
error = await callback_error(LLMtrack(api_key="invalid-integration-key", max_retries=1))
|
|
38
|
+
assert error.code == "INVALID_API_KEY", error.code
|
|
39
|
+
await check("invalid key is reported and track never raises", invalid)
|
|
40
|
+
async def visibility():
|
|
41
|
+
warnings=[]; c=LLMtrack(api_key=API_KEY, environment=f"sdk-mismatch-{uuid.uuid4()}", on_warning=warnings.append)
|
|
42
|
+
await c.track_sync(**EVENT)
|
|
43
|
+
assert any(w.code == "NOT_DASHBOARD_VISIBLE" for w in warnings), "use a free-plan test key bound to another environment"
|
|
44
|
+
await check("free-plan visibility warning", visibility)
|
|
45
|
+
async def pricing():
|
|
46
|
+
warnings=[]; c=LLMtrack(api_key=API_KEY, on_warning=warnings.append)
|
|
47
|
+
await c.track_sync(**{**EVENT,"model":f"unknown-integration-{uuid.uuid4()}"})
|
|
48
|
+
assert any(w.code == "UNKNOWN_MODEL" for w in warnings), "missing UNKNOWN_MODEL warning"
|
|
49
|
+
await check("unknown-model pricing warning", pricing)
|
|
50
|
+
async def invalid_payload(**changes):
|
|
51
|
+
try: await client.track_sync(**{**EVENT,**changes})
|
|
52
|
+
except LLMtrackError as exc:
|
|
53
|
+
assert exc.code == "INVALID_PAYLOAD"; return
|
|
54
|
+
raise AssertionError("expected INVALID_PAYLOAD")
|
|
55
|
+
await check("oversized metadata is rejected client-side", lambda: invalid_payload(metadata={"value":"x"*8193}))
|
|
56
|
+
await check("negative tokens are rejected client-side", lambda: invalid_payload(prompt_tokens=-1))
|
|
57
|
+
async def duplicate():
|
|
58
|
+
key=str(uuid.uuid4()); await client.track_sync(**EVENT,idempotency_key=key)
|
|
59
|
+
second=await client.track_sync(**EVENT,idempotency_key=key); assert second["duplicate"] is True
|
|
60
|
+
await check("same idempotency key is a successful duplicate", duplicate)
|
|
61
|
+
async def network():
|
|
62
|
+
error=await callback_error(LLMtrack(api_key=API_KEY,base_url="http://127.0.0.1:1",timeout_ms=250,max_retries=1))
|
|
63
|
+
assert error.code == "NETWORK_ERROR", error.code
|
|
64
|
+
await check("unreachable network failure is reported without raising", network)
|
|
65
|
+
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
raise SystemExit(1 if failures else 0)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "llmtrack-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Server-side SDK for tracking LLM token usage, costs, and observability data in LLMtrack"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
13
|
+
keywords = ["llm", "openai", "anthropic", "cost", "cost-tracking", "observability", "tokens", "ai"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://llm-track.com"
|
|
17
|
+
Documentation = "https://llm-track.com/docs"
|
|
18
|
+
Repository = "https://github.com/othmanemer9-cloud/llmtrack-sdks"
|
|
19
|
+
Issues = "https://github.com/othmanemer9-cloud/llmtrack-sdks/issues"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
test = ["pytest>=8", "pytest-asyncio>=0.24"]
|
|
23
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.24", "build>=1"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/llmtrack_sdk"]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# NOTE: This file is auto generated by OpenAPI Generator.
|
|
2
|
+
# URL: https://openapi-generator.tech
|
|
3
|
+
#
|
|
4
|
+
# ref: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
5
|
+
|
|
6
|
+
name: llmtrack_generated Python package
|
|
7
|
+
|
|
8
|
+
on: [push, pull_request]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v4
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
pip install -r test-requirements.txt
|
|
32
|
+
- name: Test with pytest
|
|
33
|
+
run: |
|
|
34
|
+
pytest --cov=llmtrack_generated
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
# Usually these files are written by a python script from a template
|
|
29
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
*,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
venv/
|
|
48
|
+
.venv/
|
|
49
|
+
.python-version
|
|
50
|
+
.pytest_cache
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
|
|
59
|
+
# Sphinx documentation
|
|
60
|
+
docs/_build/
|
|
61
|
+
|
|
62
|
+
# PyBuilder
|
|
63
|
+
target/
|
|
64
|
+
|
|
65
|
+
# Ipython Notebook
|
|
66
|
+
.ipynb_checkpoints
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# NOTE: This file is auto generated by OpenAPI Generator.
|
|
2
|
+
# URL: https://openapi-generator.tech
|
|
3
|
+
#
|
|
4
|
+
# ref: https://docs.gitlab.com/ee/ci/README.html
|
|
5
|
+
# ref: https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
|
|
6
|
+
|
|
7
|
+
stages:
|
|
8
|
+
- test
|
|
9
|
+
|
|
10
|
+
.pytest:
|
|
11
|
+
stage: test
|
|
12
|
+
script:
|
|
13
|
+
- pip install -r requirements.txt
|
|
14
|
+
- pip install -r test-requirements.txt
|
|
15
|
+
- pytest --cov=llmtrack_generated
|
|
16
|
+
|
|
17
|
+
pytest-3.10:
|
|
18
|
+
extends: .pytest
|
|
19
|
+
image: python:3.10-alpine
|
|
20
|
+
pytest-3.11:
|
|
21
|
+
extends: .pytest
|
|
22
|
+
image: python:3.11-alpine
|
|
23
|
+
pytest-3.12:
|
|
24
|
+
extends: .pytest
|
|
25
|
+
image: python:3.12-alpine
|
|
26
|
+
pytest-3.13:
|
|
27
|
+
extends: .pytest
|
|
28
|
+
image: python:3.13-alpine
|
|
29
|
+
pytest-3.14:
|
|
30
|
+
extends: .pytest
|
|
31
|
+
image: python:3.14-alpine
|