revenium-python-sdk 0.1.4__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.4/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.5}/PKG-INFO +60 -29
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/README.md +59 -28
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/pyproject.toml +1 -1
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/__init__.py +6 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/__init__.py +2 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/enforcement.py +5 -5
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/exceptions.py +11 -3
- revenium_python_sdk-0.1.5/revenium_middleware/agentic_outcomes.py +348 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/bedrock_adapter.py +5 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/__init__.py +2 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/exceptions.py +5 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/middleware.py +6 -6
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5/revenium_python_sdk.egg-info}/PKG-INFO +60 -29
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/SOURCES.txt +1 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/LICENSE +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/context.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/metering.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/_metering.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/utils.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/summary_printer.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/setup.cfg +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/tests/test_metering.py +0 -0
{revenium_python_sdk-0.1.4/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
|
|
@@ -1138,7 +1167,9 @@ Trace ID: abc-123
|
|
|
1138
1167
|
|
|
1139
1168
|
## Cost Controls / Enforcement
|
|
1140
1169
|
|
|
1141
|
-
Block outbound provider requests client-side when a Revenium cost
|
|
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.
|
|
1142
1173
|
|
|
1143
1174
|
Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
|
|
1144
1175
|
|
|
@@ -1164,7 +1195,7 @@ REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
|
|
|
1164
1195
|
| `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
|
|
1165
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. |
|
|
1166
1197
|
| `REVENIUM_CB_POLL_INTERVAL_SECONDS` | `60` | Background poll interval for rule refreshes. |
|
|
1167
|
-
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `
|
|
1198
|
+
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
|
|
1168
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. |
|
|
1169
1200
|
|
|
1170
1201
|
### Public API
|
|
@@ -1188,7 +1219,7 @@ The pre-call check fires before every chat / embeddings / responses call. When t
|
|
|
1188
1219
|
### Exception Contract
|
|
1189
1220
|
|
|
1190
1221
|
```python
|
|
1191
|
-
from revenium_middleware.openai import
|
|
1222
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1192
1223
|
```
|
|
1193
1224
|
|
|
1194
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:
|
|
@@ -1202,20 +1233,20 @@ When a tripped rule matches the current request, the middleware raises before th
|
|
|
1202
1233
|
| `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
|
|
1203
1234
|
| `rule_id` | `str \| int \| None` | Server-side rule identifier |
|
|
1204
1235
|
|
|
1205
|
-
`
|
|
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.
|
|
1206
1237
|
|
|
1207
1238
|
```python
|
|
1208
|
-
from revenium_middleware.openai import
|
|
1239
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1209
1240
|
import openai
|
|
1210
1241
|
|
|
1211
1242
|
client = openai.OpenAI()
|
|
1212
1243
|
|
|
1213
1244
|
try:
|
|
1214
1245
|
response = client.chat.completions.create(
|
|
1215
|
-
model="gpt-
|
|
1246
|
+
model="gpt-5.5",
|
|
1216
1247
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1217
1248
|
)
|
|
1218
|
-
except
|
|
1249
|
+
except BudgetExceededError as exc:
|
|
1219
1250
|
print(f"Cost limit reached: {exc.message}")
|
|
1220
1251
|
print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
|
|
1221
1252
|
```
|
|
@@ -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-5.5",
|
|
110
110
|
messages=[{"role": "user", "content": "Hello!"}]
|
|
111
111
|
)
|
|
112
112
|
print(response.choices[0].message.content)
|
|
@@ -115,6 +115,30 @@ print(response.choices[0].message.content)
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
+
## Agentic Outcomes (Outcome-Based Metering)
|
|
119
|
+
|
|
120
|
+
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from revenium_middleware.agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings
|
|
124
|
+
|
|
125
|
+
settings = AgenticOutcomeSettings(api_key="rev_sk_...")
|
|
126
|
+
client = AgenticOutcomeClient(settings)
|
|
127
|
+
|
|
128
|
+
client.emit_completion(...) # one per LLM call
|
|
129
|
+
client.emit_tool_event(...) # one per tool / step
|
|
130
|
+
client.report_outcome(job_id, {...}) # close the job with a terminal outcome
|
|
131
|
+
client.close()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
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.
|
|
135
|
+
|
|
136
|
+
See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demos (sales / coding / support) with configurable failure rates and outcome distributions.
|
|
137
|
+
|
|
138
|
+
**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).
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
118
142
|
## Provider Usage Guides
|
|
119
143
|
|
|
120
144
|
### OpenAI
|
|
@@ -132,7 +156,7 @@ client = openai.OpenAI()
|
|
|
132
156
|
|
|
133
157
|
# Basic chat completion
|
|
134
158
|
response = client.chat.completions.create(
|
|
135
|
-
model="gpt-
|
|
159
|
+
model="gpt-5.5",
|
|
136
160
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
137
161
|
usage_metadata={
|
|
138
162
|
"organizationName": "AcmeCorp",
|
|
@@ -144,7 +168,7 @@ response = client.chat.completions.create(
|
|
|
144
168
|
|
|
145
169
|
# Streaming
|
|
146
170
|
stream = client.chat.completions.create(
|
|
147
|
-
model="gpt-
|
|
171
|
+
model="gpt-5.5",
|
|
148
172
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
149
173
|
stream=True
|
|
150
174
|
)
|
|
@@ -204,7 +228,7 @@ client = anthropic.Anthropic()
|
|
|
204
228
|
|
|
205
229
|
# Basic message
|
|
206
230
|
message = client.messages.create(
|
|
207
|
-
model="claude-
|
|
231
|
+
model="claude-opus-4-7",
|
|
208
232
|
max_tokens=100,
|
|
209
233
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
210
234
|
usage_metadata={
|
|
@@ -216,7 +240,7 @@ message = client.messages.create(
|
|
|
216
240
|
|
|
217
241
|
# Streaming
|
|
218
242
|
with client.messages.stream(
|
|
219
|
-
model="claude-
|
|
243
|
+
model="claude-opus-4-7",
|
|
220
244
|
max_tokens=200,
|
|
221
245
|
messages=[{"role": "user", "content": "Tell me a story"}],
|
|
222
246
|
usage_metadata={"task_type": "creative"}
|
|
@@ -242,7 +266,7 @@ client = anthropic.AnthropicBedrock(
|
|
|
242
266
|
)
|
|
243
267
|
|
|
244
268
|
message = client.messages.create(
|
|
245
|
-
model="
|
|
269
|
+
model="claude-opus-4-7",
|
|
246
270
|
max_tokens=100,
|
|
247
271
|
messages=[{"role": "user", "content": "Hello from Bedrock!"}]
|
|
248
272
|
)
|
|
@@ -266,6 +290,11 @@ message = client.messages.create(
|
|
|
266
290
|
|
|
267
291
|
| Anthropic Model | Bedrock Model ID |
|
|
268
292
|
|----------------|------------------|
|
|
293
|
+
| `claude-opus-4-7` | `anthropic.claude-opus-4-7` |
|
|
294
|
+
| `us.claude-opus-4-7` | `us.anthropic.claude-opus-4-7` |
|
|
295
|
+
| `eu.claude-opus-4-7` | `eu.anthropic.claude-opus-4-7` |
|
|
296
|
+
| `au.claude-opus-4-7` | `au.anthropic.claude-opus-4-7` |
|
|
297
|
+
| `global.claude-opus-4-7` | `global.anthropic.claude-opus-4-7` |
|
|
269
298
|
| `claude-3-opus-20240229` | `anthropic.claude-3-opus-20240229-v1:0` |
|
|
270
299
|
| `claude-3-sonnet-20240229` | `anthropic.claude-3-sonnet-20240229-v1:0` |
|
|
271
300
|
| `claude-3-haiku-20240307` | `us.anthropic.claude-3-5-haiku-20241022-v1:0` |
|
|
@@ -430,7 +459,7 @@ litellm.api_base = os.getenv("LITELLM_PROXY_URL")
|
|
|
430
459
|
litellm.api_key = os.getenv("LITELLM_API_KEY")
|
|
431
460
|
|
|
432
461
|
response = litellm.completion(
|
|
433
|
-
model="gpt-
|
|
462
|
+
model="gpt-5.5",
|
|
434
463
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
435
464
|
usage_metadata={
|
|
436
465
|
"organizationName": "AcmeCorp",
|
|
@@ -588,7 +617,7 @@ handler = ReveniumCallbackHandler(
|
|
|
588
617
|
agent_name="support_agent"
|
|
589
618
|
)
|
|
590
619
|
|
|
591
|
-
llm = ChatOpenAI(model="gpt-
|
|
620
|
+
llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
|
|
592
621
|
response = llm.invoke("Hello!")
|
|
593
622
|
```
|
|
594
623
|
|
|
@@ -627,7 +656,7 @@ result = agent.invoke(
|
|
|
627
656
|
from revenium_middleware_langchain import AsyncReveniumCallbackHandler
|
|
628
657
|
|
|
629
658
|
handler = AsyncReveniumCallbackHandler(trace_id="async-session")
|
|
630
|
-
llm = ChatOpenAI(model="gpt-
|
|
659
|
+
llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
|
|
631
660
|
response = await llm.ainvoke("Hello!")
|
|
632
661
|
```
|
|
633
662
|
|
|
@@ -673,7 +702,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
673
702
|
|
|
674
703
|
```python
|
|
675
704
|
response = client.chat.completions.create(
|
|
676
|
-
model="gpt-
|
|
705
|
+
model="gpt-5.5",
|
|
677
706
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
678
707
|
usage_metadata={
|
|
679
708
|
"trace_id": "conv-28a7e9d4",
|
|
@@ -736,7 +765,7 @@ REVENIUM_TRACE_TYPE=customer-support
|
|
|
736
765
|
|
|
737
766
|
```python
|
|
738
767
|
response = client.chat.completions.create(
|
|
739
|
-
model="gpt-
|
|
768
|
+
model="gpt-5.5",
|
|
740
769
|
messages=[{"role": "user", "content": "Hello!"}],
|
|
741
770
|
usage_metadata={
|
|
742
771
|
"environment": "production",
|
|
@@ -760,7 +789,7 @@ workflow_id = str(uuid.uuid4())
|
|
|
760
789
|
|
|
761
790
|
# Step 1: Parent operation
|
|
762
791
|
parent_response = client.chat.completions.create(
|
|
763
|
-
model="gpt-
|
|
792
|
+
model="gpt-5.5",
|
|
764
793
|
messages=[{"role": "user", "content": "Analyze this document"}],
|
|
765
794
|
usage_metadata={
|
|
766
795
|
"trace_id": "analysis-session-456",
|
|
@@ -771,7 +800,7 @@ parent_response = client.chat.completions.create(
|
|
|
771
800
|
|
|
772
801
|
# Step 2: Child operation linked to parent
|
|
773
802
|
child_response = client.chat.completions.create(
|
|
774
|
-
model="gpt-
|
|
803
|
+
model="gpt-5.5",
|
|
775
804
|
messages=[{"role": "user", "content": "Summarize findings"}],
|
|
776
805
|
usage_metadata={
|
|
777
806
|
"trace_id": "analysis-session-456",
|
|
@@ -802,7 +831,7 @@ from revenium_middleware import revenium_metadata
|
|
|
802
831
|
def handle_customer_query(question: str) -> str:
|
|
803
832
|
# All API calls automatically include the decorator metadata
|
|
804
833
|
response = client.chat.completions.create(
|
|
805
|
-
model="gpt-
|
|
834
|
+
model="gpt-5.5",
|
|
806
835
|
messages=[{"role": "user", "content": question}]
|
|
807
836
|
)
|
|
808
837
|
return response.choices[0].message.content
|
|
@@ -841,13 +870,13 @@ def outer_function():
|
|
|
841
870
|
def mixed_metadata():
|
|
842
871
|
# Uses decorator metadata
|
|
843
872
|
response1 = client.chat.completions.create(
|
|
844
|
-
model="gpt-
|
|
873
|
+
model="gpt-5.5",
|
|
845
874
|
messages=[{"role": "user", "content": "Hello"}]
|
|
846
875
|
)
|
|
847
876
|
|
|
848
877
|
# API-level metadata overrides decorator's task_type
|
|
849
878
|
response2 = client.chat.completions.create(
|
|
850
|
-
model="gpt-
|
|
879
|
+
model="gpt-5.5",
|
|
851
880
|
messages=[{"role": "user", "content": "Hello"}],
|
|
852
881
|
usage_metadata={
|
|
853
882
|
"task_type": "special-override", # Overrides decorator
|
|
@@ -876,7 +905,7 @@ from revenium_middleware import revenium_meter, revenium_metadata
|
|
|
876
905
|
def premium_feature(prompt: str) -> str:
|
|
877
906
|
# This WILL be metered (decorated with @revenium_meter)
|
|
878
907
|
response = client.chat.completions.create(
|
|
879
|
-
model="gpt-
|
|
908
|
+
model="gpt-5.5",
|
|
880
909
|
messages=[{"role": "user", "content": prompt}]
|
|
881
910
|
)
|
|
882
911
|
return response.choices[0].message.content
|
|
@@ -884,7 +913,7 @@ def premium_feature(prompt: str) -> str:
|
|
|
884
913
|
def free_feature(prompt: str) -> str:
|
|
885
914
|
# This will NOT be metered (no @revenium_meter decorator)
|
|
886
915
|
response = client.chat.completions.create(
|
|
887
|
-
model="gpt-
|
|
916
|
+
model="gpt-5.5",
|
|
888
917
|
messages=[{"role": "user", "content": prompt}]
|
|
889
918
|
)
|
|
890
919
|
return response.choices[0].message.content
|
|
@@ -970,7 +999,7 @@ from openai import OpenAI
|
|
|
970
999
|
|
|
971
1000
|
client = OpenAI()
|
|
972
1001
|
response = client.chat.completions.create(
|
|
973
|
-
model="gpt-
|
|
1002
|
+
model="gpt-5.5",
|
|
974
1003
|
messages=[
|
|
975
1004
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
976
1005
|
{"role": "user", "content": "What is the capital of France?"}
|
|
@@ -1017,7 +1046,7 @@ export REVENIUM_TEAM_ID=your-team-id-here
|
|
|
1017
1046
|
============================================================
|
|
1018
1047
|
REVENIUM USAGE SUMMARY
|
|
1019
1048
|
============================================================
|
|
1020
|
-
Model: gpt-
|
|
1049
|
+
Model: gpt-5.5
|
|
1021
1050
|
Provider: OPENAI
|
|
1022
1051
|
Duration: 1.23s
|
|
1023
1052
|
|
|
@@ -1035,7 +1064,7 @@ Trace ID: abc-123
|
|
|
1035
1064
|
### JSON Format
|
|
1036
1065
|
|
|
1037
1066
|
```json
|
|
1038
|
-
{"model":"gpt-
|
|
1067
|
+
{"model":"gpt-5.5","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1039
1068
|
```
|
|
1040
1069
|
|
|
1041
1070
|
### Cost Status
|
|
@@ -1050,7 +1079,9 @@ Trace ID: abc-123
|
|
|
1050
1079
|
|
|
1051
1080
|
## Cost Controls / Enforcement
|
|
1052
1081
|
|
|
1053
|
-
Block outbound provider requests client-side when a Revenium cost
|
|
1082
|
+
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.
|
|
1083
|
+
|
|
1084
|
+
> **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.
|
|
1054
1085
|
|
|
1055
1086
|
Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
|
|
1056
1087
|
|
|
@@ -1076,7 +1107,7 @@ REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
|
|
|
1076
1107
|
| `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
|
|
1077
1108
|
| `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. |
|
|
1078
1109
|
| `REVENIUM_CB_POLL_INTERVAL_SECONDS` | `60` | Background poll interval for rule refreshes. |
|
|
1079
|
-
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `
|
|
1110
|
+
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
|
|
1080
1111
|
| `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. |
|
|
1081
1112
|
|
|
1082
1113
|
### Public API
|
|
@@ -1100,7 +1131,7 @@ The pre-call check fires before every chat / embeddings / responses call. When t
|
|
|
1100
1131
|
### Exception Contract
|
|
1101
1132
|
|
|
1102
1133
|
```python
|
|
1103
|
-
from revenium_middleware.openai import
|
|
1134
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1104
1135
|
```
|
|
1105
1136
|
|
|
1106
1137
|
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:
|
|
@@ -1114,20 +1145,20 @@ When a tripped rule matches the current request, the middleware raises before th
|
|
|
1114
1145
|
| `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
|
|
1115
1146
|
| `rule_id` | `str \| int \| None` | Server-side rule identifier |
|
|
1116
1147
|
|
|
1117
|
-
`
|
|
1148
|
+
`BudgetExceededError` does **not** inherit from `ReveniumMiddlewareError`, so the OpenAI middleware's `handle_exception_safely` decorator never swallows it — it always reaches your `except` block.
|
|
1118
1149
|
|
|
1119
1150
|
```python
|
|
1120
|
-
from revenium_middleware.openai import
|
|
1151
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1121
1152
|
import openai
|
|
1122
1153
|
|
|
1123
1154
|
client = openai.OpenAI()
|
|
1124
1155
|
|
|
1125
1156
|
try:
|
|
1126
1157
|
response = client.chat.completions.create(
|
|
1127
|
-
model="gpt-
|
|
1158
|
+
model="gpt-5.5",
|
|
1128
1159
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1129
1160
|
)
|
|
1130
|
-
except
|
|
1161
|
+
except BudgetExceededError as exc:
|
|
1131
1162
|
print(f"Cost limit reached: {exc.message}")
|
|
1132
1163
|
print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
|
|
1133
1164
|
```
|
|
@@ -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.5"
|
|
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"
|
|
@@ -157,6 +157,9 @@ from ._core import ( # noqa: E402
|
|
|
157
157
|
# Re-export tool metering utilities from revenium_metering (v6.8.2+)
|
|
158
158
|
from revenium_metering import meter_tool, report_tool_call, configure # noqa: E402
|
|
159
159
|
|
|
160
|
+
# Agentic-outcome client (used by examples/agentic_outcomes/ pack)
|
|
161
|
+
from .agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings # noqa: E402
|
|
162
|
+
|
|
160
163
|
__all__ = [
|
|
161
164
|
# Metering exports
|
|
162
165
|
"client",
|
|
@@ -181,4 +184,7 @@ __all__ = [
|
|
|
181
184
|
"meter_tool",
|
|
182
185
|
"report_tool_call",
|
|
183
186
|
"configure",
|
|
187
|
+
# Agentic-outcome exports
|
|
188
|
+
"AgenticOutcomeClient",
|
|
189
|
+
"AgenticOutcomeSettings",
|
|
184
190
|
]
|
{revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/__init__.py
RENAMED
|
@@ -6,7 +6,7 @@ provider-specific middleware implementations.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .metering import run_async_in_thread, shutdown_event, client
|
|
9
|
-
from .exceptions import
|
|
9
|
+
from .exceptions import BudgetExceededError
|
|
10
10
|
from .enforcement import check_enforcement, is_circuit_breaker_enabled, stop_polling
|
|
11
11
|
from .context import (
|
|
12
12
|
is_inside_decorated_function,
|
|
@@ -49,7 +49,7 @@ __all__ = [
|
|
|
49
49
|
"run_async_in_thread",
|
|
50
50
|
"shutdown_event",
|
|
51
51
|
# Enforcement / circuit breaker
|
|
52
|
-
"
|
|
52
|
+
"BudgetExceededError",
|
|
53
53
|
"check_enforcement",
|
|
54
54
|
"is_circuit_breaker_enabled",
|
|
55
55
|
"stop_polling",
|
{revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/enforcement.py
RENAMED
|
@@ -3,7 +3,7 @@ Enforcement engine for the Revenium circuit breaker.
|
|
|
3
3
|
|
|
4
4
|
Polls cost-limit rules from the Revenium API in a daemon thread and caches
|
|
5
5
|
them in memory. ``check_enforcement(...)`` is a pre-call hook that raises
|
|
6
|
-
``
|
|
6
|
+
``BudgetExceededError`` when a tripped rule matches the current
|
|
7
7
|
request, blocking the outbound provider call before any spend occurs.
|
|
8
8
|
|
|
9
9
|
Opt-in via ``REVENIUM_CIRCUIT_BREAKER_ENABLED``. Disabled by default so the
|
|
@@ -21,7 +21,7 @@ from urllib.parse import quote, urlparse
|
|
|
21
21
|
import httpx
|
|
22
22
|
|
|
23
23
|
from .config import Config
|
|
24
|
-
from .exceptions import
|
|
24
|
+
from .exceptions import BudgetExceededError
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger("revenium_middleware.extension")
|
|
27
27
|
|
|
@@ -276,7 +276,7 @@ def check_enforcement(usage_metadata: Optional[dict] = None) -> None:
|
|
|
276
276
|
is disabled or no rules are tripped.
|
|
277
277
|
|
|
278
278
|
Raises:
|
|
279
|
-
|
|
279
|
+
BudgetExceededError: when a cost-limit rule blocks the call.
|
|
280
280
|
All structured fields (``rule_name``, ``current_value``,
|
|
281
281
|
``threshold``, ``resets_at``, ``rule_id``) are populated when the
|
|
282
282
|
server provides them.
|
|
@@ -295,7 +295,7 @@ def check_enforcement(usage_metadata: Optional[dict] = None) -> None:
|
|
|
295
295
|
# valid initialized state and must pass through. Use the snapshot taken
|
|
296
296
|
# under _cache_lock so the decision can't see a torn write.
|
|
297
297
|
if _fail_mode_is_closed() and not initialized:
|
|
298
|
-
raise
|
|
298
|
+
raise BudgetExceededError(
|
|
299
299
|
"Request blocked: enforcement cache is uninitialized and "
|
|
300
300
|
"REVENIUM_CB_FAIL_MODE=closed."
|
|
301
301
|
)
|
|
@@ -319,7 +319,7 @@ def check_enforcement(usage_metadata: Optional[dict] = None) -> None:
|
|
|
319
319
|
continue
|
|
320
320
|
|
|
321
321
|
rule_name = rule.get("name", "cost limit")
|
|
322
|
-
raise
|
|
322
|
+
raise BudgetExceededError(
|
|
323
323
|
message=f"Request blocked by Revenium enforcement rule: {rule_name}",
|
|
324
324
|
rule_name=rule_name,
|
|
325
325
|
current_value=_coerce_float(rule.get("currentValue")),
|