revenium-python-sdk 0.1.0__py3-none-any.whl

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.
Files changed (73) hide show
  1. revenium_middleware/__init__.py +184 -0
  2. revenium_middleware/_core/__init__.py +65 -0
  3. revenium_middleware/_core/config.py +165 -0
  4. revenium_middleware/_core/context.py +109 -0
  5. revenium_middleware/_core/decorators.py +202 -0
  6. revenium_middleware/_core/metering.py +207 -0
  7. revenium_middleware/_core/prompt_extraction.py +55 -0
  8. revenium_middleware/_core/subscriber.py +51 -0
  9. revenium_middleware/_core/trace_fields.py +265 -0
  10. revenium_middleware/anthropic/__init__.py +108 -0
  11. revenium_middleware/anthropic/bedrock_adapter.py +753 -0
  12. revenium_middleware/anthropic/config.py +29 -0
  13. revenium_middleware/anthropic/middleware.py +1070 -0
  14. revenium_middleware/anthropic/prompt_extractor.py +178 -0
  15. revenium_middleware/anthropic/provider.py +141 -0
  16. revenium_middleware/anthropic/summary_printer.py +286 -0
  17. revenium_middleware/anthropic/trace_fields.py +158 -0
  18. revenium_middleware/google/__init__.py +114 -0
  19. revenium_middleware/google/common/__init__.py +127 -0
  20. revenium_middleware/google/common/exceptions.py +137 -0
  21. revenium_middleware/google/common/protocols.py +192 -0
  22. revenium_middleware/google/common/summary_printer.py +271 -0
  23. revenium_middleware/google/common/trace_fields.py +205 -0
  24. revenium_middleware/google/common/types.py +208 -0
  25. revenium_middleware/google/common/utils.py +1111 -0
  26. revenium_middleware/google/config.py +64 -0
  27. revenium_middleware/google/google_ai/__init__.py +53 -0
  28. revenium_middleware/google/google_ai/middleware.py +667 -0
  29. revenium_middleware/google/google_ai/provider.py +135 -0
  30. revenium_middleware/google/prompt_extractor.py +396 -0
  31. revenium_middleware/google/vertex_ai/__init__.py +56 -0
  32. revenium_middleware/google/vertex_ai/middleware.py +1162 -0
  33. revenium_middleware/google/vertex_ai/provider.py +99 -0
  34. revenium_middleware/litellm/__init__.py +25 -0
  35. revenium_middleware/litellm/client/__init__.py +81 -0
  36. revenium_middleware/litellm/client/config.py +53 -0
  37. revenium_middleware/litellm/client/context.py +198 -0
  38. revenium_middleware/litellm/client/decorators.py +912 -0
  39. revenium_middleware/litellm/client/hooks.py +192 -0
  40. revenium_middleware/litellm/client/integrations/__init__.py +26 -0
  41. revenium_middleware/litellm/client/integrations/crewai.py +446 -0
  42. revenium_middleware/litellm/client/middleware.py +321 -0
  43. revenium_middleware/litellm/client/summary_printer.py +314 -0
  44. revenium_middleware/litellm/client/trace_fields.py +51 -0
  45. revenium_middleware/litellm/client/validation.py +207 -0
  46. revenium_middleware/litellm/proxy/__init__.py +25 -0
  47. revenium_middleware/litellm/proxy/middleware.py +217 -0
  48. revenium_middleware/ollama/__init__.py +28 -0
  49. revenium_middleware/ollama/middleware.py +569 -0
  50. revenium_middleware/ollama/trace_fields.py +63 -0
  51. revenium_middleware/openai/__init__.py +23 -0
  52. revenium_middleware/openai/azure_config.py +169 -0
  53. revenium_middleware/openai/azure_model_resolver.py +219 -0
  54. revenium_middleware/openai/config.py +45 -0
  55. revenium_middleware/openai/exceptions.py +115 -0
  56. revenium_middleware/openai/langchain/__init__.py +114 -0
  57. revenium_middleware/openai/langchain/_utils.py +129 -0
  58. revenium_middleware/openai/langchain/unified_handler.py +526 -0
  59. revenium_middleware/openai/middleware.py +1451 -0
  60. revenium_middleware/openai/prompt_extractor.py +173 -0
  61. revenium_middleware/openai/provider.py +170 -0
  62. revenium_middleware/openai/summary_printer.py +292 -0
  63. revenium_middleware/openai/trace_fields.py +98 -0
  64. revenium_middleware/perplexity/__init__.py +97 -0
  65. revenium_middleware/perplexity/middleware.py +379 -0
  66. revenium_middleware/perplexity/perplexity_sdk.py +256 -0
  67. revenium_middleware/perplexity/provider.py +84 -0
  68. revenium_middleware/perplexity/trace_fields.py +25 -0
  69. revenium_python_sdk-0.1.0.dist-info/METADATA +252 -0
  70. revenium_python_sdk-0.1.0.dist-info/RECORD +73 -0
  71. revenium_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  72. revenium_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  73. revenium_python_sdk-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,256 @@
1
+ """
2
+ Middleware for native Perplexity SDK.
3
+
4
+ This module provides metering support for the native perplexity-py SDK.
5
+ """
6
+ import datetime
7
+ import logging
8
+ from typing import Dict, Any
9
+
10
+ import wrapt
11
+ from revenium_middleware import (
12
+ client,
13
+ run_async_in_thread,
14
+ shutdown_event,
15
+ merge_metadata,
16
+ )
17
+
18
+ from .provider import get_provider_metadata, Provider
19
+ from .trace_fields import (
20
+ get_environment,
21
+ get_region,
22
+ get_credential_alias,
23
+ get_trace_type,
24
+ get_trace_name,
25
+ get_parent_transaction_id,
26
+ get_transaction_name,
27
+ get_retry_number
28
+ )
29
+ from .middleware import (
30
+ OperationType,
31
+ get_stop_reason,
32
+ extract_token_usage,
33
+ build_trace_fields
34
+ )
35
+
36
+ logger = logging.getLogger("revenium_middleware.perplexity.sdk")
37
+
38
+
39
+ def perplexity_create_wrapper(wrapped, instance, args, kwargs):
40
+ """
41
+ Wrapper for perplexity.chat.completions.create to add Revenium metering.
42
+
43
+ This wrapper intercepts calls to the native Perplexity SDK and sends
44
+ usage data to Revenium.
45
+ """
46
+ logger.debug("Native Perplexity SDK chat completion wrapper called")
47
+
48
+ # Get usage_metadata from extra_body if present
49
+ extra_body = kwargs.get('extra_body', {})
50
+ api_metadata = extra_body.pop('usage_metadata', {}) if isinstance(extra_body, dict) else {}
51
+
52
+ # Merge with decorator metadata (API metadata takes precedence)
53
+ usage_metadata = merge_metadata(api_metadata)
54
+
55
+ # Get model from kwargs
56
+ model = kwargs.get('model', 'sonar')
57
+
58
+ # Check if streaming
59
+ is_streaming = kwargs.get('stream', False)
60
+
61
+ # Record request time
62
+ request_time_dt = datetime.datetime.now(datetime.timezone.utc)
63
+
64
+ # Generate transaction ID
65
+ transaction_id = f"perplexity-sdk-{request_time_dt.timestamp()}"
66
+
67
+ # Call original method
68
+ logger.debug(
69
+ f"Calling original Perplexity SDK create with model: {model}, "
70
+ f"streaming: {is_streaming}"
71
+ )
72
+ response = wrapped(*args, **kwargs)
73
+
74
+ # Handle response based on streaming
75
+ if is_streaming:
76
+ # For streaming, wrap the iterator
77
+ logger.debug("Wrapping streaming response")
78
+ return PerplexityStreamWrapper(
79
+ response,
80
+ model,
81
+ request_time_dt,
82
+ transaction_id,
83
+ usage_metadata
84
+ )
85
+ else:
86
+ # For non-streaming, send metering data
87
+ logger.debug("Sending metering data for non-streaming response")
88
+ run_async_in_thread(
89
+ send_perplexity_metering_data(
90
+ response=response,
91
+ model=model,
92
+ request_time_dt=request_time_dt,
93
+ transaction_id=transaction_id,
94
+ usage_metadata=usage_metadata,
95
+ is_streaming=False
96
+ )
97
+ )
98
+
99
+ return response
100
+
101
+
102
+ class PerplexityStreamWrapper:
103
+ """Wrapper for Perplexity streaming responses to track usage."""
104
+
105
+ def __init__(self, stream, model, request_time_dt, transaction_id, usage_metadata):
106
+ self.stream = stream
107
+ self.model = model
108
+ self.request_time_dt = request_time_dt
109
+ self.transaction_id = transaction_id
110
+ self.usage_metadata = usage_metadata
111
+ self.chunks = []
112
+ self.last_chunk = None
113
+
114
+ def __iter__(self):
115
+ """Iterate over stream chunks and collect usage data."""
116
+ for chunk in self.stream:
117
+ self.chunks.append(chunk)
118
+ self.last_chunk = chunk
119
+ yield chunk
120
+
121
+ # After stream completes, send metering data
122
+ logger.debug("Stream completed, sending metering data")
123
+ run_async_in_thread(
124
+ send_perplexity_metering_data(
125
+ response=self.last_chunk,
126
+ model=self.model,
127
+ request_time_dt=self.request_time_dt,
128
+ transaction_id=self.transaction_id,
129
+ usage_metadata=self.usage_metadata,
130
+ is_streaming=True,
131
+ chunks=self.chunks
132
+ )
133
+ )
134
+
135
+
136
+ async def send_perplexity_metering_data(
137
+ response,
138
+ model: str,
139
+ request_time_dt: datetime.datetime,
140
+ transaction_id: str,
141
+ usage_metadata: Dict[str, Any],
142
+ is_streaming: bool,
143
+ chunks=None
144
+ ):
145
+ """
146
+ Send metering data to Revenium for native Perplexity SDK.
147
+
148
+ This function extracts usage information from the Perplexity response
149
+ and sends it to Revenium's metering API.
150
+ """
151
+ try:
152
+ # Extract usage data from response
153
+ if hasattr(response, 'usage') and response.usage:
154
+ usage = response.usage
155
+ input_tokens = getattr(usage, 'prompt_tokens', 0)
156
+ output_tokens = getattr(usage, 'completion_tokens', 0)
157
+ total_tokens = getattr(usage, 'total_tokens', input_tokens + output_tokens)
158
+ else:
159
+ logger.warning("No usage data found in response")
160
+ input_tokens = 0
161
+ output_tokens = 0
162
+ total_tokens = 0
163
+
164
+ # Get finish reason
165
+ finish_reason = None
166
+ if hasattr(response, 'choices') and response.choices:
167
+ finish_reason = getattr(response.choices[0], 'finish_reason', None)
168
+
169
+ # Map to Revenium stop reason
170
+ stop_reason = get_stop_reason(finish_reason)
171
+
172
+ # Calculate duration
173
+ response_time_dt = datetime.datetime.now(datetime.timezone.utc)
174
+ duration_ms = int((response_time_dt - request_time_dt).total_seconds() * 1000)
175
+
176
+ # Get provider metadata
177
+ provider_metadata = get_provider_metadata(Provider.PERPLEXITY)
178
+
179
+ # Build trace fields
180
+ trace_fields = build_trace_fields()
181
+
182
+ # Detect operation type (native Perplexity SDK only supports chat)
183
+ operation_type = OperationType.CHAT
184
+
185
+ # Build completion args matching middleware.py schema
186
+ completion_args = {
187
+ "model": model,
188
+ "provider": provider_metadata["provider"],
189
+ "operation_type": operation_type.value,
190
+ "input_token_count": input_tokens,
191
+ "output_token_count": output_tokens,
192
+ "total_token_count": total_tokens,
193
+ "stop_reason": stop_reason,
194
+ "request_time": request_time_dt.strftime("%Y-%m-%dT%H:%M:%SZ"),
195
+ "response_time": response_time_dt.strftime("%Y-%m-%dT%H:%M:%SZ"),
196
+ "completion_start_time": response_time_dt.strftime("%Y-%m-%dT%H:%M:%SZ"),
197
+ "request_duration": duration_ms,
198
+ "transaction_id": transaction_id,
199
+ "is_streamed": is_streaming,
200
+ "cost_type": "AI",
201
+ "cache_creation_token_count": 0,
202
+ "cache_read_token_count": 0,
203
+ "reasoning_token_count": 0,
204
+ }
205
+
206
+ # Add optional fields from usage_metadata if they have values
207
+ if usage_metadata.get("trace_id"):
208
+ completion_args["trace_id"] = usage_metadata.get("trace_id")
209
+ if usage_metadata.get("task_type"):
210
+ completion_args["task_type"] = usage_metadata.get("task_type")
211
+ if usage_metadata.get("organization_id"):
212
+ completion_args["organization_id"] = usage_metadata.get("organization_id")
213
+ if usage_metadata.get("subscription_id"):
214
+ completion_args["subscription_id"] = usage_metadata.get("subscription_id")
215
+ if usage_metadata.get("product_id"):
216
+ completion_args["product_id"] = usage_metadata.get("product_id")
217
+ if usage_metadata.get("agent"):
218
+ completion_args["agent"] = usage_metadata.get("agent")
219
+ if usage_metadata.get("subscriber"):
220
+ completion_args["subscriber"] = usage_metadata.get("subscriber")
221
+
222
+ # Add custom metadata fields (service, step, service_name, etc.)
223
+ # These are additional fields that may be used for tracing/tracking
224
+ custom_fields = ["service", "step", "service_name"]
225
+ for field in custom_fields:
226
+ if usage_metadata.get(field):
227
+ completion_args[field] = usage_metadata.get(field)
228
+
229
+ # Add trace visualization fields from trace_fields
230
+ for key, value in trace_fields.items():
231
+ if value is not None:
232
+ completion_args[key] = value
233
+
234
+ # Send to Revenium
235
+ logger.debug(f"Sending metering data to Revenium: {completion_args}")
236
+ result = client.ai.create_completion(**completion_args)
237
+ logger.debug(f"Metering call result: {result}")
238
+
239
+ except Exception as e:
240
+ if not shutdown_event.is_set():
241
+ logger.warning(f"Error in metering call: {str(e)}")
242
+
243
+
244
+ # Try to patch the Perplexity SDK if it's installed
245
+ try:
246
+ import perplexity.resources.chat.completions # noqa: F401
247
+
248
+ # Apply the wrapper
249
+ wrapt.wrap_function_wrapper(
250
+ 'perplexity.resources.chat.completions',
251
+ 'Completions.create',
252
+ perplexity_create_wrapper
253
+ )
254
+ logger.debug("Successfully patched native Perplexity SDK")
255
+ except (ImportError, AttributeError) as e:
256
+ logger.debug(f"Native Perplexity SDK not available or incompatible: {e}")
@@ -0,0 +1,84 @@
1
+ """
2
+ Provider detection for Perplexity AI API.
3
+
4
+ This module handles detection of Perplexity as the AI provider
5
+ and provides metadata about the provider.
6
+ """
7
+ import logging
8
+ from enum import Enum
9
+ from typing import Optional, Any, Dict
10
+
11
+ logger = logging.getLogger("revenium_middleware.perplexity.provider")
12
+
13
+
14
+ class Provider(str, Enum):
15
+ """Supported AI providers."""
16
+ PERPLEXITY = "PERPLEXITY"
17
+ OPENAI = "OPENAI" # Fallback for OpenAI-compatible APIs
18
+
19
+
20
+ def detect_provider(client: Optional[Any] = None, base_url: Optional[str] = None) -> Provider:
21
+ """
22
+ Detect which AI provider is being used based on available information.
23
+
24
+ Detection priority:
25
+ 1. Base URL substring matching ("perplexity.ai")
26
+ 2. Client base_url attribute
27
+ 3. Default to PERPLEXITY (since this is Perplexity middleware)
28
+
29
+ Args:
30
+ client: OpenAI client instance
31
+ base_url: Base URL for API calls
32
+
33
+ Returns:
34
+ Provider enum indicating detected provider
35
+ """
36
+ logger.debug("Detecting AI provider...")
37
+
38
+ # 1. Check base URL for Perplexity substring
39
+ if base_url and "perplexity" in str(base_url).lower():
40
+ logger.debug(f"Perplexity provider detected via base_url: {base_url}")
41
+ return Provider.PERPLEXITY
42
+
43
+ # 2. Check for client base_url if not provided directly
44
+ if client and hasattr(client, 'base_url') and client.base_url:
45
+ if "perplexity" in str(client.base_url).lower():
46
+ logger.debug(f"Perplexity provider detected via client.base_url: {client.base_url}")
47
+ return Provider.PERPLEXITY
48
+
49
+ # 3. Default to Perplexity (this is Perplexity middleware)
50
+ logger.debug("Defaulting to Perplexity provider")
51
+ return Provider.PERPLEXITY
52
+
53
+
54
+ def get_provider_metadata(provider: Provider) -> Dict[str, str]:
55
+ """
56
+ Get metadata for the detected provider.
57
+
58
+ Args:
59
+ provider: Provider enum value
60
+
61
+ Returns:
62
+ Dictionary with provider metadata
63
+ """
64
+ metadata = {
65
+ "provider": provider.value,
66
+ "model_source": provider.value,
67
+ }
68
+
69
+ logger.debug(f"Provider metadata: {metadata}")
70
+ return metadata
71
+
72
+
73
+ def is_perplexity_provider(provider: Provider) -> bool:
74
+ """
75
+ Check if the provider is Perplexity.
76
+
77
+ Args:
78
+ provider: Provider enum value
79
+
80
+ Returns:
81
+ True if provider is Perplexity, False otherwise
82
+ """
83
+ return provider == Provider.PERPLEXITY
84
+
@@ -0,0 +1,25 @@
1
+ """
2
+ Trace visualization fields for Perplexity AI API.
3
+
4
+ This module handles extraction and validation of trace visualization fields
5
+ for distributed tracing and analytics.
6
+
7
+ All functions are imported from _core.trace_fields. Perplexity has no
8
+ provider-specific trace field functions.
9
+ """
10
+
11
+ from revenium_middleware._core.trace_fields import ( # noqa: F401 — re-exported
12
+ TRACE_TYPE_MAX_LENGTH,
13
+ TRACE_NAME_MAX_LENGTH,
14
+ TRACE_TYPE_PATTERN,
15
+ get_environment,
16
+ get_region,
17
+ get_credential_alias,
18
+ get_trace_type,
19
+ get_trace_name,
20
+ get_parent_transaction_id,
21
+ get_transaction_name,
22
+ get_retry_number,
23
+ validate_trace_type,
24
+ validate_trace_name,
25
+ )
@@ -0,0 +1,252 @@
1
+ Metadata-Version: 2.4
2
+ Name: revenium-python-sdk
3
+ Version: 0.1.0
4
+ Summary: The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, and Perplexity.
5
+ Author-email: Revenium <support@revenium.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/revenium/revenium-python-sdk
8
+ Project-URL: Bug Tracker, https://github.com/revenium/revenium-python-sdk/issues
9
+ Project-URL: Documentation, https://docs.revenium.io
10
+ Keywords: sdk,ai,llm,middleware,metering,revenium,openai,anthropic,google,ollama,litellm,perplexity
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: revenium_metering>=6.8.2
26
+ Provides-Extra: openai
27
+ Requires-Dist: wrapt; extra == "openai"
28
+ Requires-Dist: openai>=1.0.0; extra == "openai"
29
+ Requires-Dist: python-dotenv>=0.19.0; extra == "openai"
30
+ Provides-Extra: langchain
31
+ Requires-Dist: langchain>=0.1.16; extra == "langchain"
32
+ Requires-Dist: langchain-openai>=0.1.0; extra == "langchain"
33
+ Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
34
+ Provides-Extra: perplexity
35
+ Requires-Dist: wrapt; extra == "perplexity"
36
+ Requires-Dist: python-dotenv>=0.19.0; extra == "perplexity"
37
+ Provides-Extra: perplexity-openai
38
+ Requires-Dist: wrapt; extra == "perplexity-openai"
39
+ Requires-Dist: openai>=1.0.0; extra == "perplexity-openai"
40
+ Requires-Dist: python-dotenv>=0.19.0; extra == "perplexity-openai"
41
+ Provides-Extra: perplexity-native
42
+ Requires-Dist: wrapt; extra == "perplexity-native"
43
+ Requires-Dist: perplexityai>=0.1.0; extra == "perplexity-native"
44
+ Requires-Dist: python-dotenv>=0.19.0; extra == "perplexity-native"
45
+ Provides-Extra: google
46
+ Requires-Dist: wrapt; extra == "google"
47
+ Provides-Extra: google-genai
48
+ Requires-Dist: wrapt; extra == "google-genai"
49
+ Requires-Dist: google-genai>=0.1.0; extra == "google-genai"
50
+ Requires-Dist: python-dotenv; extra == "google-genai"
51
+ Provides-Extra: google-vertex
52
+ Requires-Dist: wrapt; extra == "google-vertex"
53
+ Requires-Dist: vertexai>=1.0.0; extra == "google-vertex"
54
+ Requires-Dist: python-dotenv; extra == "google-vertex"
55
+ Provides-Extra: anthropic
56
+ Requires-Dist: wrapt; extra == "anthropic"
57
+ Requires-Dist: anthropic; extra == "anthropic"
58
+ Requires-Dist: python-dotenv>=0.19.0; extra == "anthropic"
59
+ Provides-Extra: ollama
60
+ Requires-Dist: wrapt; extra == "ollama"
61
+ Requires-Dist: ollama; extra == "ollama"
62
+ Provides-Extra: litellm
63
+ Requires-Dist: wrapt; extra == "litellm"
64
+ Requires-Dist: litellm; extra == "litellm"
65
+ Provides-Extra: litellm-proxy
66
+ Requires-Dist: wrapt; extra == "litellm-proxy"
67
+ Requires-Dist: litellm[proxy]; extra == "litellm-proxy"
68
+ Provides-Extra: dev
69
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
70
+ Requires-Dist: pytest-asyncio; extra == "dev"
71
+ Requires-Dist: pytest-cov; extra == "dev"
72
+ Requires-Dist: flake8; extra == "dev"
73
+ Requires-Dist: black; extra == "dev"
74
+ Requires-Dist: mypy; extra == "dev"
75
+ Requires-Dist: freezegun; extra == "dev"
76
+ Requires-Dist: openai-responses>=0.12.0; extra == "dev"
77
+ Requires-Dist: requests; extra == "dev"
78
+ Requires-Dist: wrapt; extra == "dev"
79
+ Requires-Dist: python-dotenv>=0.19.0; extra == "dev"
80
+ Requires-Dist: anthropic; extra == "dev"
81
+ Requires-Dist: boto3; extra == "dev"
82
+ Requires-Dist: ollama; extra == "dev"
83
+ Dynamic: license-file
84
+
85
+ # Revenium Python SDK
86
+
87
+ [![PyPI version](https://img.shields.io/pypi/v/revenium-python-sdk.svg)](https://pypi.org/project/revenium-python-sdk/)
88
+ [![Python Versions](https://img.shields.io/pypi/pyversions/revenium-python-sdk.svg)](https://pypi.org/project/revenium-python-sdk/)
89
+ [![Documentation](https://img.shields.io/badge/docs-revenium.io-blue)](https://docs.revenium.io)
90
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
91
+
92
+ The official Revenium Python SDK — unified AI metering middleware for deeply attributed AI usage metrics. Supports OpenAI, Anthropic, Google (Gemini/Vertex AI), Ollama, LiteLLM, and Perplexity.
93
+
94
+ ## Features
95
+
96
+ - **Unified SDK**: Single package with middleware for all major AI providers
97
+ - **Asynchronous Processing**: Background thread management for non-blocking metering operations
98
+ - **Graceful Shutdown**: Ensures all metering data is properly sent even during application shutdown
99
+ - **Decorator Support**: `@revenium_meter` and `@revenium_metadata` for easy integration
100
+ - **Tool Metering**: Meter arbitrary tool/function calls alongside LLM API metering
101
+
102
+ ## Supported Providers
103
+
104
+ | Provider | Extra | Install Command |
105
+ |----------|-------|----------------|
106
+ | OpenAI | `openai` | `pip install revenium-python-sdk[openai]` |
107
+ | Anthropic | `anthropic` | `pip install revenium-python-sdk[anthropic]` |
108
+ | Google Gemini | `google-genai` | `pip install revenium-python-sdk[google-genai]` |
109
+ | Google Vertex AI | `google-vertex` | `pip install revenium-python-sdk[google-vertex]` |
110
+ | Ollama | `ollama` | `pip install revenium-python-sdk[ollama]` |
111
+ | LiteLLM | `litellm` | `pip install revenium-python-sdk[litellm]` |
112
+ | LiteLLM Proxy | `litellm-proxy` | `pip install revenium-python-sdk[litellm-proxy]` |
113
+ | Perplexity (OpenAI) | `perplexity-openai` | `pip install revenium-python-sdk[perplexity-openai]` |
114
+ | Perplexity (Native) | `perplexity-native` | `pip install revenium-python-sdk[perplexity-native]` |
115
+ | LangChain | `langchain` | `pip install revenium-python-sdk[langchain]` |
116
+
117
+ ## Installation
118
+
119
+ ```bash
120
+ # Core SDK
121
+ pip install revenium-python-sdk
122
+
123
+ # With a specific provider
124
+ pip install revenium-python-sdk[openai]
125
+
126
+ # Multiple providers
127
+ pip install revenium-python-sdk[openai,anthropic,ollama]
128
+ ```
129
+
130
+ ## Quick Start
131
+
132
+ ```python
133
+ from revenium_middleware import client, run_async_in_thread, shutdown_event
134
+
135
+ # Record usage directly
136
+ client.record_usage(
137
+ model="gpt-4o",
138
+ prompt_tokens=500,
139
+ completion_tokens=200,
140
+ user_id="user123",
141
+ session_id="session456"
142
+ )
143
+
144
+ # Run async metering tasks in background threads
145
+ async def async_metering_task():
146
+ await client.async_record_usage(
147
+ model="gpt-3.5-turbo",
148
+ prompt_tokens=300,
149
+ completion_tokens=150,
150
+ user_id="user789"
151
+ )
152
+
153
+ thread = run_async_in_thread(async_metering_task())
154
+
155
+ # Application continues while metering happens in background
156
+ ```
157
+
158
+ ## Provider-Specific Usage
159
+
160
+ Each provider has its own middleware module. See the `examples/` directory for detailed usage:
161
+
162
+ - `examples/openai/` — OpenAI and Azure OpenAI examples
163
+ - `examples/anthropic/` — Anthropic and Bedrock examples
164
+ - `examples/google/` — Google AI and Vertex AI examples
165
+ - `examples/ollama/` — Ollama examples
166
+ - `examples/litellm/` — LiteLLM client and proxy examples
167
+ - `examples/perplexity/` — Perplexity examples
168
+
169
+ ## Tool Metering
170
+
171
+ The `meter_tool` decorator lets you meter arbitrary tool/function calls (web scrapers, image generators, database lookups, etc.) alongside your LLM API metering. This is available via `revenium_metering` v6.8.2+.
172
+
173
+ ```python
174
+ from revenium_middleware import meter_tool, configure
175
+
176
+ # Configure the metering client
177
+ configure(
178
+ metering_url="https://api.revenium.io/meter",
179
+ api_key="your-api-key",
180
+ )
181
+
182
+ # Decorate any tool function to automatically meter it
183
+ @meter_tool("my-web-scraper", operation="scrape")
184
+ def scrape_website(url):
185
+ # Your scraping logic here
186
+ return {"pages": 5, "data_mb": 2.3}
187
+
188
+ # The decorator captures timing, success/failure, and reports to Revenium
189
+ result = scrape_website("https://example.com")
190
+ ```
191
+
192
+ You can also report tool calls manually:
193
+
194
+ ```python
195
+ from revenium_middleware import report_tool_call
196
+
197
+ report_tool_call(
198
+ tool_id="my-tool",
199
+ operation="fetch",
200
+ duration_ms=1234,
201
+ success=True,
202
+ usage_metadata={"records": 42},
203
+ )
204
+ ```
205
+
206
+ ## Compatibility
207
+
208
+ - Python 3.8+
209
+ - Compatible with all supported AI providers
210
+
211
+ ## Logging
212
+
213
+ This module uses Python's standard logging system. You can control the log level by setting the `REVENIUM_LOG_LEVEL` environment variable:
214
+
215
+ ```bash
216
+ # Enable debug logging
217
+ export REVENIUM_LOG_LEVEL=DEBUG
218
+
219
+ # Or when running your script
220
+ REVENIUM_LOG_LEVEL=DEBUG python your_script.py
221
+ ```
222
+
223
+ Available log levels:
224
+ - `DEBUG`: Detailed debugging information
225
+ - `INFO`: General information (default)
226
+ - `WARNING`: Warning messages only
227
+ - `ERROR`: Error messages only
228
+ - `CRITICAL`: Critical error messages only
229
+
230
+ ## Documentation
231
+
232
+ For detailed documentation, visit [docs.revenium.io](https://docs.revenium.io)
233
+
234
+ ## Contributing
235
+
236
+ See [CONTRIBUTING.md](./CONTRIBUTING.md)
237
+
238
+ ## Code of Conduct
239
+
240
+ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
241
+
242
+ ## Security
243
+
244
+ See [SECURITY.md](./SECURITY.md)
245
+
246
+ ## License
247
+
248
+ This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
249
+
250
+ ## Acknowledgments
251
+
252
+ - Built by the Revenium team