revenium-python-sdk 0.3.0__tar.gz → 0.4.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.3.0 → revenium_python_sdk-0.4.0}/PKG-INFO +108 -4
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/README.md +107 -3
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/pyproject.toml +1 -1
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/__init__.py +19 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/config.py +9 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/context.py +44 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/exceptions.py +42 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/fields.py +24 -1
- revenium_python_sdk-0.4.0/revenium_middleware/_core/outcomes.py +424 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/trace_fields.py +58 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/resources/ai.py +8 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/ai_create_completion_params.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/agentic_outcomes.py +58 -104
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/bedrock_transport.py +1 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/middleware.py +9 -2
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/trace_fields.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/fal/_metering.py +2 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/fal/trace_fields.py +6 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/trace_fields.py +4 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/utils.py +5 -0
- revenium_python_sdk-0.4.0/revenium_middleware/job_context.py +508 -0
- revenium_python_sdk-0.4.0/revenium_middleware/job_history.py +127 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/__init__.py +2 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/decorators.py +110 -1
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/integrations/crewai.py +151 -24
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/middleware.py +5 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/trace_fields.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/ollama/middleware.py +5 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/ollama/trace_fields.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/middleware.py +14 -3
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/trace_fields.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/perplexity/middleware.py +4 -1
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/perplexity/perplexity_sdk.py +4 -1
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/perplexity/trace_fields.py +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_python_sdk.egg-info/PKG-INFO +108 -4
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_python_sdk.egg-info/SOURCES.txt +3 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/tests/test_metering.py +30 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/LICENSE +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/decorators.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/enforcement.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/log_sanitize.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/metering.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/metering_buffer.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/metering_submission.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/patch_registry.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/prompt_extraction.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/subscriber.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/LICENSE +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_base_client.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_client.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_compat.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_constants.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_exceptions.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_files.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_models.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_qs.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_resource.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_response.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_streaming.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_types.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_logs.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_streams.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_sync.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_transform.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_typing.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_utils/_utils.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/_version.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/context.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/decorator.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/py.typed +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/resources/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/resources/apis.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/resources/events.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/ai_create_audio_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/ai_create_image_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/ai_create_video_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/event_create_params.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/bedrock_adapter.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/provider.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/anthropic/stream_create.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/fal/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/fal/config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/fal/middleware.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/exceptions.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/protocols.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/common/types.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/google_ai/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/google_ai/middleware.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/google_ai/provider.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/google/vertex_ai/provider.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/context.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/hooks.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/client/validation.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/proxy/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/litellm/proxy/middleware.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/ollama/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/azure_config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/azure_model_resolver.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/config.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/exceptions.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/langchain/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/langchain/_utils.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/prompt_extractor.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/openai/provider.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/perplexity/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/perplexity/provider.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/webhooks/__init__.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/webhooks/_verify.py +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_python_sdk.egg-info/requires.txt +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_python_sdk.egg-info/top_level.txt +0 -0
- {revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.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.4.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
|
|
@@ -212,6 +212,70 @@ print(response.choices[0].message.content)
|
|
|
212
212
|
|
|
213
213
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
214
214
|
|
|
215
|
+
> **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`.
|
|
216
|
+
|
|
217
|
+
### JobContext
|
|
218
|
+
|
|
219
|
+
`JobContext` is the recommended high-level API: every AI call made inside the block is automatically metered against the job (all provider middlewares pick the job fields up from context), and the job's business outcome is reported when the work is done.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from revenium_middleware import JobContext
|
|
223
|
+
|
|
224
|
+
with JobContext("loan-app-12345", type="loan_processing", version="2.1") as job:
|
|
225
|
+
response = client.chat.completions.create(...) # metered against the job automatically
|
|
226
|
+
job.report_outcome(
|
|
227
|
+
execution_status="SUCCESS", # SUCCESS | FAILED | CANCELLED
|
|
228
|
+
outcome_type="CONVERTED",
|
|
229
|
+
outcome_value=500.0,
|
|
230
|
+
outcome_currency="USD",
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- **Async:** `async with JobContext(...) as job:` works identically.
|
|
235
|
+
- **Auto-FAILED:** if an unhandled exception escapes the block before an outcome was reported, the context automatically reports `execution_status="FAILED"` (error message and class in metadata) and always re-raises the original exception.
|
|
236
|
+
- **Blocking:** outcome calls are synchronous HTTP requests with retries; tune how long they may block with the `retry_attempts`, `retry_initial_seconds`, and `retry_max_seconds` arguments, accepted by the `JobContext` constructor, `JobContext.attach()`, `get_outcome_history()`, and the CrewAI wrapper's `report_job_outcome`/`amend_job_outcome`.
|
|
237
|
+
- **Team resolution:** explicit `team_id=` > `REVENIUM_TEAM_ID` > automatic resolution from the API key; `OutcomeReportingError` is raised if none of these yields a team.
|
|
238
|
+
- **Nesting:** a nested `JobContext` is a different job (replace, not merge); exiting the inner context restores the outer job's fields.
|
|
239
|
+
|
|
240
|
+
To tag AI calls with job fields without a context manager, use per-call `usage_metadata={"agentic_job_id": ...}`, the `@track_job` decorator (LiteLLM), or the process-wide `REVENIUM_AGENTIC_JOB_*` environment variables — see [Optional Environment Variables](#optional-environment-variables).
|
|
241
|
+
|
|
242
|
+
### Amending an Outcome
|
|
243
|
+
|
|
244
|
+
Outcomes are amendable: when the business result changes after the fact, amend the recorded outcome instead of re-reporting it. `JobContext.attach()` returns a lightweight handle to an existing job — it is not entered as a context manager and does not touch AI-call scoping — so amendments work from a different process than the one that ran the job.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from revenium_middleware import JobContext, get_outcome_history
|
|
248
|
+
|
|
249
|
+
# Two weeks after the agent converted the lead at $500,
|
|
250
|
+
# the customer expands to the annual plan.
|
|
251
|
+
job = JobContext.attach("sales-lead-8842")
|
|
252
|
+
job.amend_outcome(
|
|
253
|
+
reason="Customer expanded to the annual plan after the initial conversion",
|
|
254
|
+
outcome_value=750.0,
|
|
255
|
+
)
|
|
256
|
+
job.close()
|
|
257
|
+
|
|
258
|
+
history = get_outcome_history("sales-lead-8842")
|
|
259
|
+
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`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.
|
|
263
|
+
|
|
264
|
+
### Outcome Exceptions
|
|
265
|
+
|
|
266
|
+
All outcome exceptions are importable from `revenium_middleware` and share the `OutcomeReportingError` base, so `except OutcomeReportingError:` catches the whole family:
|
|
267
|
+
|
|
268
|
+
| Exception | Raised when | What to do |
|
|
269
|
+
|-----------|-------------|------------|
|
|
270
|
+
| `OutcomeReportingError` | Base class — configuration failures (no API key available, unresolvable `team_id`) | Fix the key / team configuration |
|
|
271
|
+
| `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` |
|
|
272
|
+
| `OutcomeNotReportedError` | Amending a job that has no outcome yet (backend 422) | Call `report_outcome()` first |
|
|
273
|
+
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) | Refetch with `get_outcome_history()` and retry — the SDK does not auto-retry |
|
|
274
|
+
|
|
275
|
+
### Low-Level Client
|
|
276
|
+
|
|
277
|
+
For manual control over every metric (one `emit_completion` per LLM call, one `emit_tool_event` per tool/step), use `AgenticOutcomeClient` directly:
|
|
278
|
+
|
|
215
279
|
```python
|
|
216
280
|
from revenium_middleware.agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings
|
|
217
281
|
|
|
@@ -678,8 +742,9 @@ LiteLLM provides additional tracking decorators beyond the standard `@revenium_m
|
|
|
678
742
|
| `@track_product()` | Track product-specific usage |
|
|
679
743
|
| `@track_subscriber()` | Identify end users |
|
|
680
744
|
| `@track_quality()` | Track response quality scores |
|
|
745
|
+
| `@track_job()` | Inject agentic job fields for cost/ROI correlation, e.g. `@track_job(job_id="loan-app-12345", type="loan_processing")` |
|
|
681
746
|
|
|
682
|
-
|
|
747
|
+
The tracking decorators above support static values, extraction from function arguments (`name_from_arg`), or extraction from object attributes (`name_from_attr`); `@track_job` supports static values and argument extraction (`job_id_from_arg`, `type_from_arg`) but has no attribute variant.
|
|
683
748
|
|
|
684
749
|
#### CrewAI Integration
|
|
685
750
|
|
|
@@ -689,6 +754,35 @@ pip install "revenium-python-sdk[litellm]" crewai
|
|
|
689
754
|
|
|
690
755
|
Pre-built wrapper for tracking CrewAI agent executions. **Note:** CrewAI requires Python 3.12 or earlier.
|
|
691
756
|
|
|
757
|
+
**Job outcome tracking:** pass the `agentic_job_*` kwargs to tie every LLM call in the crew to one agentic job, then report (or later amend) the job's business outcome. Requires a write-scope key (`rev_sk_`) — see [Agentic Outcomes](#agentic-outcomes-outcome-based-metering).
|
|
758
|
+
|
|
759
|
+
```python
|
|
760
|
+
from revenium_middleware.litellm.client.integrations.crewai import ReveniumCrewWrapper
|
|
761
|
+
|
|
762
|
+
crew = ReveniumCrewWrapper(
|
|
763
|
+
agents=[support_agent],
|
|
764
|
+
tasks=[triage_task],
|
|
765
|
+
organization_id="AcmeCorp",
|
|
766
|
+
subscription_id="82764738",
|
|
767
|
+
product_id="Platinum",
|
|
768
|
+
agentic_job_id="support-ticket-456",
|
|
769
|
+
agentic_job_name="Support Ticket Triage",
|
|
770
|
+
agentic_job_type="customer_support",
|
|
771
|
+
agentic_job_version="2.0",
|
|
772
|
+
)
|
|
773
|
+
result = crew.kickoff()
|
|
774
|
+
|
|
775
|
+
crew.report_job_outcome(
|
|
776
|
+
execution_status="SUCCESS",
|
|
777
|
+
outcome_type="DEFLECTED",
|
|
778
|
+
outcome_value=25.0,
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
# Later, if the business result changes:
|
|
782
|
+
# crew.amend_job_outcome(reason="Ticket reopened and escalated to a human agent",
|
|
783
|
+
# outcome_type="ESCALATED", outcome_value=0.0)
|
|
784
|
+
```
|
|
785
|
+
|
|
692
786
|
**LiteLLM environment variables:**
|
|
693
787
|
- `LITELLM_PROXY_URL` - Your LiteLLM proxy URL
|
|
694
788
|
- `LITELLM_API_KEY` - Your LiteLLM proxy API key
|
|
@@ -931,6 +1025,7 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
931
1025
|
| `parent_transaction_id` | `REVENIUM_PARENT_TRANSACTION_ID` | Parent transaction ID | Link child operations to parents across microservices |
|
|
932
1026
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
933
1027
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
1028
|
+
| `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 |
|
|
934
1029
|
|
|
935
1030
|
**Note:** `operation_type` (e.g., `CHAT`, `EMBED`, `TOOL_CALL`) and `operation_subtype` (e.g., `function_call`, `streaming`) are automatically detected by the middleware and cannot be overridden.
|
|
936
1031
|
|
|
@@ -958,7 +1053,8 @@ response = client.chat.completions.create(
|
|
|
958
1053
|
"trace_type": "customer-support",
|
|
959
1054
|
"trace_name": "Support Chat Session",
|
|
960
1055
|
"transaction_name": "Generate Response",
|
|
961
|
-
"parent_transaction_id": "parent-txn-123"
|
|
1056
|
+
"parent_transaction_id": "parent-txn-123",
|
|
1057
|
+
"ticket_id": "JIRA-123"
|
|
962
1058
|
}
|
|
963
1059
|
)
|
|
964
1060
|
```
|
|
@@ -1362,7 +1458,7 @@ print(get_buffer_stats())
|
|
|
1362
1458
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1363
1459
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1364
1460
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1365
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1461
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups and outcome reporting (JobContext team resolution) |
|
|
1366
1462
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1367
1463
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1368
1464
|
| `REVENIUM_CREDENTIAL_ALIAS` | - | Human-readable API key name |
|
|
@@ -1371,10 +1467,18 @@ print(get_buffer_stats())
|
|
|
1371
1467
|
| `REVENIUM_PARENT_TRANSACTION_ID` | - | Parent transaction ID for distributed tracing |
|
|
1372
1468
|
| `REVENIUM_TRANSACTION_NAME` | - | Human-friendly operation name |
|
|
1373
1469
|
| `REVENIUM_RETRY_NUMBER` | - | Retry attempt number |
|
|
1470
|
+
| `REVENIUM_AGENTIC_JOB_ID` | - | Agentic job instance ID attached to all completions in the process (triggers backend job auto-creation) |
|
|
1471
|
+
| `REVENIUM_AGENTIC_JOB_NAME` | - | Human-readable agentic job name |
|
|
1472
|
+
| `REVENIUM_AGENTIC_JOB_TYPE` | - | Agentic job type category |
|
|
1473
|
+
| `REVENIUM_AGENTIC_JOB_VERSION` | - | Agentic job version |
|
|
1474
|
+
| `REVENIUM_OUTCOME_API_KEY` | - | Write-scope key (`rev_sk_`) for the agentic outcomes API (report/amend/history); falls back to `REVENIUM_METERING_API_KEY` |
|
|
1475
|
+
| `REVENIUM_PROFITSTREAM_BASE_URL` | `https://api.revenium.io` | Agentic outcomes API base URL |
|
|
1374
1476
|
| `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection |
|
|
1375
1477
|
| `REVENIUM_BUFFER_MAX_SIZE` | `1000` | Store-and-forward buffer capacity (oldest events evicted when full) |
|
|
1376
1478
|
| `REVENIUM_BUFFER_FLUSH_INTERVAL` | `30` | Seconds between automatic replay attempts for buffered events |
|
|
1377
1479
|
|
|
1480
|
+
Per-call `usage_metadata` values take precedence over the `REVENIUM_AGENTIC_JOB_*` environment variables, and the LiteLLM proxy path sources job fields from `x-revenium-*` headers only — these process-level env fallbacks do not apply to proxied traffic.
|
|
1481
|
+
|
|
1378
1482
|
### Provider-Specific Environment Variables
|
|
1379
1483
|
|
|
1380
1484
|
| Variable | Provider | Description |
|
|
@@ -119,6 +119,70 @@ print(response.choices[0].message.content)
|
|
|
119
119
|
|
|
120
120
|
Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
|
|
121
121
|
|
|
122
|
+
> **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`.
|
|
123
|
+
|
|
124
|
+
### JobContext
|
|
125
|
+
|
|
126
|
+
`JobContext` is the recommended high-level API: every AI call made inside the block is automatically metered against the job (all provider middlewares pick the job fields up from context), and the job's business outcome is reported when the work is done.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from revenium_middleware import JobContext
|
|
130
|
+
|
|
131
|
+
with JobContext("loan-app-12345", type="loan_processing", version="2.1") as job:
|
|
132
|
+
response = client.chat.completions.create(...) # metered against the job automatically
|
|
133
|
+
job.report_outcome(
|
|
134
|
+
execution_status="SUCCESS", # SUCCESS | FAILED | CANCELLED
|
|
135
|
+
outcome_type="CONVERTED",
|
|
136
|
+
outcome_value=500.0,
|
|
137
|
+
outcome_currency="USD",
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **Async:** `async with JobContext(...) as job:` works identically.
|
|
142
|
+
- **Auto-FAILED:** if an unhandled exception escapes the block before an outcome was reported, the context automatically reports `execution_status="FAILED"` (error message and class in metadata) and always re-raises the original exception.
|
|
143
|
+
- **Blocking:** outcome calls are synchronous HTTP requests with retries; tune how long they may block with the `retry_attempts`, `retry_initial_seconds`, and `retry_max_seconds` arguments, accepted by the `JobContext` constructor, `JobContext.attach()`, `get_outcome_history()`, and the CrewAI wrapper's `report_job_outcome`/`amend_job_outcome`.
|
|
144
|
+
- **Team resolution:** explicit `team_id=` > `REVENIUM_TEAM_ID` > automatic resolution from the API key; `OutcomeReportingError` is raised if none of these yields a team.
|
|
145
|
+
- **Nesting:** a nested `JobContext` is a different job (replace, not merge); exiting the inner context restores the outer job's fields.
|
|
146
|
+
|
|
147
|
+
To tag AI calls with job fields without a context manager, use per-call `usage_metadata={"agentic_job_id": ...}`, the `@track_job` decorator (LiteLLM), or the process-wide `REVENIUM_AGENTIC_JOB_*` environment variables — see [Optional Environment Variables](#optional-environment-variables).
|
|
148
|
+
|
|
149
|
+
### Amending an Outcome
|
|
150
|
+
|
|
151
|
+
Outcomes are amendable: when the business result changes after the fact, amend the recorded outcome instead of re-reporting it. `JobContext.attach()` returns a lightweight handle to an existing job — it is not entered as a context manager and does not touch AI-call scoping — so amendments work from a different process than the one that ran the job.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from revenium_middleware import JobContext, get_outcome_history
|
|
155
|
+
|
|
156
|
+
# Two weeks after the agent converted the lead at $500,
|
|
157
|
+
# the customer expands to the annual plan.
|
|
158
|
+
job = JobContext.attach("sales-lead-8842")
|
|
159
|
+
job.amend_outcome(
|
|
160
|
+
reason="Customer expanded to the annual plan after the initial conversion",
|
|
161
|
+
outcome_value=750.0,
|
|
162
|
+
)
|
|
163
|
+
job.close()
|
|
164
|
+
|
|
165
|
+
history = get_outcome_history("sales-lead-8842")
|
|
166
|
+
# List[JobOutcomeAmendment], ordered by amendment_sequence (1 = the initial report)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`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.
|
|
170
|
+
|
|
171
|
+
### Outcome Exceptions
|
|
172
|
+
|
|
173
|
+
All outcome exceptions are importable from `revenium_middleware` and share the `OutcomeReportingError` base, so `except OutcomeReportingError:` catches the whole family:
|
|
174
|
+
|
|
175
|
+
| Exception | Raised when | What to do |
|
|
176
|
+
|-----------|-------------|------------|
|
|
177
|
+
| `OutcomeReportingError` | Base class — configuration failures (no API key available, unresolvable `team_id`) | Fix the key / team configuration |
|
|
178
|
+
| `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` |
|
|
179
|
+
| `OutcomeNotReportedError` | Amending a job that has no outcome yet (backend 422) | Call `report_outcome()` first |
|
|
180
|
+
| `OutcomeAmendConflictError` | A concurrent amendment changed the outcome (backend 409, optimistic lock) | Refetch with `get_outcome_history()` and retry — the SDK does not auto-retry |
|
|
181
|
+
|
|
182
|
+
### Low-Level Client
|
|
183
|
+
|
|
184
|
+
For manual control over every metric (one `emit_completion` per LLM call, one `emit_tool_event` per tool/step), use `AgenticOutcomeClient` directly:
|
|
185
|
+
|
|
122
186
|
```python
|
|
123
187
|
from revenium_middleware.agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings
|
|
124
188
|
|
|
@@ -585,8 +649,9 @@ LiteLLM provides additional tracking decorators beyond the standard `@revenium_m
|
|
|
585
649
|
| `@track_product()` | Track product-specific usage |
|
|
586
650
|
| `@track_subscriber()` | Identify end users |
|
|
587
651
|
| `@track_quality()` | Track response quality scores |
|
|
652
|
+
| `@track_job()` | Inject agentic job fields for cost/ROI correlation, e.g. `@track_job(job_id="loan-app-12345", type="loan_processing")` |
|
|
588
653
|
|
|
589
|
-
|
|
654
|
+
The tracking decorators above support static values, extraction from function arguments (`name_from_arg`), or extraction from object attributes (`name_from_attr`); `@track_job` supports static values and argument extraction (`job_id_from_arg`, `type_from_arg`) but has no attribute variant.
|
|
590
655
|
|
|
591
656
|
#### CrewAI Integration
|
|
592
657
|
|
|
@@ -596,6 +661,35 @@ pip install "revenium-python-sdk[litellm]" crewai
|
|
|
596
661
|
|
|
597
662
|
Pre-built wrapper for tracking CrewAI agent executions. **Note:** CrewAI requires Python 3.12 or earlier.
|
|
598
663
|
|
|
664
|
+
**Job outcome tracking:** pass the `agentic_job_*` kwargs to tie every LLM call in the crew to one agentic job, then report (or later amend) the job's business outcome. Requires a write-scope key (`rev_sk_`) — see [Agentic Outcomes](#agentic-outcomes-outcome-based-metering).
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
from revenium_middleware.litellm.client.integrations.crewai import ReveniumCrewWrapper
|
|
668
|
+
|
|
669
|
+
crew = ReveniumCrewWrapper(
|
|
670
|
+
agents=[support_agent],
|
|
671
|
+
tasks=[triage_task],
|
|
672
|
+
organization_id="AcmeCorp",
|
|
673
|
+
subscription_id="82764738",
|
|
674
|
+
product_id="Platinum",
|
|
675
|
+
agentic_job_id="support-ticket-456",
|
|
676
|
+
agentic_job_name="Support Ticket Triage",
|
|
677
|
+
agentic_job_type="customer_support",
|
|
678
|
+
agentic_job_version="2.0",
|
|
679
|
+
)
|
|
680
|
+
result = crew.kickoff()
|
|
681
|
+
|
|
682
|
+
crew.report_job_outcome(
|
|
683
|
+
execution_status="SUCCESS",
|
|
684
|
+
outcome_type="DEFLECTED",
|
|
685
|
+
outcome_value=25.0,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# Later, if the business result changes:
|
|
689
|
+
# crew.amend_job_outcome(reason="Ticket reopened and escalated to a human agent",
|
|
690
|
+
# outcome_type="ESCALATED", outcome_value=0.0)
|
|
691
|
+
```
|
|
692
|
+
|
|
599
693
|
**LiteLLM environment variables:**
|
|
600
694
|
- `LITELLM_PROXY_URL` - Your LiteLLM proxy URL
|
|
601
695
|
- `LITELLM_API_KEY` - Your LiteLLM proxy API key
|
|
@@ -838,6 +932,7 @@ Enhanced observability fields for tracking AI operations across environments, re
|
|
|
838
932
|
| `parent_transaction_id` | `REVENIUM_PARENT_TRANSACTION_ID` | Parent transaction ID | Link child operations to parents across microservices |
|
|
839
933
|
| `transaction_name` | `REVENIUM_TRANSACTION_NAME` | Human-friendly operation name | Label operations (e.g., `"Generate Response"`, `"Analyze Sentiment"`) |
|
|
840
934
|
| `retry_number` | `REVENIUM_RETRY_NUMBER` | Retry attempt number (0 = first attempt) | Track retry attempts for failed operations |
|
|
935
|
+
| `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 |
|
|
841
936
|
|
|
842
937
|
**Note:** `operation_type` (e.g., `CHAT`, `EMBED`, `TOOL_CALL`) and `operation_subtype` (e.g., `function_call`, `streaming`) are automatically detected by the middleware and cannot be overridden.
|
|
843
938
|
|
|
@@ -865,7 +960,8 @@ response = client.chat.completions.create(
|
|
|
865
960
|
"trace_type": "customer-support",
|
|
866
961
|
"trace_name": "Support Chat Session",
|
|
867
962
|
"transaction_name": "Generate Response",
|
|
868
|
-
"parent_transaction_id": "parent-txn-123"
|
|
963
|
+
"parent_transaction_id": "parent-txn-123",
|
|
964
|
+
"ticket_id": "JIRA-123"
|
|
869
965
|
}
|
|
870
966
|
)
|
|
871
967
|
```
|
|
@@ -1269,7 +1365,7 @@ print(get_buffer_stats())
|
|
|
1269
1365
|
| `REVENIUM_LOG_LEVEL` | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
|
1270
1366
|
| `REVENIUM_CAPTURE_PROMPTS` | `false` | Enable prompt capture |
|
|
1271
1367
|
| `REVENIUM_SELECTIVE_METERING` | `false` | Only meter `@revenium_meter` decorated functions |
|
|
1272
|
-
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups |
|
|
1368
|
+
| `REVENIUM_TEAM_ID` | - | Team ID for cost lookups and outcome reporting (JobContext team resolution) |
|
|
1273
1369
|
| `REVENIUM_ENVIRONMENT` | - | Deployment environment (auto-detects from `ENVIRONMENT`, `DEPLOYMENT_ENV`) |
|
|
1274
1370
|
| `REVENIUM_REGION` | - | Cloud region (auto-detects from `AWS_REGION`, `AZURE_REGION`, `GCP_REGION`) |
|
|
1275
1371
|
| `REVENIUM_CREDENTIAL_ALIAS` | - | Human-readable API key name |
|
|
@@ -1278,10 +1374,18 @@ print(get_buffer_stats())
|
|
|
1278
1374
|
| `REVENIUM_PARENT_TRANSACTION_ID` | - | Parent transaction ID for distributed tracing |
|
|
1279
1375
|
| `REVENIUM_TRANSACTION_NAME` | - | Human-friendly operation name |
|
|
1280
1376
|
| `REVENIUM_RETRY_NUMBER` | - | Retry attempt number |
|
|
1377
|
+
| `REVENIUM_AGENTIC_JOB_ID` | - | Agentic job instance ID attached to all completions in the process (triggers backend job auto-creation) |
|
|
1378
|
+
| `REVENIUM_AGENTIC_JOB_NAME` | - | Human-readable agentic job name |
|
|
1379
|
+
| `REVENIUM_AGENTIC_JOB_TYPE` | - | Agentic job type category |
|
|
1380
|
+
| `REVENIUM_AGENTIC_JOB_VERSION` | - | Agentic job version |
|
|
1381
|
+
| `REVENIUM_OUTCOME_API_KEY` | - | Write-scope key (`rev_sk_`) for the agentic outcomes API (report/amend/history); falls back to `REVENIUM_METERING_API_KEY` |
|
|
1382
|
+
| `REVENIUM_PROFITSTREAM_BASE_URL` | `https://api.revenium.io` | Agentic outcomes API base URL |
|
|
1281
1383
|
| `REVENIUM_BEDROCK_DISABLE` | - | Set to `1` to disable Bedrock auto-detection |
|
|
1282
1384
|
| `REVENIUM_BUFFER_MAX_SIZE` | `1000` | Store-and-forward buffer capacity (oldest events evicted when full) |
|
|
1283
1385
|
| `REVENIUM_BUFFER_FLUSH_INTERVAL` | `30` | Seconds between automatic replay attempts for buffered events |
|
|
1284
1386
|
|
|
1387
|
+
Per-call `usage_metadata` values take precedence over the `REVENIUM_AGENTIC_JOB_*` environment variables, and the LiteLLM proxy path sources job fields from `x-revenium-*` headers only — these process-level env fallbacks do not apply to proxied traffic.
|
|
1388
|
+
|
|
1285
1389
|
### Provider-Specific Environment Variables
|
|
1286
1390
|
|
|
1287
1391
|
| Variable | Provider | Description |
|
|
@@ -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.4.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"
|
|
@@ -166,6 +166,16 @@ from ._metering import meter_tool, report_tool_call, configure # noqa: E402
|
|
|
166
166
|
# Agentic-outcome client (used by examples/agentic_outcomes/ pack)
|
|
167
167
|
from .agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings # noqa: E402
|
|
168
168
|
|
|
169
|
+
# Public job-context surface (BACK-777)
|
|
170
|
+
from .job_context import JobContext # noqa: E402
|
|
171
|
+
from .job_history import JobOutcomeAmendment, get_outcome_history # noqa: E402
|
|
172
|
+
from ._core.exceptions import ( # noqa: E402
|
|
173
|
+
OutcomeAlreadyReportedError,
|
|
174
|
+
OutcomeAmendConflictError,
|
|
175
|
+
OutcomeNotReportedError,
|
|
176
|
+
OutcomeReportingError,
|
|
177
|
+
)
|
|
178
|
+
|
|
169
179
|
__all__ = [
|
|
170
180
|
# Metering exports
|
|
171
181
|
"client",
|
|
@@ -199,4 +209,13 @@ __all__ = [
|
|
|
199
209
|
# Agentic-outcome exports
|
|
200
210
|
"AgenticOutcomeClient",
|
|
201
211
|
"AgenticOutcomeSettings",
|
|
212
|
+
# Job-context exports (BACK-777)
|
|
213
|
+
"JobContext",
|
|
214
|
+
"OutcomeReportingError",
|
|
215
|
+
"OutcomeAlreadyReportedError",
|
|
216
|
+
# Outcome amendment / history exports (BACK-777 Phase 3)
|
|
217
|
+
"get_outcome_history",
|
|
218
|
+
"JobOutcomeAmendment",
|
|
219
|
+
"OutcomeNotReportedError",
|
|
220
|
+
"OutcomeAmendConflictError",
|
|
202
221
|
]
|
|
@@ -56,6 +56,15 @@ class Config:
|
|
|
56
56
|
ENV_REVENIUM_PARENT_TRANSACTION_ID: str = "REVENIUM_PARENT_TRANSACTION_ID"
|
|
57
57
|
ENV_REVENIUM_TRANSACTION_NAME: str = "REVENIUM_TRANSACTION_NAME"
|
|
58
58
|
ENV_REVENIUM_RETRY_NUMBER: str = "REVENIUM_RETRY_NUMBER"
|
|
59
|
+
ENV_REVENIUM_TICKET_ID: str = "REVENIUM_TICKET_ID"
|
|
60
|
+
|
|
61
|
+
# Agentic job field environment variables (BACK-777 fallbacks)
|
|
62
|
+
ENV_REVENIUM_AGENTIC_JOB_ID: str = "REVENIUM_AGENTIC_JOB_ID"
|
|
63
|
+
ENV_REVENIUM_AGENTIC_JOB_NAME: str = "REVENIUM_AGENTIC_JOB_NAME"
|
|
64
|
+
ENV_REVENIUM_AGENTIC_JOB_TYPE: str = "REVENIUM_AGENTIC_JOB_TYPE"
|
|
65
|
+
ENV_REVENIUM_AGENTIC_JOB_VERSION: str = "REVENIUM_AGENTIC_JOB_VERSION"
|
|
66
|
+
ENV_REVENIUM_OUTCOME_API_KEY: str = "REVENIUM_OUTCOME_API_KEY"
|
|
67
|
+
ENV_REVENIUM_PROFITSTREAM_BASE_URL: str = "REVENIUM_PROFITSTREAM_BASE_URL"
|
|
59
68
|
|
|
60
69
|
# Prompt capture settings
|
|
61
70
|
ENV_REVENIUM_CAPTURE_PROMPTS: str = "REVENIUM_CAPTURE_PROMPTS"
|
{revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/context.py
RENAMED
|
@@ -30,6 +30,12 @@ _idempotency_key_context: contextvars.ContextVar[Optional[str]] = contextvars.Co
|
|
|
30
30
|
'revenium_idempotency_key', default=None
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
+
# Context variable holding agentic job fields (wire-name keys) for the current scope.
|
|
34
|
+
# This is the seam the public JobContext (BACK-777 Phase 2) builds on.
|
|
35
|
+
_agentic_job_context: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar(
|
|
36
|
+
'revenium_agentic_job', default=None
|
|
37
|
+
)
|
|
38
|
+
|
|
33
39
|
|
|
34
40
|
def is_inside_decorated_function() -> bool:
|
|
35
41
|
"""
|
|
@@ -172,3 +178,41 @@ def idempotency_key(key: str) -> Iterator[None]:
|
|
|
172
178
|
finally:
|
|
173
179
|
_idempotency_key_context.reset(token)
|
|
174
180
|
|
|
181
|
+
|
|
182
|
+
def get_agentic_job_fields() -> Optional[Dict[str, Any]]:
|
|
183
|
+
"""Return the agentic job fields set on the current context, or None.
|
|
184
|
+
|
|
185
|
+
Keys are wire names (``agenticJobId``, ``agenticJobName``, ``agenticJobType``,
|
|
186
|
+
``agenticJobVersion``) — the same keys ``extract_agentic_job_fields`` emits,
|
|
187
|
+
so the fallback merge in ``_core/fields.py`` is key-aligned.
|
|
188
|
+
"""
|
|
189
|
+
return _agentic_job_context.get()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def set_agentic_job_fields(
|
|
193
|
+
job_id: Optional[str] = None,
|
|
194
|
+
name: Optional[str] = None,
|
|
195
|
+
type: Optional[str] = None,
|
|
196
|
+
version: Optional[str] = None,
|
|
197
|
+
) -> contextvars.Token:
|
|
198
|
+
"""Set agentic job fields on the current context. Returns a Token for reset().
|
|
199
|
+
|
|
200
|
+
Maps snake-case arguments to wire-name keys, omitting Nones. Callers own the
|
|
201
|
+
token and must reset it (``_agentic_job_context.reset(token)``) when the scope
|
|
202
|
+
ends — the JobContext context manager in Phase 2 does this on exit.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: if no field is provided.
|
|
206
|
+
"""
|
|
207
|
+
fields: Dict[str, Any] = {}
|
|
208
|
+
if job_id is not None:
|
|
209
|
+
fields["agenticJobId"] = job_id
|
|
210
|
+
if name is not None:
|
|
211
|
+
fields["agenticJobName"] = name
|
|
212
|
+
if type is not None:
|
|
213
|
+
fields["agenticJobType"] = type
|
|
214
|
+
if version is not None:
|
|
215
|
+
fields["agenticJobVersion"] = version
|
|
216
|
+
if not fields:
|
|
217
|
+
raise ValueError("set_agentic_job_fields requires at least one field")
|
|
218
|
+
return _agentic_job_context.set(fields)
|
{revenium_python_sdk-0.3.0 → revenium_python_sdk-0.4.0}/revenium_middleware/_core/exceptions.py
RENAMED
|
@@ -41,3 +41,45 @@ class BudgetExceededError(Exception):
|
|
|
41
41
|
# `except ReveniumCostLimitExceeded:` continues to catch the new exception
|
|
42
42
|
# unchanged. Plan to remove in a future major release.
|
|
43
43
|
ReveniumCostLimitExceeded = BudgetExceededError
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OutcomeReportingError(Exception):
|
|
47
|
+
"""Raised when an agentic job outcome cannot be reported.
|
|
48
|
+
|
|
49
|
+
Base of the outcome exception family (BACK-777). Configuration failures
|
|
50
|
+
(unresolvable team_id, missing API key) raise this class directly;
|
|
51
|
+
backend-state conditions raise the subclasses.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OutcomeAlreadyReportedError(OutcomeReportingError):
|
|
56
|
+
"""The job already has an outcome (backend 409 with amendment guidance).
|
|
57
|
+
|
|
58
|
+
Callers can inspect ``reported_at`` / ``amendment_count`` and decide to
|
|
59
|
+
amend (``amend_outcome``, BACK-777 Phase 3).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
message: str,
|
|
65
|
+
reported_at: Optional[str] = None,
|
|
66
|
+
amendment_count: Optional[int] = None,
|
|
67
|
+
):
|
|
68
|
+
super().__init__(message)
|
|
69
|
+
self.reported_at = reported_at
|
|
70
|
+
self.amendment_count = amendment_count
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class OutcomeNotReportedError(OutcomeReportingError):
|
|
74
|
+
"""Amendment attempted on a job with no outcome yet (backend 422).
|
|
75
|
+
|
|
76
|
+
Report the initial outcome with ``report_outcome`` first.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class OutcomeAmendConflictError(OutcomeReportingError):
|
|
81
|
+
"""Concurrent amendment changed the outcome row (backend 409, optimistic lock).
|
|
82
|
+
|
|
83
|
+
Retryable by the caller: refetch the current state (``get_outcome_history``)
|
|
84
|
+
and re-issue the amendment. The SDK does not auto-retry.
|
|
85
|
+
"""
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
import threading
|
|
3
4
|
import warnings
|
|
4
5
|
from typing import Any, Dict, Mapping, Optional, Set, Tuple
|
|
5
6
|
|
|
7
|
+
from .config import Config
|
|
8
|
+
from .context import get_agentic_job_fields
|
|
9
|
+
|
|
6
10
|
logger = logging.getLogger(__name__)
|
|
7
11
|
|
|
8
12
|
# Dedup set for deprecated-alias logger.warning so high-volume callers don't get a flood
|
|
@@ -19,6 +23,13 @@ AGENTIC_JOB_FIELD_MAP = {
|
|
|
19
23
|
"agenticJobVersion": ("agentic_job_version", "agenticJobVersion"),
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
_AGENTIC_JOB_ENV_MAP = {
|
|
27
|
+
"agenticJobId": Config.ENV_REVENIUM_AGENTIC_JOB_ID,
|
|
28
|
+
"agenticJobName": Config.ENV_REVENIUM_AGENTIC_JOB_NAME,
|
|
29
|
+
"agenticJobType": Config.ENV_REVENIUM_AGENTIC_JOB_TYPE,
|
|
30
|
+
"agenticJobVersion": Config.ENV_REVENIUM_AGENTIC_JOB_VERSION,
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
|
|
23
34
|
def extract_field_with_fallback(
|
|
24
35
|
source: Mapping[str, Any],
|
|
@@ -96,13 +107,25 @@ def extract_common_metadata(source: Mapping[str, Any]) -> Dict[str, Any]:
|
|
|
96
107
|
|
|
97
108
|
|
|
98
109
|
def extract_agentic_job_fields(source: Mapping[str, Any]) -> Dict[str, Any]:
|
|
110
|
+
"""Resolve agentic job fields with per-field precedence:
|
|
111
|
+
|
|
112
|
+
explicit source metadata (snake then camel alias) > job contextvar >
|
|
113
|
+
``REVENIUM_AGENTIC_JOB_*`` env var. Each field resolves independently.
|
|
114
|
+
"""
|
|
115
|
+
context_fields = get_agentic_job_fields() or {}
|
|
99
116
|
result = {}
|
|
100
117
|
for wire_name, aliases in AGENTIC_JOB_FIELD_MAP.items():
|
|
118
|
+
value = None
|
|
101
119
|
for alias in aliases:
|
|
102
120
|
value = source.get(alias)
|
|
103
121
|
if value is not None:
|
|
104
|
-
result[wire_name] = value
|
|
105
122
|
break
|
|
123
|
+
if value is None:
|
|
124
|
+
value = context_fields.get(wire_name)
|
|
125
|
+
if value is None:
|
|
126
|
+
value = os.getenv(_AGENTIC_JOB_ENV_MAP[wire_name]) or None
|
|
127
|
+
if value is not None:
|
|
128
|
+
result[wire_name] = value
|
|
106
129
|
return result
|
|
107
130
|
|
|
108
131
|
|