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.
- revenium_middleware/__init__.py +184 -0
- revenium_middleware/_core/__init__.py +65 -0
- revenium_middleware/_core/config.py +165 -0
- revenium_middleware/_core/context.py +109 -0
- revenium_middleware/_core/decorators.py +202 -0
- revenium_middleware/_core/metering.py +207 -0
- revenium_middleware/_core/prompt_extraction.py +55 -0
- revenium_middleware/_core/subscriber.py +51 -0
- revenium_middleware/_core/trace_fields.py +265 -0
- revenium_middleware/anthropic/__init__.py +108 -0
- revenium_middleware/anthropic/bedrock_adapter.py +753 -0
- revenium_middleware/anthropic/config.py +29 -0
- revenium_middleware/anthropic/middleware.py +1070 -0
- revenium_middleware/anthropic/prompt_extractor.py +178 -0
- revenium_middleware/anthropic/provider.py +141 -0
- revenium_middleware/anthropic/summary_printer.py +286 -0
- revenium_middleware/anthropic/trace_fields.py +158 -0
- revenium_middleware/google/__init__.py +114 -0
- revenium_middleware/google/common/__init__.py +127 -0
- revenium_middleware/google/common/exceptions.py +137 -0
- revenium_middleware/google/common/protocols.py +192 -0
- revenium_middleware/google/common/summary_printer.py +271 -0
- revenium_middleware/google/common/trace_fields.py +205 -0
- revenium_middleware/google/common/types.py +208 -0
- revenium_middleware/google/common/utils.py +1111 -0
- revenium_middleware/google/config.py +64 -0
- revenium_middleware/google/google_ai/__init__.py +53 -0
- revenium_middleware/google/google_ai/middleware.py +667 -0
- revenium_middleware/google/google_ai/provider.py +135 -0
- revenium_middleware/google/prompt_extractor.py +396 -0
- revenium_middleware/google/vertex_ai/__init__.py +56 -0
- revenium_middleware/google/vertex_ai/middleware.py +1162 -0
- revenium_middleware/google/vertex_ai/provider.py +99 -0
- revenium_middleware/litellm/__init__.py +25 -0
- revenium_middleware/litellm/client/__init__.py +81 -0
- revenium_middleware/litellm/client/config.py +53 -0
- revenium_middleware/litellm/client/context.py +198 -0
- revenium_middleware/litellm/client/decorators.py +912 -0
- revenium_middleware/litellm/client/hooks.py +192 -0
- revenium_middleware/litellm/client/integrations/__init__.py +26 -0
- revenium_middleware/litellm/client/integrations/crewai.py +446 -0
- revenium_middleware/litellm/client/middleware.py +321 -0
- revenium_middleware/litellm/client/summary_printer.py +314 -0
- revenium_middleware/litellm/client/trace_fields.py +51 -0
- revenium_middleware/litellm/client/validation.py +207 -0
- revenium_middleware/litellm/proxy/__init__.py +25 -0
- revenium_middleware/litellm/proxy/middleware.py +217 -0
- revenium_middleware/ollama/__init__.py +28 -0
- revenium_middleware/ollama/middleware.py +569 -0
- revenium_middleware/ollama/trace_fields.py +63 -0
- revenium_middleware/openai/__init__.py +23 -0
- revenium_middleware/openai/azure_config.py +169 -0
- revenium_middleware/openai/azure_model_resolver.py +219 -0
- revenium_middleware/openai/config.py +45 -0
- revenium_middleware/openai/exceptions.py +115 -0
- revenium_middleware/openai/langchain/__init__.py +114 -0
- revenium_middleware/openai/langchain/_utils.py +129 -0
- revenium_middleware/openai/langchain/unified_handler.py +526 -0
- revenium_middleware/openai/middleware.py +1451 -0
- revenium_middleware/openai/prompt_extractor.py +173 -0
- revenium_middleware/openai/provider.py +170 -0
- revenium_middleware/openai/summary_printer.py +292 -0
- revenium_middleware/openai/trace_fields.py +98 -0
- revenium_middleware/perplexity/__init__.py +97 -0
- revenium_middleware/perplexity/middleware.py +379 -0
- revenium_middleware/perplexity/perplexity_sdk.py +256 -0
- revenium_middleware/perplexity/provider.py +84 -0
- revenium_middleware/perplexity/trace_fields.py +25 -0
- revenium_python_sdk-0.1.0.dist-info/METADATA +252 -0
- revenium_python_sdk-0.1.0.dist-info/RECORD +73 -0
- revenium_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- revenium_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
[](https://pypi.org/project/revenium-python-sdk/)
|
|
88
|
+
[](https://pypi.org/project/revenium-python-sdk/)
|
|
89
|
+
[](https://docs.revenium.io)
|
|
90
|
+
[](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
|