revenium-python-sdk 0.5.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/PKG-INFO +76 -3
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/README.md +75 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/pyproject.toml +2 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/__init__.py +17 -0
- revenium_python_sdk-0.7.0/revenium_middleware/_core/cache_tokens.py +109 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/config.py +7 -0
- revenium_python_sdk-0.7.0/revenium_middleware/_core/enforcement.py +817 -0
- revenium_python_sdk-0.7.0/revenium_middleware/_core/fields.py +334 -0
- revenium_python_sdk-0.7.0/revenium_middleware/_core/load_diagnostics.py +68 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering.py +17 -9
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering_buffer.py +42 -2
- revenium_python_sdk-0.7.0/revenium_middleware/_core/metering_status.py +172 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering_submission.py +20 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/decorator.py +77 -4
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/ai.py +742 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_audio_params.py +75 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_completion_params.py +140 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_image_params.py +94 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_video_params.py +95 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/event_create_params.py +2 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/agentic_outcomes.py +16 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/bedrock_adapter.py +8 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/bedrock_transport.py +7 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/middleware.py +32 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/stream_create.py +7 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/_metering.py +32 -4
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/__init__.py +4 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/utils.py +31 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/middleware.py +1 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/job_context.py +72 -20
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/job_history.py +5 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/middleware.py +6 -9
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/proxy/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/proxy/middleware.py +43 -16
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/middleware.py +7 -1
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/__init__.py +3 -2
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/middleware.py +65 -4
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/__init__.py +14 -3
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/middleware.py +4 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/perplexity_sdk.py +4 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/PKG-INFO +76 -3
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/SOURCES.txt +3 -0
- revenium_python_sdk-0.7.0/tests/test_metering.py +955 -0
- revenium_python_sdk-0.5.0/revenium_middleware/_core/enforcement.py +0 -412
- revenium_python_sdk-0.5.0/revenium_middleware/_core/fields.py +0 -138
- revenium_python_sdk-0.5.0/tests/test_metering.py +0 -106
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/LICENSE +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/context.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/exceptions.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/log_sanitize.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/outcomes.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/LICENSE +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_base_client.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_client.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_compat.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_constants.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_exceptions.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_files.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_models.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_qs.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_resource.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_response.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_streaming.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_types.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_logs.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_streams.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_sync.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_transform.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_typing.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_utils.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_version.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/context.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/py.typed +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/apis.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/events.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/_metadata.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/anthropic_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/litellm_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/ollama_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/openai_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/openai_embedding_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/universal_driver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/exceptions.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/webhooks/__init__.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_middleware/webhooks/_verify.py +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.5.0 → revenium_python_sdk-0.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: revenium-python-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -214,6 +214,52 @@ print(response.choices[0].message.content)
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
+
## Metering Error Visibility
|
|
218
|
+
|
|
219
|
+
Metering runs in background threads and never raises into your code path — a
|
|
220
|
+
metering failure will never break your AI calls. To make failures observable
|
|
221
|
+
anyway, the SDK provides two mechanisms:
|
|
222
|
+
|
|
223
|
+
**Subscribe to failures** with a callback:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
import revenium_middleware
|
|
227
|
+
|
|
228
|
+
@revenium_middleware.on_metering_error
|
|
229
|
+
def alert_on_metering_failure(event):
|
|
230
|
+
# event.error -- the exception (e.g. an HTTP 401/500 from Revenium)
|
|
231
|
+
# event.operation -- "completion", "image", "tool", ... (may be None)
|
|
232
|
+
# event.timestamp -- UTC datetime of the failure
|
|
233
|
+
my_monitoring.notify(f"Revenium metering failed: {event.error}")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Callbacks run on the background metering thread; exceptions they raise are
|
|
237
|
+
suppressed and logged, so they can never disrupt your application.
|
|
238
|
+
|
|
239
|
+
Authentication headers (`x-api-key`, `authorization`) on any HTTP
|
|
240
|
+
request/response attached to the exception are redacted before the error is
|
|
241
|
+
exposed to callbacks or `last_error`.
|
|
242
|
+
|
|
243
|
+
**Poll the status counters:**
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
status = revenium_middleware.get_metering_status()
|
|
247
|
+
print(status.success_count) # events delivered successfully
|
|
248
|
+
print(status.error_count) # delivery failures
|
|
249
|
+
print(status.last_error) # most recent exception, or None
|
|
250
|
+
print(status.last_error_at) # UTC datetime of the most recent failure
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`reset_metering_status()` zeroes the counters and clears registered
|
|
254
|
+
callbacks; `remove_metering_error_callback(cb)` unsubscribes a single one.
|
|
255
|
+
|
|
256
|
+
Failures are also logged at **ERROR** level on the `revenium_middleware`
|
|
257
|
+
logger, including HTTP 4xx/5xx responses, a missing
|
|
258
|
+
`REVENIUM_METERING_API_KEY`, and a provider middleware that fails to import
|
|
259
|
+
when the provider's SDK is installed.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
217
263
|
## Agentic Outcomes (Outcome-Based Metering)
|
|
218
264
|
|
|
219
265
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
@@ -265,7 +311,10 @@ history = get_outcome_history("sales-lead-8842")
|
|
|
265
311
|
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
266
312
|
```
|
|
267
313
|
|
|
268
|
-
`amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`), and returns the updated job as a dict.
|
|
314
|
+
`amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`), and returns the updated job as a dict.
|
|
315
|
+
|
|
316
|
+
- **`reason` vs `outcome_reason`:** `reason` is the amendment's own audit justification (why the record changed); `outcome_reason` is the business explanation of why the job failed or was cancelled. Use `outcome_reason` for failure explanations rather than burying them in `metadata` — it is a first-class field on the outcome and is returned on every `get_outcome_history()` row.
|
|
317
|
+
- **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
|
|
269
318
|
|
|
270
319
|
### Outcome Exceptions
|
|
271
320
|
|
|
@@ -733,6 +782,7 @@ litellm_settings:
|
|
|
733
782
|
```
|
|
734
783
|
|
|
735
784
|
When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
|
|
785
|
+
Reasoning effort travels as `x-revenium-effort` on the proxied request.
|
|
736
786
|
|
|
737
787
|
#### LiteLLM Decorators
|
|
738
788
|
|
|
@@ -1025,6 +1075,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
1025
1075
|
| `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
|
|
1026
1076
|
| `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
|
|
1027
1077
|
| `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
|
|
1078
|
+
| `effort` | Reasoning effort level requested of the model (free-form string, max 16 chars, `^[A-Za-z0-9_-]+$`) | Report what share of AI spend is high-effort reasoning, and compare cost per effort level |
|
|
1028
1079
|
|
|
1029
1080
|
**Example:**
|
|
1030
1081
|
|
|
@@ -1047,11 +1098,27 @@ response = client.chat.completions.create(
|
|
|
1047
1098
|
"subscription_id": "pro-plan-Q1",
|
|
1048
1099
|
"productName": "customer-support-chatbot",
|
|
1049
1100
|
"agent": "support-agent",
|
|
1050
|
-
"response_quality_score": 0.92
|
|
1101
|
+
"response_quality_score": 0.92,
|
|
1102
|
+
"effort": "high"
|
|
1051
1103
|
}
|
|
1052
1104
|
)
|
|
1053
1105
|
```
|
|
1054
1106
|
|
|
1107
|
+
**Reasoning effort:** `effort` records how hard the model was asked to think, so
|
|
1108
|
+
high-effort reasoning spend can be separated from the rest. It is a free-form
|
|
1109
|
+
string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
|
|
1110
|
+
`high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
|
|
1111
|
+
never heard of is passed through untouched rather than rejected or rewritten. The
|
|
1112
|
+
Revenium backend owns validation (at most 16 characters, matching
|
|
1113
|
+
`^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
|
|
1114
|
+
of being silently dropped, so it surfaces in the log and in
|
|
1115
|
+
`get_metering_status()`. It is distinct from the reported reasoning token count,
|
|
1116
|
+
which measures the tokens actually spent rather than the level requested.
|
|
1117
|
+
|
|
1118
|
+
Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
|
|
1119
|
+
mode, where per-call attribution travels as request headers rather than
|
|
1120
|
+
`usage_metadata`, send `x-revenium-effort` instead.
|
|
1121
|
+
|
|
1055
1122
|
**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.
|
|
1056
1123
|
|
|
1057
1124
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
@@ -1075,6 +1142,12 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
1075
1142
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
1076
1143
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
1077
1144
|
| `ticket_id` | `REVENIUM_TICKET_ID` | External ticket or issue ID (e.g., Jira, Linear) (max 256 chars) | Attribute AI costs to individual tickets or issues |
|
|
1145
|
+
| `skill_name` | `REVENIUM_SKILL_NAME` | Name of the agent skill that produced the call (max 256 chars) | Attribute AI costs to the skill that generated them |
|
|
1146
|
+
| `skill_source` | `REVENIUM_SKILL_SOURCE` | Where the skill was loaded from — accepted values: `bundled`, `projectSettings`, `userSettings`, `plugin` (case-sensitive) | Classify skill origin in the shared skill catalog |
|
|
1147
|
+
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
1148
|
+
| `skill_plugin_name` | `REVENIUM_SKILL_PLUGIN_NAME` | Plugin providing the skill, when `skill_source` is `plugin` (max 256 chars) | Attribute costs to a specific plugin |
|
|
1149
|
+
| `skill_marketplace_name` | `REVENIUM_SKILL_MARKETPLACE_NAME` | Marketplace the skill or plugin was installed from (max 256 chars) | Track marketplace-sourced skill usage |
|
|
1150
|
+
| `skill_invocation_trigger` | `REVENIUM_SKILL_INVOCATION_TRIGGER` | What triggered the skill (max 32 chars; common values: `user-slash`, `claude-proactive`, `nested-skill`) | Separate user-invoked from proactive skill usage |
|
|
1078
1151
|
|
|
1079
1152
|
**Note:** `operation_type` (e.g., `CHAT`, `EMBED`, `TOOL_CALL`) and `operation_subtype` (e.g., `function_call`, `streaming`) are automatically detected by the middleware and cannot be overridden.
|
|
1080
1153
|
|
|
@@ -117,6 +117,52 @@ print(response.choices[0].message.content)
|
|
|
117
117
|
|
|
118
118
|
---
|
|
119
119
|
|
|
120
|
+
## Metering Error Visibility
|
|
121
|
+
|
|
122
|
+
Metering runs in background threads and never raises into your code path — a
|
|
123
|
+
metering failure will never break your AI calls. To make failures observable
|
|
124
|
+
anyway, the SDK provides two mechanisms:
|
|
125
|
+
|
|
126
|
+
**Subscribe to failures** with a callback:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import revenium_middleware
|
|
130
|
+
|
|
131
|
+
@revenium_middleware.on_metering_error
|
|
132
|
+
def alert_on_metering_failure(event):
|
|
133
|
+
# event.error -- the exception (e.g. an HTTP 401/500 from Revenium)
|
|
134
|
+
# event.operation -- "completion", "image", "tool", ... (may be None)
|
|
135
|
+
# event.timestamp -- UTC datetime of the failure
|
|
136
|
+
my_monitoring.notify(f"Revenium metering failed: {event.error}")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Callbacks run on the background metering thread; exceptions they raise are
|
|
140
|
+
suppressed and logged, so they can never disrupt your application.
|
|
141
|
+
|
|
142
|
+
Authentication headers (`x-api-key`, `authorization`) on any HTTP
|
|
143
|
+
request/response attached to the exception are redacted before the error is
|
|
144
|
+
exposed to callbacks or `last_error`.
|
|
145
|
+
|
|
146
|
+
**Poll the status counters:**
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
status = revenium_middleware.get_metering_status()
|
|
150
|
+
print(status.success_count) # events delivered successfully
|
|
151
|
+
print(status.error_count) # delivery failures
|
|
152
|
+
print(status.last_error) # most recent exception, or None
|
|
153
|
+
print(status.last_error_at) # UTC datetime of the most recent failure
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`reset_metering_status()` zeroes the counters and clears registered
|
|
157
|
+
callbacks; `remove_metering_error_callback(cb)` unsubscribes a single one.
|
|
158
|
+
|
|
159
|
+
Failures are also logged at **ERROR** level on the `revenium_middleware`
|
|
160
|
+
logger, including HTTP 4xx/5xx responses, a missing
|
|
161
|
+
`REVENIUM_METERING_API_KEY`, and a provider middleware that fails to import
|
|
162
|
+
when the provider's SDK is installed.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
120
166
|
## Agentic Outcomes (Outcome-Based Metering)
|
|
121
167
|
|
|
122
168
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
@@ -168,7 +214,10 @@ history = get_outcome_history("sales-lead-8842")
|
|
|
168
214
|
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
169
215
|
```
|
|
170
216
|
|
|
171
|
-
`amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`), and returns the updated job as a dict.
|
|
217
|
+
`amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`), and returns the updated job as a dict.
|
|
218
|
+
|
|
219
|
+
- **`reason` vs `outcome_reason`:** `reason` is the amendment's own audit justification (why the record changed); `outcome_reason` is the business explanation of why the job failed or was cancelled. Use `outcome_reason` for failure explanations rather than burying them in `metadata` — it is a first-class field on the outcome and is returned on every `get_outcome_history()` row.
|
|
220
|
+
- **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
|
|
172
221
|
|
|
173
222
|
### Outcome Exceptions
|
|
174
223
|
|
|
@@ -636,6 +685,7 @@ litellm_settings:
|
|
|
636
685
|
```
|
|
637
686
|
|
|
638
687
|
When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
|
|
688
|
+
Reasoning effort travels as `x-revenium-effort` on the proxied request.
|
|
639
689
|
|
|
640
690
|
#### LiteLLM Decorators
|
|
641
691
|
|
|
@@ -928,6 +978,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
928
978
|
| `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
|
|
929
979
|
| `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
|
|
930
980
|
| `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
|
|
981
|
+
| `effort` | Reasoning effort level requested of the model (free-form string, max 16 chars, `^[A-Za-z0-9_-]+$`) | Report what share of AI spend is high-effort reasoning, and compare cost per effort level |
|
|
931
982
|
|
|
932
983
|
**Example:**
|
|
933
984
|
|
|
@@ -950,11 +1001,27 @@ response = client.chat.completions.create(
|
|
|
950
1001
|
"subscription_id": "pro-plan-Q1",
|
|
951
1002
|
"productName": "customer-support-chatbot",
|
|
952
1003
|
"agent": "support-agent",
|
|
953
|
-
"response_quality_score": 0.92
|
|
1004
|
+
"response_quality_score": 0.92,
|
|
1005
|
+
"effort": "high"
|
|
954
1006
|
}
|
|
955
1007
|
)
|
|
956
1008
|
```
|
|
957
1009
|
|
|
1010
|
+
**Reasoning effort:** `effort` records how hard the model was asked to think, so
|
|
1011
|
+
high-effort reasoning spend can be separated from the rest. It is a free-form
|
|
1012
|
+
string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
|
|
1013
|
+
`high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
|
|
1014
|
+
never heard of is passed through untouched rather than rejected or rewritten. The
|
|
1015
|
+
Revenium backend owns validation (at most 16 characters, matching
|
|
1016
|
+
`^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
|
|
1017
|
+
of being silently dropped, so it surfaces in the log and in
|
|
1018
|
+
`get_metering_status()`. It is distinct from the reported reasoning token count,
|
|
1019
|
+
which measures the tokens actually spent rather than the level requested.
|
|
1020
|
+
|
|
1021
|
+
Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
|
|
1022
|
+
mode, where per-call attribution travels as request headers rather than
|
|
1023
|
+
`usage_metadata`, send `x-revenium-effort` instead.
|
|
1024
|
+
|
|
958
1025
|
**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.
|
|
959
1026
|
|
|
960
1027
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
@@ -978,6 +1045,12 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
978
1045
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
979
1046
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
980
1047
|
| `ticket_id` | `REVENIUM_TICKET_ID` | External ticket or issue ID (e.g., Jira, Linear) (max 256 chars) | Attribute AI costs to individual tickets or issues |
|
|
1048
|
+
| `skill_name` | `REVENIUM_SKILL_NAME` | Name of the agent skill that produced the call (max 256 chars) | Attribute AI costs to the skill that generated them |
|
|
1049
|
+
| `skill_source` | `REVENIUM_SKILL_SOURCE` | Where the skill was loaded from — accepted values: `bundled`, `projectSettings`, `userSettings`, `plugin` (case-sensitive) | Classify skill origin in the shared skill catalog |
|
|
1050
|
+
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
1051
|
+
| `skill_plugin_name` | `REVENIUM_SKILL_PLUGIN_NAME` | Plugin providing the skill, when `skill_source` is `plugin` (max 256 chars) | Attribute costs to a specific plugin |
|
|
1052
|
+
| `skill_marketplace_name` | `REVENIUM_SKILL_MARKETPLACE_NAME` | Marketplace the skill or plugin was installed from (max 256 chars) | Track marketplace-sourced skill usage |
|
|
1053
|
+
| `skill_invocation_trigger` | `REVENIUM_SKILL_INVOCATION_TRIGGER` | What triggered the skill (max 32 chars; common values: `user-slash`, `claude-proactive`, `nested-skill`) | Separate user-invoked from proactive skill usage |
|
|
981
1054
|
|
|
982
1055
|
**Note:** `operation_type` (e.g., `CHAT`, `EMBED`, `TOOL_CALL`) and `operation_subtype` (e.g., `function_call`, `streaming`) are automatically detected by the middleware and cannot be overridden.
|
|
983
1056
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revenium-python-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
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"
|
|
@@ -124,6 +124,7 @@ dev = [
|
|
|
124
124
|
]
|
|
125
125
|
|
|
126
126
|
[tool.pytest.ini_options]
|
|
127
|
+
testpaths = ["tests"]
|
|
127
128
|
markers = [
|
|
128
129
|
"unit: Unit tests (fast, no external dependencies)",
|
|
129
130
|
"e2e: end-to-end tests requiring live services",
|
|
@@ -160,6 +160,16 @@ from ._core import ( # noqa: E402
|
|
|
160
160
|
is_selective_metering_enabled,
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
+
# Metering error visibility (BACK-778): status counters + failure callbacks
|
|
164
|
+
from ._core.metering_status import ( # noqa: E402
|
|
165
|
+
MeteringErrorEvent,
|
|
166
|
+
MeteringStatus,
|
|
167
|
+
get_metering_status,
|
|
168
|
+
on_metering_error,
|
|
169
|
+
remove_metering_error_callback,
|
|
170
|
+
reset_metering_status,
|
|
171
|
+
)
|
|
172
|
+
|
|
163
173
|
# Re-export tool metering utilities from the in-package metering module (BACK-2151)
|
|
164
174
|
from ._metering import meter_tool, report_tool_call, configure # noqa: E402
|
|
165
175
|
|
|
@@ -202,6 +212,13 @@ __all__ = [
|
|
|
202
212
|
"set_idempotency_key",
|
|
203
213
|
# Config exports
|
|
204
214
|
"is_selective_metering_enabled",
|
|
215
|
+
# Metering error visibility exports (BACK-778)
|
|
216
|
+
"MeteringErrorEvent",
|
|
217
|
+
"MeteringStatus",
|
|
218
|
+
"get_metering_status",
|
|
219
|
+
"on_metering_error",
|
|
220
|
+
"remove_metering_error_callback",
|
|
221
|
+
"reset_metering_status",
|
|
205
222
|
# Tool metering exports
|
|
206
223
|
"meter_tool",
|
|
207
224
|
"report_tool_call",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Tolerant field access for provider usage objects (dict or attribute-style).
|
|
2
|
+
|
|
3
|
+
Cache token fields differ by provider surface:
|
|
4
|
+
|
|
5
|
+
- Anthropic-style: top-level ``cache_read_input_tokens`` / ``cache_creation_input_tokens``,
|
|
6
|
+
plus the nested per-TTL breakdown under ``cache_creation``.
|
|
7
|
+
- OpenAI-style: nested ``prompt_tokens_details.cached_tokens``; no separate
|
|
8
|
+
cache-creation count.
|
|
9
|
+
|
|
10
|
+
See BACK-2391 for the history of this bug class (hardcoded/zeroed cache-token
|
|
11
|
+
fields recurring across multiple emitters) and BACK-1925 for the LiteLLM
|
|
12
|
+
client's cache-token contract, which this module's defaults must not break.
|
|
13
|
+
"""
|
|
14
|
+
from numbers import Number
|
|
15
|
+
from typing import Any, Dict, NamedTuple, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CacheTokens(NamedTuple):
|
|
19
|
+
cache_read_tokens: int
|
|
20
|
+
cache_creation_tokens: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get(source: Any, name: str) -> Any:
|
|
24
|
+
"""Read `name` from a dict or an attribute-style object; None if absent."""
|
|
25
|
+
if source is None:
|
|
26
|
+
return None
|
|
27
|
+
if isinstance(source, dict):
|
|
28
|
+
return source.get(name)
|
|
29
|
+
return getattr(source, name, None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_usage_field(usage: Any, name: str, default: int = 0) -> Any:
|
|
33
|
+
"""Read a single usage field through the same tolerant accessor as
|
|
34
|
+
`extract_cache_tokens`, so cache fields and base token fields (prompt/
|
|
35
|
+
completion/total) are read consistently from the same dict-or-object
|
|
36
|
+
usage value. Missing or None fields return `default`.
|
|
37
|
+
|
|
38
|
+
Normalizing a usage object to a plain dict *before* reading it (e.g. to
|
|
39
|
+
simplify a `.get(...)` call) silently drops every field for
|
|
40
|
+
attribute-style usage objects that fall through that normalization --
|
|
41
|
+
exactly the inconsistency this function exists to prevent. Callers with
|
|
42
|
+
multiple fields to read from the same `usage` value should read all of
|
|
43
|
+
them through this function (or `extract_cache_tokens`) rather than
|
|
44
|
+
normalizing `usage` itself first.
|
|
45
|
+
"""
|
|
46
|
+
value = _get(usage, name)
|
|
47
|
+
return default if value is None else value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def extract_cache_tokens(usage: Any) -> CacheTokens:
|
|
51
|
+
"""Extract (cache_read_tokens, cache_creation_tokens) from a usage object.
|
|
52
|
+
|
|
53
|
+
Tolerates dicts and attribute-style objects (SimpleNamespace, SDK response
|
|
54
|
+
models) and treats missing or None fields as 0. Checked in order:
|
|
55
|
+
|
|
56
|
+
1. OpenAI-style nested cache reads: ``usage.prompt_tokens_details.cached_tokens``
|
|
57
|
+
2. Anthropic-style top-level cache reads: ``usage.cache_read_input_tokens``
|
|
58
|
+
3. Anthropic-style top-level cache creation: ``usage.cache_creation_input_tokens``
|
|
59
|
+
"""
|
|
60
|
+
prompt_details = _get(usage, "prompt_tokens_details")
|
|
61
|
+
cache_read_tokens = get_usage_field(prompt_details, "cached_tokens", 0)
|
|
62
|
+
if not cache_read_tokens:
|
|
63
|
+
cache_read_tokens = get_usage_field(usage, "cache_read_input_tokens", 0)
|
|
64
|
+
cache_creation_tokens = get_usage_field(usage, "cache_creation_input_tokens", 0)
|
|
65
|
+
|
|
66
|
+
return CacheTokens(cache_read_tokens=cache_read_tokens, cache_creation_tokens=cache_creation_tokens)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _optional_token_count(source: Any, name: str) -> Optional[int]:
|
|
70
|
+
"""Read an integral token count, or None when it is absent or unusable.
|
|
71
|
+
|
|
72
|
+
Unlike `get_usage_field`, a missing field is reported as None rather than
|
|
73
|
+
coerced to 0, so callers can tell "the provider reported zero" apart from
|
|
74
|
+
"the provider reported nothing". Non-numeric values (including the
|
|
75
|
+
attributes that attribute-style test doubles auto-create on access) count
|
|
76
|
+
as nothing rather than being forwarded into a metering payload.
|
|
77
|
+
"""
|
|
78
|
+
value = _get(source, name)
|
|
79
|
+
if isinstance(value, bool) or not isinstance(value, Number):
|
|
80
|
+
return None
|
|
81
|
+
return int(value)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def extract_cache_creation_ttl_counts(usage: Any) -> Dict[str, int]:
|
|
85
|
+
"""Extract the per-TTL cache-creation breakdown as metering parameters.
|
|
86
|
+
|
|
87
|
+
Anthropic reports the split under a nested ``usage.cache_creation`` object
|
|
88
|
+
(``ephemeral_5m_input_tokens`` / ``ephemeral_1h_input_tokens``). That
|
|
89
|
+
object is absent on older responses and whenever the extended cache-TTL
|
|
90
|
+
beta is not in play, so it is read defensively.
|
|
91
|
+
|
|
92
|
+
Returns metering parameter names mapped to the counts the provider
|
|
93
|
+
actually reported, ready to be merged into a metering payload. A bucket the
|
|
94
|
+
provider did not report is left out entirely instead of being sent as a
|
|
95
|
+
zero: the aggregate ``cache_creation_token_count`` stays authoritative and
|
|
96
|
+
the backend prices the flat fallback from it when no split is present,
|
|
97
|
+
whereas an explicit zero would assert a split that never happened. A
|
|
98
|
+
bucket the provider does report as 0 is forwarded as 0.
|
|
99
|
+
"""
|
|
100
|
+
cache_creation = _get(usage, "cache_creation")
|
|
101
|
+
|
|
102
|
+
counts: Dict[str, int] = {}
|
|
103
|
+
ephemeral_5m = _optional_token_count(cache_creation, "ephemeral_5m_input_tokens")
|
|
104
|
+
if ephemeral_5m is not None:
|
|
105
|
+
counts["cache_creation5m_token_count"] = ephemeral_5m
|
|
106
|
+
ephemeral_1h = _optional_token_count(cache_creation, "ephemeral_1h_input_tokens")
|
|
107
|
+
if ephemeral_1h is not None:
|
|
108
|
+
counts["cache_creation1h_token_count"] = ephemeral_1h
|
|
109
|
+
return counts
|
|
@@ -63,6 +63,13 @@ class Config:
|
|
|
63
63
|
ENV_REVENIUM_AGENTIC_JOB_NAME: str = "REVENIUM_AGENTIC_JOB_NAME"
|
|
64
64
|
ENV_REVENIUM_AGENTIC_JOB_TYPE: str = "REVENIUM_AGENTIC_JOB_TYPE"
|
|
65
65
|
ENV_REVENIUM_AGENTIC_JOB_VERSION: str = "REVENIUM_AGENTIC_JOB_VERSION"
|
|
66
|
+
# Skill attribution environment variables
|
|
67
|
+
ENV_REVENIUM_SKILL_INVOCATION_TRIGGER: str = "REVENIUM_SKILL_INVOCATION_TRIGGER"
|
|
68
|
+
ENV_REVENIUM_SKILL_KIND: str = "REVENIUM_SKILL_KIND"
|
|
69
|
+
ENV_REVENIUM_SKILL_MARKETPLACE_NAME: str = "REVENIUM_SKILL_MARKETPLACE_NAME"
|
|
70
|
+
ENV_REVENIUM_SKILL_NAME: str = "REVENIUM_SKILL_NAME"
|
|
71
|
+
ENV_REVENIUM_SKILL_PLUGIN_NAME: str = "REVENIUM_SKILL_PLUGIN_NAME"
|
|
72
|
+
ENV_REVENIUM_SKILL_SOURCE: str = "REVENIUM_SKILL_SOURCE"
|
|
66
73
|
ENV_REVENIUM_OUTCOME_API_KEY: str = "REVENIUM_OUTCOME_API_KEY"
|
|
67
74
|
ENV_REVENIUM_PROFITSTREAM_BASE_URL: str = "REVENIUM_PROFITSTREAM_BASE_URL"
|
|
68
75
|
|