revenium-python-sdk 0.1.3__tar.gz → 0.1.5__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.3/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.5}/PKG-INFO +159 -22
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/README.md +158 -21
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/pyproject.toml +1 -1
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/__init__.py +6 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/__init__.py +7 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/config.py +9 -0
- revenium_python_sdk-0.1.5/revenium_middleware/_core/enforcement.py +344 -0
- revenium_python_sdk-0.1.5/revenium_middleware/_core/exceptions.py +43 -0
- revenium_python_sdk-0.1.5/revenium_middleware/agentic_outcomes.py +348 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/bedrock_adapter.py +5 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/__init__.py +3 -1
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/exceptions.py +8 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/middleware.py +20 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5/revenium_python_sdk.egg-info}/PKG-INFO +159 -22
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/SOURCES.txt +3 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/LICENSE +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/context.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/metering.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/_metering.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/utils.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/middleware.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/setup.cfg +0 -0
- {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/tests/test_metering.py +0 -0
{revenium_python_sdk-0.1.3/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.5}/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.5
|
|
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-5.5",
|
|
198
198
|
messages=[{"role": "user", "content": "Hello!"}]
|
|
199
199
|
)
|
|
200
200
|
print(response.choices[0].message.content)
|
|
@@ -203,6 +203,30 @@ print(response.choices[0].message.content)
|
|
|
203
203
|
|
|
204
204
|
---
|
|
205
205
|
|
|
206
|
+
## Agentic Outcomes (Outcome-Based Metering)
|
|
207
|
+
|
|
208
|
+
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from revenium_middleware.agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings
|
|
212
|
+
|
|
213
|
+
settings = AgenticOutcomeSettings(api_key="rev_sk_...")
|
|
214
|
+
client = AgenticOutcomeClient(settings)
|
|
215
|
+
|
|
216
|
+
client.emit_completion(...) # one per LLM call
|
|
217
|
+
client.emit_tool_event(...) # one per tool / step
|
|
218
|
+
client.report_outcome(job_id, {...}) # close the job with a terminal outcome
|
|
219
|
+
client.close()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The job is created implicitly by the first metric ingested for `agenticJobId`. Call `client.create_job(job_id)` explicitly if you need to record an agent run before emitting any metrics.
|
|
223
|
+
|
|
224
|
+
See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demos (sales / coding / support) with configurable failure rates and outcome distributions.
|
|
225
|
+
|
|
226
|
+
**API reference:** [docs.revenium.io](https://docs.revenium.io) · per-endpoint reference at [revenium.readme.io/reference/meter_ai_completion](https://revenium.readme.io/reference/meter_ai_completion).
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
206
230
|
## Provider Usage Guides
|
|
207
231
|
|
|
208
232
|
### OpenAI
|
|
@@ -220,7 +244,7 @@ client = openai.OpenAI()
|
|
|
220
244
|
|
|
221
245
|
# Basic chat completion
|
|
222
246
|
response = client.chat.completions.create(
|
|
223
|
-
model="gpt-
|
|
247
|
+
model="gpt-5.5",
|
|
224
248
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
225
249
|
usage_metadata={
|
|
226
250
|
"organizationName": "AcmeCorp",
|
|
@@ -232,7 +256,7 @@ response = client.chat.completions.create(
|
|
|
232
256
|
|
|
233
257
|
# Streaming
|
|
234
258
|
stream = client.chat.completions.create(
|
|
235
|
-
model="gpt-
|
|
259
|
+
model="gpt-5.5",
|
|
236
260
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
237
261
|
stream=True
|
|
238
262
|
)
|
|
@@ -292,7 +316,7 @@ client = anthropic.Anthropic()
|
|
|
292
316
|
|
|
293
317
|
# Basic message
|
|
294
318
|
message = client.messages.create(
|
|
295
|
-
model="claude-
|
|
319
|
+
model="claude-opus-4-7",
|
|
296
320
|
max_tokens=100,
|
|
297
321
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
298
322
|
usage_metadata={
|
|
@@ -304,7 +328,7 @@ message = client.messages.create(
|
|
|
304
328
|
|
|
305
329
|
# Streaming
|
|
306
330
|
with client.messages.stream(
|
|
307
|
-
model="claude-
|
|
331
|
+
model="claude-opus-4-7",
|
|
308
332
|
max_tokens=200,
|
|
309
333
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
310
334
|
usage_metadata={"task_type": "creative"}
|
|
@@ -330,7 +354,7 @@ client = anthropic.AnthropicBedrock(
|
|
|
330
354
|
)
|
|
331
355
|
|
|
332
356
|
message = client.messages.create(
|
|
333
|
-
model="
|
|
357
|
+
model="claude-opus-4-7",
|
|
334
358
|
max_tokens=100,
|
|
335
359
|
messages=[{"role": "user", "content": "Hello from Bedrock!"}]
|
|
336
360
|
)
|
|
@@ -354,6 +378,11 @@ message = client.messages.create(
|
|
|
354
378
|
|
|
355
379
|
| Anthropic Model | Bedrock Model ID |
|
|
356
380
|
|----------------|------------------|
|
|
381
|
+
| `claude-opus-4-7` | `anthropic.claude-opus-4-7` |
|
|
382
|
+
| `us.claude-opus-4-7` | `us.anthropic.claude-opus-4-7` |
|
|
383
|
+
| `eu.claude-opus-4-7` | `eu.anthropic.claude-opus-4-7` |
|
|
384
|
+
| `au.claude-opus-4-7` | `au.anthropic.claude-opus-4-7` |
|
|
385
|
+
| `global.claude-opus-4-7` | `global.anthropic.claude-opus-4-7` |
|
|
357
386
|
| `claude-3-opus-20240229` | `anthropic.claude-3-opus-20240229-v1:0` |
|
|
358
387
|
| `claude-3-sonnet-20240229` | `anthropic.claude-3-sonnet-20240229-v1:0` |
|
|
359
388
|
| `claude-3-haiku-20240307` | `us.anthropic.claude-3-5-haiku-20241022-v1:0` |
|
|
@@ -518,7 +547,7 @@ litellm.api_base = os.getenv("LITELLM_PROXY_URL")
|
|
|
518
547
|
litellm.api_key = os.getenv("LITELLM_API_KEY")
|
|
519
548
|
|
|
520
549
|
response = litellm.completion(
|
|
521
|
-
model="gpt-
|
|
550
|
+
model="gpt-5.5",
|
|
522
551
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
523
552
|
usage_metadata={
|
|
524
553
|
"organizationName": "AcmeCorp",
|
|
@@ -676,7 +705,7 @@ handler = ReveniumCallbackHandler(
|
|
|
676
705
|
agent_name="support_agent"
|
|
677
706
|
)
|
|
678
707
|
|
|
679
|
-
llm = ChatOpenAI(model="gpt-
|
|
708
|
+
llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
|
|
680
709
|
response = llm.invoke("Hello!")
|
|
681
710
|
```
|
|
682
711
|
|
|
@@ -715,7 +744,7 @@ result = agent.invoke(
|
|
|
715
744
|
from revenium_middleware_langchain import AsyncReveniumCallbackHandler
|
|
716
745
|
|
|
717
746
|
handler = AsyncReveniumCallbackHandler(trace_id="async-session")
|
|
718
|
-
llm = ChatOpenAI(model="gpt-
|
|
747
|
+
llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
|
|
719
748
|
response = await llm.ainvoke("Hello!")
|
|
720
749
|
```
|
|
721
750
|
|
|
@@ -761,7 +790,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
761
790
|
|
|
762
791
|
```python
|
|
763
792
|
response = client.chat.completions.create(
|
|
764
|
-
model="gpt-
|
|
793
|
+
model="gpt-5.5",
|
|
765
794
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
766
795
|
usage_metadata={
|
|
767
796
|
"trace_id": "conv-28a7e9d4",
|
|
@@ -824,7 +853,7 @@ REVENIUM_TRACE_TYPE=customer-support
|
|
|
824
853
|
|
|
825
854
|
```python
|
|
826
855
|
response = client.chat.completions.create(
|
|
827
|
-
model="gpt-
|
|
856
|
+
model="gpt-5.5",
|
|
828
857
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
829
858
|
usage_metadata={
|
|
830
859
|
"environment": "production",
|
|
@@ -848,7 +877,7 @@ workflow_id = str(uuid.uuid4())
|
|
|
848
877
|
|
|
849
878
|
# Step 1: Parent operation
|
|
850
879
|
parent_response = client.chat.completions.create(
|
|
851
|
-
model="gpt-
|
|
880
|
+
model="gpt-5.5",
|
|
852
881
|
messages=[{"role": "user", "content": "Analyze this document"}],
|
|
853
882
|
usage_metadata={
|
|
854
883
|
"trace_id": "analysis-session-456",
|
|
@@ -859,7 +888,7 @@ parent_response = client.chat.completions.create(
|
|
|
859
888
|
|
|
860
889
|
# Step 2: Child operation linked to parent
|
|
861
890
|
child_response = client.chat.completions.create(
|
|
862
|
-
model="gpt-
|
|
891
|
+
model="gpt-5.5",
|
|
863
892
|
messages=[{"role": "user", "content": "Summarize findings"}],
|
|
864
893
|
usage_metadata={
|
|
865
894
|
"trace_id": "analysis-session-456",
|
|
@@ -890,7 +919,7 @@ from revenium_middleware import revenium_metadata
|
|
|
890
919
|
def handle_customer_query(question: str) -> str:
|
|
891
920
|
# All API calls automatically include the decorator metadata
|
|
892
921
|
response = client.chat.completions.create(
|
|
893
|
-
model="gpt-
|
|
922
|
+
model="gpt-5.5",
|
|
894
923
|
messages=[{"role": "user", "content": question}]
|
|
895
924
|
)
|
|
896
925
|
return response.choices[0].message.content
|
|
@@ -929,13 +958,13 @@ def outer_function():
|
|
|
929
958
|
def mixed_metadata():
|
|
930
959
|
# Uses decorator metadata
|
|
931
960
|
response1 = client.chat.completions.create(
|
|
932
|
-
model="gpt-
|
|
961
|
+
model="gpt-5.5",
|
|
933
962
|
messages=[{"role": "user", "content": "Hello"}]
|
|
934
963
|
)
|
|
935
964
|
|
|
936
965
|
# API-level metadata overrides decorator's task_type
|
|
937
966
|
response2 = client.chat.completions.create(
|
|
938
|
-
model="gpt-
|
|
967
|
+
model="gpt-5.5",
|
|
939
968
|
messages=[{"role": "user", "content": "Hello"}],
|
|
940
969
|
usage_metadata={
|
|
941
970
|
"task_type": "special-override", # Overrides decorator
|
|
@@ -964,7 +993,7 @@ from revenium_middleware import revenium_meter, revenium_metadata
|
|
|
964
993
|
def premium_feature(prompt: str) -> str:
|
|
965
994
|
# This WILL be metered (decorated with @revenium_meter)
|
|
966
995
|
response = client.chat.completions.create(
|
|
967
|
-
model="gpt-
|
|
996
|
+
model="gpt-5.5",
|
|
968
997
|
messages=[{"role": "user", "content": prompt}]
|
|
969
998
|
)
|
|
970
999
|
return response.choices[0].message.content
|
|
@@ -972,7 +1001,7 @@ def premium_feature(prompt: str) -> str:
|
|
|
972
1001
|
def free_feature(prompt: str) -> str:
|
|
973
1002
|
# This will NOT be metered (no @revenium_meter decorator)
|
|
974
1003
|
response = client.chat.completions.create(
|
|
975
|
-
model="gpt-
|
|
1004
|
+
model="gpt-5.5",
|
|
976
1005
|
messages=[{"role": "user", "content": prompt}]
|
|
977
1006
|
)
|
|
978
1007
|
return response.choices[0].message.content
|
|
@@ -1058,7 +1087,7 @@ from openai import OpenAI
|
|
|
1058
1087
|
|
|
1059
1088
|
client = OpenAI()
|
|
1060
1089
|
response = client.chat.completions.create(
|
|
1061
|
-
model="gpt-
|
|
1090
|
+
model="gpt-5.5",
|
|
1062
1091
|
messages=[
|
|
1063
1092
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
1064
1093
|
{"role": "user", "content": "What is the capital of France?"}
|
|
@@ -1105,7 +1134,7 @@ export REVENIUM_TEAM_ID=your-team-id-here
|
|
|
1105
1134
|
============================================================
|
|
1106
1135
|
REVENIUM USAGE SUMMARY
|
|
1107
1136
|
============================================================
|
|
1108
|
-
Model: gpt-
|
|
1137
|
+
Model: gpt-5.5
|
|
1109
1138
|
Provider: OPENAI
|
|
1110
1139
|
Duration: 1.23s
|
|
1111
1140
|
|
|
@@ -1123,7 +1152,7 @@ Trace ID: abc-123
|
|
|
1123
1152
|
### JSON Format
|
|
1124
1153
|
|
|
1125
1154
|
```json
|
|
1126
|
-
{"model":"gpt-
|
|
1155
|
+
{"model":"gpt-5.5","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1127
1156
|
```
|
|
1128
1157
|
|
|
1129
1158
|
### Cost Status
|
|
@@ -1136,6 +1165,108 @@ Trace ID: abc-123
|
|
|
1136
1165
|
|
|
1137
1166
|
---
|
|
1138
1167
|
|
|
1168
|
+
## Cost Controls / Enforcement
|
|
1169
|
+
|
|
1170
|
+
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.
|
|
1171
|
+
|
|
1172
|
+
> **Terminology note:** The customer-facing entity is called a **cost control**, served by the backend at `/v2/api/ai/cost-controls`. This SDK polls a separate compiled-rules feed at `/v2/api/ai/enforcement-rules/{teamId}` and is unaffected by changes to the CRUD path — no SDK upgrade is required.
|
|
1173
|
+
|
|
1174
|
+
Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
|
|
1175
|
+
|
|
1176
|
+
### Enable
|
|
1177
|
+
|
|
1178
|
+
```bash
|
|
1179
|
+
pip install 'revenium-python-sdk[openai]'
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
```env
|
|
1183
|
+
REVENIUM_CIRCUIT_BREAKER_ENABLED=true
|
|
1184
|
+
REVENIUM_METERING_API_KEY=hak_your_key_here
|
|
1185
|
+
REVENIUM_TEAM_ID=your_hashed_team_id
|
|
1186
|
+
REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
### Environment Variables
|
|
1190
|
+
|
|
1191
|
+
| Variable | Default | Description |
|
|
1192
|
+
|----------|---------|-------------|
|
|
1193
|
+
| `REVENIUM_CIRCUIT_BREAKER_ENABLED` | `false` | Master switch. `true` / `1` / `yes` / `on` to enable. |
|
|
1194
|
+
| `REVENIUM_BYPASS` | `false` | When `true`, every `check_enforcement` call short-circuits to a no-op. Useful for incident response. |
|
|
1195
|
+
| `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
|
|
1196
|
+
| `REVENIUM_ENFORCEMENT_BASE_URL` | origin of `REVENIUM_METERING_BASE_URL` | Base URL for the enforcement API. Set when the enforcement API lives behind a context-path. |
|
|
1197
|
+
| `REVENIUM_CB_POLL_INTERVAL_SECONDS` | `60` | Background poll interval for rule refreshes. |
|
|
1198
|
+
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
|
|
1199
|
+
| `REVENIUM_CACHE_DIR` | — | When set, the rule cache is mirrored to `<dir>/revenium_enforcement_rules.json` so a restarted process doesn't fail-closed on the very first call. |
|
|
1200
|
+
|
|
1201
|
+
### Public API
|
|
1202
|
+
|
|
1203
|
+
Enforcement auto-initializes when the OpenAI middleware loads:
|
|
1204
|
+
|
|
1205
|
+
```python
|
|
1206
|
+
import revenium_middleware.openai # auto-instruments openai
|
|
1207
|
+
import openai
|
|
1208
|
+
|
|
1209
|
+
client = openai.OpenAI()
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
The pre-call check fires before every chat / embeddings / responses call. When the circuit breaker is disabled, it is a no-op. When enabled:
|
|
1213
|
+
|
|
1214
|
+
1. A daemon thread (`revenium-enforcement-poll`) starts on first use.
|
|
1215
|
+
2. It polls `GET {REVENIUM_ENFORCEMENT_BASE_URL}/v2/api/ai/enforcement-rules/{REVENIUM_TEAM_ID}` every `REVENIUM_CB_POLL_INTERVAL_SECONDS` with the `x-api-key` header.
|
|
1216
|
+
3. Rules are cached in-process (120 s TTL, refresh-on-stale with thundering-herd guard).
|
|
1217
|
+
4. `204 No Content` is treated as "no rules configured" — the cache is cleared.
|
|
1218
|
+
|
|
1219
|
+
### Exception Contract
|
|
1220
|
+
|
|
1221
|
+
```python
|
|
1222
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
When a tripped rule matches the current request, the middleware raises before the OpenAI call is made. All structured fields are populated when the server provides them:
|
|
1226
|
+
|
|
1227
|
+
| Attribute | Type | Description |
|
|
1228
|
+
|-----------|------|-------------|
|
|
1229
|
+
| `message` | `str` | Human-readable reason, e.g. `"Request blocked by Revenium enforcement rule: monthly-gpt4-cap"` |
|
|
1230
|
+
| `rule_name` | `str \| None` | Server-side rule name |
|
|
1231
|
+
| `current_value` | `float \| None` | Current metric value at the time of the block |
|
|
1232
|
+
| `threshold` | `float \| None` | Configured limit |
|
|
1233
|
+
| `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
|
|
1234
|
+
| `rule_id` | `str \| int \| None` | Server-side rule identifier |
|
|
1235
|
+
|
|
1236
|
+
`BudgetExceededError` does **not** inherit from `ReveniumMiddlewareError`, so the OpenAI middleware's `handle_exception_safely` decorator never swallows it — it always reaches your `except` block.
|
|
1237
|
+
|
|
1238
|
+
```python
|
|
1239
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1240
|
+
import openai
|
|
1241
|
+
|
|
1242
|
+
client = openai.OpenAI()
|
|
1243
|
+
|
|
1244
|
+
try:
|
|
1245
|
+
response = client.chat.completions.create(
|
|
1246
|
+
model="gpt-5.5",
|
|
1247
|
+
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1248
|
+
)
|
|
1249
|
+
except BudgetExceededError as exc:
|
|
1250
|
+
print(f"Cost limit reached: {exc.message}")
|
|
1251
|
+
print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
### Fail-Open vs Fail-Closed
|
|
1255
|
+
|
|
1256
|
+
By default (`REVENIUM_CB_FAIL_MODE=open`) enforcement failures never propagate to user code. If the rule fetch errors (network, 5xx, auth), the previous in-memory cache is preserved and a debug log line is emitted. If there is no cache yet, enforcement behaves as if no rules are configured and the request continues.
|
|
1257
|
+
|
|
1258
|
+
Set `REVENIUM_CB_FAIL_MODE=closed` to refuse calls until at least one rule fetch (or `REVENIUM_CACHE_DIR` snapshot) succeeds. Pair with `REVENIUM_CACHE_DIR` so a process restart loads the last-known rules rather than blocking every call until the first poll completes.
|
|
1259
|
+
|
|
1260
|
+
### Shadow Mode
|
|
1261
|
+
|
|
1262
|
+
Rules with `shadowMode: true` are observe-and-log: they are skipped by `check_enforcement`. Use shadow mode on the server side to audit a rule before flipping it to enforce.
|
|
1263
|
+
|
|
1264
|
+
### End-to-End Example
|
|
1265
|
+
|
|
1266
|
+
See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_demo.py) for a runnable end-to-end demo using a seeded budget rule.
|
|
1267
|
+
|
|
1268
|
+
---
|
|
1269
|
+
|
|
1139
1270
|
## Configuration Reference
|
|
1140
1271
|
|
|
1141
1272
|
### Required Environment Variables
|
|
@@ -1238,6 +1369,12 @@ Available log levels:
|
|
|
1238
1369
|
|
|
1239
1370
|
For detailed documentation, visit [docs.revenium.io](https://docs.revenium.io)
|
|
1240
1371
|
|
|
1372
|
+
### Server-Side Cost Controls
|
|
1373
|
+
|
|
1374
|
+
Cost controls (spend limits, throttling, alerts) are managed server-side in Revenium, not in this SDK. The SDK reports usage; Revenium evaluates it against your configured cost controls.
|
|
1375
|
+
|
|
1376
|
+
The cost-controls API endpoint is `/v2/api/ai/cost-controls`. This Python SDK does not call the endpoint directly — no SDK changes are required to use cost controls. If you manage cost controls via the Revenium API, HTTP client, or `curl`, see [docs.revenium.io](https://docs.revenium.io) for the current API reference.
|
|
1377
|
+
|
|
1241
1378
|
## Contributing
|
|
1242
1379
|
|
|
1243
1380
|
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|