ModelMetre 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.
Files changed (35) hide show
  1. modelmetre-0.1.0/PKG-INFO +241 -0
  2. modelmetre-0.1.0/README.md +218 -0
  3. modelmetre-0.1.0/pyproject.toml +57 -0
  4. modelmetre-0.1.0/setup.cfg +4 -0
  5. modelmetre-0.1.0/src/ModelMetre.egg-info/PKG-INFO +241 -0
  6. modelmetre-0.1.0/src/ModelMetre.egg-info/SOURCES.txt +33 -0
  7. modelmetre-0.1.0/src/ModelMetre.egg-info/dependency_links.txt +1 -0
  8. modelmetre-0.1.0/src/ModelMetre.egg-info/requires.txt +11 -0
  9. modelmetre-0.1.0/src/ModelMetre.egg-info/top_level.txt +1 -0
  10. modelmetre-0.1.0/src/llmcost/__init__.py +39 -0
  11. modelmetre-0.1.0/src/llmcost/api/__init__.py +10 -0
  12. modelmetre-0.1.0/src/llmcost/api/routes.py +11 -0
  13. modelmetre-0.1.0/src/llmcost/api/telemetry.py +106 -0
  14. modelmetre-0.1.0/src/llmcost/auth/__init__.py +1 -0
  15. modelmetre-0.1.0/src/llmcost/auth/config.py +24 -0
  16. modelmetre-0.1.0/src/llmcost/client.py +208 -0
  17. modelmetre-0.1.0/src/llmcost/constants.py +0 -0
  18. modelmetre-0.1.0/src/llmcost/errors.py +0 -0
  19. modelmetre-0.1.0/src/llmcost/middleware/__init__.py +0 -0
  20. modelmetre-0.1.0/src/llmcost/middleware/error_handler.py +0 -0
  21. modelmetre-0.1.0/src/llmcost/middleware/rate_limit.py +33 -0
  22. modelmetre-0.1.0/src/llmcost/plugins/__init__.py +0 -0
  23. modelmetre-0.1.0/src/llmcost/plugins/base_plugin.py +0 -0
  24. modelmetre-0.1.0/src/llmcost/pricing/__init__.py +41 -0
  25. modelmetre-0.1.0/src/llmcost/pricing/aggregator.py +228 -0
  26. modelmetre-0.1.0/src/llmcost/pricing/extractors.py +122 -0
  27. modelmetre-0.1.0/src/llmcost/pricing/interceptor.py +145 -0
  28. modelmetre-0.1.0/src/llmcost/sdk.py +143 -0
  29. modelmetre-0.1.0/src/llmcost/utils/__init__.py +0 -0
  30. modelmetre-0.1.0/src/llmcost/utils/cache.py +0 -0
  31. modelmetre-0.1.0/src/llmcost/utils/helpers.py +0 -0
  32. modelmetre-0.1.0/src/llmcost/utils/http.py +0 -0
  33. modelmetre-0.1.0/src/llmcost/utils/logger.py +0 -0
  34. modelmetre-0.1.0/src/llmcost/utils/retry.py +0 -0
  35. modelmetre-0.1.0/src/llmcost/utils/validation.py +0 -0
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: ModelMetre
3
+ Version: 0.1.0
4
+ Summary: Multi-provider LLM cost observability SDK
5
+ Author-email: Jyotir Nandan <jyotirnandan92129@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/Kakashi15-pix/ModelMetre
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: requests>=2.31.0
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Requires-Dist: httpx>=0.24.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
18
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
19
+ Requires-Dist: pytest-mock>=3.11.0; extra == "dev"
20
+ Requires-Dist: black>=23.0.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.0.280; extra == "dev"
22
+ Requires-Dist: mypy>=1.4.0; extra == "dev"
23
+
24
+ # LLM Cost Observability SDK
25
+
26
+ Client-side SDK for provider response interception, usage extraction, request buffering, and optional telemetry flush to a backend cost pipeline.
27
+ ## What This SDK Does
28
+
29
+ - Wraps provider SDK methods without changing normal response behavior.
30
+ - Extracts usage metadata from provider responses locally.
31
+ - Buffers request details in-memory.
32
+ - Flushes buffered batches on size/time thresholds.
33
+ - Optionally sends flushed batches to backend telemetry endpoint.
34
+ - Supports lazy API-key verification for direct server API calls via `CostAnalyticsClient`.
35
+
36
+ ## What This SDK Does Not Do On Client
37
+
38
+ - Does not perform authoritative upstream pricing sync on the client.
39
+ - Does not do final backend-grade cost computation in the main intercept-and-flush path.
40
+ - Does not require provider credentials to leave the client environment.
41
+
42
+ ## Architecture (Current)
43
+
44
+ 1. Your app calls provider SDK (Anthropic/OpenAI/custom).
45
+ 2. Wrapped method runs, returns original provider response.
46
+ 3. Interceptor extracts usage/model/stop reason.
47
+ 4. `RequestDetailsBuffer` stores request details locally.
48
+ 5. Buffer flushes when:
49
+ - `FLUSH_BATCH_SIZE` reached (default `50`), or
50
+ - timer hits `FLUSH_INTERVAL_SECONDS` (default `30`).
51
+ 6. If telemetry is configured, flushed batch is POSTed to backend (`/v1/telemetry/flush` by default).
52
+ 7. Backend resolves pricing and computes final costs.
53
+
54
+ ## Package Layout (Relevant)
55
+
56
+ - `my-sdk/src/sdk.py`: `CostAnalyticsSDK` facade.
57
+ - `my-sdk/src/pricing/interceptor.py`: wrapping and extraction pipeline.
58
+ - `my-sdk/src/pricing/extractors.py`: provider extractors.
59
+ - `my-sdk/src/pricing/aggregator.py`: request buffer + flush triggers.
60
+ - `my-sdk/src/api/telemetry.py`: telemetry sender with failed flush retention/retry behavior.
61
+ - `my-sdk/src/client.py`: authenticated API client (`CostAnalyticsClient`) with lazy key verification.
62
+ - `my-sdk/src/auth/Config.py`: env-based API key loading (`CA_API_KEY`).
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install -e .
68
+ ```
69
+
70
+ ## Quick Start
71
+
72
+ ### 1) Wrap a Provider Client
73
+
74
+ ```python
75
+ from anthropic import Anthropic
76
+ from sdk import CostAnalyticsSDK
77
+
78
+ sdk = CostAnalyticsSDK(server_url="https://telemetry.example.com")
79
+
80
+ client = Anthropic()
81
+ client = sdk.wrap_client(
82
+ client=client,
83
+ provider="anthropic",
84
+ method_path="messages.create",
85
+ )
86
+
87
+ response = client.messages.create(
88
+ model="claude-3-haiku-20240307",
89
+ max_tokens=128,
90
+ messages=[{"role": "user", "content": "Hello"}],
91
+ )
92
+
93
+ print(sdk.get_metrics())
94
+ ```
95
+
96
+ ### 2) Wrap Other Client Shapes
97
+
98
+ ```python
99
+ from sdk import CostAnalyticsSDK
100
+
101
+ sdk = CostAnalyticsSDK(server_url="https://telemetry.example.com")
102
+
103
+ client = sdk.wrap_client(
104
+ client=client,
105
+ provider="custom-provider",
106
+ method_path="responses.create",
107
+ )
108
+ ```
109
+
110
+ ### 3) Manual Flush
111
+
112
+ ```python
113
+ from pricing import get_request_buffer
114
+
115
+ buffer = get_request_buffer()
116
+ buffer.flush()
117
+ ```
118
+
119
+ ## Core APIs
120
+
121
+ ### `CostAnalyticsSDK` (`my-sdk/src/sdk.py`)
122
+
123
+ - `CostAnalyticsSDK(server_url: Optional[str] = None)`
124
+ - `wrap_client(client, provider, method_path, response_to_dict=None, metadata=None)`
125
+ - `process_response(response, provider, request_id=None, metadata=None)`
126
+ - `get_metrics()`
127
+ - `get_pending_requests()`
128
+ - `flush_buffer()`
129
+
130
+ `get_metrics()` currently returns buffer-oriented metrics:
131
+
132
+ ```json
133
+ {
134
+ "buffer_size": 12,
135
+ "pending_requests": 12
136
+ }
137
+ ```
138
+
139
+ ### Buffer (`my-sdk/src/pricing/aggregator.py`)
140
+
141
+ - `FLUSH_BATCH_SIZE = 50`
142
+ - `FLUSH_INTERVAL_SECONDS = 30`
143
+ - `get_request_buffer()` / `get_cost_aggregator()`
144
+ - `RequestDetailsBuffer.record_request(...)`
145
+ - `RequestDetailsBuffer.flush()`
146
+ - `RequestDetailsBuffer.get_pending_requests()`
147
+
148
+ ### Telemetry (`my-sdk/src/api/telemetry.py`)
149
+
150
+ `TelemetryClient` behavior:
151
+
152
+ - Sends flush payload to `POST {server_url}/v1/telemetry/flush` (default endpoint path).
153
+ - Includes headers:
154
+ - `Content-Type: application/json`
155
+ - `X-Client-ID: <uuid>`
156
+ - optional `Authorization: Bearer <api_key>`
157
+ - On flush failure, retains up to last 5 failed batches in-memory.
158
+ - If failure looks like "request not received", retries once immediately.
159
+ - Failed batches are retried on subsequent flush attempts.
160
+
161
+ ## Auth for SDK-to-Server API Calls
162
+
163
+ `CostAnalyticsClient` (`my-sdk/src/client.py`) supports direct authenticated calls to server APIs.
164
+
165
+ - Reads `CA_API_KEY` when `api_key` not passed.
166
+ - Performs lazy auth verification against `GET /v1/auth/verify` (default path).
167
+ - Sends request metadata headers:
168
+ - `Authorization`
169
+ - `X-CA-Key-Id`
170
+ - `X-CA-User-Id`
171
+ - `X-Request-Id`
172
+ - `X-CA-Provider`
173
+ - `X-CA-Model`
174
+
175
+ Example:
176
+
177
+ ```python
178
+ from client import CostAnalyticsClient
179
+
180
+ client = CostAnalyticsClient(
181
+ api_key="ca_live_...",
182
+ base_url="https://api.example.com",
183
+ )
184
+
185
+ resp = client.request("GET", "/v1/costs")
186
+ print(resp.status_code)
187
+ ```
188
+
189
+ ## Environment Variables
190
+
191
+ - `CA_API_KEY`: client API key for `CostAnalyticsClient`.
192
+ - `CA_API_BASE_URL`: optional base URL override for `CostAnalyticsClient`.
193
+
194
+ Note: backend services may require additional variables (for example server HMAC secret) that are configured in the server repository.
195
+
196
+ ## Backend Contract Expectations
197
+
198
+ Telemetry flush endpoint should accept payload in this shape:
199
+
200
+ ```json
201
+ {
202
+ "client_id": "uuid",
203
+ "batch": [
204
+ {
205
+ "timestamp": "2026-01-01T00:00:00.000000",
206
+ "request_id": "req-123",
207
+ "model": "claude-3-haiku-20240307",
208
+ "provider": "anthropic",
209
+ "input_tokens": 100,
210
+ "output_tokens": 50,
211
+ "cache_read_tokens": 0,
212
+ "cache_creation_tokens": 0,
213
+ "stop_reason": "end_turn",
214
+ "metadata": {"method": "messages.create"}
215
+ }
216
+ ]
217
+ }
218
+ ```
219
+
220
+ Auth verification endpoint expected by `CostAnalyticsClient`:
221
+
222
+ - `GET /v1/auth/verify`
223
+ - Response:
224
+
225
+ ```json
226
+ {
227
+ "user_id": "...",
228
+ "api_key_id": "..."
229
+ }
230
+ ```
231
+
232
+ ## Testing
233
+
234
+ ```bash
235
+ pip install -e ".[dev]"
236
+ pytest tests/ -v
237
+ ```
238
+
239
+ ## Notes on Compatibility Surface
240
+
241
+ The SDK exposes one generic extractor/interceptor path. Provider names are metadata values passed to `wrap_client`; authoritative pricing resolution lives on the backend.
@@ -0,0 +1,218 @@
1
+ # LLM Cost Observability SDK
2
+
3
+ Client-side SDK for provider response interception, usage extraction, request buffering, and optional telemetry flush to a backend cost pipeline.
4
+ ## What This SDK Does
5
+
6
+ - Wraps provider SDK methods without changing normal response behavior.
7
+ - Extracts usage metadata from provider responses locally.
8
+ - Buffers request details in-memory.
9
+ - Flushes buffered batches on size/time thresholds.
10
+ - Optionally sends flushed batches to backend telemetry endpoint.
11
+ - Supports lazy API-key verification for direct server API calls via `CostAnalyticsClient`.
12
+
13
+ ## What This SDK Does Not Do On Client
14
+
15
+ - Does not perform authoritative upstream pricing sync on the client.
16
+ - Does not do final backend-grade cost computation in the main intercept-and-flush path.
17
+ - Does not require provider credentials to leave the client environment.
18
+
19
+ ## Architecture (Current)
20
+
21
+ 1. Your app calls provider SDK (Anthropic/OpenAI/custom).
22
+ 2. Wrapped method runs, returns original provider response.
23
+ 3. Interceptor extracts usage/model/stop reason.
24
+ 4. `RequestDetailsBuffer` stores request details locally.
25
+ 5. Buffer flushes when:
26
+ - `FLUSH_BATCH_SIZE` reached (default `50`), or
27
+ - timer hits `FLUSH_INTERVAL_SECONDS` (default `30`).
28
+ 6. If telemetry is configured, flushed batch is POSTed to backend (`/v1/telemetry/flush` by default).
29
+ 7. Backend resolves pricing and computes final costs.
30
+
31
+ ## Package Layout (Relevant)
32
+
33
+ - `my-sdk/src/sdk.py`: `CostAnalyticsSDK` facade.
34
+ - `my-sdk/src/pricing/interceptor.py`: wrapping and extraction pipeline.
35
+ - `my-sdk/src/pricing/extractors.py`: provider extractors.
36
+ - `my-sdk/src/pricing/aggregator.py`: request buffer + flush triggers.
37
+ - `my-sdk/src/api/telemetry.py`: telemetry sender with failed flush retention/retry behavior.
38
+ - `my-sdk/src/client.py`: authenticated API client (`CostAnalyticsClient`) with lazy key verification.
39
+ - `my-sdk/src/auth/Config.py`: env-based API key loading (`CA_API_KEY`).
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install -e .
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ### 1) Wrap a Provider Client
50
+
51
+ ```python
52
+ from anthropic import Anthropic
53
+ from sdk import CostAnalyticsSDK
54
+
55
+ sdk = CostAnalyticsSDK(server_url="https://telemetry.example.com")
56
+
57
+ client = Anthropic()
58
+ client = sdk.wrap_client(
59
+ client=client,
60
+ provider="anthropic",
61
+ method_path="messages.create",
62
+ )
63
+
64
+ response = client.messages.create(
65
+ model="claude-3-haiku-20240307",
66
+ max_tokens=128,
67
+ messages=[{"role": "user", "content": "Hello"}],
68
+ )
69
+
70
+ print(sdk.get_metrics())
71
+ ```
72
+
73
+ ### 2) Wrap Other Client Shapes
74
+
75
+ ```python
76
+ from sdk import CostAnalyticsSDK
77
+
78
+ sdk = CostAnalyticsSDK(server_url="https://telemetry.example.com")
79
+
80
+ client = sdk.wrap_client(
81
+ client=client,
82
+ provider="custom-provider",
83
+ method_path="responses.create",
84
+ )
85
+ ```
86
+
87
+ ### 3) Manual Flush
88
+
89
+ ```python
90
+ from pricing import get_request_buffer
91
+
92
+ buffer = get_request_buffer()
93
+ buffer.flush()
94
+ ```
95
+
96
+ ## Core APIs
97
+
98
+ ### `CostAnalyticsSDK` (`my-sdk/src/sdk.py`)
99
+
100
+ - `CostAnalyticsSDK(server_url: Optional[str] = None)`
101
+ - `wrap_client(client, provider, method_path, response_to_dict=None, metadata=None)`
102
+ - `process_response(response, provider, request_id=None, metadata=None)`
103
+ - `get_metrics()`
104
+ - `get_pending_requests()`
105
+ - `flush_buffer()`
106
+
107
+ `get_metrics()` currently returns buffer-oriented metrics:
108
+
109
+ ```json
110
+ {
111
+ "buffer_size": 12,
112
+ "pending_requests": 12
113
+ }
114
+ ```
115
+
116
+ ### Buffer (`my-sdk/src/pricing/aggregator.py`)
117
+
118
+ - `FLUSH_BATCH_SIZE = 50`
119
+ - `FLUSH_INTERVAL_SECONDS = 30`
120
+ - `get_request_buffer()` / `get_cost_aggregator()`
121
+ - `RequestDetailsBuffer.record_request(...)`
122
+ - `RequestDetailsBuffer.flush()`
123
+ - `RequestDetailsBuffer.get_pending_requests()`
124
+
125
+ ### Telemetry (`my-sdk/src/api/telemetry.py`)
126
+
127
+ `TelemetryClient` behavior:
128
+
129
+ - Sends flush payload to `POST {server_url}/v1/telemetry/flush` (default endpoint path).
130
+ - Includes headers:
131
+ - `Content-Type: application/json`
132
+ - `X-Client-ID: <uuid>`
133
+ - optional `Authorization: Bearer <api_key>`
134
+ - On flush failure, retains up to last 5 failed batches in-memory.
135
+ - If failure looks like "request not received", retries once immediately.
136
+ - Failed batches are retried on subsequent flush attempts.
137
+
138
+ ## Auth for SDK-to-Server API Calls
139
+
140
+ `CostAnalyticsClient` (`my-sdk/src/client.py`) supports direct authenticated calls to server APIs.
141
+
142
+ - Reads `CA_API_KEY` when `api_key` not passed.
143
+ - Performs lazy auth verification against `GET /v1/auth/verify` (default path).
144
+ - Sends request metadata headers:
145
+ - `Authorization`
146
+ - `X-CA-Key-Id`
147
+ - `X-CA-User-Id`
148
+ - `X-Request-Id`
149
+ - `X-CA-Provider`
150
+ - `X-CA-Model`
151
+
152
+ Example:
153
+
154
+ ```python
155
+ from client import CostAnalyticsClient
156
+
157
+ client = CostAnalyticsClient(
158
+ api_key="ca_live_...",
159
+ base_url="https://api.example.com",
160
+ )
161
+
162
+ resp = client.request("GET", "/v1/costs")
163
+ print(resp.status_code)
164
+ ```
165
+
166
+ ## Environment Variables
167
+
168
+ - `CA_API_KEY`: client API key for `CostAnalyticsClient`.
169
+ - `CA_API_BASE_URL`: optional base URL override for `CostAnalyticsClient`.
170
+
171
+ Note: backend services may require additional variables (for example server HMAC secret) that are configured in the server repository.
172
+
173
+ ## Backend Contract Expectations
174
+
175
+ Telemetry flush endpoint should accept payload in this shape:
176
+
177
+ ```json
178
+ {
179
+ "client_id": "uuid",
180
+ "batch": [
181
+ {
182
+ "timestamp": "2026-01-01T00:00:00.000000",
183
+ "request_id": "req-123",
184
+ "model": "claude-3-haiku-20240307",
185
+ "provider": "anthropic",
186
+ "input_tokens": 100,
187
+ "output_tokens": 50,
188
+ "cache_read_tokens": 0,
189
+ "cache_creation_tokens": 0,
190
+ "stop_reason": "end_turn",
191
+ "metadata": {"method": "messages.create"}
192
+ }
193
+ ]
194
+ }
195
+ ```
196
+
197
+ Auth verification endpoint expected by `CostAnalyticsClient`:
198
+
199
+ - `GET /v1/auth/verify`
200
+ - Response:
201
+
202
+ ```json
203
+ {
204
+ "user_id": "...",
205
+ "api_key_id": "..."
206
+ }
207
+ ```
208
+
209
+ ## Testing
210
+
211
+ ```bash
212
+ pip install -e ".[dev]"
213
+ pytest tests/ -v
214
+ ```
215
+
216
+ ## Notes on Compatibility Surface
217
+
218
+ The SDK exposes one generic extractor/interceptor path. Provider names are metadata values passed to `wrap_client`; authoritative pricing resolution lives on the backend.
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ModelMetre"
7
+ version = "0.1.0"
8
+ description = "Multi-provider LLM cost observability SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license ="MIT"
12
+ authors = [
13
+ {name = "Jyotir Nandan", email = "jyotirnandan92129@gmail.com"}
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+
21
+ dependencies = [
22
+ "requests>=2.31.0",
23
+ "pydantic>=2.0.0",
24
+ "httpx>=0.24.0",
25
+ ]
26
+ [project.urls]
27
+ Repository = "https://github.com/Kakashi15-pix/ModelMetre"
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=7.4.0",
32
+ "pytest-cov>=4.1.0",
33
+ "pytest-mock>=3.11.0",
34
+ "black>=23.0.0",
35
+ "ruff>=0.0.280",
36
+ "mypy>=1.4.0",
37
+ ]
38
+
39
+ [tool.setuptools]
40
+ package-dir = {"" = "src"}
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.black]
46
+ line-length = 88
47
+ target-version = ['py39']
48
+
49
+ [tool.ruff]
50
+ line-length = 88
51
+ target-version = "py39"
52
+
53
+ [tool.mypy]
54
+ python_version = "3.9"
55
+ warn_return_any = true
56
+ warn_unused_configs = true
57
+ disallow_untyped_defs = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+