revenium-python-sdk 0.1.4__tar.gz → 0.1.6__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.6}/PKG-INFO +46 -72
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/README.md +45 -71
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/pyproject.toml +1 -1
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/__init__.py +6 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/__init__.py +2 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/config.py +2 -41
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/enforcement.py +5 -5
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/exceptions.py +11 -3
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/fields.py +28 -6
- revenium_python_sdk-0.1.6/revenium_middleware/agentic_outcomes.py +348 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/bedrock_adapter.py +5 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/config.py +0 -3
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/middleware.py +0 -75
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/__init__.py +0 -14
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/trace_fields.py +3 -11
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/utils.py +0 -66
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/config.py +0 -1
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/config.py +0 -16
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/middleware.py +0 -20
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/__init__.py +2 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/config.py +0 -3
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/exceptions.py +5 -2
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/middleware.py +6 -32
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6/revenium_python_sdk.egg-info}/PKG-INFO +46 -72
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_python_sdk.egg-info/SOURCES.txt +1 -4
- revenium_python_sdk-0.1.4/revenium_middleware/anthropic/summary_printer.py +0 -286
- revenium_python_sdk-0.1.4/revenium_middleware/google/common/summary_printer.py +0 -271
- revenium_python_sdk-0.1.4/revenium_middleware/litellm/client/summary_printer.py +0 -314
- revenium_python_sdk-0.1.4/revenium_middleware/openai/summary_printer.py +0 -292
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/LICENSE +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/context.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/metering.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/fal/_metering.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/litellm/proxy/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/ollama/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/perplexity/middleware.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/setup.cfg +0 -0
- {revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/tests/test_metering.py +0 -0
{revenium_python_sdk-0.1.4/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.6}/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.6
|
|
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
|
|
@@ -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
|
|
@@ -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` |
|
|
@@ -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-4o-mini", 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-4o-mini", callbacks=[handler])
|
|
719
748
|
response = await llm.ainvoke("Hello!")
|
|
720
749
|
```
|
|
721
750
|
|
|
@@ -783,7 +812,7 @@ response = client.chat.completions.create(
|
|
|
783
812
|
)
|
|
784
813
|
```
|
|
785
814
|
|
|
786
|
-
**Deprecation notice:** The
|
|
815
|
+
**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.
|
|
787
816
|
|
|
788
817
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
789
818
|
|
|
@@ -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-4o",
|
|
996
|
+
model="gpt-4o-mini",
|
|
968
997
|
messages=[{"role": "user", "content": prompt}]
|
|
969
998
|
)
|
|
970
999
|
return response.choices[0].message.content
|
|
@@ -1080,65 +1109,11 @@ Prompt capture works with both streaming and non-streaming requests, and with mu
|
|
|
1080
1109
|
|
|
1081
1110
|
---
|
|
1082
1111
|
|
|
1083
|
-
## Terminal Summary Output
|
|
1084
|
-
|
|
1085
|
-
Display a cost and usage summary in your terminal after each API request. Useful for development, debugging, and monitoring AI costs in real-time.
|
|
1086
|
-
|
|
1087
|
-
### Configuration
|
|
1088
|
-
|
|
1089
|
-
| Environment Variable | Values | Description |
|
|
1090
|
-
|---------------------|--------|-------------|
|
|
1091
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` (default), `true` or `human`, `json` | Controls output format |
|
|
1092
|
-
| `REVENIUM_TEAM_ID` | Your team ID | Required to fetch and display cost information |
|
|
1093
|
-
|
|
1094
|
-
```bash
|
|
1095
|
-
# Enable human-readable output
|
|
1096
|
-
export REVENIUM_PRINT_SUMMARY=human
|
|
1097
|
-
|
|
1098
|
-
# Required for cost display (find in Revenium web app)
|
|
1099
|
-
export REVENIUM_TEAM_ID=your-team-id-here
|
|
1100
|
-
```
|
|
1101
|
-
|
|
1102
|
-
### Human-Readable Format
|
|
1103
|
-
|
|
1104
|
-
```
|
|
1105
|
-
============================================================
|
|
1106
|
-
REVENIUM USAGE SUMMARY
|
|
1107
|
-
============================================================
|
|
1108
|
-
Model: gpt-4o-mini
|
|
1109
|
-
Provider: OPENAI
|
|
1110
|
-
Duration: 1.23s
|
|
1111
|
-
|
|
1112
|
-
Token Usage:
|
|
1113
|
-
Input Tokens: 150
|
|
1114
|
-
Output Tokens: 250
|
|
1115
|
-
Total Tokens: 400
|
|
1116
|
-
|
|
1117
|
-
Cost: $0.000045
|
|
1118
|
-
|
|
1119
|
-
Trace ID: abc-123
|
|
1120
|
-
============================================================
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
### JSON Format
|
|
1124
|
-
|
|
1125
|
-
```json
|
|
1126
|
-
{"model":"gpt-4o-mini","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
### Cost Status
|
|
1130
|
-
|
|
1131
|
-
| Scenario | Display |
|
|
1132
|
-
|----------|---------|
|
|
1133
|
-
| Cost available | `$0.000045` |
|
|
1134
|
-
| `REVENIUM_TEAM_ID` set, cost pending | `Pending (aggregating... check Revenium dashboard)` |
|
|
1135
|
-
| `REVENIUM_TEAM_ID` not set | `Add REVENIUM_TEAM_ID to see pricing` |
|
|
1136
|
-
|
|
1137
|
-
---
|
|
1138
|
-
|
|
1139
1112
|
## Cost Controls / Enforcement
|
|
1140
1113
|
|
|
1141
|
-
Block outbound provider requests client-side when a Revenium cost
|
|
1114
|
+
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.
|
|
1115
|
+
|
|
1116
|
+
> **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
1117
|
|
|
1143
1118
|
Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
|
|
1144
1119
|
|
|
@@ -1164,7 +1139,7 @@ REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
|
|
|
1164
1139
|
| `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
|
|
1165
1140
|
| `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
1141
|
| `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 `
|
|
1142
|
+
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
|
|
1168
1143
|
| `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
1144
|
|
|
1170
1145
|
### Public API
|
|
@@ -1188,7 +1163,7 @@ The pre-call check fires before every chat / embeddings / responses call. When t
|
|
|
1188
1163
|
### Exception Contract
|
|
1189
1164
|
|
|
1190
1165
|
```python
|
|
1191
|
-
from revenium_middleware.openai import
|
|
1166
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1192
1167
|
```
|
|
1193
1168
|
|
|
1194
1169
|
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,10 +1177,10 @@ When a tripped rule matches the current request, the middleware raises before th
|
|
|
1202
1177
|
| `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
|
|
1203
1178
|
| `rule_id` | `str \| int \| None` | Server-side rule identifier |
|
|
1204
1179
|
|
|
1205
|
-
`
|
|
1180
|
+
`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
1181
|
|
|
1207
1182
|
```python
|
|
1208
|
-
from revenium_middleware.openai import
|
|
1183
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1209
1184
|
import openai
|
|
1210
1185
|
|
|
1211
1186
|
client = openai.OpenAI()
|
|
@@ -1215,7 +1190,7 @@ try:
|
|
|
1215
1190
|
model="gpt-4o-mini",
|
|
1216
1191
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1217
1192
|
)
|
|
1218
|
-
except
|
|
1193
|
+
except BudgetExceededError as exc:
|
|
1219
1194
|
print(f"Cost limit reached: {exc.message}")
|
|
1220
1195
|
print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
|
|
1221
1196
|
```
|
|
@@ -1251,9 +1226,8 @@ See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_
|
|
|
1251
1226
|
| `REVENIUM_METERING_BASE_URL` | `https://api.revenium.ai` | Revenium API endpoint |
|
|
1252
1227
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1253
1228
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1254
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` | Terminal output: `false`, `true`/`human`, `json` |
|
|
1255
1229
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1256
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost
|
|
1230
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1257
1231
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1258
1232
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1259
1233
|
| `REVENIUM_CREDENTIAL_ALIAS` | - | Human-readable API key name |
|
|
@@ -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
|
|
@@ -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` |
|
|
@@ -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-4o-mini", 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-4o-mini", callbacks=[handler])
|
|
631
660
|
response = await llm.ainvoke("Hello!")
|
|
632
661
|
```
|
|
633
662
|
|
|
@@ -695,7 +724,7 @@ response = client.chat.completions.create(
|
|
|
695
724
|
)
|
|
696
725
|
```
|
|
697
726
|
|
|
698
|
-
**Deprecation notice:** The
|
|
727
|
+
**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.
|
|
699
728
|
|
|
700
729
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
701
730
|
|
|
@@ -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-4o",
|
|
908
|
+
model="gpt-4o-mini",
|
|
880
909
|
messages=[{"role": "user", "content": prompt}]
|
|
881
910
|
)
|
|
882
911
|
return response.choices[0].message.content
|
|
@@ -992,65 +1021,11 @@ Prompt capture works with both streaming and non-streaming requests, and with mu
|
|
|
992
1021
|
|
|
993
1022
|
---
|
|
994
1023
|
|
|
995
|
-
## Terminal Summary Output
|
|
996
|
-
|
|
997
|
-
Display a cost and usage summary in your terminal after each API request. Useful for development, debugging, and monitoring AI costs in real-time.
|
|
998
|
-
|
|
999
|
-
### Configuration
|
|
1000
|
-
|
|
1001
|
-
| Environment Variable | Values | Description |
|
|
1002
|
-
|---------------------|--------|-------------|
|
|
1003
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` (default), `true` or `human`, `json` | Controls output format |
|
|
1004
|
-
| `REVENIUM_TEAM_ID` | Your team ID | Required to fetch and display cost information |
|
|
1005
|
-
|
|
1006
|
-
```bash
|
|
1007
|
-
# Enable human-readable output
|
|
1008
|
-
export REVENIUM_PRINT_SUMMARY=human
|
|
1009
|
-
|
|
1010
|
-
# Required for cost display (find in Revenium web app)
|
|
1011
|
-
export REVENIUM_TEAM_ID=your-team-id-here
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
### Human-Readable Format
|
|
1015
|
-
|
|
1016
|
-
```
|
|
1017
|
-
============================================================
|
|
1018
|
-
REVENIUM USAGE SUMMARY
|
|
1019
|
-
============================================================
|
|
1020
|
-
Model: gpt-4o-mini
|
|
1021
|
-
Provider: OPENAI
|
|
1022
|
-
Duration: 1.23s
|
|
1023
|
-
|
|
1024
|
-
Token Usage:
|
|
1025
|
-
Input Tokens: 150
|
|
1026
|
-
Output Tokens: 250
|
|
1027
|
-
Total Tokens: 400
|
|
1028
|
-
|
|
1029
|
-
Cost: $0.000045
|
|
1030
|
-
|
|
1031
|
-
Trace ID: abc-123
|
|
1032
|
-
============================================================
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
### JSON Format
|
|
1036
|
-
|
|
1037
|
-
```json
|
|
1038
|
-
{"model":"gpt-4o-mini","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
### Cost Status
|
|
1042
|
-
|
|
1043
|
-
| Scenario | Display |
|
|
1044
|
-
|----------|---------|
|
|
1045
|
-
| Cost available | `$0.000045` |
|
|
1046
|
-
| `REVENIUM_TEAM_ID` set, cost pending | `Pending (aggregating... check Revenium dashboard)` |
|
|
1047
|
-
| `REVENIUM_TEAM_ID` not set | `Add REVENIUM_TEAM_ID to see pricing` |
|
|
1048
|
-
|
|
1049
|
-
---
|
|
1050
|
-
|
|
1051
1024
|
## Cost Controls / Enforcement
|
|
1052
1025
|
|
|
1053
|
-
Block outbound provider requests client-side when a Revenium cost
|
|
1026
|
+
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.
|
|
1027
|
+
|
|
1028
|
+
> **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
1029
|
|
|
1055
1030
|
Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
|
|
1056
1031
|
|
|
@@ -1076,7 +1051,7 @@ REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
|
|
|
1076
1051
|
| `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
|
|
1077
1052
|
| `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
1053
|
| `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 `
|
|
1054
|
+
| `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
|
|
1080
1055
|
| `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
1056
|
|
|
1082
1057
|
### Public API
|
|
@@ -1100,7 +1075,7 @@ The pre-call check fires before every chat / embeddings / responses call. When t
|
|
|
1100
1075
|
### Exception Contract
|
|
1101
1076
|
|
|
1102
1077
|
```python
|
|
1103
|
-
from revenium_middleware.openai import
|
|
1078
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1104
1079
|
```
|
|
1105
1080
|
|
|
1106
1081
|
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,10 +1089,10 @@ When a tripped rule matches the current request, the middleware raises before th
|
|
|
1114
1089
|
| `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
|
|
1115
1090
|
| `rule_id` | `str \| int \| None` | Server-side rule identifier |
|
|
1116
1091
|
|
|
1117
|
-
`
|
|
1092
|
+
`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
1093
|
|
|
1119
1094
|
```python
|
|
1120
|
-
from revenium_middleware.openai import
|
|
1095
|
+
from revenium_middleware.openai import BudgetExceededError
|
|
1121
1096
|
import openai
|
|
1122
1097
|
|
|
1123
1098
|
client = openai.OpenAI()
|
|
@@ -1127,7 +1102,7 @@ try:
|
|
|
1127
1102
|
model="gpt-4o-mini",
|
|
1128
1103
|
messages=[{"role": "user", "content": "Summarize the meeting notes"}],
|
|
1129
1104
|
)
|
|
1130
|
-
except
|
|
1105
|
+
except BudgetExceededError as exc:
|
|
1131
1106
|
print(f"Cost limit reached: {exc.message}")
|
|
1132
1107
|
print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
|
|
1133
1108
|
```
|
|
@@ -1163,9 +1138,8 @@ See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_
|
|
|
1163
1138
|
| `REVENIUM_METERING_BASE_URL` | `https://api.revenium.ai` | Revenium API endpoint |
|
|
1164
1139
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1165
1140
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1166
|
-
| `REVENIUM_PRINT_SUMMARY` | `false` | Terminal output: `false`, `true`/`human`, `json` |
|
|
1167
1141
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1168
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost
|
|
1142
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1169
1143
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1170
1144
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1171
1145
|
| `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.6"
|
|
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.6}/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",
|
|
@@ -8,13 +8,10 @@ this via class inheritance and re-export symbols for backward compatibility.
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
-
from typing import Set,
|
|
11
|
+
from typing import Set, Optional
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
|
-
# Type alias for summary format
|
|
16
|
-
SummaryFormat = Literal["human", "json"]
|
|
17
|
-
|
|
18
15
|
|
|
19
16
|
class Config:
|
|
20
17
|
"""Shared configuration constants for all Revenium middleware providers."""
|
|
@@ -65,13 +62,9 @@ class Config:
|
|
|
65
62
|
CAPTURE_PROMPTS: bool = False
|
|
66
63
|
MAX_PROMPT_LENGTH: int = 50_000 # Maximum characters per prompt field
|
|
67
64
|
|
|
68
|
-
#
|
|
69
|
-
ENV_REVENIUM_PRINT_SUMMARY: str = "REVENIUM_PRINT_SUMMARY"
|
|
65
|
+
# Revenium API targeting
|
|
70
66
|
ENV_REVENIUM_TEAM_ID: str = "REVENIUM_TEAM_ID"
|
|
71
67
|
ENV_REVENIUM_BASE_URL: str = "REVENIUM_METERING_BASE_URL"
|
|
72
|
-
SUMMARY_RETRY_ATTEMPTS: int = 3
|
|
73
|
-
SUMMARY_RETRY_DELAY: float = 2.0
|
|
74
|
-
SUMMARY_API_TIMEOUT: float = 5.0
|
|
75
68
|
DEFAULT_BASE_URL: str = "https://api.revenium.ai"
|
|
76
69
|
|
|
77
70
|
# Enforcement / circuit breaker — see _core/enforcement.py.
|
|
@@ -139,38 +132,6 @@ def get_timeout_config() -> dict:
|
|
|
139
132
|
}
|
|
140
133
|
|
|
141
134
|
|
|
142
|
-
def parse_print_summary_value(value: Optional[str]) -> Union[bool, SummaryFormat]:
|
|
143
|
-
"""
|
|
144
|
-
Parse REVENIUM_PRINT_SUMMARY environment variable value.
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
False if disabled, 'human' or 'json' if enabled
|
|
148
|
-
"""
|
|
149
|
-
if value is None:
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
value_lower = value.lower().strip()
|
|
153
|
-
|
|
154
|
-
if value_lower in ('false', '0', 'no', 'off', 'disabled', ''):
|
|
155
|
-
return False
|
|
156
|
-
elif value_lower in ('true', '1', 'yes', 'on', 'enabled', 'human'):
|
|
157
|
-
return 'human'
|
|
158
|
-
elif value_lower == 'json':
|
|
159
|
-
return 'json'
|
|
160
|
-
else:
|
|
161
|
-
logger.warning(
|
|
162
|
-
f"Invalid REVENIUM_PRINT_SUMMARY value '{value}'. "
|
|
163
|
-
f"Expected 'true', 'human', 'json', or 'false'. Defaulting to disabled."
|
|
164
|
-
)
|
|
165
|
-
return False
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def get_print_summary_config() -> Union[bool, SummaryFormat]:
|
|
169
|
-
"""Get print summary configuration from environment."""
|
|
170
|
-
value = get_config_value(Config.ENV_REVENIUM_PRINT_SUMMARY)
|
|
171
|
-
return parse_print_summary_value(value)
|
|
172
|
-
|
|
173
|
-
|
|
174
135
|
def get_team_id() -> Optional[str]:
|
|
175
136
|
"""Get Revenium team ID from environment."""
|
|
176
137
|
return get_config_value(Config.ENV_REVENIUM_TEAM_ID)
|
{revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/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")),
|
{revenium_python_sdk-0.1.4 → revenium_python_sdk-0.1.6}/revenium_middleware/_core/exceptions.py
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Core exceptions shared across all Revenium middleware providers.
|
|
3
3
|
|
|
4
|
-
The unified SDK ships ``
|
|
4
|
+
The unified SDK ships ``BudgetExceededError`` from ``_core`` so every
|
|
5
5
|
provider subpackage (openai, anthropic, google, …) raises the same exception
|
|
6
|
-
type and downstream callers can ``except
|
|
6
|
+
type and downstream callers can ``except BudgetExceededError`` once.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from typing import Optional, Union
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class BudgetExceededError(Exception):
|
|
13
13
|
"""Raised when a Revenium enforcement rule blocks the outbound request.
|
|
14
14
|
|
|
15
15
|
Inherits directly from ``Exception`` (not from any middleware-error base)
|
|
@@ -33,3 +33,11 @@ class ReveniumCostLimitExceeded(Exception):
|
|
|
33
33
|
self.threshold = threshold
|
|
34
34
|
self.resets_at = resets_at
|
|
35
35
|
self.rule_id = rule_id
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Deprecated alias preserved for backward compatibility. The exception was
|
|
39
|
+
# renamed in v0.1.5 to align with the Go and Node SDKs and the backend
|
|
40
|
+
# `BudgetExceededException`. Existing code that does
|
|
41
|
+
# `except ReveniumCostLimitExceeded:` continues to catch the new exception
|
|
42
|
+
# unchanged. Plan to remove in a future major release.
|
|
43
|
+
ReveniumCostLimitExceeded = BudgetExceededError
|