revenium-python-sdk 0.4.0__tar.gz → 0.6.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.4.0 → revenium_python_sdk-0.6.0}/PKG-INFO +102 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/README.md +97 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/pyproject.toml +7 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/__init__.py +17 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/config.py +7 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/fields.py +42 -0
- revenium_python_sdk-0.6.0/revenium_middleware/_core/load_diagnostics.py +68 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/metering.py +17 -9
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/metering_buffer.py +42 -2
- revenium_python_sdk-0.6.0/revenium_middleware/_core/metering_status.py +172 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/metering_submission.py +20 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/decorator.py +15 -4
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/resources/ai.py +422 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/ai_create_audio_params.py +54 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/ai_create_completion_params.py +52 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/ai_create_image_params.py +56 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/ai_create_video_params.py +54 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/event_create_params.py +2 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/fal/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/fal/_metering.py +13 -4
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/__init__.py +4 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/utils.py +10 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/google_ai/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/google_ai/middleware.py +1 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/vertex_ai/__init__.py +3 -2
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/__init__.py +35 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/_metadata.py +10 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/anthropic_driver.py +87 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/litellm_driver.py +527 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/ollama_driver.py +87 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/openai_driver.py +133 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/openai_embedding_driver.py +121 -0
- revenium_python_sdk-0.6.0/revenium_middleware/griptape/universal_driver.py +527 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/proxy/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/proxy/middleware.py +1 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/ollama/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/__init__.py +3 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/middleware.py +7 -2
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/perplexity/__init__.py +14 -3
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_python_sdk.egg-info/PKG-INFO +102 -1
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_python_sdk.egg-info/SOURCES.txt +10 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_python_sdk.egg-info/requires.txt +9 -0
- revenium_python_sdk-0.6.0/tests/test_metering.py +411 -0
- revenium_python_sdk-0.4.0/tests/test_metering.py +0 -106
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/LICENSE +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/context.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/enforcement.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/exceptions.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/log_sanitize.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/outcomes.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/LICENSE +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_base_client.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_client.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_compat.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_constants.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_exceptions.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_files.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_models.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_qs.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_resource.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_response.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_streaming.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_types.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_logs.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_streams.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_sync.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_transform.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_typing.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_utils/_utils.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/_version.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/context.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/py.typed +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/resources/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/resources/apis.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/resources/events.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/agentic_outcomes.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/bedrock_adapter.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/bedrock_transport.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/stream_create.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/anthropic/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/fal/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/job_context.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/job_history.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/ollama/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/ollama/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/exceptions.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/openai/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/perplexity/middleware.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/perplexity/trace_fields.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/webhooks/__init__.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/webhooks/_verify.py +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.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.6.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
|
|
@@ -36,6 +36,9 @@ Provides-Extra: langchain
|
|
|
36
36
|
Requires-Dist: langchain>=0.1.16; extra == "langchain"
|
|
37
37
|
Requires-Dist: langchain-openai>=0.1.0; extra == "langchain"
|
|
38
38
|
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
|
|
39
|
+
Provides-Extra: griptape
|
|
40
|
+
Requires-Dist: griptape<2.0.0,>=1.0.0; python_version >= "3.10" and extra == "griptape"
|
|
41
|
+
Requires-Dist: requests>=2.25.0; extra == "griptape"
|
|
39
42
|
Provides-Extra: perplexity
|
|
40
43
|
Requires-Dist: wrapt>=1.14.0; extra == "perplexity"
|
|
41
44
|
Requires-Dist: python-dotenv>=0.19.0; extra == "perplexity"
|
|
@@ -89,6 +92,7 @@ Requires-Dist: python-dotenv>=0.19.0; extra == "dev"
|
|
|
89
92
|
Requires-Dist: anthropic>=0.30.0; extra == "dev"
|
|
90
93
|
Requires-Dist: boto3; extra == "dev"
|
|
91
94
|
Requires-Dist: ollama>=0.3.0; extra == "dev"
|
|
95
|
+
Requires-Dist: griptape<2.0.0,>=1.0.0; python_version >= "3.10" and extra == "dev"
|
|
92
96
|
Dynamic: license-file
|
|
93
97
|
|
|
94
98
|
# Revenium Python SDK
|
|
@@ -132,6 +136,7 @@ The official Revenium Python SDK — unified AI metering middleware for deeply a
|
|
|
132
136
|
| Perplexity (Native SDK) | `perplexity-native` | `pip install revenium-python-sdk[perplexity-native]` |
|
|
133
137
|
| fal.ai | `fal` | `pip install revenium-python-sdk[fal]` |
|
|
134
138
|
| LangChain | `langchain` | `pip install revenium-python-sdk[langchain]` |
|
|
139
|
+
| Griptape | `griptape` | `pip install revenium-python-sdk[griptape]` |
|
|
135
140
|
|
|
136
141
|
## Feature Matrix
|
|
137
142
|
|
|
@@ -147,6 +152,7 @@ The official Revenium Python SDK — unified AI metering middleware for deeply a
|
|
|
147
152
|
| Terminal Summary | Yes | Yes | Yes | Yes | Yes | - | - |
|
|
148
153
|
| Azure / Bedrock | Azure | Bedrock | Vertex AI | - | All | - | - |
|
|
149
154
|
| LangChain Integration | Yes | - | - | - | - | - | - |
|
|
155
|
+
| Griptape Integration | Yes | Yes | - | Yes | Yes | - | - |
|
|
150
156
|
| CrewAI Integration | - | - | - | - | Yes | - | - |
|
|
151
157
|
| Proxy Mode | - | - | - | - | Yes | - | - |
|
|
152
158
|
|
|
@@ -208,6 +214,52 @@ print(response.choices[0].message.content)
|
|
|
208
214
|
|
|
209
215
|
---
|
|
210
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
|
+
|
|
211
263
|
## Agentic Outcomes (Outcome-Based Metering)
|
|
212
264
|
|
|
213
265
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
@@ -959,6 +1011,49 @@ Credentials come from the standard environment variables (`REVENIUM_METERING_API
|
|
|
959
1011
|
|
|
960
1012
|
---
|
|
961
1013
|
|
|
1014
|
+
### Griptape
|
|
1015
|
+
|
|
1016
|
+
Metered prompt and embedding drivers for [Griptape](https://github.com/griptape-ai/griptape) applications. Requires Python 3.10+.
|
|
1017
|
+
|
|
1018
|
+
```bash
|
|
1019
|
+
pip install "revenium-python-sdk[griptape,openai]" # OpenAI
|
|
1020
|
+
pip install "revenium-python-sdk[griptape,anthropic]" # Anthropic
|
|
1021
|
+
pip install "revenium-python-sdk[griptape,ollama]" # Ollama
|
|
1022
|
+
pip install "revenium-python-sdk[griptape,litellm,litellm-proxy]" # 100+ providers via LiteLLM
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
`ReveniumDriver` auto-detects the provider from the model name (`gpt-*` → OpenAI, `claude-*` → Anthropic, `llama`/`mistral`/... → Ollama, anything else → LiteLLM) and wraps the matching Griptape prompt driver with Revenium metering:
|
|
1026
|
+
|
|
1027
|
+
```python
|
|
1028
|
+
import os
|
|
1029
|
+
from griptape.structures import Agent
|
|
1030
|
+
from revenium_middleware.griptape import ReveniumDriver
|
|
1031
|
+
|
|
1032
|
+
os.environ["REVENIUM_METERING_API_KEY"] = "your_revenium_key"
|
|
1033
|
+
|
|
1034
|
+
agent = Agent(prompt_driver=ReveniumDriver(
|
|
1035
|
+
model="gpt-4o-mini",
|
|
1036
|
+
usage_metadata={"task_type": "demo"},
|
|
1037
|
+
))
|
|
1038
|
+
agent.run("Hello!")
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
Force a provider with `force_provider="litellm"`, or wrap an existing driver with `ReveniumDriver(base_driver=...)`.
|
|
1042
|
+
|
|
1043
|
+
**Embeddings:**
|
|
1044
|
+
|
|
1045
|
+
```python
|
|
1046
|
+
from revenium_middleware.griptape import ReveniumEmbeddingDriver
|
|
1047
|
+
|
|
1048
|
+
driver = ReveniumEmbeddingDriver(model="text-embedding-3-large")
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
**Provider-specific drivers:** for direct control, use `ReveniumOpenAiDriver`, `ReveniumAnthropicDriver`, `ReveniumOllamaDriver`, `ReveniumLiteLLMDriver` or `ReveniumOpenAiEmbeddingDriver` — each subclasses the corresponding Griptape driver and accepts a `usage_metadata` dict (see [Metadata Fields](#metadata-fields)).
|
|
1052
|
+
|
|
1053
|
+
**Migrating from `revenium-griptape`:** the standalone package is deprecated — install the `griptape` extra and change `from revenium_griptape import ReveniumDriver` to `from revenium_middleware.griptape import ReveniumDriver`. All driver class names are unchanged. One behaviour difference: the old package called `load_dotenv()` automatically at import time; the SDK never mutates your environment on import, so if you keep credentials in a `.env` file, call `load_dotenv()` yourself before creating a driver.
|
|
1054
|
+
|
|
1055
|
+
---
|
|
1056
|
+
|
|
962
1057
|
## Metadata Fields
|
|
963
1058
|
|
|
964
1059
|
Add business context to any API call by passing a `usage_metadata` dictionary. All fields are optional.
|
|
@@ -1026,6 +1121,12 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
1026
1121
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
1027
1122
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
1028
1123
|
| `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 |
|
|
1124
|
+
| `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 |
|
|
1125
|
+
| `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 |
|
|
1126
|
+
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
1127
|
+
| `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 |
|
|
1128
|
+
| `skill_marketplace_name` | `REVENIUM_SKILL_MARKETPLACE_NAME` | Marketplace the skill or plugin was installed from (max 256 chars) | Track marketplace-sourced skill usage |
|
|
1129
|
+
| `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 |
|
|
1029
1130
|
|
|
1030
1131
|
**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.
|
|
1031
1132
|
|
|
@@ -39,6 +39,7 @@ The official Revenium Python SDK — unified AI metering middleware for deeply a
|
|
|
39
39
|
| Perplexity (Native SDK) | `perplexity-native` | `pip install revenium-python-sdk[perplexity-native]` |
|
|
40
40
|
| fal.ai | `fal` | `pip install revenium-python-sdk[fal]` |
|
|
41
41
|
| LangChain | `langchain` | `pip install revenium-python-sdk[langchain]` |
|
|
42
|
+
| Griptape | `griptape` | `pip install revenium-python-sdk[griptape]` |
|
|
42
43
|
|
|
43
44
|
## Feature Matrix
|
|
44
45
|
|
|
@@ -54,6 +55,7 @@ The official Revenium Python SDK — unified AI metering middleware for deeply a
|
|
|
54
55
|
| Terminal Summary | Yes | Yes | Yes | Yes | Yes | - | - |
|
|
55
56
|
| Azure / Bedrock | Azure | Bedrock | Vertex AI | - | All | - | - |
|
|
56
57
|
| LangChain Integration | Yes | - | - | - | - | - | - |
|
|
58
|
+
| Griptape Integration | Yes | Yes | - | Yes | Yes | - | - |
|
|
57
59
|
| CrewAI Integration | - | - | - | - | Yes | - | - |
|
|
58
60
|
| Proxy Mode | - | - | - | - | Yes | - | - |
|
|
59
61
|
|
|
@@ -115,6 +117,52 @@ print(response.choices[0].message.content)
|
|
|
115
117
|
|
|
116
118
|
---
|
|
117
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
|
+
|
|
118
166
|
## Agentic Outcomes (Outcome-Based Metering)
|
|
119
167
|
|
|
120
168
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
@@ -866,6 +914,49 @@ Credentials come from the standard environment variables (`REVENIUM_METERING_API
|
|
|
866
914
|
|
|
867
915
|
---
|
|
868
916
|
|
|
917
|
+
### Griptape
|
|
918
|
+
|
|
919
|
+
Metered prompt and embedding drivers for [Griptape](https://github.com/griptape-ai/griptape) applications. Requires Python 3.10+.
|
|
920
|
+
|
|
921
|
+
```bash
|
|
922
|
+
pip install "revenium-python-sdk[griptape,openai]" # OpenAI
|
|
923
|
+
pip install "revenium-python-sdk[griptape,anthropic]" # Anthropic
|
|
924
|
+
pip install "revenium-python-sdk[griptape,ollama]" # Ollama
|
|
925
|
+
pip install "revenium-python-sdk[griptape,litellm,litellm-proxy]" # 100+ providers via LiteLLM
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
`ReveniumDriver` auto-detects the provider from the model name (`gpt-*` → OpenAI, `claude-*` → Anthropic, `llama`/`mistral`/... → Ollama, anything else → LiteLLM) and wraps the matching Griptape prompt driver with Revenium metering:
|
|
929
|
+
|
|
930
|
+
```python
|
|
931
|
+
import os
|
|
932
|
+
from griptape.structures import Agent
|
|
933
|
+
from revenium_middleware.griptape import ReveniumDriver
|
|
934
|
+
|
|
935
|
+
os.environ["REVENIUM_METERING_API_KEY"] = "your_revenium_key"
|
|
936
|
+
|
|
937
|
+
agent = Agent(prompt_driver=ReveniumDriver(
|
|
938
|
+
model="gpt-4o-mini",
|
|
939
|
+
usage_metadata={"task_type": "demo"},
|
|
940
|
+
))
|
|
941
|
+
agent.run("Hello!")
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Force a provider with `force_provider="litellm"`, or wrap an existing driver with `ReveniumDriver(base_driver=...)`.
|
|
945
|
+
|
|
946
|
+
**Embeddings:**
|
|
947
|
+
|
|
948
|
+
```python
|
|
949
|
+
from revenium_middleware.griptape import ReveniumEmbeddingDriver
|
|
950
|
+
|
|
951
|
+
driver = ReveniumEmbeddingDriver(model="text-embedding-3-large")
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
**Provider-specific drivers:** for direct control, use `ReveniumOpenAiDriver`, `ReveniumAnthropicDriver`, `ReveniumOllamaDriver`, `ReveniumLiteLLMDriver` or `ReveniumOpenAiEmbeddingDriver` — each subclasses the corresponding Griptape driver and accepts a `usage_metadata` dict (see [Metadata Fields](#metadata-fields)).
|
|
955
|
+
|
|
956
|
+
**Migrating from `revenium-griptape`:** the standalone package is deprecated — install the `griptape` extra and change `from revenium_griptape import ReveniumDriver` to `from revenium_middleware.griptape import ReveniumDriver`. All driver class names are unchanged. One behaviour difference: the old package called `load_dotenv()` automatically at import time; the SDK never mutates your environment on import, so if you keep credentials in a `.env` file, call `load_dotenv()` yourself before creating a driver.
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
869
960
|
## Metadata Fields
|
|
870
961
|
|
|
871
962
|
Add business context to any API call by passing a `usage_metadata` dictionary. All fields are optional.
|
|
@@ -933,6 +1024,12 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
933
1024
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
934
1025
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
935
1026
|
| `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 |
|
|
1027
|
+
| `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 |
|
|
1028
|
+
| `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 |
|
|
1029
|
+
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
1030
|
+
| `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 |
|
|
1031
|
+
| `skill_marketplace_name` | `REVENIUM_SKILL_MARKETPLACE_NAME` | Marketplace the skill or plugin was installed from (max 256 chars) | Track marketplace-sourced skill usage |
|
|
1032
|
+
| `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 |
|
|
936
1033
|
|
|
937
1034
|
**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.
|
|
938
1035
|
|
|
@@ -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.6.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"
|
|
@@ -51,6 +51,10 @@ langchain = [
|
|
|
51
51
|
"langchain-openai>=0.1.0",
|
|
52
52
|
"langchain-core>=0.1.0",
|
|
53
53
|
]
|
|
54
|
+
griptape = [
|
|
55
|
+
"griptape>=1.0.0,<2.0.0; python_version >= '3.10'",
|
|
56
|
+
"requests>=2.25.0",
|
|
57
|
+
]
|
|
54
58
|
perplexity = [
|
|
55
59
|
"wrapt>=1.14.0",
|
|
56
60
|
"python-dotenv>=0.19.0",
|
|
@@ -116,9 +120,11 @@ dev = [
|
|
|
116
120
|
"anthropic>=0.30.0",
|
|
117
121
|
"boto3",
|
|
118
122
|
"ollama>=0.3.0",
|
|
123
|
+
"griptape>=1.0.0,<2.0.0; python_version >= '3.10'",
|
|
119
124
|
]
|
|
120
125
|
|
|
121
126
|
[tool.pytest.ini_options]
|
|
127
|
+
testpaths = ["tests"]
|
|
122
128
|
markers = [
|
|
123
129
|
"unit: Unit tests (fast, no external dependencies)",
|
|
124
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",
|
|
@@ -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
|
|
|
@@ -30,6 +30,26 @@ _AGENTIC_JOB_ENV_MAP = {
|
|
|
30
30
|
"agenticJobVersion": Config.ENV_REVENIUM_AGENTIC_JOB_VERSION,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
# Keys are the typed snake_case params on create_completion; values are the
|
|
34
|
+
# accepted usage_metadata aliases in precedence order.
|
|
35
|
+
SKILL_FIELD_MAP = {
|
|
36
|
+
"skill_invocation_trigger": ("skill_invocation_trigger", "skillInvocationTrigger"),
|
|
37
|
+
"skill_kind": ("skill_kind", "skillKind"),
|
|
38
|
+
"skill_marketplace_name": ("skill_marketplace_name", "skillMarketplaceName"),
|
|
39
|
+
"skill_name": ("skill_name", "skillName"),
|
|
40
|
+
"skill_plugin_name": ("skill_plugin_name", "skillPluginName"),
|
|
41
|
+
"skill_source": ("skill_source", "skillSource"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_SKILL_ENV_MAP = {
|
|
45
|
+
"skill_invocation_trigger": Config.ENV_REVENIUM_SKILL_INVOCATION_TRIGGER,
|
|
46
|
+
"skill_kind": Config.ENV_REVENIUM_SKILL_KIND,
|
|
47
|
+
"skill_marketplace_name": Config.ENV_REVENIUM_SKILL_MARKETPLACE_NAME,
|
|
48
|
+
"skill_name": Config.ENV_REVENIUM_SKILL_NAME,
|
|
49
|
+
"skill_plugin_name": Config.ENV_REVENIUM_SKILL_PLUGIN_NAME,
|
|
50
|
+
"skill_source": Config.ENV_REVENIUM_SKILL_SOURCE,
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
|
|
34
54
|
def extract_field_with_fallback(
|
|
35
55
|
source: Mapping[str, Any],
|
|
@@ -129,6 +149,28 @@ def extract_agentic_job_fields(source: Mapping[str, Any]) -> Dict[str, Any]:
|
|
|
129
149
|
return result
|
|
130
150
|
|
|
131
151
|
|
|
152
|
+
def extract_skill_fields(source: Mapping[str, Any]) -> Dict[str, Any]:
|
|
153
|
+
"""Resolve skill attribution fields with per-field precedence:
|
|
154
|
+
|
|
155
|
+
explicit source metadata (snake then camel alias) >
|
|
156
|
+
``REVENIUM_SKILL_*`` env var. Each field resolves independently and
|
|
157
|
+
absent fields are omitted entirely (never emitted as None). Returns
|
|
158
|
+
snake_case keys matching the typed create_completion parameters.
|
|
159
|
+
"""
|
|
160
|
+
result = {}
|
|
161
|
+
for param_name, aliases in SKILL_FIELD_MAP.items():
|
|
162
|
+
value = None
|
|
163
|
+
for alias in aliases:
|
|
164
|
+
value = source.get(alias)
|
|
165
|
+
if value is not None:
|
|
166
|
+
break
|
|
167
|
+
if value is None:
|
|
168
|
+
value = os.getenv(_SKILL_ENV_MAP[param_name]) or None
|
|
169
|
+
if value is not None:
|
|
170
|
+
result[param_name] = value
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
|
|
132
174
|
def merge_extra_body(existing: Optional[Dict[str, Any]], agentic_fields: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
133
175
|
if not agentic_fields:
|
|
134
176
|
return existing if existing else None
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Log-level policy for provider middleware load failures.
|
|
2
|
+
|
|
3
|
+
Every provider package guards its middleware import with ``except
|
|
4
|
+
ImportError`` so that optional providers can be absent. That guard used to
|
|
5
|
+
log at DEBUG unconditionally, which hid real breakage: a customer with the
|
|
6
|
+
provider SDK installed whose middleware failed to import (missing ``wrapt``,
|
|
7
|
+
a broken internal import, a version conflict) got zero metering with no
|
|
8
|
+
visible signal.
|
|
9
|
+
|
|
10
|
+
The policy here distinguishes the two cases:
|
|
11
|
+
|
|
12
|
+
- Provider SDK **installed** but middleware import failed → ERROR, including
|
|
13
|
+
the underlying exception. Metering was expected to work and silently won't.
|
|
14
|
+
- Provider SDK **absent** → DEBUG. The provider simply isn't in use (e.g.
|
|
15
|
+
``revenium_middleware.griptape`` imports several provider middlewares
|
|
16
|
+
eagerly and most installs only have one provider SDK).
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import importlib.util
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Tuple
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("revenium_middleware")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _package_installed(package: str) -> bool:
|
|
28
|
+
try:
|
|
29
|
+
return importlib.util.find_spec(package) is not None
|
|
30
|
+
except ModuleNotFoundError:
|
|
31
|
+
# find_spec("google.genai") raises when the parent package is absent.
|
|
32
|
+
return False
|
|
33
|
+
except Exception:
|
|
34
|
+
# Any other spec-lookup failure means the package exists but is
|
|
35
|
+
# broken — fail toward visibility.
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def log_middleware_load_failure(
|
|
40
|
+
provider_label: str,
|
|
41
|
+
exc: BaseException,
|
|
42
|
+
required_packages: Tuple[str, ...],
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Log a provider middleware import failure at the appropriate level.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
provider_label: Human-readable provider name for the log message.
|
|
48
|
+
exc: The ImportError (or subclass) that aborted the middleware load.
|
|
49
|
+
required_packages: Import names of the provider's own SDK package(s).
|
|
50
|
+
If any of them is installed, the failure is unexpected and logged
|
|
51
|
+
at ERROR; if none are, the provider is not in use and the failure
|
|
52
|
+
is logged at DEBUG.
|
|
53
|
+
"""
|
|
54
|
+
if any(_package_installed(pkg) for pkg in required_packages):
|
|
55
|
+
logger.error(
|
|
56
|
+
"Revenium %s middleware could not load: %s. "
|
|
57
|
+
"Metering will NOT be active for %s calls.",
|
|
58
|
+
provider_label,
|
|
59
|
+
exc,
|
|
60
|
+
provider_label,
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
logger.debug(
|
|
64
|
+
"%s SDK not installed (%s); Revenium %s middleware not loaded",
|
|
65
|
+
provider_label,
|
|
66
|
+
exc,
|
|
67
|
+
provider_label,
|
|
68
|
+
)
|
{revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/metering.py
RENAMED
|
@@ -22,7 +22,8 @@ def _build_metering_client(
|
|
|
22
22
|
) -> Optional[ReveniumMetering]:
|
|
23
23
|
"""Construct a ReveniumMetering client from raw env-var inputs.
|
|
24
24
|
|
|
25
|
-
Returns ``None`` when the API key is missing/empty (logs
|
|
25
|
+
Returns ``None`` when the API key is missing/empty (logs an error --
|
|
26
|
+
metering silently disabled is the failure mode this SDK must surface).
|
|
26
27
|
Raises ``ValueError`` when the API key is present but malformed.
|
|
27
28
|
Falls back to the library default when ``base_url_raw`` is set but not
|
|
28
29
|
a valid http(s) URL.
|
|
@@ -40,7 +41,10 @@ def _build_metering_client(
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
if not api_key:
|
|
43
|
-
logger.
|
|
44
|
+
logger.error(
|
|
45
|
+
"REVENIUM_METERING_API_KEY environment variable is not set. "
|
|
46
|
+
"Metering is disabled and no usage data will be sent to Revenium."
|
|
47
|
+
)
|
|
44
48
|
return None
|
|
45
49
|
|
|
46
50
|
validate_api_key(api_key)
|
|
@@ -86,10 +90,11 @@ def initialize_metering(api_key: Optional[str] = None, base_url: Optional[str] =
|
|
|
86
90
|
settings hooks) or to reconfigure at runtime. Explicit arguments take
|
|
87
91
|
precedence over ``REVENIUM_METERING_API_KEY`` / ``REVENIUM_METERING_BASE_URL``.
|
|
88
92
|
|
|
89
|
-
Returns True when metering is enabled after the call
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
Returns True when metering is enabled after the call; a missing/empty key
|
|
94
|
+
logs an ERROR and leaves metering disabled. Raises ``ValueError`` for a
|
|
95
|
+
malformed API key -- explicit configuration fails loudly, unlike the lazy
|
|
96
|
+
``get_client()`` path, which logs a warning once for a malformed env key
|
|
97
|
+
and leaves metering disabled.
|
|
93
98
|
"""
|
|
94
99
|
global client
|
|
95
100
|
key = api_key if api_key is not None else os.environ.get("REVENIUM_METERING_API_KEY")
|
|
@@ -107,8 +112,9 @@ def get_client() -> Optional[ReveniumMetering]:
|
|
|
107
112
|
``REVENIUM_METERING_API_KEY`` appears, the next metering event builds the
|
|
108
113
|
client instead of silently no-opping forever.
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
A missing/unset key is logged at ERROR level by the build path; a
|
|
116
|
+
malformed env key is logged once as a warning and metering stays
|
|
117
|
+
disabled, whereas the explicit ``initialize_metering()`` raises
|
|
112
118
|
``ValueError`` so programmatic misconfiguration fails loudly.
|
|
113
119
|
"""
|
|
114
120
|
global _last_failed_key
|
|
@@ -240,8 +246,10 @@ class MeteringThread(threading.Thread):
|
|
|
240
246
|
# Log errors unless it's during shutdown
|
|
241
247
|
if not shutdown_event.is_set():
|
|
242
248
|
self.error = e
|
|
249
|
+
from revenium_middleware._core.metering_status import record_metering_error
|
|
250
|
+
record_metering_error(e)
|
|
243
251
|
# Use exc_info=True to include traceback in the log
|
|
244
|
-
logger.
|
|
252
|
+
logger.error(f"Error in metering thread {self.name}: {str(e)}", exc_info=True)
|
|
245
253
|
else:
|
|
246
254
|
logger.debug(f"Exception ignored in metering thread {self.name} during shutdown: {str(e)}")
|
|
247
255
|
finally:
|
{revenium_python_sdk-0.4.0 → revenium_python_sdk-0.6.0}/revenium_middleware/_core/metering_buffer.py
RENAMED
|
@@ -13,10 +13,15 @@ import os
|
|
|
13
13
|
import threading
|
|
14
14
|
import time
|
|
15
15
|
from collections import deque
|
|
16
|
-
from typing import Any, Callable, Deque, Dict, Optional
|
|
16
|
+
from typing import Any, Callable, Deque, Dict, List, Optional, Tuple
|
|
17
17
|
|
|
18
18
|
import httpx
|
|
19
19
|
|
|
20
|
+
from revenium_middleware._core.metering_status import (
|
|
21
|
+
record_metering_error,
|
|
22
|
+
record_metering_success,
|
|
23
|
+
)
|
|
24
|
+
|
|
20
25
|
logger = logging.getLogger("revenium_middleware")
|
|
21
26
|
|
|
22
27
|
DEFAULT_MAX_SIZE = 1000
|
|
@@ -196,12 +201,18 @@ class MeteringBuffer:
|
|
|
196
201
|
"""
|
|
197
202
|
sent = expired = discarded = 0
|
|
198
203
|
started = time.monotonic()
|
|
204
|
+
# Status recording is deferred until _flush_lock is released:
|
|
205
|
+
# record_metering_error() runs subscriber callbacks synchronously, and
|
|
206
|
+
# a callback that calls back into this buffer would self-deadlock on
|
|
207
|
+
# the non-reentrant lock.
|
|
208
|
+
deferred_outcomes: List[Optional[Tuple[BaseException, str]]] = []
|
|
199
209
|
|
|
200
210
|
with self._flush_lock:
|
|
201
211
|
while True:
|
|
202
212
|
if deadline_seconds is not None and time.monotonic() - started >= deadline_seconds:
|
|
203
213
|
break
|
|
204
214
|
|
|
215
|
+
expired_kind: Optional[str] = None
|
|
205
216
|
with self._lock:
|
|
206
217
|
if not self._events:
|
|
207
218
|
break
|
|
@@ -210,7 +221,19 @@ class MeteringBuffer:
|
|
|
210
221
|
self._events.popleft()
|
|
211
222
|
self._total_expired += 1
|
|
212
223
|
expired += 1
|
|
213
|
-
|
|
224
|
+
expired_kind = event.kind
|
|
225
|
+
if expired_kind is not None:
|
|
226
|
+
# Expiry is a terminal failure with no delivery exception
|
|
227
|
+
# in hand; synthesize one so status counters and
|
|
228
|
+
# on_metering_error subscribers still get the signal.
|
|
229
|
+
deferred_outcomes.append((
|
|
230
|
+
TimeoutError(
|
|
231
|
+
f"buffered {expired_kind} metering event expired after "
|
|
232
|
+
f"{self._max_age_seconds:.0f}s without successful replay"
|
|
233
|
+
),
|
|
234
|
+
expired_kind,
|
|
235
|
+
))
|
|
236
|
+
continue
|
|
214
237
|
|
|
215
238
|
# Cap each replay call so a single slow network call cannot
|
|
216
239
|
# blow through the flush deadline (e.g. the shutdown budget).
|
|
@@ -232,16 +255,23 @@ class MeteringBuffer:
|
|
|
232
255
|
# Only count the discard if the event is still at the
|
|
233
256
|
# front; a concurrent push at capacity may have evicted
|
|
234
257
|
# (and counted) it already.
|
|
258
|
+
discarded_here = False
|
|
235
259
|
with self._lock:
|
|
236
260
|
if self._events and self._events[0] is event:
|
|
237
261
|
self._events.popleft()
|
|
238
262
|
self._total_discarded += 1
|
|
239
263
|
discarded += 1
|
|
264
|
+
discarded_here = True
|
|
265
|
+
if discarded_here:
|
|
266
|
+
# Terminal failure: the event is gone for good, so
|
|
267
|
+
# surface it to status counters and subscribers.
|
|
268
|
+
deferred_outcomes.append((exc, event.kind))
|
|
240
269
|
logger.debug("Discarded buffered event after permanent failure: %s", exc)
|
|
241
270
|
continue
|
|
242
271
|
|
|
243
272
|
# Same identity guard: only count the replay if we actually
|
|
244
273
|
# popped this event (not concurrently evicted-and-counted).
|
|
274
|
+
replayed_here = False
|
|
245
275
|
with self._lock:
|
|
246
276
|
if self._events and self._events[0] is event:
|
|
247
277
|
self._events.popleft()
|
|
@@ -249,6 +279,16 @@ class MeteringBuffer:
|
|
|
249
279
|
if len(self._events) < self._max_size:
|
|
250
280
|
self._was_full = False
|
|
251
281
|
sent += 1
|
|
282
|
+
replayed_here = True
|
|
283
|
+
if replayed_here:
|
|
284
|
+
deferred_outcomes.append(None)
|
|
285
|
+
|
|
286
|
+
for outcome in deferred_outcomes:
|
|
287
|
+
if outcome is None:
|
|
288
|
+
record_metering_success()
|
|
289
|
+
else:
|
|
290
|
+
error, kind = outcome
|
|
291
|
+
record_metering_error(error, operation=kind)
|
|
252
292
|
|
|
253
293
|
remaining = self.stats()["size"]
|
|
254
294
|
if sent or expired or discarded:
|