revenium-python-sdk 0.1.5__tar.gz → 0.1.7__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.
- {revenium_python_sdk-0.1.5/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.7}/PKG-INFO +112 -77
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/README.md +111 -76
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/pyproject.toml +1 -1
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/__init__.py +6 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/__init__.py +8 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/config.py +2 -41
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/context.py +66 -1
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/fields.py +23 -5
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/metering.py +13 -4
- revenium_python_sdk-0.1.7/revenium_middleware/_core/metering_submission.py +68 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/agentic_outcomes.py +3 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/bedrock_adapter.py +35 -34
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/config.py +0 -3
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/middleware.py +184 -258
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/fal/_metering.py +42 -37
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/__init__.py +0 -14
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/trace_fields.py +3 -11
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/utils.py +4 -69
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/config.py +0 -1
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/config.py +0 -16
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/middleware.py +2 -21
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/proxy/middleware.py +3 -2
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/ollama/middleware.py +3 -2
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/config.py +0 -3
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/middleware.py +2 -27
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/perplexity/middleware.py +2 -1
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/perplexity/perplexity_sdk.py +2 -1
- revenium_python_sdk-0.1.7/revenium_middleware/webhooks/__init__.py +4 -0
- revenium_python_sdk-0.1.7/revenium_middleware/webhooks/_verify.py +74 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7/revenium_python_sdk.egg-info}/PKG-INFO +112 -77
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_python_sdk.egg-info/SOURCES.txt +3 -4
- revenium_python_sdk-0.1.5/revenium_middleware/anthropic/summary_printer.py +0 -286
- revenium_python_sdk-0.1.5/revenium_middleware/google/common/summary_printer.py +0 -271
- revenium_python_sdk-0.1.5/revenium_middleware/litellm/client/summary_printer.py +0 -314
- revenium_python_sdk-0.1.5/revenium_middleware/openai/summary_printer.py +0 -292
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/LICENSE +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/enforcement.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/exceptions.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/exceptions.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/setup.cfg +0 -0
- {revenium_python_sdk-0.1.5 → revenium_python_sdk-0.1.7}/tests/test_metering.py +0 -0
{revenium_python_sdk-0.1.5/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.7}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: revenium-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai.
|
|
5
5
|
Author-email: Revenium <support@revenium.io>
|
|
6
6
|
License: MIT
|
|
@@ -194,7 +194,7 @@ import revenium_middleware_openai # Auto-initializes on import
|
|
|
194
194
|
|
|
195
195
|
client = openai.OpenAI()
|
|
196
196
|
response = client.chat.completions.create(
|
|
197
|
-
model="gpt-
|
|
197
|
+
model="gpt-4o-mini",
|
|
198
198
|
messages=[{"role": "user", "content": "Hello!"}]
|
|
199
199
|
)
|
|
200
200
|
print(response.choices[0].message.content)
|
|
@@ -227,6 +227,98 @@ See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demo
|
|
|
227
227
|
|
|
228
228
|
---
|
|
229
229
|
|
|
230
|
+
## Idempotency
|
|
231
|
+
|
|
232
|
+
Every metering POST from the provider middleware automatically includes an `Idempotency-Key` header. If the Revenium API receives the same key with the same body within 24 hours, it returns the cached response instead of double-billing — making metering submissions safe to retry.
|
|
233
|
+
|
|
234
|
+
### Default
|
|
235
|
+
|
|
236
|
+
A fresh UUID v4 is generated automatically for every metering call. No action required.
|
|
237
|
+
|
|
238
|
+
### Override
|
|
239
|
+
|
|
240
|
+
Use the `idempotency_key` context manager to tie metering to a business-level identifier so the same logical operation never double-meters across retries:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from revenium_middleware import idempotency_key
|
|
244
|
+
|
|
245
|
+
with idempotency_key(f"order-{order_id}"):
|
|
246
|
+
response = openai.chat.completions.create(...)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The context manager is backed by `contextvars`, so it scopes correctly across threads and asyncio tasks.
|
|
250
|
+
|
|
251
|
+
### Backend behavior
|
|
252
|
+
|
|
253
|
+
| Scenario | Backend response |
|
|
254
|
+
| -- | -- |
|
|
255
|
+
| First call with key K and body B | Executes, caches for 24h |
|
|
256
|
+
| Retry with same K and same B | Returns cached response (no double-bill) |
|
|
257
|
+
| Same K with different B | `409 idempotency_key_mismatch` |
|
|
258
|
+
| Concurrent in-flight with same K | `409 idempotency_key_in_progress` + `Retry-After: 1` |
|
|
259
|
+
| Malformed key | `400 invalid_idempotency_key` |
|
|
260
|
+
|
|
261
|
+
See [docs.revenium.io/integrations/idempotency](https://docs.revenium.io/integrations/idempotency) for full backend semantics.
|
|
262
|
+
|
|
263
|
+
### Key format
|
|
264
|
+
|
|
265
|
+
`Idempotency-Key` must be 1–255 printable ASCII characters. UUID v4 is the recommended format and what the SDK generates by default.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Webhook Signature Verification
|
|
270
|
+
|
|
271
|
+
Revenium signs every outbound webhook with HMAC-SHA256 when a signing secret is configured. The SDK ships a verification helper so your handler can validate signatures without writing crypto.
|
|
272
|
+
|
|
273
|
+
Two headers arrive on every signed delivery:
|
|
274
|
+
|
|
275
|
+
| Header | Value |
|
|
276
|
+
| -- | -- |
|
|
277
|
+
| `X-Revenium-Signature-256` | `sha256=<hex>`. During a 24h rotation overlap: `sha256=A, sha256=B`. |
|
|
278
|
+
| `X-Revenium-Webhook-Timestamp` | Unix seconds at signing time. |
|
|
279
|
+
|
|
280
|
+
### FastAPI example
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
import os
|
|
284
|
+
|
|
285
|
+
from fastapi import FastAPI, Header, HTTPException, Request
|
|
286
|
+
|
|
287
|
+
from revenium_middleware.webhooks import verify_signature
|
|
288
|
+
|
|
289
|
+
app = FastAPI()
|
|
290
|
+
SIGNING_SECRETS = [os.environ["REVENIUM_WEBHOOK_SECRET"]]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@app.post("/webhooks/revenium")
|
|
294
|
+
async def receive(
|
|
295
|
+
request: Request,
|
|
296
|
+
x_revenium_signature_256: str = Header(...),
|
|
297
|
+
x_revenium_webhook_timestamp: str = Header(...),
|
|
298
|
+
):
|
|
299
|
+
body = await request.body()
|
|
300
|
+
if not verify_signature(
|
|
301
|
+
payload=body,
|
|
302
|
+
signature_header=x_revenium_signature_256,
|
|
303
|
+
timestamp_header=x_revenium_webhook_timestamp,
|
|
304
|
+
secrets=SIGNING_SECRETS,
|
|
305
|
+
):
|
|
306
|
+
raise HTTPException(status_code=401, detail="Invalid signature")
|
|
307
|
+
|
|
308
|
+
# ... process the event
|
|
309
|
+
return {"ok": True}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Secret rotation
|
|
313
|
+
|
|
314
|
+
When you rotate a signing secret in the Revenium dashboard with the default 24-hour overlap, both the old and new secrets are active simultaneously and every webhook is signed with both. Supply both values in `SIGNING_SECRETS` during the overlap window; remove the old one once it expires.
|
|
315
|
+
|
|
316
|
+
### Webhooks without a signing secret
|
|
317
|
+
|
|
318
|
+
Webhook deliveries without a configured signing secret arrive without HMAC headers. If your endpoint receives both signed and unsigned traffic, branch on header presence: treat missing headers as legacy unsigned mode and missing-signature-on-signed-only endpoints as an authentication failure.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
230
322
|
## Provider Usage Guides
|
|
231
323
|
|
|
232
324
|
### OpenAI
|
|
@@ -244,7 +336,7 @@ client = openai.OpenAI()
|
|
|
244
336
|
|
|
245
337
|
# Basic chat completion
|
|
246
338
|
response = client.chat.completions.create(
|
|
247
|
-
model="gpt-
|
|
339
|
+
model="gpt-4o-mini",
|
|
248
340
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
249
341
|
usage_metadata={
|
|
250
342
|
"organizationName": "AcmeCorp",
|
|
@@ -256,7 +348,7 @@ response = client.chat.completions.create(
|
|
|
256
348
|
|
|
257
349
|
# Streaming
|
|
258
350
|
stream = client.chat.completions.create(
|
|
259
|
-
model="gpt-
|
|
351
|
+
model="gpt-4o-mini",
|
|
260
352
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
261
353
|
stream=True
|
|
262
354
|
)
|
|
@@ -547,7 +639,7 @@ litellm.api_base = os.getenv("LITELLM_PROXY_URL")
|
|
|
547
639
|
litellm.api_key = os.getenv("LITELLM_API_KEY")
|
|
548
640
|
|
|
549
641
|
response = litellm.completion(
|
|
550
|
-
model="gpt-
|
|
642
|
+
model="gpt-4o-mini",
|
|
551
643
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
552
644
|
usage_metadata={
|
|
553
645
|
"organizationName": "AcmeCorp",
|
|
@@ -705,7 +797,7 @@ handler = ReveniumCallbackHandler(
|
|
|
705
797
|
agent_name="support_agent"
|
|
706
798
|
)
|
|
707
799
|
|
|
708
|
-
llm = ChatOpenAI(model="gpt-
|
|
800
|
+
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
|
|
709
801
|
response = llm.invoke("Hello!")
|
|
710
802
|
```
|
|
711
803
|
|
|
@@ -744,7 +836,7 @@ result = agent.invoke(
|
|
|
744
836
|
from revenium_middleware_langchain import AsyncReveniumCallbackHandler
|
|
745
837
|
|
|
746
838
|
handler = AsyncReveniumCallbackHandler(trace_id="async-session")
|
|
747
|
-
llm = ChatOpenAI(model="gpt-
|
|
839
|
+
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
|
|
748
840
|
response = await llm.ainvoke("Hello!")
|
|
749
841
|
```
|
|
750
842
|
|
|
@@ -790,7 +882,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
790
882
|
|
|
791
883
|
```python
|
|
792
884
|
response = client.chat.completions.create(
|
|
793
|
-
model="gpt-
|
|
885
|
+
model="gpt-4o-mini",
|
|
794
886
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
795
887
|
usage_metadata={
|
|
796
888
|
"trace_id": "conv-28a7e9d4",
|
|
@@ -812,7 +904,7 @@ response = client.chat.completions.create(
|
|
|
812
904
|
)
|
|
813
905
|
```
|
|
814
906
|
|
|
815
|
-
**Deprecation notice:** The
|
|
907
|
+
**Deprecation notice:** The legacy field aliases `organizationId`, `organization_id`, `productId`, and `product_id` are accepted by this SDK only as an input-layer convenience and emit a `DeprecationWarning`. The Revenium backend no longer accepts them — they are translated to `organizationName` / `productName` before the wire call. Migrate to `organization_name` / `organizationName` and `product_name` / `productName` now; the input-layer aliases will be removed in the next major release.
|
|
816
908
|
|
|
817
909
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
818
910
|
|
|
@@ -853,7 +945,7 @@ REVENIUM_TRACE_TYPE=customer-support
|
|
|
853
945
|
|
|
854
946
|
```python
|
|
855
947
|
response = client.chat.completions.create(
|
|
856
|
-
model="gpt-
|
|
948
|
+
model="gpt-4o-mini",
|
|
857
949
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
858
950
|
usage_metadata={
|
|
859
951
|
"environment": "production",
|
|
@@ -877,7 +969,7 @@ workflow_id = str(uuid.uuid4())
|
|
|
877
969
|
|
|
878
970
|
# Step 1: Parent operation
|
|
879
971
|
parent_response = client.chat.completions.create(
|
|
880
|
-
model="gpt-
|
|
972
|
+
model="gpt-4o-mini",
|
|
881
973
|
messages=[{"role": "user", "content": "Analyze this document"}],
|
|
882
974
|
usage_metadata={
|
|
883
975
|
"trace_id": "analysis-session-456",
|
|
@@ -888,7 +980,7 @@ parent_response = client.chat.completions.create(
|
|
|
888
980
|
|
|
889
981
|
# Step 2: Child operation linked to parent
|
|
890
982
|
child_response = client.chat.completions.create(
|
|
891
|
-
model="gpt-
|
|
983
|
+
model="gpt-4o-mini",
|
|
892
984
|
messages=[{"role": "user", "content": "Summarize findings"}],
|
|
893
985
|
usage_metadata={
|
|
894
986
|
"trace_id": "analysis-session-456",
|
|
@@ -919,7 +1011,7 @@ from revenium_middleware import revenium_metadata
|
|
|
919
1011
|
def handle_customer_query(question: str) -> str:
|
|
920
1012
|
# All API calls automatically include the decorator metadata
|
|
921
1013
|
response = client.chat.completions.create(
|
|
922
|
-
model="gpt-
|
|
1014
|
+
model="gpt-4o-mini",
|
|
923
1015
|
messages=[{"role": "user", "content": question}]
|
|
924
1016
|
)
|
|
925
1017
|
return response.choices[0].message.content
|
|
@@ -958,13 +1050,13 @@ def outer_function():
|
|
|
958
1050
|
def mixed_metadata():
|
|
959
1051
|
# Uses decorator metadata
|
|
960
1052
|
response1 = client.chat.completions.create(
|
|
961
|
-
model="gpt-
|
|
1053
|
+
model="gpt-4o-mini",
|
|
962
1054
|
messages=[{"role": "user", "content": "Hello"}]
|
|
963
1055
|
)
|
|
964
1056
|
|
|
965
1057
|
# API-level metadata overrides decorator's task_type
|
|
966
1058
|
response2 = client.chat.completions.create(
|
|
967
|
-
model="gpt-
|
|
1059
|
+
model="gpt-4o-mini",
|
|
968
1060
|
messages=[{"role": "user", "content": "Hello"}],
|
|
969
1061
|
usage_metadata={
|
|
970
1062
|
"task_type": "special-override", # Overrides decorator
|
|
@@ -993,7 +1085,7 @@ from revenium_middleware import revenium_meter, revenium_metadata
|
|
|
993
1085
|
def premium_feature(prompt: str) -> str:
|
|
994
1086
|
# This WILL be metered (decorated with @revenium_meter)
|
|
995
1087
|
response = client.chat.completions.create(
|
|
996
|
-
model="gpt-
|
|
1088
|
+
model="gpt-4o-mini",
|
|
997
1089
|
messages=[{"role": "user", "content": prompt}]
|
|
998
1090
|
)
|
|
999
1091
|
return response.choices[0].message.content
|
|
@@ -1001,7 +1093,7 @@ def premium_feature(prompt: str) -> str:
|
|
|
1001
1093
|
def free_feature(prompt: str) -> str:
|
|
1002
1094
|
# This will NOT be metered (no @revenium_meter decorator)
|
|
1003
1095
|
response = client.chat.completions.create(
|
|
1004
|
-
model="gpt-
|
|
1096
|
+
model="gpt-4o-mini",
|
|
1005
1097
|
messages=[{"role": "user", "content": prompt}]
|
|
1006
1098
|
)
|
|
1007
1099
|
return response.choices[0].message.content
|
|
@@ -1087,7 +1179,7 @@ from openai import OpenAI
|
|
|
1087
1179
|
|
|
1088
1180
|
client = OpenAI()
|
|
1089
1181
|
response = client.chat.completions.create(
|
|
1090
|
-
model="gpt-
|
|
1182
|
+
model="gpt-4o-mini",
|
|
1091
1183
|
messages=[
|
|
1092
1184
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
1093
1185
|
{"role": "user", "content": "What is the capital of France?"}
|
|
@@ -1109,62 +1201,6 @@ Prompt capture works with both streaming and non-streaming requests, and with mu
|
|
|
1109
1201
|
|
|
1110
1202
|
---
|
|
1111
1203
|
|
|
1112
|
-
## Terminal Summary Output
|
|
1113
|
-
|
|
1114
|
-
Display a cost and usage summary in your terminal after each API request. Useful for development, debugging, and monitoring AI costs in real-time.
|
|
1115
|
-
|
|
1116
|
-
### Configuration
|
|
1117
|
-
|
|
1118
|
-
| Environment Variable | Values | Description |
|
|
1119
|
-
|---------------------|--------|-------------|
|
|
1120
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` (default), `true` or `human`, `json` | Controls output format |
|
|
1121
|
-
| `REVENIUM_TEAM_ID` | Your team ID | Required to fetch and display cost information |
|
|
1122
|
-
|
|
1123
|
-
```bash
|
|
1124
|
-
# Enable human-readable output
|
|
1125
|
-
export REVENIUM_PRINT_SUMMARY=human
|
|
1126
|
-
|
|
1127
|
-
# Required for cost display (find in Revenium web app)
|
|
1128
|
-
export REVENIUM_TEAM_ID=your-team-id-here
|
|
1129
|
-
```
|
|
1130
|
-
|
|
1131
|
-
### Human-Readable Format
|
|
1132
|
-
|
|
1133
|
-
```
|
|
1134
|
-
============================================================
|
|
1135
|
-
REVENIUM USAGE SUMMARY
|
|
1136
|
-
============================================================
|
|
1137
|
-
Model: gpt-5.5
|
|
1138
|
-
Provider: OPENAI
|
|
1139
|
-
Duration: 1.23s
|
|
1140
|
-
|
|
1141
|
-
Token Usage:
|
|
1142
|
-
Input Tokens: 150
|
|
1143
|
-
Output Tokens: 250
|
|
1144
|
-
Total Tokens: 400
|
|
1145
|
-
|
|
1146
|
-
Cost: $0.000045
|
|
1147
|
-
|
|
1148
|
-
Trace ID: abc-123
|
|
1149
|
-
============================================================
|
|
1150
|
-
```
|
|
1151
|
-
|
|
1152
|
-
### JSON Format
|
|
1153
|
-
|
|
1154
|
-
```json
|
|
1155
|
-
{"model":"gpt-5.5","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1156
|
-
```
|
|
1157
|
-
|
|
1158
|
-
### Cost Status
|
|
1159
|
-
|
|
1160
|
-
| Scenario | Display |
|
|
1161
|
-
|----------|---------|
|
|
1162
|
-
| Cost available | `$0.000045` |
|
|
1163
|
-
| `REVENIUM_TEAM_ID` set, cost pending | `Pending (aggregating... check Revenium dashboard)` |
|
|
1164
|
-
| `REVENIUM_TEAM_ID` not set | `Add REVENIUM_TEAM_ID to see pricing` |
|
|
1165
|
-
|
|
1166
|
-
---
|
|
1167
|
-
|
|
1168
1204
|
## Cost Controls / Enforcement
|
|
1169
1205
|
|
|
1170
1206
|
Block outbound provider requests client-side when a Revenium cost control trips. When the circuit breaker is enabled, the middleware polls compiled enforcement rules from the Revenium API in a background daemon thread and raises `BudgetExceededError` **before** the upstream call, preventing spend beyond the configured limit.
|
|
@@ -1243,7 +1279,7 @@ client = openai.OpenAI()
|
|
|
1243
1279
|
|
|
1244
1280
|
try:
|
|
1245
1281
|
response = client.chat.completions.create(
|
|
1246
|
-
model="gpt-
|
|
1282
|
+
model="gpt-4o-mini",
|
|
1247
1283
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1248
1284
|
)
|
|
1249
1285
|
except BudgetExceededError as exc:
|
|
@@ -1282,9 +1318,8 @@ See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_
|
|
|
1282
1318
|
| `REVENIUM_METERING_BASE_URL` | `https://api.revenium.ai` | Revenium API endpoint |
|
|
1283
1319
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1284
1320
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1285
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` | Terminal output: `false`, `true`/`human`, `json` |
|
|
1286
1321
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1287
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost
|
|
1322
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1288
1323
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1289
1324
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1290
1325
|
| `REVENIUM_CREDENTIAL_ALIAS` | - | Human-readable API key name |
|
|
@@ -106,7 +106,7 @@ import revenium_middleware_openai # Auto-initializes on import
|
|
|
106
106
|
|
|
107
107
|
client = openai.OpenAI()
|
|
108
108
|
response = client.chat.completions.create(
|
|
109
|
-
model="gpt-
|
|
109
|
+
model="gpt-4o-mini",
|
|
110
110
|
messages=[{"role": "user", "content": "Hello!"}]
|
|
111
111
|
)
|
|
112
112
|
print(response.choices[0].message.content)
|
|
@@ -139,6 +139,98 @@ See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demo
|
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
142
|
+
## Idempotency
|
|
143
|
+
|
|
144
|
+
Every metering POST from the provider middleware automatically includes an `Idempotency-Key` header. If the Revenium API receives the same key with the same body within 24 hours, it returns the cached response instead of double-billing — making metering submissions safe to retry.
|
|
145
|
+
|
|
146
|
+
### Default
|
|
147
|
+
|
|
148
|
+
A fresh UUID v4 is generated automatically for every metering call. No action required.
|
|
149
|
+
|
|
150
|
+
### Override
|
|
151
|
+
|
|
152
|
+
Use the `idempotency_key` context manager to tie metering to a business-level identifier so the same logical operation never double-meters across retries:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from revenium_middleware import idempotency_key
|
|
156
|
+
|
|
157
|
+
with idempotency_key(f"order-{order_id}"):
|
|
158
|
+
response = openai.chat.completions.create(...)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The context manager is backed by `contextvars`, so it scopes correctly across threads and asyncio tasks.
|
|
162
|
+
|
|
163
|
+
### Backend behavior
|
|
164
|
+
|
|
165
|
+
| Scenario | Backend response |
|
|
166
|
+
| -- | -- |
|
|
167
|
+
| First call with key K and body B | Executes, caches for 24h |
|
|
168
|
+
| Retry with same K and same B | Returns cached response (no double-bill) |
|
|
169
|
+
| Same K with different B | `409 idempotency_key_mismatch` |
|
|
170
|
+
| Concurrent in-flight with same K | `409 idempotency_key_in_progress` + `Retry-After: 1` |
|
|
171
|
+
| Malformed key | `400 invalid_idempotency_key` |
|
|
172
|
+
|
|
173
|
+
See [docs.revenium.io/integrations/idempotency](https://docs.revenium.io/integrations/idempotency) for full backend semantics.
|
|
174
|
+
|
|
175
|
+
### Key format
|
|
176
|
+
|
|
177
|
+
`Idempotency-Key` must be 1–255 printable ASCII characters. UUID v4 is the recommended format and what the SDK generates by default.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Webhook Signature Verification
|
|
182
|
+
|
|
183
|
+
Revenium signs every outbound webhook with HMAC-SHA256 when a signing secret is configured. The SDK ships a verification helper so your handler can validate signatures without writing crypto.
|
|
184
|
+
|
|
185
|
+
Two headers arrive on every signed delivery:
|
|
186
|
+
|
|
187
|
+
| Header | Value |
|
|
188
|
+
| -- | -- |
|
|
189
|
+
| `X-Revenium-Signature-256` | `sha256=<hex>`. During a 24h rotation overlap: `sha256=A, sha256=B`. |
|
|
190
|
+
| `X-Revenium-Webhook-Timestamp` | Unix seconds at signing time. |
|
|
191
|
+
|
|
192
|
+
### FastAPI example
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
import os
|
|
196
|
+
|
|
197
|
+
from fastapi import FastAPI, Header, HTTPException, Request
|
|
198
|
+
|
|
199
|
+
from revenium_middleware.webhooks import verify_signature
|
|
200
|
+
|
|
201
|
+
app = FastAPI()
|
|
202
|
+
SIGNING_SECRETS = [os.environ["REVENIUM_WEBHOOK_SECRET"]]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.post("/webhooks/revenium")
|
|
206
|
+
async def receive(
|
|
207
|
+
request: Request,
|
|
208
|
+
x_revenium_signature_256: str = Header(...),
|
|
209
|
+
x_revenium_webhook_timestamp: str = Header(...),
|
|
210
|
+
):
|
|
211
|
+
body = await request.body()
|
|
212
|
+
if not verify_signature(
|
|
213
|
+
payload=body,
|
|
214
|
+
signature_header=x_revenium_signature_256,
|
|
215
|
+
timestamp_header=x_revenium_webhook_timestamp,
|
|
216
|
+
secrets=SIGNING_SECRETS,
|
|
217
|
+
):
|
|
218
|
+
raise HTTPException(status_code=401, detail="Invalid signature")
|
|
219
|
+
|
|
220
|
+
# ... process the event
|
|
221
|
+
return {"ok": True}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Secret rotation
|
|
225
|
+
|
|
226
|
+
When you rotate a signing secret in the Revenium dashboard with the default 24-hour overlap, both the old and new secrets are active simultaneously and every webhook is signed with both. Supply both values in `SIGNING_SECRETS` during the overlap window; remove the old one once it expires.
|
|
227
|
+
|
|
228
|
+
### Webhooks without a signing secret
|
|
229
|
+
|
|
230
|
+
Webhook deliveries without a configured signing secret arrive without HMAC headers. If your endpoint receives both signed and unsigned traffic, branch on header presence: treat missing headers as legacy unsigned mode and missing-signature-on-signed-only endpoints as an authentication failure.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
142
234
|
## Provider Usage Guides
|
|
143
235
|
|
|
144
236
|
### OpenAI
|
|
@@ -156,7 +248,7 @@ client = openai.OpenAI()
|
|
|
156
248
|
|
|
157
249
|
# Basic chat completion
|
|
158
250
|
response = client.chat.completions.create(
|
|
159
|
-
model="gpt-
|
|
251
|
+
model="gpt-4o-mini",
|
|
160
252
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
161
253
|
usage_metadata={
|
|
162
254
|
"organizationName": "AcmeCorp",
|
|
@@ -168,7 +260,7 @@ response = client.chat.completions.create(
|
|
|
168
260
|
|
|
169
261
|
# Streaming
|
|
170
262
|
stream = client.chat.completions.create(
|
|
171
|
-
model="gpt-
|
|
263
|
+
model="gpt-4o-mini",
|
|
172
264
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
173
265
|
stream=True
|
|
174
266
|
)
|
|
@@ -459,7 +551,7 @@ litellm.api_base = os.getenv("LITELLM_PROXY_URL")
|
|
|
459
551
|
litellm.api_key = os.getenv("LITELLM_API_KEY")
|
|
460
552
|
|
|
461
553
|
response = litellm.completion(
|
|
462
|
-
model="gpt-
|
|
554
|
+
model="gpt-4o-mini",
|
|
463
555
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
464
556
|
usage_metadata={
|
|
465
557
|
"organizationName": "AcmeCorp",
|
|
@@ -617,7 +709,7 @@ handler = ReveniumCallbackHandler(
|
|
|
617
709
|
agent_name="support_agent"
|
|
618
710
|
)
|
|
619
711
|
|
|
620
|
-
llm = ChatOpenAI(model="gpt-
|
|
712
|
+
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
|
|
621
713
|
response = llm.invoke("Hello!")
|
|
622
714
|
```
|
|
623
715
|
|
|
@@ -656,7 +748,7 @@ result = agent.invoke(
|
|
|
656
748
|
from revenium_middleware_langchain import AsyncReveniumCallbackHandler
|
|
657
749
|
|
|
658
750
|
handler = AsyncReveniumCallbackHandler(trace_id="async-session")
|
|
659
|
-
llm = ChatOpenAI(model="gpt-
|
|
751
|
+
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
|
|
660
752
|
response = await llm.ainvoke("Hello!")
|
|
661
753
|
```
|
|
662
754
|
|
|
@@ -702,7 +794,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
702
794
|
|
|
703
795
|
```python
|
|
704
796
|
response = client.chat.completions.create(
|
|
705
|
-
model="gpt-
|
|
797
|
+
model="gpt-4o-mini",
|
|
706
798
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
707
799
|
usage_metadata={
|
|
708
800
|
"trace_id": "conv-28a7e9d4",
|
|
@@ -724,7 +816,7 @@ response = client.chat.completions.create(
|
|
|
724
816
|
)
|
|
725
817
|
```
|
|
726
818
|
|
|
727
|
-
**Deprecation notice:** The
|
|
819
|
+
**Deprecation notice:** The legacy field aliases `organizationId`, `organization_id`, `productId`, and `product_id` are accepted by this SDK only as an input-layer convenience and emit a `DeprecationWarning`. The Revenium backend no longer accepts them — they are translated to `organizationName` / `productName` before the wire call. Migrate to `organization_name` / `organizationName` and `product_name` / `productName` now; the input-layer aliases will be removed in the next major release.
|
|
728
820
|
|
|
729
821
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
730
822
|
|
|
@@ -765,7 +857,7 @@ REVENIUM_TRACE_TYPE=customer-support
|
|
|
765
857
|
|
|
766
858
|
```python
|
|
767
859
|
response = client.chat.completions.create(
|
|
768
|
-
model="gpt-
|
|
860
|
+
model="gpt-4o-mini",
|
|
769
861
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
770
862
|
usage_metadata={
|
|
771
863
|
"environment": "production",
|
|
@@ -789,7 +881,7 @@ workflow_id = str(uuid.uuid4())
|
|
|
789
881
|
|
|
790
882
|
# Step 1: Parent operation
|
|
791
883
|
parent_response = client.chat.completions.create(
|
|
792
|
-
model="gpt-
|
|
884
|
+
model="gpt-4o-mini",
|
|
793
885
|
messages=[{"role": "user", "content": "Analyze this document"}],
|
|
794
886
|
usage_metadata={
|
|
795
887
|
"trace_id": "analysis-session-456",
|
|
@@ -800,7 +892,7 @@ parent_response = client.chat.completions.create(
|
|
|
800
892
|
|
|
801
893
|
# Step 2: Child operation linked to parent
|
|
802
894
|
child_response = client.chat.completions.create(
|
|
803
|
-
model="gpt-
|
|
895
|
+
model="gpt-4o-mini",
|
|
804
896
|
messages=[{"role": "user", "content": "Summarize findings"}],
|
|
805
897
|
usage_metadata={
|
|
806
898
|
"trace_id": "analysis-session-456",
|
|
@@ -831,7 +923,7 @@ from revenium_middleware import revenium_metadata
|
|
|
831
923
|
def handle_customer_query(question: str) -> str:
|
|
832
924
|
# All API calls automatically include the decorator metadata
|
|
833
925
|
response = client.chat.completions.create(
|
|
834
|
-
model="gpt-
|
|
926
|
+
model="gpt-4o-mini",
|
|
835
927
|
messages=[{"role": "user", "content": question}]
|
|
836
928
|
)
|
|
837
929
|
return response.choices[0].message.content
|
|
@@ -870,13 +962,13 @@ def outer_function():
|
|
|
870
962
|
def mixed_metadata():
|
|
871
963
|
# Uses decorator metadata
|
|
872
964
|
response1 = client.chat.completions.create(
|
|
873
|
-
model="gpt-
|
|
965
|
+
model="gpt-4o-mini",
|
|
874
966
|
messages=[{"role": "user", "content": "Hello"}]
|
|
875
967
|
)
|
|
876
968
|
|
|
877
969
|
# API-level metadata overrides decorator's task_type
|
|
878
970
|
response2 = client.chat.completions.create(
|
|
879
|
-
model="gpt-
|
|
971
|
+
model="gpt-4o-mini",
|
|
880
972
|
messages=[{"role": "user", "content": "Hello"}],
|
|
881
973
|
usage_metadata={
|
|
882
974
|
"task_type": "special-override", # Overrides decorator
|
|
@@ -905,7 +997,7 @@ from revenium_middleware import revenium_meter, revenium_metadata
|
|
|
905
997
|
def premium_feature(prompt: str) -> str:
|
|
906
998
|
# This WILL be metered (decorated with @revenium_meter)
|
|
907
999
|
response = client.chat.completions.create(
|
|
908
|
-
model="gpt-
|
|
1000
|
+
model="gpt-4o-mini",
|
|
909
1001
|
messages=[{"role": "user", "content": prompt}]
|
|
910
1002
|
)
|
|
911
1003
|
return response.choices[0].message.content
|
|
@@ -913,7 +1005,7 @@ def premium_feature(prompt: str) -> str:
|
|
|
913
1005
|
def free_feature(prompt: str) -> str:
|
|
914
1006
|
# This will NOT be metered (no @revenium_meter decorator)
|
|
915
1007
|
response = client.chat.completions.create(
|
|
916
|
-
model="gpt-
|
|
1008
|
+
model="gpt-4o-mini",
|
|
917
1009
|
messages=[{"role": "user", "content": prompt}]
|
|
918
1010
|
)
|
|
919
1011
|
return response.choices[0].message.content
|
|
@@ -999,7 +1091,7 @@ from openai import OpenAI
|
|
|
999
1091
|
|
|
1000
1092
|
client = OpenAI()
|
|
1001
1093
|
response = client.chat.completions.create(
|
|
1002
|
-
model="gpt-
|
|
1094
|
+
model="gpt-4o-mini",
|
|
1003
1095
|
messages=[
|
|
1004
1096
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
1005
1097
|
{"role": "user", "content": "What is the capital of France?"}
|
|
@@ -1021,62 +1113,6 @@ Prompt capture works with both streaming and non-streaming requests, and with mu
|
|
|
1021
1113
|
|
|
1022
1114
|
---
|
|
1023
1115
|
|
|
1024
|
-
## Terminal Summary Output
|
|
1025
|
-
|
|
1026
|
-
Display a cost and usage summary in your terminal after each API request. Useful for development, debugging, and monitoring AI costs in real-time.
|
|
1027
|
-
|
|
1028
|
-
### Configuration
|
|
1029
|
-
|
|
1030
|
-
| Environment Variable | Values | Description |
|
|
1031
|
-
|---------------------|--------|-------------|
|
|
1032
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` (default), `true` or `human`, `json` | Controls output format |
|
|
1033
|
-
| `REVENIUM_TEAM_ID` | Your team ID | Required to fetch and display cost information |
|
|
1034
|
-
|
|
1035
|
-
```bash
|
|
1036
|
-
# Enable human-readable output
|
|
1037
|
-
export REVENIUM_PRINT_SUMMARY=human
|
|
1038
|
-
|
|
1039
|
-
# Required for cost display (find in Revenium web app)
|
|
1040
|
-
export REVENIUM_TEAM_ID=your-team-id-here
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
### Human-Readable Format
|
|
1044
|
-
|
|
1045
|
-
```
|
|
1046
|
-
============================================================
|
|
1047
|
-
REVENIUM USAGE SUMMARY
|
|
1048
|
-
============================================================
|
|
1049
|
-
Model: gpt-5.5
|
|
1050
|
-
Provider: OPENAI
|
|
1051
|
-
Duration: 1.23s
|
|
1052
|
-
|
|
1053
|
-
Token Usage:
|
|
1054
|
-
Input Tokens: 150
|
|
1055
|
-
Output Tokens: 250
|
|
1056
|
-
Total Tokens: 400
|
|
1057
|
-
|
|
1058
|
-
Cost: $0.000045
|
|
1059
|
-
|
|
1060
|
-
Trace ID: abc-123
|
|
1061
|
-
============================================================
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
### JSON Format
|
|
1065
|
-
|
|
1066
|
-
```json
|
|
1067
|
-
{"model":"gpt-5.5","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
### Cost Status
|
|
1071
|
-
|
|
1072
|
-
| Scenario | Display |
|
|
1073
|
-
|----------|---------|
|
|
1074
|
-
| Cost available | `$0.000045` |
|
|
1075
|
-
| `REVENIUM_TEAM_ID` set, cost pending | `Pending (aggregating... check Revenium dashboard)` |
|
|
1076
|
-
| `REVENIUM_TEAM_ID` not set | `Add REVENIUM_TEAM_ID to see pricing` |
|
|
1077
|
-
|
|
1078
|
-
---
|
|
1079
|
-
|
|
1080
1116
|
## Cost Controls / Enforcement
|
|
1081
1117
|
|
|
1082
1118
|
Block outbound provider requests client-side when a Revenium cost control trips. When the circuit breaker is enabled, the middleware polls compiled enforcement rules from the Revenium API in a background daemon thread and raises `BudgetExceededError` **before** the upstream call, preventing spend beyond the configured limit.
|
|
@@ -1155,7 +1191,7 @@ client = openai.OpenAI()
|
|
|
1155
1191
|
|
|
1156
1192
|
try:
|
|
1157
1193
|
response = client.chat.completions.create(
|
|
1158
|
-
model="gpt-
|
|
1194
|
+
model="gpt-4o-mini",
|
|
1159
1195
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1160
1196
|
)
|
|
1161
1197
|
except BudgetExceededError as exc:
|
|
@@ -1194,9 +1230,8 @@ See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_
|
|
|
1194
1230
|
| `REVENIUM_METERING_BASE_URL` | `https://api.revenium.ai` | Revenium API endpoint |
|
|
1195
1231
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1196
1232
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1197
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` | Terminal output: `false`, `true`/`human`, `json` |
|
|
1198
1233
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1199
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost
|
|
1234
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1200
1235
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1201
1236
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1202
1237
|
| `REVENIUM_CREDENTIAL_ALIAS` | - | Human-readable API key name |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revenium-python-sdk"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|