revenium-python-sdk 0.6.0__tar.gz → 0.7.0__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 (150) hide show
  1. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/PKG-INFO +24 -3
  2. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/README.md +23 -2
  3. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/pyproject.toml +1 -1
  4. revenium_python_sdk-0.7.0/revenium_middleware/_core/cache_tokens.py +109 -0
  5. revenium_python_sdk-0.7.0/revenium_middleware/_core/enforcement.py +817 -0
  6. revenium_python_sdk-0.7.0/revenium_middleware/_core/fields.py +334 -0
  7. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/decorator.py +62 -0
  8. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/ai.py +320 -0
  9. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_audio_params.py +34 -13
  10. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_completion_params.py +94 -6
  11. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_image_params.py +48 -10
  12. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/ai_create_video_params.py +51 -10
  13. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/agentic_outcomes.py +16 -0
  14. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/bedrock_adapter.py +8 -1
  15. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/bedrock_transport.py +7 -0
  16. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/middleware.py +32 -1
  17. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/stream_create.py +7 -1
  18. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/_metering.py +19 -0
  19. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/utils.py +21 -0
  20. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/job_context.py +72 -20
  21. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/job_history.py +5 -0
  22. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/middleware.py +6 -9
  23. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/proxy/middleware.py +42 -15
  24. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/middleware.py +7 -1
  25. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/middleware.py +59 -3
  26. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/middleware.py +4 -0
  27. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/perplexity_sdk.py +4 -0
  28. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/PKG-INFO +24 -3
  29. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/SOURCES.txt +1 -0
  30. revenium_python_sdk-0.7.0/tests/test_metering.py +955 -0
  31. revenium_python_sdk-0.6.0/revenium_middleware/_core/enforcement.py +0 -412
  32. revenium_python_sdk-0.6.0/revenium_middleware/_core/fields.py +0 -180
  33. revenium_python_sdk-0.6.0/tests/test_metering.py +0 -411
  34. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/LICENSE +0 -0
  35. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/__init__.py +0 -0
  36. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/__init__.py +0 -0
  37. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/config.py +0 -0
  38. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/context.py +0 -0
  39. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/decorators.py +0 -0
  40. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/exceptions.py +0 -0
  41. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/load_diagnostics.py +0 -0
  42. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/log_sanitize.py +0 -0
  43. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering.py +0 -0
  44. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering_buffer.py +0 -0
  45. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering_status.py +0 -0
  46. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/metering_submission.py +0 -0
  47. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/outcomes.py +0 -0
  48. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/patch_registry.py +0 -0
  49. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/prompt_extraction.py +0 -0
  50. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/subscriber.py +0 -0
  51. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_core/trace_fields.py +0 -0
  52. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/LICENSE +0 -0
  53. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/__init__.py +0 -0
  54. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_base_client.py +0 -0
  55. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_client.py +0 -0
  56. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_compat.py +0 -0
  57. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_constants.py +0 -0
  58. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_exceptions.py +0 -0
  59. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_files.py +0 -0
  60. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_models.py +0 -0
  61. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_qs.py +0 -0
  62. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_resource.py +0 -0
  63. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_response.py +0 -0
  64. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_streaming.py +0 -0
  65. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_types.py +0 -0
  66. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/__init__.py +0 -0
  67. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_logs.py +0 -0
  68. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
  69. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
  70. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
  71. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_streams.py +0 -0
  72. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_sync.py +0 -0
  73. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_transform.py +0 -0
  74. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_typing.py +0 -0
  75. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_utils/_utils.py +0 -0
  76. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/_version.py +0 -0
  77. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/context.py +0 -0
  78. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/py.typed +0 -0
  79. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/__init__.py +0 -0
  80. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/apis.py +0 -0
  81. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/resources/events.py +0 -0
  82. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/__init__.py +0 -0
  83. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
  84. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
  85. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/event_create_params.py +0 -0
  86. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
  87. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/__init__.py +0 -0
  88. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/config.py +0 -0
  89. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
  90. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/provider.py +0 -0
  91. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/anthropic/trace_fields.py +0 -0
  92. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/__init__.py +0 -0
  93. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/config.py +0 -0
  94. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/middleware.py +0 -0
  95. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/fal/trace_fields.py +0 -0
  96. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/__init__.py +0 -0
  97. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/__init__.py +0 -0
  98. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/exceptions.py +0 -0
  99. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/protocols.py +0 -0
  100. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/trace_fields.py +0 -0
  101. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/common/types.py +0 -0
  102. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/config.py +0 -0
  103. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/__init__.py +0 -0
  104. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/middleware.py +0 -0
  105. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/google_ai/provider.py +0 -0
  106. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/prompt_extractor.py +0 -0
  107. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
  108. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
  109. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/google/vertex_ai/provider.py +0 -0
  110. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/__init__.py +0 -0
  111. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/_metadata.py +0 -0
  112. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/anthropic_driver.py +0 -0
  113. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/litellm_driver.py +0 -0
  114. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/ollama_driver.py +0 -0
  115. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/openai_driver.py +0 -0
  116. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/openai_embedding_driver.py +0 -0
  117. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/griptape/universal_driver.py +0 -0
  118. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/__init__.py +0 -0
  119. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/__init__.py +0 -0
  120. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/config.py +0 -0
  121. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/context.py +0 -0
  122. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/decorators.py +0 -0
  123. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/hooks.py +0 -0
  124. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
  125. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
  126. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/trace_fields.py +0 -0
  127. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/client/validation.py +0 -0
  128. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/litellm/proxy/__init__.py +0 -0
  129. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/__init__.py +0 -0
  130. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/ollama/trace_fields.py +0 -0
  131. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/__init__.py +0 -0
  132. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/azure_config.py +0 -0
  133. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/azure_model_resolver.py +0 -0
  134. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/config.py +0 -0
  135. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/exceptions.py +0 -0
  136. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/__init__.py +0 -0
  137. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/_utils.py +0 -0
  138. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
  139. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/prompt_extractor.py +0 -0
  140. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/provider.py +0 -0
  141. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/openai/trace_fields.py +0 -0
  142. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/__init__.py +0 -0
  143. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/provider.py +0 -0
  144. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/perplexity/trace_fields.py +0 -0
  145. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/webhooks/__init__.py +0 -0
  146. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_middleware/webhooks/_verify.py +0 -0
  147. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
  148. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/requires.txt +0 -0
  149. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/revenium_python_sdk.egg-info/top_level.txt +0 -0
  150. {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revenium-python-sdk
3
- Version: 0.6.0
3
+ Version: 0.7.0
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
@@ -311,7 +311,10 @@ history = get_outcome_history("sales-lead-8842")
311
311
  # List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
312
312
  ```
313
313
 
314
- `amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`), and returns the updated job as a dict.
314
+ `amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`), and returns the updated job as a dict.
315
+
316
+ - **`reason` vs `outcome_reason`:** `reason` is the amendment's own audit justification (why the record changed); `outcome_reason` is the business explanation of why the job failed or was cancelled. Use `outcome_reason` for failure explanations rather than burying them in `metadata` — it is a first-class field on the outcome and is returned on every `get_outcome_history()` row.
317
+ - **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
315
318
 
316
319
  ### Outcome Exceptions
317
320
 
@@ -779,6 +782,7 @@ litellm_settings:
779
782
  ```
780
783
 
781
784
  When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
785
+ Reasoning effort travels as `x-revenium-effort` on the proxied request.
782
786
 
783
787
  #### LiteLLM Decorators
784
788
 
@@ -1071,6 +1075,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
1071
1075
  | `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
1072
1076
  | `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
1073
1077
  | `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
1078
+ | `effort` | Reasoning effort level requested of the model (free-form string, max 16 chars, `^[A-Za-z0-9_-]+$`) | Report what share of AI spend is high-effort reasoning, and compare cost per effort level |
1074
1079
 
1075
1080
  **Example:**
1076
1081
 
@@ -1093,11 +1098,27 @@ response = client.chat.completions.create(
1093
1098
  "subscription_id": "pro-plan-Q1",
1094
1099
  "productName": "customer-support-chatbot",
1095
1100
  "agent": "support-agent",
1096
- "response_quality_score": 0.92
1101
+ "response_quality_score": 0.92,
1102
+ "effort": "high"
1097
1103
  }
1098
1104
  )
1099
1105
  ```
1100
1106
 
1107
+ **Reasoning effort:** `effort` records how hard the model was asked to think, so
1108
+ high-effort reasoning spend can be separated from the rest. It is a free-form
1109
+ string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
1110
+ `high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
1111
+ never heard of is passed through untouched rather than rejected or rewritten. The
1112
+ Revenium backend owns validation (at most 16 characters, matching
1113
+ `^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
1114
+ of being silently dropped, so it surfaces in the log and in
1115
+ `get_metering_status()`. It is distinct from the reported reasoning token count,
1116
+ which measures the tokens actually spent rather than the level requested.
1117
+
1118
+ Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
1119
+ mode, where per-call attribution travels as request headers rather than
1120
+ `usage_metadata`, send `x-revenium-effort` instead.
1121
+
1101
1122
  **Deprecation notice:** The legacy field aliases `organizationId`, `organization_id`, `productId`, and `product_id` are accepted by this SDK only as an input-layer convenience and emit a `DeprecationWarning`. The Revenium backend no longer accepts them — they are translated to `organizationName` / `productName` before the wire call. Migrate to `organization_name` / `organizationName` and `product_name` / `productName` now; the input-layer aliases will be removed in the next major release.
1102
1123
 
1103
1124
  **API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
@@ -214,7 +214,10 @@ history = get_outcome_history("sales-lead-8842")
214
214
  # List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
215
215
  ```
216
216
 
217
- `amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`), and returns the updated job as a dict.
217
+ `amend_outcome()` takes a mandatory non-blank `reason` plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`), and returns the updated job as a dict.
218
+
219
+ - **`reason` vs `outcome_reason`:** `reason` is the amendment's own audit justification (why the record changed); `outcome_reason` is the business explanation of why the job failed or was cancelled. Use `outcome_reason` for failure explanations rather than burying them in `metadata` — it is a first-class field on the outcome and is returned on every `get_outcome_history()` row.
220
+ - **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
218
221
 
219
222
  ### Outcome Exceptions
220
223
 
@@ -682,6 +685,7 @@ litellm_settings:
682
685
  ```
683
686
 
684
687
  When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
688
+ Reasoning effort travels as `x-revenium-effort` on the proxied request.
685
689
 
686
690
  #### LiteLLM Decorators
687
691
 
@@ -974,6 +978,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
974
978
  | `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
975
979
  | `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
976
980
  | `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
981
+ | `effort` | Reasoning effort level requested of the model (free-form string, max 16 chars, `^[A-Za-z0-9_-]+$`) | Report what share of AI spend is high-effort reasoning, and compare cost per effort level |
977
982
 
978
983
  **Example:**
979
984
 
@@ -996,11 +1001,27 @@ response = client.chat.completions.create(
996
1001
  "subscription_id": "pro-plan-Q1",
997
1002
  "productName": "customer-support-chatbot",
998
1003
  "agent": "support-agent",
999
- "response_quality_score": 0.92
1004
+ "response_quality_score": 0.92,
1005
+ "effort": "high"
1000
1006
  }
1001
1007
  )
1002
1008
  ```
1003
1009
 
1010
+ **Reasoning effort:** `effort` records how hard the model was asked to think, so
1011
+ high-effort reasoning spend can be separated from the rest. It is a free-form
1012
+ string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
1013
+ `high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
1014
+ never heard of is passed through untouched rather than rejected or rewritten. The
1015
+ Revenium backend owns validation (at most 16 characters, matching
1016
+ `^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
1017
+ of being silently dropped, so it surfaces in the log and in
1018
+ `get_metering_status()`. It is distinct from the reported reasoning token count,
1019
+ which measures the tokens actually spent rather than the level requested.
1020
+
1021
+ Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
1022
+ mode, where per-call attribution travels as request headers rather than
1023
+ `usage_metadata`, send `x-revenium-effort` instead.
1024
+
1004
1025
  **Deprecation notice:** The legacy field aliases `organizationId`, `organization_id`, `productId`, and `product_id` are accepted by this SDK only as an input-layer convenience and emit a `DeprecationWarning`. The Revenium backend no longer accepts them — they are translated to `organizationName` / `productName` before the wire call. Migrate to `organization_name` / `organizationName` and `product_name` / `productName` now; the input-layer aliases will be removed in the next major release.
1005
1026
 
1006
1027
  **API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "revenium-python-sdk"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,109 @@
1
+ """Tolerant field access for provider usage objects (dict or attribute-style).
2
+
3
+ Cache token fields differ by provider surface:
4
+
5
+ - Anthropic-style: top-level ``cache_read_input_tokens`` / ``cache_creation_input_tokens``,
6
+ plus the nested per-TTL breakdown under ``cache_creation``.
7
+ - OpenAI-style: nested ``prompt_tokens_details.cached_tokens``; no separate
8
+ cache-creation count.
9
+
10
+ See BACK-2391 for the history of this bug class (hardcoded/zeroed cache-token
11
+ fields recurring across multiple emitters) and BACK-1925 for the LiteLLM
12
+ client's cache-token contract, which this module's defaults must not break.
13
+ """
14
+ from numbers import Number
15
+ from typing import Any, Dict, NamedTuple, Optional
16
+
17
+
18
+ class CacheTokens(NamedTuple):
19
+ cache_read_tokens: int
20
+ cache_creation_tokens: int
21
+
22
+
23
+ def _get(source: Any, name: str) -> Any:
24
+ """Read `name` from a dict or an attribute-style object; None if absent."""
25
+ if source is None:
26
+ return None
27
+ if isinstance(source, dict):
28
+ return source.get(name)
29
+ return getattr(source, name, None)
30
+
31
+
32
+ def get_usage_field(usage: Any, name: str, default: int = 0) -> Any:
33
+ """Read a single usage field through the same tolerant accessor as
34
+ `extract_cache_tokens`, so cache fields and base token fields (prompt/
35
+ completion/total) are read consistently from the same dict-or-object
36
+ usage value. Missing or None fields return `default`.
37
+
38
+ Normalizing a usage object to a plain dict *before* reading it (e.g. to
39
+ simplify a `.get(...)` call) silently drops every field for
40
+ attribute-style usage objects that fall through that normalization --
41
+ exactly the inconsistency this function exists to prevent. Callers with
42
+ multiple fields to read from the same `usage` value should read all of
43
+ them through this function (or `extract_cache_tokens`) rather than
44
+ normalizing `usage` itself first.
45
+ """
46
+ value = _get(usage, name)
47
+ return default if value is None else value
48
+
49
+
50
+ def extract_cache_tokens(usage: Any) -> CacheTokens:
51
+ """Extract (cache_read_tokens, cache_creation_tokens) from a usage object.
52
+
53
+ Tolerates dicts and attribute-style objects (SimpleNamespace, SDK response
54
+ models) and treats missing or None fields as 0. Checked in order:
55
+
56
+ 1. OpenAI-style nested cache reads: ``usage.prompt_tokens_details.cached_tokens``
57
+ 2. Anthropic-style top-level cache reads: ``usage.cache_read_input_tokens``
58
+ 3. Anthropic-style top-level cache creation: ``usage.cache_creation_input_tokens``
59
+ """
60
+ prompt_details = _get(usage, "prompt_tokens_details")
61
+ cache_read_tokens = get_usage_field(prompt_details, "cached_tokens", 0)
62
+ if not cache_read_tokens:
63
+ cache_read_tokens = get_usage_field(usage, "cache_read_input_tokens", 0)
64
+ cache_creation_tokens = get_usage_field(usage, "cache_creation_input_tokens", 0)
65
+
66
+ return CacheTokens(cache_read_tokens=cache_read_tokens, cache_creation_tokens=cache_creation_tokens)
67
+
68
+
69
+ def _optional_token_count(source: Any, name: str) -> Optional[int]:
70
+ """Read an integral token count, or None when it is absent or unusable.
71
+
72
+ Unlike `get_usage_field`, a missing field is reported as None rather than
73
+ coerced to 0, so callers can tell "the provider reported zero" apart from
74
+ "the provider reported nothing". Non-numeric values (including the
75
+ attributes that attribute-style test doubles auto-create on access) count
76
+ as nothing rather than being forwarded into a metering payload.
77
+ """
78
+ value = _get(source, name)
79
+ if isinstance(value, bool) or not isinstance(value, Number):
80
+ return None
81
+ return int(value)
82
+
83
+
84
+ def extract_cache_creation_ttl_counts(usage: Any) -> Dict[str, int]:
85
+ """Extract the per-TTL cache-creation breakdown as metering parameters.
86
+
87
+ Anthropic reports the split under a nested ``usage.cache_creation`` object
88
+ (``ephemeral_5m_input_tokens`` / ``ephemeral_1h_input_tokens``). That
89
+ object is absent on older responses and whenever the extended cache-TTL
90
+ beta is not in play, so it is read defensively.
91
+
92
+ Returns metering parameter names mapped to the counts the provider
93
+ actually reported, ready to be merged into a metering payload. A bucket the
94
+ provider did not report is left out entirely instead of being sent as a
95
+ zero: the aggregate ``cache_creation_token_count`` stays authoritative and
96
+ the backend prices the flat fallback from it when no split is present,
97
+ whereas an explicit zero would assert a split that never happened. A
98
+ bucket the provider does report as 0 is forwarded as 0.
99
+ """
100
+ cache_creation = _get(usage, "cache_creation")
101
+
102
+ counts: Dict[str, int] = {}
103
+ ephemeral_5m = _optional_token_count(cache_creation, "ephemeral_5m_input_tokens")
104
+ if ephemeral_5m is not None:
105
+ counts["cache_creation5m_token_count"] = ephemeral_5m
106
+ ephemeral_1h = _optional_token_count(cache_creation, "ephemeral_1h_input_tokens")
107
+ if ephemeral_1h is not None:
108
+ counts["cache_creation1h_token_count"] = ephemeral_1h
109
+ return counts