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,99 @@
1
+ """
2
+ Provider detection and configuration for Vertex AI SDK support.
3
+
4
+ This module handles provider detection for the native Vertex AI SDK (vertexai package).
5
+ All Vertex AI SDK usage reports as "Google" provider for unified analytics.
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from ..common import ProviderMetadata
11
+
12
+ logger = logging.getLogger("revenium_middleware.extension")
13
+
14
+
15
+ def detect_provider() -> str:
16
+ """
17
+ Detect Vertex AI provider configuration.
18
+
19
+ For the native Vertex AI SDK, we always return "vertex_ai" since
20
+ this module only handles the vertexai package.
21
+
22
+ Returns:
23
+ Always returns "vertex_ai" for this module
24
+ """
25
+ logger.debug("Vertex AI SDK provider detected")
26
+ return "vertex_ai"
27
+
28
+
29
+ def get_provider_metadata() -> ProviderMetadata:
30
+ """
31
+ Get provider metadata for Vertex AI SDK usage records.
32
+
33
+ Returns standardized "Google" provider name for unified analytics.
34
+
35
+ Returns:
36
+ ProviderMetadata with "Google" provider name
37
+ """
38
+ return ProviderMetadata.for_vertex_ai_sdk()
39
+
40
+
41
+ def validate_vertex_ai_configuration() -> bool:
42
+ """
43
+ Validate that Vertex AI is properly configured.
44
+
45
+ Checks for required environment variables and configuration.
46
+
47
+ Returns:
48
+ True if Vertex AI is properly configured, False otherwise
49
+ """
50
+ # Check for required Vertex AI configuration
51
+ project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
52
+ location = os.getenv("GOOGLE_CLOUD_LOCATION")
53
+
54
+ if not project_id:
55
+ logger.warning(
56
+ "GOOGLE_CLOUD_PROJECT environment variable not set for Vertex AI"
57
+ )
58
+ return False
59
+
60
+ if not location:
61
+ logger.warning(
62
+ "GOOGLE_CLOUD_LOCATION environment variable not set for Vertex AI"
63
+ )
64
+ return False
65
+
66
+ logger.debug(
67
+ f"Vertex AI configuration validated: project={project_id}, location={location}"
68
+ )
69
+ return True
70
+
71
+
72
+ def get_vertex_ai_config() -> dict:
73
+ """
74
+ Get Vertex AI configuration from environment variables.
75
+
76
+ Returns:
77
+ Dictionary with Vertex AI configuration
78
+ """
79
+ return {
80
+ "project_id": os.getenv("GOOGLE_CLOUD_PROJECT"),
81
+ "location": os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1"),
82
+ "credentials_path": os.getenv("GOOGLE_APPLICATION_CREDENTIALS"),
83
+ }
84
+
85
+
86
+ def is_vertex_ai_available() -> bool:
87
+ """
88
+ Check if Vertex AI SDK is available and properly configured.
89
+
90
+ Returns:
91
+ True if Vertex AI SDK is available and configured, False otherwise
92
+ """
93
+ try:
94
+ import vertexai
95
+
96
+ return validate_vertex_ai_configuration()
97
+ except ImportError:
98
+ logger.debug("Vertex AI SDK not available")
99
+ return False
@@ -0,0 +1,25 @@
1
+ """
2
+ Revenium Middleware for LiteLLM.
3
+
4
+ Provides both client-side (wrapt-based) and proxy-side (custom logger) middleware
5
+ for metering LiteLLM usage.
6
+ """
7
+
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Conditionally import subpackages
13
+ try:
14
+ import litellm as _litellm # noqa: F401
15
+ from . import client
16
+ from . import proxy
17
+ except ImportError:
18
+ logger.debug("LiteLLM SDK (litellm) not available, middleware not loaded")
19
+ client = None # type: ignore
20
+ proxy = None # type: ignore
21
+
22
+ __all__ = [
23
+ "client",
24
+ "proxy",
25
+ ]
@@ -0,0 +1,81 @@
1
+ """
2
+ Revenium LiteLLM Client Middleware
3
+
4
+ When you install and import this library, it will automatically hook
5
+ litellm.completion using wrapt, and log token usage after each request.
6
+
7
+ New in v0.2.0:
8
+ - Context-based metadata injection via metadata_context
9
+ - Type-safe validation with UsageMetadata (requires pydantic)
10
+ - Decorator-based metadata injection (8 decorators available)
11
+ - Framework integrations (CrewAI integration available)
12
+
13
+ Basic Usage:
14
+ >>> import revenium_middleware.litellm.client.middleware
15
+ >>> import litellm
16
+ >>> response = litellm.completion(
17
+ ... model="gpt-4",
18
+ ... messages=[{"role": "user", "content": "Hello"}],
19
+ ... usage_metadata={"agent": "my-agent"}
20
+ ... )
21
+
22
+ Context-Based Usage:
23
+ >>> from revenium_middleware.litellm.client import metadata_context
24
+ >>> with metadata_context.set(agent="Lead Analyst", task_type="research"):
25
+ ... response = litellm.completion(...) # Metadata auto-injected
26
+ """
27
+ import logging
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Import components that don't require litellm SDK
32
+ from .context import metadata_context, MetadataContext
33
+ from .validation import UsageMetadata, Subscriber, SubscriberCredential, PYDANTIC_AVAILABLE
34
+ from .decorators import (
35
+ track_agent,
36
+ track_task,
37
+ track_trace,
38
+ track_organization,
39
+ track_subscription,
40
+ track_product,
41
+ track_subscriber,
42
+ track_quality
43
+ )
44
+ from .hooks import (
45
+ register_metadata_hook,
46
+ unregister_metadata_hook,
47
+ clear_metadata_hooks,
48
+ get_registered_hooks
49
+ )
50
+
51
+ # Conditionally import middleware (requires litellm SDK)
52
+ try:
53
+ import litellm as _litellm # noqa: F401
54
+ from .middleware import completion_wrapper
55
+ except ImportError:
56
+ logger.debug("LiteLLM SDK (litellm) not available, middleware not loaded")
57
+ completion_wrapper = None # type: ignore
58
+
59
+ __version__ = "0.2.0"
60
+
61
+ __all__ = [
62
+ 'completion_wrapper',
63
+ 'metadata_context',
64
+ 'MetadataContext',
65
+ 'UsageMetadata',
66
+ 'Subscriber',
67
+ 'SubscriberCredential',
68
+ 'PYDANTIC_AVAILABLE',
69
+ 'track_agent',
70
+ 'track_task',
71
+ 'track_trace',
72
+ 'track_organization',
73
+ 'track_subscription',
74
+ 'track_product',
75
+ 'track_subscriber',
76
+ 'track_quality',
77
+ 'register_metadata_hook',
78
+ 'unregister_metadata_hook',
79
+ 'clear_metadata_hooks',
80
+ 'get_registered_hooks',
81
+ ]
@@ -0,0 +1,53 @@
1
+ """
2
+ Configuration for Revenium LiteLLM middleware.
3
+
4
+ Imports shared config from core and adds LiteLLM-specific settings.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional
9
+
10
+ from revenium_middleware._core.config import ( # noqa: F401
11
+ Config as _CoreConfig,
12
+ SummaryFormat,
13
+ parse_print_summary_value,
14
+ get_print_summary_config,
15
+ get_team_id,
16
+ get_base_url,
17
+ )
18
+
19
+ # Re-export environment variable names for backward compatibility
20
+ ENV_REVENIUM_PRINT_SUMMARY = _CoreConfig.ENV_REVENIUM_PRINT_SUMMARY
21
+ ENV_REVENIUM_TEAM_ID = _CoreConfig.ENV_REVENIUM_TEAM_ID
22
+ ENV_REVENIUM_METERING_BASE_URL = _CoreConfig.ENV_REVENIUM_BASE_URL
23
+ ENV_REVENIUM_METERING_API_KEY = _CoreConfig.ENV_REVENIUM_API_KEY
24
+
25
+ # Summary settings (LiteLLM uses a shorter retry delay)
26
+ SUMMARY_RETRY_ATTEMPTS = _CoreConfig.SUMMARY_RETRY_ATTEMPTS
27
+ SUMMARY_RETRY_DELAY: float = 1.0 # LiteLLM uses 1.0s vs core's 2.0s
28
+ SUMMARY_API_TIMEOUT = _CoreConfig.SUMMARY_API_TIMEOUT
29
+
30
+ DEFAULT_BASE_URL = _CoreConfig.DEFAULT_BASE_URL
31
+
32
+
33
+ def get_api_key() -> Optional[str]:
34
+ """Get the Revenium API key from environment."""
35
+ return os.getenv(ENV_REVENIUM_METERING_API_KEY)
36
+
37
+
38
+ __all__ = [
39
+ 'ENV_REVENIUM_PRINT_SUMMARY',
40
+ 'ENV_REVENIUM_TEAM_ID',
41
+ 'ENV_REVENIUM_METERING_BASE_URL',
42
+ 'ENV_REVENIUM_METERING_API_KEY',
43
+ 'SUMMARY_RETRY_ATTEMPTS',
44
+ 'SUMMARY_RETRY_DELAY',
45
+ 'SUMMARY_API_TIMEOUT',
46
+ 'DEFAULT_BASE_URL',
47
+ 'SummaryFormat',
48
+ 'parse_print_summary_value',
49
+ 'get_print_summary_config',
50
+ 'get_team_id',
51
+ 'get_base_url',
52
+ 'get_api_key',
53
+ ]
@@ -0,0 +1,198 @@
1
+ """
2
+ Context-based metadata injection for Revenium LiteLLM middleware.
3
+
4
+ This module provides thread-safe context management for injecting metadata into
5
+ LiteLLM completion calls without requiring explicit kwargs. It uses Python's
6
+ contextvars module to maintain isolated context per execution thread/task.
7
+
8
+ Example:
9
+ >>> from revenium_middleware.litellm.client import metadata_context
10
+ >>>
11
+ >>> # Using context manager
12
+ >>> with metadata_context.set(agent="Lead Analyst", task_type="research"):
13
+ ... response = litellm.completion(model="gpt-4", messages=[...])
14
+ ... # Metadata automatically injected
15
+ >>>
16
+ >>> # Using direct API
17
+ >>> metadata_context.update(trace_id="abc-123")
18
+ >>> response = litellm.completion(model="gpt-4", messages=[...])
19
+ >>> metadata_context.clear()
20
+ """
21
+
22
+ import contextvars
23
+ from typing import Dict, Any, Optional
24
+
25
+
26
+ # Thread-safe context variable for storing metadata
27
+ _metadata_context: contextvars.ContextVar[Dict[str, Any]] = contextvars.ContextVar(
28
+ 'revenium_metadata',
29
+ default={}
30
+ )
31
+
32
+
33
+ class MetadataContext:
34
+ """
35
+ Thread-safe metadata context manager for Revenium middleware.
36
+
37
+ This class provides methods to set, get, update, and clear metadata that
38
+ will be automatically injected into LiteLLM completion calls. It uses
39
+ Python's contextvars module to ensure thread-safety and proper isolation
40
+ in async contexts.
41
+
42
+ All metadata set through this API will be merged with explicit usage_metadata
43
+ kwargs, with explicit kwargs taking precedence.
44
+ """
45
+
46
+ @staticmethod
47
+ def set(**kwargs) -> '_MetadataContextManager':
48
+ """
49
+ Set metadata for the current context using a context manager.
50
+
51
+ This method returns a context manager that sets the metadata on entry
52
+ and restores the previous metadata on exit. This is the recommended
53
+ way to set metadata for a specific scope.
54
+
55
+ Args:
56
+ **kwargs: Metadata fields to set (e.g., agent="Lead Analyst",
57
+ task_type="research", trace_id="abc-123")
58
+
59
+ Returns:
60
+ _MetadataContextManager: Context manager for scoped metadata
61
+
62
+ Example:
63
+ >>> with metadata_context.set(agent="Analyst", task_type="research"):
64
+ ... # All LiteLLM calls here get this metadata
65
+ ... response = litellm.completion(...)
66
+ """
67
+ return _MetadataContextManager(kwargs)
68
+
69
+ @staticmethod
70
+ def get() -> Dict[str, Any]:
71
+ """
72
+ Get the current context metadata.
73
+
74
+ Returns a copy of the current metadata dictionary. Modifying the
75
+ returned dictionary will not affect the context.
76
+
77
+ Returns:
78
+ Dict[str, Any]: Copy of current metadata
79
+
80
+ Example:
81
+ >>> metadata_context.update(agent="Analyst")
82
+ >>> current = metadata_context.get()
83
+ >>> print(current)
84
+ {'agent': 'Analyst'}
85
+ """
86
+ return _metadata_context.get().copy()
87
+
88
+ @staticmethod
89
+ def update(**kwargs) -> None:
90
+ """
91
+ Update the current context metadata.
92
+
93
+ This merges the provided metadata with existing metadata in the
94
+ current context. Unlike set(), this does not use a context manager
95
+ and the metadata persists until explicitly cleared.
96
+
97
+ Args:
98
+ **kwargs: Metadata fields to update
99
+
100
+ Example:
101
+ >>> metadata_context.update(agent="Analyst")
102
+ >>> metadata_context.update(task_type="research")
103
+ >>> # Both fields are now set
104
+ >>> metadata_context.clear() # Clean up when done
105
+ """
106
+ current = _metadata_context.get().copy()
107
+ current.update(kwargs)
108
+ _metadata_context.set(current)
109
+
110
+ @staticmethod
111
+ def clear(*keys: str) -> None:
112
+ """
113
+ Clear specific metadata fields or all metadata.
114
+
115
+ If no keys are provided, clears all metadata. If keys are provided,
116
+ only those specific fields are removed.
117
+
118
+ Args:
119
+ *keys: Optional field names to clear. If not provided, clears all.
120
+
121
+ Example:
122
+ >>> metadata_context.update(agent="A", task_type="T", trace_id="123")
123
+ >>> metadata_context.clear("agent") # Remove only agent
124
+ >>> metadata_context.clear() # Remove all remaining
125
+ """
126
+ if not keys:
127
+ # Clear all metadata
128
+ _metadata_context.set({})
129
+ else:
130
+ # Clear specific keys
131
+ current = _metadata_context.get().copy()
132
+ for key in keys:
133
+ current.pop(key, None)
134
+ _metadata_context.set(current)
135
+
136
+
137
+ class _MetadataContextManager:
138
+ """
139
+ Context manager for scoped metadata injection.
140
+
141
+ This class implements the context manager protocol to provide scoped
142
+ metadata that is automatically cleaned up on exit. It saves the previous
143
+ metadata state on entry and restores it on exit.
144
+
145
+ This class should not be instantiated directly. Use MetadataContext.set()
146
+ instead.
147
+ """
148
+
149
+ def __init__(self, metadata: Dict[str, Any]):
150
+ """
151
+ Initialize the context manager.
152
+
153
+ Args:
154
+ metadata: Metadata dictionary to set in this context
155
+ """
156
+ self.metadata = metadata
157
+ self.token: Optional[contextvars.Token] = None
158
+
159
+ def __enter__(self) -> '_MetadataContextManager':
160
+ """
161
+ Enter the context and set metadata.
162
+
163
+ Saves the current metadata state and sets the new metadata.
164
+
165
+ Returns:
166
+ Self for use in with statements
167
+ """
168
+ # Merge new metadata with existing context
169
+ current = _metadata_context.get().copy()
170
+ current.update(self.metadata)
171
+ self.token = _metadata_context.set(current)
172
+ return self
173
+
174
+ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
175
+ """
176
+ Exit the context and restore previous metadata.
177
+
178
+ Restores the metadata state that existed before entering this context.
179
+
180
+ Args:
181
+ exc_type: Exception type if an exception occurred
182
+ exc_val: Exception value if an exception occurred
183
+ exc_tb: Exception traceback if an exception occurred
184
+
185
+ Returns:
186
+ False to propagate any exception that occurred
187
+ """
188
+ if self.token is not None:
189
+ _metadata_context.reset(self.token)
190
+ return False
191
+
192
+
193
+ # Global instance for convenient access
194
+ metadata_context = MetadataContext()
195
+
196
+
197
+ __all__ = ['MetadataContext', 'metadata_context']
198
+