revenium-python-sdk 0.6.0__tar.gz → 0.8.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.
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/PKG-INFO +149 -8
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/README.md +148 -7
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/pyproject.toml +1 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/__init__.py +20 -0
- revenium_python_sdk-0.8.0/revenium_middleware/_core/cache_tokens.py +109 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/config.py +23 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/context.py +30 -1
- revenium_python_sdk-0.8.0/revenium_middleware/_core/enforcement.py +1348 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/exceptions.py +22 -5
- revenium_python_sdk-0.8.0/revenium_middleware/_core/fields.py +363 -0
- revenium_python_sdk-0.8.0/revenium_middleware/_core/outcomes.py +728 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/trace_fields.py +82 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/decorator.py +62 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/resources/ai.py +352 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/ai_create_audio_params.py +37 -13
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/ai_create_completion_params.py +97 -6
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/ai_create_image_params.py +51 -10
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/ai_create_video_params.py +54 -10
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/agentic_outcomes.py +118 -7
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/bedrock_adapter.py +8 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/bedrock_transport.py +8 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/middleware.py +40 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/stream_create.py +7 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/fal/_metering.py +29 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/fal/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/utils.py +33 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/job_context.py +338 -51
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/job_history.py +13 -10
- revenium_python_sdk-0.8.0/revenium_middleware/job_type_economics.py +214 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/middleware.py +10 -9
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/proxy/middleware.py +42 -15
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/ollama/middleware.py +12 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/ollama/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/middleware.py +72 -6
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/perplexity/middleware.py +8 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/perplexity/perplexity_sdk.py +8 -1
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/perplexity/trace_fields.py +3 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_python_sdk.egg-info/PKG-INFO +149 -8
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_python_sdk.egg-info/SOURCES.txt +2 -0
- revenium_python_sdk-0.8.0/tests/test_metering.py +955 -0
- revenium_python_sdk-0.6.0/revenium_middleware/_core/enforcement.py +0 -412
- revenium_python_sdk-0.6.0/revenium_middleware/_core/fields.py +0 -180
- revenium_python_sdk-0.6.0/revenium_middleware/_core/outcomes.py +0 -424
- revenium_python_sdk-0.6.0/tests/test_metering.py +0 -411
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/LICENSE +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/load_diagnostics.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/log_sanitize.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/metering.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/metering_buffer.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/metering_status.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/metering_submission.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/LICENSE +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_base_client.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_client.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_compat.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_constants.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_exceptions.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_files.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_models.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_qs.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_resource.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_response.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_streaming.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_types.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_logs.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_streams.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_sync.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_transform.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_typing.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_utils/_utils.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/_version.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/context.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/py.typed +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/resources/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/resources/apis.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/resources/events.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/event_create_params.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/_metadata.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/anthropic_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/litellm_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/ollama_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/openai_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/openai_embedding_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/griptape/universal_driver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/decorators.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/exceptions.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/webhooks/__init__.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_middleware/webhooks/_verify.py +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.6.0 → revenium_python_sdk-0.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: revenium-python-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -264,7 +264,63 @@ when the provider's SDK is installed.
|
|
|
264
264
|
|
|
265
265
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
266
266
|
|
|
267
|
-
> **You need a write-scope key (`rev_sk_`) to use the agentic outcomes API.** Metering keys (`rev_mk_`) can only meter completions and tool events — they cannot report, amend, or read job outcomes, and the SDK rejects them client-side before any HTTP request is made. Key resolution: explicit `api_key=` > `REVENIUM_OUTCOME_API_KEY` > `REVENIUM_METERING_API_KEY`.
|
|
267
|
+
> **You need a write-scope key (`rev_sk_`) to use the agentic outcomes API.** Metering keys (`rev_mk_`) can only meter completions and tool events — they cannot report, amend, or read job outcomes, and the SDK rejects them client-side before any HTTP request is made. Key resolution: explicit `api_key=` > `REVENIUM_WRITE_API_KEY` > `REVENIUM_OUTCOME_API_KEY` (deprecated fallback) > `REVENIUM_METERING_API_KEY`.
|
|
268
|
+
|
|
269
|
+
### Job-Type Economics and Outcome Facts
|
|
270
|
+
|
|
271
|
+
Keep a metering key for AI telemetry and a separate write key for outcomes and
|
|
272
|
+
job-type configuration. A registered `valuePerUnit` rule takes precedence over
|
|
273
|
+
an outcome's `outcome_value`; the backend never sums the two value sources.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from revenium_middleware import (
|
|
277
|
+
Baseline, JobTypeEconomics, PeriodFactEntry, create_baseline,
|
|
278
|
+
report_period_facts, upsert_job_type_economics,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
upsert_job_type_economics("claim", JobTypeEconomics(
|
|
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
|
+
}],
|
|
288
|
+
dimensions=[{"key": "region", "allowedValues": ["us", "ca"]}],
|
|
289
|
+
monetization={
|
|
290
|
+
"metricKey": "completed_claims", "valuePerUnit": 4.25,
|
|
291
|
+
"currency": "USD", "category": "COST_AVOIDED", "basis": "REALIZED",
|
|
292
|
+
},
|
|
293
|
+
))
|
|
294
|
+
create_baseline("claim", Baseline(
|
|
295
|
+
effective_from="2026-08-01T00:00:00Z", cost_per_unit=4.25, currency="USD",
|
|
296
|
+
))
|
|
297
|
+
report_period_facts("claim", [PeriodFactEntry(
|
|
298
|
+
period_start="2026-08-01T00:00:00Z", period_end="2026-09-01T00:00:00Z",
|
|
299
|
+
dimension_key="region", dimension_value="us",
|
|
300
|
+
key="completed_claims", value=1280,
|
|
301
|
+
)])
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`effective_from` is the only required field on a baseline; every other field
|
|
305
|
+
is optional, and a baseline without it is rejected. A job type must be
|
|
306
|
+
declared with `upsert_job_type_economics` before it accepts baselines or
|
|
307
|
+
facts, and `report_period_facts` accepts only metrics declared with
|
|
308
|
+
`"resolution": "PERIOD"`.
|
|
309
|
+
|
|
310
|
+
Job economics currency values must be USD. Baselines and period facts use
|
|
311
|
+
server-supplied attribution when their provenance,
|
|
312
|
+
reporter, and source fields are omitted. Set those fields only when you need an
|
|
313
|
+
explicit override. Economics metric directions are
|
|
314
|
+
`HIGHER_IS_BETTER` or `LOWER_IS_BETTER`; monetization categories are
|
|
315
|
+
`REVENUE`, `COST_AVOIDED`, `TIME_SAVED`, and `LEADING_VALUE`, with a
|
|
316
|
+
`REALIZED` or `EXPECTED` basis.
|
|
317
|
+
|
|
318
|
+
Use `CUSTOMER_DECLARED` or `MEASURED` for a baseline override. Use `MEASURED`,
|
|
319
|
+
`SELF_REPORTED`, or `DERIVED` for a period fact override.
|
|
320
|
+
|
|
321
|
+
Facts are append-only and keyed on the period, dimension and metric key
|
|
322
|
+
together. Re-appending that tuple supersedes the active fact, and the server
|
|
323
|
+
requires `reason=` on the entry when it does.
|
|
268
324
|
|
|
269
325
|
### JobContext
|
|
270
326
|
|
|
@@ -311,7 +367,70 @@ history = get_outcome_history("sales-lead-8842")
|
|
|
311
367
|
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
312
368
|
```
|
|
313
369
|
|
|
314
|
-
`amend_outcome()` takes
|
|
370
|
+
`amend_outcome()` takes `reason` — the amendment's audit justification, still the first positional argument — plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`, `metrics`), and returns the updated job as a dict.
|
|
371
|
+
|
|
372
|
+
- **Detecting a lost update:** `report_outcome()` and `amend_outcome()` record the job's `entityVersion` from the response on the handle (readable as `job.entity_version`). The next `amend_outcome()` on that same handle sends it as `expectedEntityVersion`, so an amendment that would overwrite a change made by another writer in the meantime raises `OutcomeAmendConflictError` instead of silently winning. Pass `expected_entity_version=` to lock against a version you fetched yourself; use a fresh `JobContext.attach()` handle — which has recorded nothing — for the old last-write-wins behavior.
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from revenium_middleware import OutcomeAmendConflictError, get_outcome_history
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
job.amend_outcome(reason="Chargeback", outcome_value=0.0)
|
|
379
|
+
except OutcomeAmendConflictError as conflict:
|
|
380
|
+
# The conflict reports the version the platform actually holds.
|
|
381
|
+
print(conflict.current_entity_version) # e.g. 9
|
|
382
|
+
|
|
383
|
+
# Look at what the other writer changed, and only re-issue the amendment
|
|
384
|
+
# if it still applies to what is recorded now.
|
|
385
|
+
history = get_outcome_history("sales-lead-8842")
|
|
386
|
+
if still_applies(history[-1]):
|
|
387
|
+
job.amend_outcome(reason="Chargeback, re-checked", outcome_value=0.0,
|
|
388
|
+
expected_entity_version=conflict.current_entity_version)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The handle also records that version, so the retry above works with or without passing `expected_entity_version=` explicitly. `current_entity_version` is `None` when the conflict body carries no version; the version then has to come from a job read (`GET /v2/api/jobs/{agenticJobId}`), which this SDK does not wrap yet, and a retry without it is unlocked (last-write-wins). `get_outcome_history()` rows carry an `amendment_sequence`, not an entity version — history tells you *what* changed, never which version to retry with.
|
|
392
|
+
|
|
393
|
+
Every outcome call replaces the recorded version with the one its response reports, including clearing it when a response carries none, so a completed call never leaves a token behind that the platform has already moved past.
|
|
394
|
+
|
|
395
|
+
- **Omitting `reason`:** an API-key caller may leave `reason` out and the platform records an automated correction reason derived from the source. A session caller must supply one; a blank string is rejected client-side either way.
|
|
396
|
+
- **`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.
|
|
397
|
+
- **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
|
|
398
|
+
- **`metrics`:** both `report_outcome()` and `amend_outcome()` accept a `metrics` argument for recording the measurable facts behind an outcome.
|
|
399
|
+
|
|
400
|
+
### Recording Metric Facts
|
|
401
|
+
|
|
402
|
+
Beyond the single `outcome_value`, a job can carry the measurable facts its job type declares — `quality_rate` and its siblings — either with the outcome or later, once they are measurable.
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from revenium_middleware import JobContext
|
|
406
|
+
|
|
407
|
+
with JobContext("claim-8842", type="claims_triage") as job:
|
|
408
|
+
...
|
|
409
|
+
job.report_outcome(
|
|
410
|
+
execution_status="SUCCESS",
|
|
411
|
+
outcome_type="CONVERTED",
|
|
412
|
+
metrics=[
|
|
413
|
+
{"key": "quality_rate", "value": 0.93, "provenance": "MEASURED"},
|
|
414
|
+
{"key": "cases_closed", "value": 12},
|
|
415
|
+
],
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Two days later a human grades a sample of that same job's output.
|
|
419
|
+
handle = JobContext.attach("claim-8842")
|
|
420
|
+
handle.append_outcome_metrics([
|
|
421
|
+
{"key": "quality_rate", "value": 0.87, "provenance": "ATTESTED",
|
|
422
|
+
"reason": "graded sample of 200 cases"},
|
|
423
|
+
])
|
|
424
|
+
handle.close()
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
- **Declare the metric first:** a fact only lands if the job type's economics contract declares that key as a `PER_JOB` metric; an undeclared key is rejected with a 400. `quality_rate` is a rate and the platform range-checks it to 0..1.
|
|
428
|
+
- **Entry shape:** `key` and `value` are required; `provenance` (`MEASURED` | `SELF_REPORTED` | `DERIVED` | `ATTESTED`), `recordedBy`, `source`, `reason` and `recordedAt` are optional. Entries are sent exactly as you write them, so the fields you omit take the platform's defaults (`SELF_REPORTED`, the calling principal, `api`) instead of being guessed by the SDK. A missing `key` or `value` — or no entries at all on `append_outcome_metrics()` — raises `ValueError` before any HTTP request, on `JobContext` and `AgenticOutcomeClient` alike.
|
|
429
|
+
- **Append-only:** facts accumulate; the SDK never dedupes or replaces one, because the platform owns fact identity. `metrics=` on `amend_outcome()` appends as part of the amendment.
|
|
430
|
+
- **Not part of outcome history:** `get_outcome_history()` returns the outcome revisions only — appended facts do not appear in those rows.
|
|
431
|
+
- **Retries:** an append is retried only on `429`, which proves the platform rejected the request before recording anything. A `502`/`503`/`504` is raised instead of retried: the facts may already be recorded, and a second append is a second fact, so the decision to resend is yours (check the recorded facts first).
|
|
432
|
+
- **Locking is unaffected:** appending facts does not change the job's `entityVersion`, so the handle keeps the version it recorded and a following `amend_outcome()` still locks against it. (`report_outcome()` and `amend_outcome()` clear the recorded version when their response carries none, because those calls advance it; an append does not.)
|
|
433
|
+
- **Why it matters:** AI Alerts evaluate `QUALITY_RATE` from these facts, so a job whose integration emits none is invisible to those rules.
|
|
315
434
|
|
|
316
435
|
### Outcome Exceptions
|
|
317
436
|
|
|
@@ -322,7 +441,7 @@ All outcome exceptions are importable from `revenium_middleware` and share the `
|
|
|
322
441
|
| `OutcomeReportingError` | Base class — configuration failures (no API key available, unresolvable `team_id`) | Fix the key / team configuration |
|
|
323
442
|
| `OutcomeAlreadyReportedError` | Re-reporting a job that already has an outcome (backend 409) | Amend with `amend_outcome()` instead; the exception carries `reported_at` and `amendment_count` |
|
|
324
443
|
| `OutcomeNotReportedError` | Amending a job that has no outcome yet (backend 422) | Call `report_outcome()` first |
|
|
325
|
-
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) |
|
|
444
|
+
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) | Re-check the outcome against `get_outcome_history()`, then retry with `expected_entity_version=conflict.current_entity_version` — the SDK does not auto-retry |
|
|
326
445
|
|
|
327
446
|
### Low-Level Client
|
|
328
447
|
|
|
@@ -337,10 +456,11 @@ client = AgenticOutcomeClient(settings)
|
|
|
337
456
|
client.emit_completion(...) # one per LLM call
|
|
338
457
|
client.emit_tool_event(...) # one per tool / step
|
|
339
458
|
client.report_outcome(job_id, {...}) # close the job with a terminal outcome
|
|
459
|
+
client.append_outcome_metrics(job_id, [...]) # append declared per-job facts later
|
|
340
460
|
client.close()
|
|
341
461
|
```
|
|
342
462
|
|
|
343
|
-
The job is created implicitly by the first metric ingested for `agenticJobId`. Call `client.create_job(job_id)` explicitly if you need to record an agent run before emitting any metrics
|
|
463
|
+
The job is created implicitly by the first metric ingested for `agenticJobId`. Call `client.create_job(job_id)` explicitly if you need to record an agent run before emitting any metrics; it returns the created job resource merged over the fields you supplied, including the `entityVersion` an outcome amendment sends back as `expectedEntityVersion`.
|
|
344
464
|
|
|
345
465
|
See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demos (sales / coding / support) with configurable failure rates and outcome distributions.
|
|
346
466
|
|
|
@@ -779,6 +899,7 @@ litellm_settings:
|
|
|
779
899
|
```
|
|
780
900
|
|
|
781
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.
|
|
782
903
|
|
|
783
904
|
#### LiteLLM Decorators
|
|
784
905
|
|
|
@@ -1071,6 +1192,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
1071
1192
|
| `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
|
|
1072
1193
|
| `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
|
|
1073
1194
|
| `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
|
|
1195
|
+
| `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
1196
|
|
|
1075
1197
|
**Example:**
|
|
1076
1198
|
|
|
@@ -1093,11 +1215,27 @@ response = client.chat.completions.create(
|
|
|
1093
1215
|
"subscription_id": "pro-plan-Q1",
|
|
1094
1216
|
"productName": "customer-support-chatbot",
|
|
1095
1217
|
"agent": "support-agent",
|
|
1096
|
-
"response_quality_score": 0.92
|
|
1218
|
+
"response_quality_score": 0.92,
|
|
1219
|
+
"effort": "high"
|
|
1097
1220
|
}
|
|
1098
1221
|
)
|
|
1099
1222
|
```
|
|
1100
1223
|
|
|
1224
|
+
**Reasoning effort:** `effort` records how hard the model was asked to think, so
|
|
1225
|
+
high-effort reasoning spend can be separated from the rest. It is a free-form
|
|
1226
|
+
string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
|
|
1227
|
+
`high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
|
|
1228
|
+
never heard of is passed through untouched rather than rejected or rewritten. The
|
|
1229
|
+
Revenium backend owns validation (at most 16 characters, matching
|
|
1230
|
+
`^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
|
|
1231
|
+
of being silently dropped, so it surfaces in the log and in
|
|
1232
|
+
`get_metering_status()`. It is distinct from the reported reasoning token count,
|
|
1233
|
+
which measures the tokens actually spent rather than the level requested.
|
|
1234
|
+
|
|
1235
|
+
Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
|
|
1236
|
+
mode, where per-call attribution travels as request headers rather than
|
|
1237
|
+
`usage_metadata`, send `x-revenium-effort` instead.
|
|
1238
|
+
|
|
1101
1239
|
**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
1240
|
|
|
1103
1241
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
@@ -1121,6 +1259,7 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
1121
1259
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
1122
1260
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
1123
1261
|
| `ticket_id` | `REVENIUM_TICKET_ID` | External ticket or issue ID (e.g., Jira, Linear) (max 256 chars) | Attribute AI costs to individual tickets or issues |
|
|
1262
|
+
| `agent_version` | _(none — per call only)_ | Version of the AI agent that produced the call (max 64 chars) | Compare cost across agent releases; not `agentic_job_version`, which versions the job definition |
|
|
1124
1263
|
| `skill_name` | `REVENIUM_SKILL_NAME` | Name of the agent skill that produced the call (max 256 chars) | Attribute AI costs to the skill that generated them |
|
|
1125
1264
|
| `skill_source` | `REVENIUM_SKILL_SOURCE` | Where the skill was loaded from — accepted values: `bundled`, `projectSettings`, `userSettings`, `plugin` (case-sensitive) | Classify skill origin in the shared skill catalog |
|
|
1126
1265
|
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
@@ -1155,7 +1294,8 @@ response = client.chat.completions.create(
|
|
|
1155
1294
|
"trace_name": "Support Chat Session",
|
|
1156
1295
|
"transaction_name": "Generate Response",
|
|
1157
1296
|
"parent_transaction_id": "parent-txn-123",
|
|
1158
|
-
"ticket_id": "JIRA-123"
|
|
1297
|
+
"ticket_id": "JIRA-123",
|
|
1298
|
+
"agent_version": "1.4.2"
|
|
1159
1299
|
}
|
|
1160
1300
|
)
|
|
1161
1301
|
```
|
|
@@ -1572,7 +1712,8 @@ print(get_buffer_stats())
|
|
|
1572
1712
|
| `REVENIUM_AGENTIC_JOB_NAME` | - | Human-readable agentic job name |
|
|
1573
1713
|
| `REVENIUM_AGENTIC_JOB_TYPE` | - | Agentic job type category |
|
|
1574
1714
|
| `REVENIUM_AGENTIC_JOB_VERSION` | - | Agentic job version |
|
|
1575
|
-
| `
|
|
1715
|
+
| `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
|
+
| `REVENIUM_OUTCOME_API_KEY` | - | Deprecated fallback name for the write-scope key; used only when `REVENIUM_WRITE_API_KEY` is unset |
|
|
1576
1717
|
| `REVENIUM_PROFITSTREAM_BASE_URL` | `https://api.revenium.io` | Agentic outcomes API base URL |
|
|
1577
1718
|
| `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection |
|
|
1578
1719
|
| `REVENIUM_BUFFER_MAX_SIZE` | `1000` | Store-and-forward buffer capacity (oldest events evicted when full) |
|
|
@@ -167,7 +167,63 @@ when the provider's SDK is installed.
|
|
|
167
167
|
|
|
168
168
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
169
169
|
|
|
170
|
-
> **You need a write-scope key (`rev_sk_`) to use the agentic outcomes API.** Metering keys (`rev_mk_`) can only meter completions and tool events — they cannot report, amend, or read job outcomes, and the SDK rejects them client-side before any HTTP request is made. Key resolution: explicit `api_key=` > `REVENIUM_OUTCOME_API_KEY` > `REVENIUM_METERING_API_KEY`.
|
|
170
|
+
> **You need a write-scope key (`rev_sk_`) to use the agentic outcomes API.** Metering keys (`rev_mk_`) can only meter completions and tool events — they cannot report, amend, or read job outcomes, and the SDK rejects them client-side before any HTTP request is made. Key resolution: explicit `api_key=` > `REVENIUM_WRITE_API_KEY` > `REVENIUM_OUTCOME_API_KEY` (deprecated fallback) > `REVENIUM_METERING_API_KEY`.
|
|
171
|
+
|
|
172
|
+
### Job-Type Economics and Outcome Facts
|
|
173
|
+
|
|
174
|
+
Keep a metering key for AI telemetry and a separate write key for outcomes and
|
|
175
|
+
job-type configuration. A registered `valuePerUnit` rule takes precedence over
|
|
176
|
+
an outcome's `outcome_value`; the backend never sums the two value sources.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from revenium_middleware import (
|
|
180
|
+
Baseline, JobTypeEconomics, PeriodFactEntry, create_baseline,
|
|
181
|
+
report_period_facts, upsert_job_type_economics,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
upsert_job_type_economics("claim", JobTypeEconomics(
|
|
185
|
+
unit_metric_key="completed_claims", unit_label="claim",
|
|
186
|
+
metrics=[{
|
|
187
|
+
"key": "completed_claims", "type": "COUNT",
|
|
188
|
+
"direction": "HIGHER_IS_BETTER", "aggregation": "SUM",
|
|
189
|
+
"resolution": "PER_JOB",
|
|
190
|
+
}],
|
|
191
|
+
dimensions=[{"key": "region", "allowedValues": ["us", "ca"]}],
|
|
192
|
+
monetization={
|
|
193
|
+
"metricKey": "completed_claims", "valuePerUnit": 4.25,
|
|
194
|
+
"currency": "USD", "category": "COST_AVOIDED", "basis": "REALIZED",
|
|
195
|
+
},
|
|
196
|
+
))
|
|
197
|
+
create_baseline("claim", Baseline(
|
|
198
|
+
effective_from="2026-08-01T00:00:00Z", cost_per_unit=4.25, currency="USD",
|
|
199
|
+
))
|
|
200
|
+
report_period_facts("claim", [PeriodFactEntry(
|
|
201
|
+
period_start="2026-08-01T00:00:00Z", period_end="2026-09-01T00:00:00Z",
|
|
202
|
+
dimension_key="region", dimension_value="us",
|
|
203
|
+
key="completed_claims", value=1280,
|
|
204
|
+
)])
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`effective_from` is the only required field on a baseline; every other field
|
|
208
|
+
is optional, and a baseline without it is rejected. A job type must be
|
|
209
|
+
declared with `upsert_job_type_economics` before it accepts baselines or
|
|
210
|
+
facts, and `report_period_facts` accepts only metrics declared with
|
|
211
|
+
`"resolution": "PERIOD"`.
|
|
212
|
+
|
|
213
|
+
Job economics currency values must be USD. Baselines and period facts use
|
|
214
|
+
server-supplied attribution when their provenance,
|
|
215
|
+
reporter, and source fields are omitted. Set those fields only when you need an
|
|
216
|
+
explicit override. Economics metric directions are
|
|
217
|
+
`HIGHER_IS_BETTER` or `LOWER_IS_BETTER`; monetization categories are
|
|
218
|
+
`REVENUE`, `COST_AVOIDED`, `TIME_SAVED`, and `LEADING_VALUE`, with a
|
|
219
|
+
`REALIZED` or `EXPECTED` basis.
|
|
220
|
+
|
|
221
|
+
Use `CUSTOMER_DECLARED` or `MEASURED` for a baseline override. Use `MEASURED`,
|
|
222
|
+
`SELF_REPORTED`, or `DERIVED` for a period fact override.
|
|
223
|
+
|
|
224
|
+
Facts are append-only and keyed on the period, dimension and metric key
|
|
225
|
+
together. Re-appending that tuple supersedes the active fact, and the server
|
|
226
|
+
requires `reason=` on the entry when it does.
|
|
171
227
|
|
|
172
228
|
### JobContext
|
|
173
229
|
|
|
@@ -214,7 +270,70 @@ history = get_outcome_history("sales-lead-8842")
|
|
|
214
270
|
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
215
271
|
```
|
|
216
272
|
|
|
217
|
-
`amend_outcome()` takes
|
|
273
|
+
`amend_outcome()` takes `reason` — the amendment's audit justification, still the first positional argument — plus the same optional fields as `report_outcome()` (`execution_status`, `outcome_type`, `outcome_value`, `outcome_currency`, `metadata`, `reported_by`, `outcome_reason`, `metrics`), and returns the updated job as a dict.
|
|
274
|
+
|
|
275
|
+
- **Detecting a lost update:** `report_outcome()` and `amend_outcome()` record the job's `entityVersion` from the response on the handle (readable as `job.entity_version`). The next `amend_outcome()` on that same handle sends it as `expectedEntityVersion`, so an amendment that would overwrite a change made by another writer in the meantime raises `OutcomeAmendConflictError` instead of silently winning. Pass `expected_entity_version=` to lock against a version you fetched yourself; use a fresh `JobContext.attach()` handle — which has recorded nothing — for the old last-write-wins behavior.
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from revenium_middleware import OutcomeAmendConflictError, get_outcome_history
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
job.amend_outcome(reason="Chargeback", outcome_value=0.0)
|
|
282
|
+
except OutcomeAmendConflictError as conflict:
|
|
283
|
+
# The conflict reports the version the platform actually holds.
|
|
284
|
+
print(conflict.current_entity_version) # e.g. 9
|
|
285
|
+
|
|
286
|
+
# Look at what the other writer changed, and only re-issue the amendment
|
|
287
|
+
# if it still applies to what is recorded now.
|
|
288
|
+
history = get_outcome_history("sales-lead-8842")
|
|
289
|
+
if still_applies(history[-1]):
|
|
290
|
+
job.amend_outcome(reason="Chargeback, re-checked", outcome_value=0.0,
|
|
291
|
+
expected_entity_version=conflict.current_entity_version)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The handle also records that version, so the retry above works with or without passing `expected_entity_version=` explicitly. `current_entity_version` is `None` when the conflict body carries no version; the version then has to come from a job read (`GET /v2/api/jobs/{agenticJobId}`), which this SDK does not wrap yet, and a retry without it is unlocked (last-write-wins). `get_outcome_history()` rows carry an `amendment_sequence`, not an entity version — history tells you *what* changed, never which version to retry with.
|
|
295
|
+
|
|
296
|
+
Every outcome call replaces the recorded version with the one its response reports, including clearing it when a response carries none, so a completed call never leaves a token behind that the platform has already moved past.
|
|
297
|
+
|
|
298
|
+
- **Omitting `reason`:** an API-key caller may leave `reason` out and the platform records an automated correction reason derived from the source. A session caller must supply one; a blank string is rejected client-side either way.
|
|
299
|
+
- **`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.
|
|
300
|
+
- **Clearing `outcome_reason`:** omit the argument to leave the stored value untouched; pass an empty string (`outcome_reason=""`) to clear it.
|
|
301
|
+
- **`metrics`:** both `report_outcome()` and `amend_outcome()` accept a `metrics` argument for recording the measurable facts behind an outcome.
|
|
302
|
+
|
|
303
|
+
### Recording Metric Facts
|
|
304
|
+
|
|
305
|
+
Beyond the single `outcome_value`, a job can carry the measurable facts its job type declares — `quality_rate` and its siblings — either with the outcome or later, once they are measurable.
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from revenium_middleware import JobContext
|
|
309
|
+
|
|
310
|
+
with JobContext("claim-8842", type="claims_triage") as job:
|
|
311
|
+
...
|
|
312
|
+
job.report_outcome(
|
|
313
|
+
execution_status="SUCCESS",
|
|
314
|
+
outcome_type="CONVERTED",
|
|
315
|
+
metrics=[
|
|
316
|
+
{"key": "quality_rate", "value": 0.93, "provenance": "MEASURED"},
|
|
317
|
+
{"key": "cases_closed", "value": 12},
|
|
318
|
+
],
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Two days later a human grades a sample of that same job's output.
|
|
322
|
+
handle = JobContext.attach("claim-8842")
|
|
323
|
+
handle.append_outcome_metrics([
|
|
324
|
+
{"key": "quality_rate", "value": 0.87, "provenance": "ATTESTED",
|
|
325
|
+
"reason": "graded sample of 200 cases"},
|
|
326
|
+
])
|
|
327
|
+
handle.close()
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- **Declare the metric first:** a fact only lands if the job type's economics contract declares that key as a `PER_JOB` metric; an undeclared key is rejected with a 400. `quality_rate` is a rate and the platform range-checks it to 0..1.
|
|
331
|
+
- **Entry shape:** `key` and `value` are required; `provenance` (`MEASURED` | `SELF_REPORTED` | `DERIVED` | `ATTESTED`), `recordedBy`, `source`, `reason` and `recordedAt` are optional. Entries are sent exactly as you write them, so the fields you omit take the platform's defaults (`SELF_REPORTED`, the calling principal, `api`) instead of being guessed by the SDK. A missing `key` or `value` — or no entries at all on `append_outcome_metrics()` — raises `ValueError` before any HTTP request, on `JobContext` and `AgenticOutcomeClient` alike.
|
|
332
|
+
- **Append-only:** facts accumulate; the SDK never dedupes or replaces one, because the platform owns fact identity. `metrics=` on `amend_outcome()` appends as part of the amendment.
|
|
333
|
+
- **Not part of outcome history:** `get_outcome_history()` returns the outcome revisions only — appended facts do not appear in those rows.
|
|
334
|
+
- **Retries:** an append is retried only on `429`, which proves the platform rejected the request before recording anything. A `502`/`503`/`504` is raised instead of retried: the facts may already be recorded, and a second append is a second fact, so the decision to resend is yours (check the recorded facts first).
|
|
335
|
+
- **Locking is unaffected:** appending facts does not change the job's `entityVersion`, so the handle keeps the version it recorded and a following `amend_outcome()` still locks against it. (`report_outcome()` and `amend_outcome()` clear the recorded version when their response carries none, because those calls advance it; an append does not.)
|
|
336
|
+
- **Why it matters:** AI Alerts evaluate `QUALITY_RATE` from these facts, so a job whose integration emits none is invisible to those rules.
|
|
218
337
|
|
|
219
338
|
### Outcome Exceptions
|
|
220
339
|
|
|
@@ -225,7 +344,7 @@ All outcome exceptions are importable from `revenium_middleware` and share the `
|
|
|
225
344
|
| `OutcomeReportingError` | Base class — configuration failures (no API key available, unresolvable `team_id`) | Fix the key / team configuration |
|
|
226
345
|
| `OutcomeAlreadyReportedError` | Re-reporting a job that already has an outcome (backend 409) | Amend with `amend_outcome()` instead; the exception carries `reported_at` and `amendment_count` |
|
|
227
346
|
| `OutcomeNotReportedError` | Amending a job that has no outcome yet (backend 422) | Call `report_outcome()` first |
|
|
228
|
-
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) |
|
|
347
|
+
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) | Re-check the outcome against `get_outcome_history()`, then retry with `expected_entity_version=conflict.current_entity_version` — the SDK does not auto-retry |
|
|
229
348
|
|
|
230
349
|
### Low-Level Client
|
|
231
350
|
|
|
@@ -240,10 +359,11 @@ client = AgenticOutcomeClient(settings)
|
|
|
240
359
|
client.emit_completion(...) # one per LLM call
|
|
241
360
|
client.emit_tool_event(...) # one per tool / step
|
|
242
361
|
client.report_outcome(job_id, {...}) # close the job with a terminal outcome
|
|
362
|
+
client.append_outcome_metrics(job_id, [...]) # append declared per-job facts later
|
|
243
363
|
client.close()
|
|
244
364
|
```
|
|
245
365
|
|
|
246
|
-
The job is created implicitly by the first metric ingested for `agenticJobId`. Call `client.create_job(job_id)` explicitly if you need to record an agent run before emitting any metrics
|
|
366
|
+
The job is created implicitly by the first metric ingested for `agenticJobId`. Call `client.create_job(job_id)` explicitly if you need to record an agent run before emitting any metrics; it returns the created job resource merged over the fields you supplied, including the `entityVersion` an outcome amendment sends back as `expectedEntityVersion`.
|
|
247
367
|
|
|
248
368
|
See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demos (sales / coding / support) with configurable failure rates and outcome distributions.
|
|
249
369
|
|
|
@@ -682,6 +802,7 @@ litellm_settings:
|
|
|
682
802
|
```
|
|
683
803
|
|
|
684
804
|
When using the LiteLLM proxy, pass metadata via HTTP headers (`x-revenium-*`).
|
|
805
|
+
Reasoning effort travels as `x-revenium-effort` on the proxied request.
|
|
685
806
|
|
|
686
807
|
#### LiteLLM Decorators
|
|
687
808
|
|
|
@@ -974,6 +1095,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
|
|
|
974
1095
|
| `productName` | Your product or feature name | Attribute AI costs to specific features (e.g., `"customer-chatbot"`, `"email-assistant"`). Auto-creates if not found |
|
|
975
1096
|
| `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows |
|
|
976
1097
|
| `response_quality_score` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
|
|
1098
|
+
| `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
1099
|
|
|
978
1100
|
**Example:**
|
|
979
1101
|
|
|
@@ -996,11 +1118,27 @@ response = client.chat.completions.create(
|
|
|
996
1118
|
"subscription_id": "pro-plan-Q1",
|
|
997
1119
|
"productName": "customer-support-chatbot",
|
|
998
1120
|
"agent": "support-agent",
|
|
999
|
-
"response_quality_score": 0.92
|
|
1121
|
+
"response_quality_score": 0.92,
|
|
1122
|
+
"effort": "high"
|
|
1000
1123
|
}
|
|
1001
1124
|
)
|
|
1002
1125
|
```
|
|
1003
1126
|
|
|
1127
|
+
**Reasoning effort:** `effort` records how hard the model was asked to think, so
|
|
1128
|
+
high-effort reasoning spend can be separated from the rest. It is a free-form
|
|
1129
|
+
string, not an enum -- vendor vocabularies differ and drift, so `low`, `medium`,
|
|
1130
|
+
`high`, `xhigh` and `ultra` are all real values, and a level this SDK release has
|
|
1131
|
+
never heard of is passed through untouched rather than rejected or rewritten. The
|
|
1132
|
+
Revenium backend owns validation (at most 16 characters, matching
|
|
1133
|
+
`^[A-Za-z0-9_-]+$`); a value it rejects fails the metering call visibly instead
|
|
1134
|
+
of being silently dropped, so it surfaces in the log and in
|
|
1135
|
+
`get_metering_status()`. It is distinct from the reported reasoning token count,
|
|
1136
|
+
which measures the tokens actually spent rather than the level requested.
|
|
1137
|
+
|
|
1138
|
+
Set it per call in `usage_metadata` on any provider integration. In LiteLLM proxy
|
|
1139
|
+
mode, where per-call attribution travels as request headers rather than
|
|
1140
|
+
`usage_metadata`, send `x-revenium-effort` instead.
|
|
1141
|
+
|
|
1004
1142
|
**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
1143
|
|
|
1006
1144
|
**API Reference:** [Complete metadata field documentation](https://revenium.readme.io/reference/meter_ai_completion)
|
|
@@ -1024,6 +1162,7 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
1024
1162
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
1025
1163
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
1026
1164
|
| `ticket_id` | `REVENIUM_TICKET_ID` | External ticket or issue ID (e.g., Jira, Linear) (max 256 chars) | Attribute AI costs to individual tickets or issues |
|
|
1165
|
+
| `agent_version` | _(none — per call only)_ | Version of the AI agent that produced the call (max 64 chars) | Compare cost across agent releases; not `agentic_job_version`, which versions the job definition |
|
|
1027
1166
|
| `skill_name` | `REVENIUM_SKILL_NAME` | Name of the agent skill that produced the call (max 256 chars) | Attribute AI costs to the skill that generated them |
|
|
1028
1167
|
| `skill_source` | `REVENIUM_SKILL_SOURCE` | Where the skill was loaded from — accepted values: `bundled`, `projectSettings`, `userSettings`, `plugin` (case-sensitive) | Classify skill origin in the shared skill catalog |
|
|
1029
1168
|
| `skill_kind` | `REVENIUM_SKILL_KIND` | Kind of skill invoked — accepted value: `workflow` (omit otherwise) | Distinguish workflow skills in reporting |
|
|
@@ -1058,7 +1197,8 @@ response = client.chat.completions.create(
|
|
|
1058
1197
|
"trace_name": "Support Chat Session",
|
|
1059
1198
|
"transaction_name": "Generate Response",
|
|
1060
1199
|
"parent_transaction_id": "parent-txn-123",
|
|
1061
|
-
"ticket_id": "JIRA-123"
|
|
1200
|
+
"ticket_id": "JIRA-123",
|
|
1201
|
+
"agent_version": "1.4.2"
|
|
1062
1202
|
}
|
|
1063
1203
|
)
|
|
1064
1204
|
```
|
|
@@ -1475,7 +1615,8 @@ print(get_buffer_stats())
|
|
|
1475
1615
|
| `REVENIUM_AGENTIC_JOB_NAME` | - | Human-readable agentic job name |
|
|
1476
1616
|
| `REVENIUM_AGENTIC_JOB_TYPE` | - | Agentic job type category |
|
|
1477
1617
|
| `REVENIUM_AGENTIC_JOB_VERSION` | - | Agentic job version |
|
|
1478
|
-
| `
|
|
1618
|
+
| `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` |
|
|
1619
|
+
| `REVENIUM_OUTCOME_API_KEY` | - | Deprecated fallback name for the write-scope key; used only when `REVENIUM_WRITE_API_KEY` is unset |
|
|
1479
1620
|
| `REVENIUM_PROFITSTREAM_BASE_URL` | `https://api.revenium.io` | Agentic outcomes API base URL |
|
|
1480
1621
|
| `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection |
|
|
1481
1622
|
| `REVENIUM_BUFFER_MAX_SIZE` | `1000` | Store-and-forward buffer capacity (oldest events evicted when full) |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revenium-python-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.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"
|
|
@@ -179,6 +179,13 @@ from .agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings # no
|
|
|
179
179
|
# Public job-context surface (BACK-777)
|
|
180
180
|
from .job_context import JobContext # noqa: E402
|
|
181
181
|
from .job_history import JobOutcomeAmendment, get_outcome_history # noqa: E402
|
|
182
|
+
from .job_type_economics import ( # noqa: E402
|
|
183
|
+
Baseline, BaselineProvenance, JobTypeEconomics, JobTypeMetricDirection,
|
|
184
|
+
JobTypeMonetizationBasis, JobTypeMonetizationCategory,
|
|
185
|
+
OutcomeMetricProvenance, PeriodFactEntry,
|
|
186
|
+
create_baseline, get_job_type_economics, list_baselines,
|
|
187
|
+
report_period_facts, upsert_job_type_economics,
|
|
188
|
+
)
|
|
182
189
|
from ._core.exceptions import ( # noqa: E402
|
|
183
190
|
OutcomeAlreadyReportedError,
|
|
184
191
|
OutcomeAmendConflictError,
|
|
@@ -235,4 +242,17 @@ __all__ = [
|
|
|
235
242
|
"JobOutcomeAmendment",
|
|
236
243
|
"OutcomeNotReportedError",
|
|
237
244
|
"OutcomeAmendConflictError",
|
|
245
|
+
"PeriodFactEntry",
|
|
246
|
+
"Baseline",
|
|
247
|
+
"BaselineProvenance",
|
|
248
|
+
"JobTypeEconomics",
|
|
249
|
+
"JobTypeMetricDirection",
|
|
250
|
+
"JobTypeMonetizationBasis",
|
|
251
|
+
"JobTypeMonetizationCategory",
|
|
252
|
+
"OutcomeMetricProvenance",
|
|
253
|
+
"report_period_facts",
|
|
254
|
+
"get_job_type_economics",
|
|
255
|
+
"upsert_job_type_economics",
|
|
256
|
+
"create_baseline",
|
|
257
|
+
"list_baselines",
|
|
238
258
|
]
|
|
@@ -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
|