revenium-python-sdk 0.8.0__tar.gz → 0.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/PKG-INFO +269 -17
  2. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/README.md +267 -15
  3. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/pyproject.toml +8 -2
  4. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/__init__.py +9 -1
  5. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/cache_tokens.py +63 -0
  6. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/config.py +26 -0
  7. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/context.py +14 -7
  8. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/enforcement.py +268 -73
  9. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/fields.py +26 -0
  10. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/outcomes.py +5 -2
  11. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/ai.py +44 -0
  12. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_completion_params.py +20 -0
  13. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/agentic_outcomes.py +8 -0
  14. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/bedrock_adapter.py +9 -1
  15. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/bedrock_transport.py +2 -0
  16. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/middleware.py +438 -181
  17. revenium_python_sdk-0.9.1/revenium_middleware/anthropic/provider.py +262 -0
  18. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/_metering.py +2 -0
  19. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/utils.py +2 -0
  20. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/middleware.py +2 -1
  21. revenium_python_sdk-0.9.1/revenium_middleware/litellm/proxy/__init__.py +61 -0
  22. revenium_python_sdk-0.9.1/revenium_middleware/litellm/proxy/_metering_owner.py +50 -0
  23. revenium_python_sdk-0.9.1/revenium_middleware/litellm/proxy/guardrail.py +1426 -0
  24. revenium_python_sdk-0.9.1/revenium_middleware/litellm/proxy/middleware.py +675 -0
  25. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/middleware.py +3 -1
  26. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/middleware.py +2 -1
  27. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/middleware.py +2 -0
  28. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/perplexity_sdk.py +2 -0
  29. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/PKG-INFO +269 -17
  30. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/SOURCES.txt +2 -0
  31. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/requires.txt +1 -1
  32. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/tests/test_metering.py +79 -0
  33. revenium_python_sdk-0.8.0/revenium_middleware/anthropic/provider.py +0 -141
  34. revenium_python_sdk-0.8.0/revenium_middleware/litellm/proxy/__init__.py +0 -26
  35. revenium_python_sdk-0.8.0/revenium_middleware/litellm/proxy/middleware.py +0 -263
  36. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/LICENSE +0 -0
  37. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/__init__.py +0 -0
  38. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/decorators.py +0 -0
  39. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/exceptions.py +0 -0
  40. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/load_diagnostics.py +0 -0
  41. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/log_sanitize.py +0 -0
  42. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering.py +0 -0
  43. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_buffer.py +0 -0
  44. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_status.py +0 -0
  45. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_submission.py +0 -0
  46. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/patch_registry.py +0 -0
  47. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/prompt_extraction.py +0 -0
  48. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/subscriber.py +0 -0
  49. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/trace_fields.py +0 -0
  50. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/LICENSE +0 -0
  51. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/__init__.py +0 -0
  52. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_base_client.py +0 -0
  53. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_client.py +0 -0
  54. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_compat.py +0 -0
  55. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_constants.py +0 -0
  56. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_exceptions.py +0 -0
  57. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_files.py +0 -0
  58. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_models.py +0 -0
  59. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_qs.py +0 -0
  60. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_resource.py +0 -0
  61. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_response.py +0 -0
  62. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_streaming.py +0 -0
  63. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_types.py +0 -0
  64. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/__init__.py +0 -0
  65. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_logs.py +0 -0
  66. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
  67. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
  68. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
  69. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_streams.py +0 -0
  70. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_sync.py +0 -0
  71. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_transform.py +0 -0
  72. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_typing.py +0 -0
  73. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_utils.py +0 -0
  74. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_version.py +0 -0
  75. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/context.py +0 -0
  76. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/decorator.py +0 -0
  77. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/py.typed +0 -0
  78. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/__init__.py +0 -0
  79. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/apis.py +0 -0
  80. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/events.py +0 -0
  81. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/__init__.py +0 -0
  82. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_audio_params.py +0 -0
  83. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_image_params.py +0 -0
  84. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_video_params.py +0 -0
  85. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
  86. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
  87. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/event_create_params.py +0 -0
  88. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
  89. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/__init__.py +0 -0
  90. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/config.py +0 -0
  91. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
  92. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/stream_create.py +0 -0
  93. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/trace_fields.py +0 -0
  94. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/__init__.py +0 -0
  95. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/config.py +0 -0
  96. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/middleware.py +0 -0
  97. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/trace_fields.py +0 -0
  98. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/__init__.py +0 -0
  99. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/__init__.py +0 -0
  100. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/exceptions.py +0 -0
  101. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/protocols.py +0 -0
  102. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/trace_fields.py +0 -0
  103. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/types.py +0 -0
  104. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/config.py +0 -0
  105. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/__init__.py +0 -0
  106. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/middleware.py +0 -0
  107. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/provider.py +0 -0
  108. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/prompt_extractor.py +0 -0
  109. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
  110. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
  111. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/provider.py +0 -0
  112. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/__init__.py +0 -0
  113. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/_metadata.py +0 -0
  114. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/anthropic_driver.py +0 -0
  115. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/litellm_driver.py +0 -0
  116. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/ollama_driver.py +0 -0
  117. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/openai_driver.py +0 -0
  118. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/openai_embedding_driver.py +0 -0
  119. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/universal_driver.py +0 -0
  120. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_context.py +0 -0
  121. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_history.py +0 -0
  122. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_type_economics.py +0 -0
  123. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/__init__.py +0 -0
  124. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/__init__.py +0 -0
  125. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/config.py +0 -0
  126. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/context.py +0 -0
  127. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/decorators.py +0 -0
  128. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/hooks.py +0 -0
  129. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
  130. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
  131. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/trace_fields.py +0 -0
  132. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/validation.py +0 -0
  133. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/__init__.py +0 -0
  134. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/trace_fields.py +0 -0
  135. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/__init__.py +0 -0
  136. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/azure_config.py +0 -0
  137. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/azure_model_resolver.py +0 -0
  138. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/config.py +0 -0
  139. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/exceptions.py +0 -0
  140. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/__init__.py +0 -0
  141. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/_utils.py +0 -0
  142. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
  143. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/prompt_extractor.py +0 -0
  144. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/provider.py +0 -0
  145. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/trace_fields.py +0 -0
  146. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/__init__.py +0 -0
  147. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/provider.py +0 -0
  148. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/trace_fields.py +0 -0
  149. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/webhooks/__init__.py +0 -0
  150. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_middleware/webhooks/_verify.py +0 -0
  151. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
  152. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/top_level.txt +0 -0
  153. {revenium_python_sdk-0.8.0 → revenium_python_sdk-0.9.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revenium-python-sdk
3
- Version: 0.8.0
3
+ Version: 0.9.1
4
4
  Summary: The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai.
5
5
  Author-email: Revenium <support@revenium.io>
6
6
  License: MIT
@@ -76,7 +76,7 @@ Requires-Dist: wrapt>=1.14.0; extra == "litellm"
76
76
  Requires-Dist: litellm>=1.40.0; extra == "litellm"
77
77
  Provides-Extra: litellm-proxy
78
78
  Requires-Dist: wrapt>=1.14.0; extra == "litellm-proxy"
79
- Requires-Dist: litellm[proxy]>=1.40.0; extra == "litellm-proxy"
79
+ Requires-Dist: litellm[proxy]>=1.93.0; extra == "litellm-proxy"
80
80
  Provides-Extra: dev
81
81
  Requires-Dist: pytest>=7.0.0; extra == "dev"
82
82
  Requires-Dist: pytest-asyncio; extra == "dev"
@@ -280,11 +280,18 @@ from revenium_middleware import (
280
280
 
281
281
  upsert_job_type_economics("claim", JobTypeEconomics(
282
282
  unit_metric_key="completed_claims", unit_label="claim",
283
- metrics=[{
284
- "key": "completed_claims", "type": "COUNT",
285
- "direction": "HIGHER_IS_BETTER", "aggregation": "SUM",
286
- "resolution": "PER_JOB",
287
- }],
283
+ metrics=[
284
+ {
285
+ "key": "completed_claims", "type": "COUNT",
286
+ "direction": "HIGHER_IS_BETTER", "aggregation": "SUM",
287
+ "resolution": "PER_JOB",
288
+ },
289
+ {
290
+ "key": "claims_processed", "type": "COUNT",
291
+ "direction": "HIGHER_IS_BETTER", "aggregation": "SUM",
292
+ "resolution": "PERIOD",
293
+ },
294
+ ],
288
295
  dimensions=[{"key": "region", "allowedValues": ["us", "ca"]}],
289
296
  monetization={
290
297
  "metricKey": "completed_claims", "valuePerUnit": 4.25,
@@ -297,7 +304,7 @@ create_baseline("claim", Baseline(
297
304
  report_period_facts("claim", [PeriodFactEntry(
298
305
  period_start="2026-08-01T00:00:00Z", period_end="2026-09-01T00:00:00Z",
299
306
  dimension_key="region", dimension_value="us",
300
- key="completed_claims", value=1280,
307
+ key="claims_processed", value=1280,
301
308
  )])
302
309
  ```
303
310
 
@@ -670,7 +677,31 @@ with client.messages.stream(
670
677
  print(text, end="", flush=True)
671
678
  ```
672
679
 
673
- **Note:** The middleware only wraps `messages.create` and `messages.stream` endpoints. Other Anthropic SDK features work normally but aren't metered.
680
+ Async streaming is metered the same way, with the same metadata:
681
+
682
+ ```python
683
+ import asyncio
684
+ import anthropic
685
+ import revenium_middleware.anthropic
686
+
687
+ client = anthropic.AsyncAnthropic()
688
+
689
+ async def main():
690
+ async with client.messages.stream(
691
+ model="claude-opus-4-7",
692
+ max_tokens=200,
693
+ messages=[{"role": "user", "content": "Tell me a story"}],
694
+ usage_metadata={"task_type": "creative"}
695
+ ) as stream:
696
+ async for text in stream.text_stream:
697
+ print(text, end="", flush=True)
698
+ # await stream.get_final_message() works too; either way the
699
+ # completion is metered once when the block exits.
700
+
701
+ asyncio.run(main())
702
+ ```
703
+
704
+ **Note:** The middleware wraps the `messages.create` and `messages.stream` endpoints, sync and async alike (including `create(stream=True)`). Other Anthropic SDK features work normally but aren't metered.
674
705
 
675
706
  #### AWS Bedrock
676
707
 
@@ -703,7 +734,7 @@ message = client.messages.create(
703
734
  | Variable | Description | Default |
704
735
  |----------|-------------|---------|
705
736
  | `AWS_REGION` | AWS region for Bedrock | `us-east-1` |
706
- | `REVENIUM_BEDROCK_DISABLE` | Set to `1` to disable Bedrock support | Not set |
737
+ | `REVENIUM_BEDROCK_DISABLE` | Set to `1` to disable Bedrock support (Bedrock detection only - Foundry detection is unaffected) | Not set |
707
738
 
708
739
  **AWS authentication** uses the standard credential chain: environment variables, `~/.aws/credentials`, IAM roles, AWS SSO. Required permissions: `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`.
709
740
 
@@ -725,6 +756,32 @@ message = client.messages.create(
725
756
 
726
757
  For other models, the middleware uses the format `anthropic.{model_name}`.
727
758
 
759
+ #### Microsoft Foundry
760
+
761
+ Claude served through Microsoft Foundry is metered by the same patched endpoints as the
762
+ direct Anthropic API - the Anthropic SDK's Foundry clients need no extra setup.
763
+
764
+ ```python
765
+ import anthropic
766
+ import revenium_middleware.anthropic
767
+
768
+ # Foundry is detected from the client class, so a custom base_url is fine too
769
+ client = anthropic.AnthropicFoundry(
770
+ resource="your-resource", # or ANTHROPIC_FOUNDRY_RESOURCE
771
+ )
772
+
773
+ message = client.messages.create(
774
+ model="claude-opus-4-7",
775
+ max_tokens=100,
776
+ messages=[{"role": "user", "content": "Hello from Foundry!"}]
777
+ )
778
+ ```
779
+
780
+ Foundry usage is reported with provider `Foundry` and model source `ANTHROPIC`, so the spend
781
+ is separated from direct-Anthropic totals while still priced against the Anthropic rate card
782
+ that Foundry bills at. `AsyncAnthropicFoundry` is metered the same way, as is
783
+ `client.messages.stream()`.
784
+
728
785
  **Examples:** `examples/anthropic/` - `anthropic-basic.py`, `anthropic-streaming.py`, `anthropic-bedrock.py`, `anthropic-advanced.py`
729
786
 
730
787
  ---
@@ -891,15 +948,187 @@ response = litellm.completion(
891
948
 
892
949
  #### Proxy Mode
893
950
 
894
- Add the callback to your LiteLLM `config.yaml` for server-side integration:
951
+ `ReveniumGuardrail` is the LiteLLM proxy integration. It is a LiteLLM
952
+ `CustomGuardrail` that **enforces the caller's budget before** the proxied call and
953
+ **meters usage after** it — successes, failures and streamed responses alike.
954
+
955
+ ```bash
956
+ pip install "revenium-python-sdk[litellm-proxy]" # requires Python 3.10+
957
+ ```
895
958
 
896
959
  ```yaml
897
- litellm_settings:
898
- callbacks: ["revenium_middleware.litellm.proxy.middleware.proxy_handler_instance"]
960
+ guardrails:
961
+ - guardrail_name: "revenium"
962
+ litellm_params:
963
+ guardrail: revenium_middleware.litellm.proxy.guardrail.ReveniumGuardrail
964
+ mode:
965
+ - "pre_call" # budget enforcement
966
+ - "post_call" # usage metering
967
+ default_on: true
899
968
  ```
900
969
 
901
- When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
902
- Reasoning effort travels as `x-revenium-effort` on the proxied request.
970
+ `guardrails` is a **top-level** key, not a member of `litellm_settings`. Nested
971
+ under `litellm_settings` it reaches LiteLLM's legacy v1 guardrail loader, which
972
+ expects a different shape and exits the proxy at startup with
973
+ `GuardrailItem() argument after ** must be a mapping, not str`.
974
+
975
+ `mode` must be a **list** to enable both hooks; a single string restricts the
976
+ guardrail to that one event type. `pre_call` alone enforces without metering;
977
+ `post_call` alone meters without enforcing.
978
+
979
+ **Budget enforcement** reuses the SDK's own circuit breaker, so a proxy enforces
980
+ exactly what every other Revenium integration enforces — including department
981
+ budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
982
+ [Cost Controls](#cost-controls). A blocked call never reaches the provider and the
983
+ caller receives HTTP 429:
984
+
985
+ ```json
986
+ {"error": {"message": "Request blocked by Revenium enforcement rule: Team Budget",
987
+ "type": "budget_exceeded", "guardrail": "revenium", "model": "gpt-4o",
988
+ "budgets": [{"name": "Team Budget", "ruleId": 7, "threshold": 10.0,
989
+ "currentValue": 11.5, "resetsAt": "2026-10-01T00:00:00Z"}]}}
990
+ ```
991
+
992
+ Enforcement **fails open**: if the enforcement path is unreachable or misbehaves,
993
+ the call proceeds. Metering is likewise non-disruptive — nothing in the post-call
994
+ path can turn a successful LLM call into an error for the client.
995
+
996
+ **Attribution** travels as `x-revenium-*` request headers (subscriber, organization,
997
+ product, trace, task type, agent, subscription, quality score, and `x-revenium-effort`
998
+ for reasoning effort), with the calling virtual key's metadata as the fallback —
999
+ `revenium_user_id`, `revenium_organization_name`, `revenium_key_name`, and
1000
+ `revenium_agentic_job_*`. Agentic job tags (`x-revenium-agentic-job-id`, `-name`,
1001
+ `-type`, `-version`) ride along for cost/ROI correlation; the job id is required for
1002
+ the others to be recorded.
1003
+
1004
+ ##### Counting a Claude Code call once (shared call id)
1005
+
1006
+ If you run Claude Code through your proxy **and** point Claude Code's own usage
1007
+ reporting at Revenium, every call is recorded twice: Claude Code files it under an
1008
+ identifier of its own making, the proxy files it under another, and the two can
1009
+ never match. `ReveniumGuardrail` makes them match, and does so by default.
1010
+
1011
+ The guardrail mints one identifier per proxied `/v1/messages` request, returns it to
1012
+ the client as the `request-id` and `x-revenium-transaction-id` response headers, and
1013
+ reports the same value as the call's transaction id. Claude Code copies `request-id`
1014
+ onto its own record, Revenium's duplicate check sees two records with one identifier,
1015
+ and one call becomes one record.
1016
+
1017
+ **It is on by default.** To opt out and keep LiteLLM's own response id as the
1018
+ transaction id, with no `request-id` header added:
1019
+
1020
+ ```bash
1021
+ export REVENIUM_LITELLM_SHARED_CALL_ID=false
1022
+ ```
1023
+
1024
+ New calls go straight back to two records when you do; records already merged stay
1025
+ merged. Four things have to be true for it to work:
1026
+
1027
+ * **LiteLLM 1.93.0 or newer.** That is the floor of the `litellm-proxy` extra, which
1028
+ is what installs the guardrail. On an older LiteLLM the guardrail logs one warning
1029
+ at startup and behaves exactly as it does when opted out: no header is added,
1030
+ the provider's response id is reported, and you get two records rather than none.
1031
+ * **`mode` includes `pre_call`.** The identifier is minted in the pre-call hook, and
1032
+ LiteLLM runs that hook only when the configured mode asks for it. A
1033
+ `mode: ["post_call"]` proxy mints nothing, and the guardrail says so at startup
1034
+ with one warning naming this variable. Add `pre_call` to the mode, or set the
1035
+ variable to `false` if the proxy only meters.
1036
+ * **The Anthropic messages route.** Other routes are untouched, so the provider's own
1037
+ `request-id` on the pass-through route is never overwritten.
1038
+ * **Both sides report to the same Revenium team.** The duplicate check is
1039
+ team-scoped.
1040
+
1041
+ **Point Claude Code at the proxy root, not at `/anthropic`.** Set `ANTHROPIC_BASE_URL`
1042
+ to the proxy's own base URL so Claude Code calls `<proxy>/v1/messages`:
1043
+
1044
+ ```bash
1045
+ export ANTHROPIC_BASE_URL=https://proxy.example.com
1046
+ ```
1047
+
1048
+ LiteLLM also offers an Anthropic pass-through at `<proxy>/anthropic/v1/messages`, and
1049
+ its own API reference recommends that route over `/v1/messages`. The shared identifier
1050
+ is minted only on `/v1/messages`. A proxy serving the pass-through route hands the
1051
+ provider's response straight back, so nothing is minted there, no `request-id` of ours
1052
+ is returned, and the counter described below stays silent on that route by design.
1053
+ The flag will appear to be on and the calls will keep being counted twice, with
1054
+ nothing in the proxy log to say why. If your Claude Code base URL ends in
1055
+ `/anthropic`, drop that suffix.
1056
+
1057
+ Only the guardrail mints. A proxy still on the deprecated callback alone gets no
1058
+ identifier and keeps reporting two records, which is one more reason to migrate.
1059
+
1060
+ One caution for a proxy running **both** the guardrail and the deprecated callback
1061
+ without `default_on: true`. That configuration meters every call twice already, and
1062
+ this flag hides the symptom rather than fixing it: both rows now carry the same
1063
+ identifier and Revenium's duplicate check keeps one. The configuration is still
1064
+ wrong. Delete the `litellm_settings.callbacks` entry.
1065
+
1066
+ Also do not register `ReveniumGuardrail` in `litellm_settings.callbacks` as well as
1067
+ in the `guardrails` block. LiteLLM keys registered callbacks on the class name plus
1068
+ its simple attributes, and the two instances differ, so both are registered and every
1069
+ logging hook runs twice.
1070
+
1071
+ If the flag is on and an Anthropic messages call is metered with no identifier on it,
1072
+ the guardrail counts it and logs a warning at most once a minute with the running
1073
+ total, so a mint that quietly stopped shows up in the proxy log rather than as a
1074
+ return of double counting. Your other routes never carry an identifier and are never
1075
+ counted or warned about, so a proxy that also serves chat completions or embeddings
1076
+ stays quiet.
1077
+
1078
+ ##### Migrating from the callback
1079
+
1080
+ `revenium_middleware.litellm.proxy.middleware.MiddlewareHandler` — the
1081
+ `litellm_settings.callbacks` entry `proxy_handler_instance` — is **deprecated**. It
1082
+ meters but never enforces a budget. It keeps working in this release and emits a
1083
+ `DeprecationWarning` (and a log line) when the proxy builds it.
1084
+
1085
+ To migrate, delete the callbacks entry and add the `guardrails` block above:
1086
+
1087
+ ```diff
1088
+ litellm_settings:
1089
+ - callbacks: ["revenium_middleware.litellm.proxy.middleware.proxy_handler_instance"]
1090
+ +
1091
+ +guardrails:
1092
+ + - guardrail_name: "revenium"
1093
+ + litellm_params:
1094
+ + guardrail: revenium_middleware.litellm.proxy.guardrail.ReveniumGuardrail
1095
+ + mode: ["pre_call", "post_call"]
1096
+ + default_on: true
1097
+ ```
1098
+
1099
+ Nothing else changes: the same headers, the same metered fields. Metered rows
1100
+ record `middleware_source: "GUARDRAIL"` instead of `"PROXY"`.
1101
+
1102
+ Leaving both enabled would meter every call twice. As a safety net for a proxy
1103
+ mid-migration, when the guardrail is configured to run on every request
1104
+ (`default_on: true` with `post_call` among its modes) it claims metering ownership
1105
+ and the deprecated callback stops submitting rows, logging once to say so. That net
1106
+ does **not** apply to a guardrail without `default_on`, without `post_call`, or
1107
+ configured with a per-tag `Mode` (which selects hooks per request): such a guardrail
1108
+ may not run on a given request, and suppressing the callback could drop metering
1109
+ entirely. A per-tag configuration logs, at info level, that it is not claiming
1110
+ ownership. Delete the callbacks entry rather than relying on the net.
1111
+
1112
+ ##### Client API and guardrails
1113
+
1114
+ `CustomGuardrail`'s lifecycle hooks cannot be hosted by LiteLLM's client API.
1115
+ Verified against **litellm 1.100.1**: `async_pre_call_hook`,
1116
+ `async_post_call_success_hook` and `async_post_call_failure_hook` are dispatched
1117
+ only from `litellm/proxy/utils.py` (`ProxyLogging`) and
1118
+ `litellm/proxy/common_request_processing.py`. The client path
1119
+ (`litellm_core_utils/litellm_logging.py`) dispatches only the `CustomLogger`
1120
+ logging events, so a `CustomGuardrail` added to `litellm.callbacks` without the
1121
+ proxy running would be a logger with no pre-call hook and no ability to block a
1122
+ call — the enforcement half would silently not exist.
1123
+
1124
+ So the client integration keeps its own path, unchanged: use
1125
+ `revenium_middleware.litellm.client` as documented above. Note that the LiteLLM
1126
+ client wrapper meters but does not currently run the pre-call circuit breaker —
1127
+ enforcement in client mode is available today through the OpenAI middleware, and
1128
+ through this guardrail in proxy mode. Both the guardrail and the client wrapper
1129
+ already share their metering plumbing (`revenium_middleware._core`: field
1130
+ extraction, cache-token extraction and `submit_ai_event`), so the guardrail adds no
1131
+ second copy of it.
903
1132
 
904
1133
  #### LiteLLM Decorators
905
1134
 
@@ -1639,6 +1868,29 @@ Set `REVENIUM_CB_FAIL_MODE=closed` to refuse calls until at least one rule fetch
1639
1868
 
1640
1869
  Rules with `shadowMode: true` are observe-and-log: they are skipped by `check_enforcement`. Use shadow mode on the server side to audit a rule before flipping it to enforce.
1641
1870
 
1871
+ ### Inspecting a Rule and Its Roster
1872
+
1873
+ Two read-only calls answer "why was this caller blocked, and who else does this rule cover?" without going anywhere near the pre-call path. Both talk to the server directly, neither is cached, and neither reads or writes the cache `check_enforcement` evaluates — so what they report is what the server holds right now.
1874
+
1875
+ ```python
1876
+ from revenium_middleware._core import (
1877
+ fetch_enforcement_rule,
1878
+ fetch_enforcement_rule_roster,
1879
+ )
1880
+
1881
+ rule = fetch_enforcement_rule("mN3xpQz") # one rule, or None
1882
+ roster = fetch_enforcement_rule_roster("mN3xpQz") # who it measures, or None
1883
+
1884
+ if roster:
1885
+ print(f"{roster['blockedCount']} over the cap, {roster['warnedCount']} warned")
1886
+ for row in roster["rows"]:
1887
+ print(f"{row['label']}: ${row['spend']} / ${row['limit']} ({row['band']})")
1888
+ ```
1889
+
1890
+ `fetch_enforcement_rule_roster` takes `page`, `size`, `search` and `band` (`BLOCKED`, `WARNED`, `UNDER`, `ALL`); the server does the filtering, sorting, banding and paging, and the three band counts always describe the whole roster rather than the page you asked for. Both calls return `None` rather than raising when the team has no such compiled rule, the rule has no reading yet, or the enforcement API cannot be reached — the same fail-open posture as the rest of the circuit breaker. An empty `rule_id` raises `ValueError`.
1891
+
1892
+ The background poller is unaffected: it keeps reading the **whole team's** rules every `REVENIUM_CB_POLL_INTERVAL_SECONDS`. That is deliberate. The server computes the department-budget maps team-wide and attaches them to the team-wide read, so a poll narrowed to a single rule would stop receiving them and department budgets would quietly stop blocking anyone. Narrowing is an explicit, opt-in inspection call and never the refresh.
1893
+
1642
1894
  ### End-to-End Example
1643
1895
 
1644
1896
  See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_demo.py) for a runnable end-to-end demo using a seeded budget rule.
@@ -1715,7 +1967,7 @@ print(get_buffer_stats())
1715
1967
  | `REVENIUM_WRITE_API_KEY` | - | Primary write-scope key (`rev_sk_`) for the agentic outcomes API (report/amend/history); falls back to `REVENIUM_OUTCOME_API_KEY` (deprecated), then `REVENIUM_METERING_API_KEY` |
1716
1968
  | `REVENIUM_OUTCOME_API_KEY` | - | Deprecated fallback name for the write-scope key; used only when `REVENIUM_WRITE_API_KEY` is unset |
1717
1969
  | `REVENIUM_PROFITSTREAM_BASE_URL` | `https://api.revenium.io` | Agentic outcomes API base URL |
1718
- | `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection |
1970
+ | `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection; Foundry detection is unaffected |
1719
1971
  | `REVENIUM_BUFFER_MAX_SIZE` | `1000` | Store-and-forward buffer capacity (oldest events evicted when full) |
1720
1972
  | `REVENIUM_BUFFER_FLUSH_INTERVAL` | `30` | Seconds between automatic replay attempts for buffered events |
1721
1973
 
@@ -1760,7 +2012,7 @@ Per-call `usage_metadata` values take precedence over the `REVENIUM_AGENTIC_JOB_
1760
2012
 
1761
2013
  **Debug mode:** Set `REVENIUM_LOG_LEVEL=DEBUG` to see detailed provider detection, routing decisions, and metering payloads.
1762
2014
 
1763
- **Force direct Anthropic API:** Set `REVENIUM_BEDROCK_DISABLE=1` to disable Bedrock auto-detection.
2015
+ **Force direct Anthropic API (instead of Bedrock):** Set `REVENIUM_BEDROCK_DISABLE=1` to disable Bedrock auto-detection. Foundry detection is unaffected - a Foundry client is still labelled `Foundry`.
1764
2016
 
1765
2017
  **Check initialization status (Anthropic):** Use `revenium_middleware.anthropic.is_initialized()` to verify setup.
1766
2018