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,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trace visualization field capture and validation.
|
|
3
|
+
|
|
4
|
+
This module provides functions to capture trace visualization fields from
|
|
5
|
+
environment variables and validate them according to the specification.
|
|
6
|
+
|
|
7
|
+
Shared functions are imported from _core.trace_fields. This module retains
|
|
8
|
+
only Anthropic-specific functions: detect_vision_content and detect_operation_type.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
|
|
13
|
+
from revenium_middleware._core.trace_fields import ( # noqa: F401 — re-exported
|
|
14
|
+
TRACE_TYPE_MAX_LENGTH,
|
|
15
|
+
TRACE_NAME_MAX_LENGTH,
|
|
16
|
+
TRACE_TYPE_PATTERN,
|
|
17
|
+
get_environment,
|
|
18
|
+
get_region,
|
|
19
|
+
get_credential_alias,
|
|
20
|
+
get_trace_type,
|
|
21
|
+
get_trace_name,
|
|
22
|
+
get_parent_transaction_id,
|
|
23
|
+
get_transaction_name,
|
|
24
|
+
get_retry_number,
|
|
25
|
+
validate_trace_type,
|
|
26
|
+
validate_trace_name,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def detect_vision_content(messages: Optional[list] = None) -> bool:
|
|
31
|
+
"""
|
|
32
|
+
Detect if messages contain vision/image content.
|
|
33
|
+
|
|
34
|
+
Anthropic vision content format:
|
|
35
|
+
{
|
|
36
|
+
"type": "image",
|
|
37
|
+
"source": {
|
|
38
|
+
"type": "base64" | "url",
|
|
39
|
+
"media_type": "image/jpeg",
|
|
40
|
+
"data": "..." # or "url": "..."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
messages: List of message objects from Anthropic API request
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if any message contains image content, False otherwise
|
|
49
|
+
"""
|
|
50
|
+
if not messages:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
for message in messages:
|
|
54
|
+
if not isinstance(message, dict):
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
content = message.get("content")
|
|
58
|
+
if content is None:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Content can be a string (no images) or a list of content blocks
|
|
62
|
+
if isinstance(content, list):
|
|
63
|
+
for block in content:
|
|
64
|
+
if isinstance(block, dict) and block.get("type") == "image":
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def detect_operation_type(
|
|
71
|
+
provider: str,
|
|
72
|
+
endpoint: str,
|
|
73
|
+
request_body: Optional[Dict[str, Any]] = None
|
|
74
|
+
) -> Dict[str, Optional[str]]:
|
|
75
|
+
"""
|
|
76
|
+
Auto-detect operation type and subtype from provider, endpoint,
|
|
77
|
+
and request.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
provider: Provider name (e.g., 'openai', 'azure_openai') or Provider enum
|
|
81
|
+
endpoint: API endpoint (e.g., '/chat/completions', '/embeddings')
|
|
82
|
+
request_body: Optional request body to check for tools/functions
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary with 'operationType' and 'operationSubtype' keys
|
|
86
|
+
"""
|
|
87
|
+
# Handle Provider enum or string
|
|
88
|
+
if hasattr(provider, 'name'):
|
|
89
|
+
# It's a Provider enum, get the name (e.g., 'OPENAI', 'AZURE_OPENAI')
|
|
90
|
+
provider_str = provider.name
|
|
91
|
+
else:
|
|
92
|
+
provider_str = str(provider)
|
|
93
|
+
|
|
94
|
+
provider_lower = provider_str.lower()
|
|
95
|
+
request_body = request_body or {}
|
|
96
|
+
|
|
97
|
+
# OpenAI and Azure OpenAI
|
|
98
|
+
if provider_lower in ('openai', 'azure_openai', 'azure'):
|
|
99
|
+
# Chat completions
|
|
100
|
+
is_chat = (
|
|
101
|
+
'chat/completions' in endpoint or
|
|
102
|
+
endpoint.endswith('/chat/completions')
|
|
103
|
+
)
|
|
104
|
+
if is_chat:
|
|
105
|
+
# Check for tools or functions
|
|
106
|
+
has_tools = (
|
|
107
|
+
request_body.get('tools') or
|
|
108
|
+
request_body.get('functions')
|
|
109
|
+
)
|
|
110
|
+
if has_tools:
|
|
111
|
+
return {
|
|
112
|
+
'operationType': 'TOOL_CALL',
|
|
113
|
+
'operationSubtype': 'function_call'
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
'operationType': 'CHAT',
|
|
117
|
+
'operationSubtype': None
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Embeddings
|
|
121
|
+
if 'embeddings' in endpoint or endpoint.endswith('/embeddings'):
|
|
122
|
+
return {
|
|
123
|
+
'operationType': 'EMBED',
|
|
124
|
+
'operationSubtype': None
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Moderations
|
|
128
|
+
if 'moderations' in endpoint or endpoint.endswith('/moderations'):
|
|
129
|
+
return {
|
|
130
|
+
'operationType': 'MODERATION',
|
|
131
|
+
'operationSubtype': None
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Anthropic
|
|
135
|
+
if provider_lower in ('anthropic', 'bedrock'):
|
|
136
|
+
# Messages endpoint (chat completions)
|
|
137
|
+
is_messages = (
|
|
138
|
+
'/messages' in endpoint or
|
|
139
|
+
endpoint.endswith('/messages')
|
|
140
|
+
)
|
|
141
|
+
if is_messages:
|
|
142
|
+
# Check for tools
|
|
143
|
+
has_tools = request_body.get('tools')
|
|
144
|
+
if has_tools:
|
|
145
|
+
return {
|
|
146
|
+
'operationType': 'TOOL_CALL',
|
|
147
|
+
'operationSubtype': None
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
'operationType': 'CHAT',
|
|
151
|
+
'operationSubtype': None
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Default fallback
|
|
155
|
+
return {
|
|
156
|
+
'operationType': 'CHAT',
|
|
157
|
+
'operationSubtype': None
|
|
158
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Revenium middleware for Google AI services.
|
|
3
|
+
|
|
4
|
+
This package provides middleware to track and meter usage of Google AI services
|
|
5
|
+
including Google AI SDK (google-genai) and Vertex AI SDK (vertexai).
|
|
6
|
+
|
|
7
|
+
The middleware automatically wraps API calls to capture usage metrics and send
|
|
8
|
+
them to Revenium for tracking and billing purposes.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
Simply import this package before using Google AI services:
|
|
12
|
+
|
|
13
|
+
import revenium_middleware.google
|
|
14
|
+
from google import genai # or import vertexai
|
|
15
|
+
|
|
16
|
+
# Your existing code works unchanged
|
|
17
|
+
client = genai.Client(api_key="your-key")
|
|
18
|
+
response = client.models.generate_content(...)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Configure logging based on REVENIUM_LOG_LEVEL environment variable
|
|
26
|
+
def _configure_logging():
|
|
27
|
+
"""Configure logging for the Revenium middleware."""
|
|
28
|
+
log_level_str = os.getenv("REVENIUM_LOG_LEVEL", "INFO").upper()
|
|
29
|
+
|
|
30
|
+
# Map string levels to logging constants
|
|
31
|
+
level_mapping = {
|
|
32
|
+
"DEBUG": logging.DEBUG,
|
|
33
|
+
"INFO": logging.INFO,
|
|
34
|
+
"WARNING": logging.WARNING,
|
|
35
|
+
"ERROR": logging.ERROR,
|
|
36
|
+
"CRITICAL": logging.CRITICAL,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
log_level = level_mapping.get(log_level_str, logging.INFO)
|
|
40
|
+
|
|
41
|
+
# Configure the revenium middleware logger
|
|
42
|
+
revenium_logger = logging.getLogger("revenium_middleware")
|
|
43
|
+
revenium_logger.setLevel(log_level)
|
|
44
|
+
|
|
45
|
+
# Only add handler if none exists to avoid duplicate logs
|
|
46
|
+
if not revenium_logger.handlers:
|
|
47
|
+
handler = logging.StreamHandler()
|
|
48
|
+
formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
|
|
49
|
+
handler.setFormatter(formatter)
|
|
50
|
+
revenium_logger.addHandler(handler)
|
|
51
|
+
|
|
52
|
+
return log_level
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Configure logging
|
|
56
|
+
_log_level = _configure_logging()
|
|
57
|
+
|
|
58
|
+
# Set up logging
|
|
59
|
+
logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
# Check if verbose startup logging is enabled
|
|
62
|
+
_verbose_startup = os.getenv("REVENIUM_VERBOSE_STARTUP", "").lower() in (
|
|
63
|
+
"true",
|
|
64
|
+
"1",
|
|
65
|
+
"yes",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if _verbose_startup:
|
|
69
|
+
logger.info("Revenium middleware initialization starting")
|
|
70
|
+
|
|
71
|
+
# Import common utilities (always available)
|
|
72
|
+
from .common import utils
|
|
73
|
+
|
|
74
|
+
# Import and activate middleware for available SDKs
|
|
75
|
+
try:
|
|
76
|
+
# Try to import Google AI SDK and activate middleware
|
|
77
|
+
if _verbose_startup:
|
|
78
|
+
logger.debug("Attempting to import google.genai")
|
|
79
|
+
import google.genai
|
|
80
|
+
|
|
81
|
+
if _verbose_startup:
|
|
82
|
+
logger.debug("google.genai imported successfully, importing middleware")
|
|
83
|
+
from .google_ai import middleware as google_ai_middleware
|
|
84
|
+
|
|
85
|
+
logger.info("Google AI SDK middleware activated")
|
|
86
|
+
except ImportError as e:
|
|
87
|
+
logger.debug("Google AI SDK (google-genai) not available: %s", e)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# Try to import Vertex AI SDK and activate middleware
|
|
91
|
+
if _verbose_startup:
|
|
92
|
+
logger.debug("Attempting to import vertexai")
|
|
93
|
+
import vertexai
|
|
94
|
+
|
|
95
|
+
if _verbose_startup:
|
|
96
|
+
logger.debug("vertexai imported successfully, importing middleware")
|
|
97
|
+
from .vertex_ai import middleware as vertex_ai_middleware
|
|
98
|
+
|
|
99
|
+
logger.info("Vertex AI SDK middleware activated")
|
|
100
|
+
except ImportError as e:
|
|
101
|
+
logger.debug("Vertex AI SDK (vertexai) not available: %s", e)
|
|
102
|
+
|
|
103
|
+
active_sdks = []
|
|
104
|
+
if "google.genai" in globals():
|
|
105
|
+
active_sdks.append("google_ai")
|
|
106
|
+
if "vertexai" in globals():
|
|
107
|
+
active_sdks.append("vertex_ai")
|
|
108
|
+
|
|
109
|
+
logger.info(
|
|
110
|
+
"Revenium middleware activated for: %s",
|
|
111
|
+
", ".join(active_sdks) if active_sdks else "none",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
__all__ = ["utils"]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common utilities and shared types for Google AI middleware.
|
|
3
|
+
|
|
4
|
+
This module contains shared functionality used by both Google AI SDK
|
|
5
|
+
and Vertex AI SDK middleware implementations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .types import (
|
|
9
|
+
OperationType,
|
|
10
|
+
Provider,
|
|
11
|
+
ProviderMetadata,
|
|
12
|
+
UsageData,
|
|
13
|
+
TokenCounts,
|
|
14
|
+
normalize_stop_reason,
|
|
15
|
+
GOOGLE_AI_STOP_REASONS,
|
|
16
|
+
VERTEX_AI_STOP_REASONS,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .utils import (
|
|
20
|
+
generate_transaction_id,
|
|
21
|
+
format_timestamp,
|
|
22
|
+
calculate_duration_ms,
|
|
23
|
+
log_token_usage,
|
|
24
|
+
log_image_usage,
|
|
25
|
+
log_video_usage,
|
|
26
|
+
create_metering_call,
|
|
27
|
+
create_image_metering_call,
|
|
28
|
+
create_video_metering_call,
|
|
29
|
+
extract_model_name,
|
|
30
|
+
extract_token_counts,
|
|
31
|
+
create_usage_data,
|
|
32
|
+
is_debug_logging_enabled,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .exceptions import (
|
|
36
|
+
ReveniumMiddlewareError,
|
|
37
|
+
MeteringError,
|
|
38
|
+
TokenExtractionError,
|
|
39
|
+
ProviderDetectionError,
|
|
40
|
+
ConfigurationError,
|
|
41
|
+
StreamingError,
|
|
42
|
+
APIResponseError,
|
|
43
|
+
handle_metering_error,
|
|
44
|
+
safe_extract,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from .protocols import (
|
|
48
|
+
UsageMetadataProtocol,
|
|
49
|
+
CandidateProtocol,
|
|
50
|
+
EmbeddingProtocol,
|
|
51
|
+
ChatResponseProtocol,
|
|
52
|
+
EmbeddingResponseProtocol,
|
|
53
|
+
StreamChunkProtocol,
|
|
54
|
+
ClientProtocol,
|
|
55
|
+
is_chat_response,
|
|
56
|
+
is_embedding_response,
|
|
57
|
+
is_stream_chunk,
|
|
58
|
+
has_usage_metadata,
|
|
59
|
+
has_token_counts,
|
|
60
|
+
safe_getattr,
|
|
61
|
+
get_token_count,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
from .summary_printer import (
|
|
65
|
+
CompletionMetrics,
|
|
66
|
+
fetch_completion_metrics,
|
|
67
|
+
format_and_print_json_summary,
|
|
68
|
+
format_and_print_human_summary,
|
|
69
|
+
print_usage_summary,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
# Types
|
|
74
|
+
"OperationType",
|
|
75
|
+
"Provider",
|
|
76
|
+
"ProviderMetadata",
|
|
77
|
+
"UsageData",
|
|
78
|
+
"TokenCounts",
|
|
79
|
+
"normalize_stop_reason",
|
|
80
|
+
"GOOGLE_AI_STOP_REASONS",
|
|
81
|
+
"VERTEX_AI_STOP_REASONS",
|
|
82
|
+
# Utils
|
|
83
|
+
"generate_transaction_id",
|
|
84
|
+
"format_timestamp",
|
|
85
|
+
"calculate_duration_ms",
|
|
86
|
+
"log_token_usage",
|
|
87
|
+
"log_image_usage",
|
|
88
|
+
"log_video_usage",
|
|
89
|
+
"create_metering_call",
|
|
90
|
+
"create_image_metering_call",
|
|
91
|
+
"create_video_metering_call",
|
|
92
|
+
"extract_model_name",
|
|
93
|
+
"extract_token_counts",
|
|
94
|
+
"create_usage_data",
|
|
95
|
+
"is_debug_logging_enabled",
|
|
96
|
+
# Exceptions
|
|
97
|
+
"ReveniumMiddlewareError",
|
|
98
|
+
"MeteringError",
|
|
99
|
+
"TokenExtractionError",
|
|
100
|
+
"ProviderDetectionError",
|
|
101
|
+
"ConfigurationError",
|
|
102
|
+
"StreamingError",
|
|
103
|
+
"APIResponseError",
|
|
104
|
+
"handle_metering_error",
|
|
105
|
+
"safe_extract",
|
|
106
|
+
# Protocols
|
|
107
|
+
"UsageMetadataProtocol",
|
|
108
|
+
"CandidateProtocol",
|
|
109
|
+
"EmbeddingProtocol",
|
|
110
|
+
"ChatResponseProtocol",
|
|
111
|
+
"EmbeddingResponseProtocol",
|
|
112
|
+
"StreamChunkProtocol",
|
|
113
|
+
"ClientProtocol",
|
|
114
|
+
"is_chat_response",
|
|
115
|
+
"is_embedding_response",
|
|
116
|
+
"is_stream_chunk",
|
|
117
|
+
"has_usage_metadata",
|
|
118
|
+
"has_token_counts",
|
|
119
|
+
"safe_getattr",
|
|
120
|
+
"get_token_count",
|
|
121
|
+
# Summary Printer
|
|
122
|
+
"CompletionMetrics",
|
|
123
|
+
"fetch_completion_metrics",
|
|
124
|
+
"format_and_print_json_summary",
|
|
125
|
+
"format_and_print_human_summary",
|
|
126
|
+
"print_usage_summary",
|
|
127
|
+
]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exception classes for Revenium Google AI middleware.
|
|
3
|
+
|
|
4
|
+
This module defines a hierarchy of exceptions for different error scenarios
|
|
5
|
+
that can occur during middleware operation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReveniumMiddlewareError(Exception):
|
|
12
|
+
"""Base exception for all Revenium middleware errors."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, message: str, details: Optional[dict] = None):
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
self.message = message
|
|
17
|
+
self.details = details or {}
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
if self.details:
|
|
21
|
+
return f"{self.message} (Details: {self.details})"
|
|
22
|
+
return self.message
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MeteringError(ReveniumMiddlewareError):
|
|
26
|
+
"""Raised when metering operations fail."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str,
|
|
31
|
+
transaction_id: Optional[str] = None,
|
|
32
|
+
api_response: Optional[Any] = None,
|
|
33
|
+
**kwargs,
|
|
34
|
+
):
|
|
35
|
+
super().__init__(message, kwargs)
|
|
36
|
+
self.transaction_id = transaction_id
|
|
37
|
+
self.api_response = api_response
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TokenExtractionError(ReveniumMiddlewareError):
|
|
41
|
+
"""Raised when token count extraction fails."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
message: str,
|
|
46
|
+
response_type: Optional[str] = None,
|
|
47
|
+
operation_type: Optional[str] = None,
|
|
48
|
+
**kwargs,
|
|
49
|
+
):
|
|
50
|
+
super().__init__(message, kwargs)
|
|
51
|
+
self.response_type = response_type
|
|
52
|
+
self.operation_type = operation_type
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ProviderDetectionError(ReveniumMiddlewareError):
|
|
56
|
+
"""Raised when provider detection fails."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, message: str, available_sdks: Optional[list] = None, **kwargs):
|
|
59
|
+
super().__init__(message, kwargs)
|
|
60
|
+
self.available_sdks = available_sdks or []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConfigurationError(ReveniumMiddlewareError):
|
|
64
|
+
"""Raised when configuration is invalid or missing."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, message: str, missing_config: Optional[list] = None, **kwargs):
|
|
67
|
+
super().__init__(message, kwargs)
|
|
68
|
+
self.missing_config = missing_config or []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class StreamingError(ReveniumMiddlewareError):
|
|
72
|
+
"""Raised when streaming operations fail."""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
message: str,
|
|
77
|
+
chunk_count: Optional[int] = None,
|
|
78
|
+
stream_state: Optional[str] = None,
|
|
79
|
+
**kwargs,
|
|
80
|
+
):
|
|
81
|
+
super().__init__(message, kwargs)
|
|
82
|
+
self.chunk_count = chunk_count
|
|
83
|
+
self.stream_state = stream_state
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class APIResponseError(ReveniumMiddlewareError):
|
|
87
|
+
"""Raised when API response is malformed or unexpected."""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
message: str,
|
|
92
|
+
response_data: Optional[Any] = None,
|
|
93
|
+
expected_format: Optional[str] = None,
|
|
94
|
+
**kwargs,
|
|
95
|
+
):
|
|
96
|
+
super().__init__(message, kwargs)
|
|
97
|
+
self.response_data = response_data
|
|
98
|
+
self.expected_format = expected_format
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Utility functions for error handling
|
|
102
|
+
def handle_metering_error(func):
|
|
103
|
+
"""Decorator to handle metering errors gracefully."""
|
|
104
|
+
|
|
105
|
+
def wrapper(*args, **kwargs):
|
|
106
|
+
try:
|
|
107
|
+
return func(*args, **kwargs)
|
|
108
|
+
except MeteringError:
|
|
109
|
+
# Re-raise metering errors as-is
|
|
110
|
+
raise
|
|
111
|
+
except Exception as e:
|
|
112
|
+
# Convert other exceptions to MeteringError
|
|
113
|
+
raise MeteringError(
|
|
114
|
+
f"Unexpected error in {func.__name__}: {str(e)}",
|
|
115
|
+
details={"original_error": type(e).__name__},
|
|
116
|
+
) from e
|
|
117
|
+
|
|
118
|
+
return wrapper
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def safe_extract(func):
|
|
122
|
+
"""Decorator to handle extraction errors gracefully."""
|
|
123
|
+
|
|
124
|
+
def wrapper(*args, **kwargs):
|
|
125
|
+
try:
|
|
126
|
+
return func(*args, **kwargs)
|
|
127
|
+
except (TokenExtractionError, APIResponseError):
|
|
128
|
+
# Re-raise extraction errors as-is
|
|
129
|
+
raise
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# Convert other exceptions to TokenExtractionError
|
|
132
|
+
raise TokenExtractionError(
|
|
133
|
+
f"Unexpected error in {func.__name__}: {str(e)}",
|
|
134
|
+
details={"original_error": type(e).__name__},
|
|
135
|
+
) from e
|
|
136
|
+
|
|
137
|
+
return wrapper
|