revenium-python-sdk 0.1.3__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {revenium_python_sdk-0.1.3/revenium_python_sdk.egg-info → revenium_python_sdk-0.1.5}/PKG-INFO +159 -22
  2. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/README.md +158 -21
  3. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/pyproject.toml +1 -1
  4. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/__init__.py +6 -0
  5. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/__init__.py +7 -0
  6. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/config.py +9 -0
  7. revenium_python_sdk-0.1.5/revenium_middleware/_core/enforcement.py +344 -0
  8. revenium_python_sdk-0.1.5/revenium_middleware/_core/exceptions.py +43 -0
  9. revenium_python_sdk-0.1.5/revenium_middleware/agentic_outcomes.py +348 -0
  10. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/bedrock_adapter.py +5 -0
  11. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/__init__.py +3 -1
  12. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/exceptions.py +8 -0
  13. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/middleware.py +20 -0
  14. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5/revenium_python_sdk.egg-info}/PKG-INFO +159 -22
  15. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/SOURCES.txt +3 -0
  16. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/LICENSE +0 -0
  17. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/context.py +0 -0
  18. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/decorators.py +0 -0
  19. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/fields.py +0 -0
  20. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/metering.py +0 -0
  21. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/patch_registry.py +0 -0
  22. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/prompt_extraction.py +0 -0
  23. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/subscriber.py +0 -0
  24. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/_core/trace_fields.py +0 -0
  25. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/__init__.py +0 -0
  26. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/config.py +0 -0
  27. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/middleware.py +0 -0
  28. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
  29. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/provider.py +0 -0
  30. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/summary_printer.py +0 -0
  31. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/anthropic/trace_fields.py +0 -0
  32. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/__init__.py +0 -0
  33. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/_metering.py +0 -0
  34. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/config.py +0 -0
  35. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/middleware.py +0 -0
  36. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/fal/trace_fields.py +0 -0
  37. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/__init__.py +0 -0
  38. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/__init__.py +0 -0
  39. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/exceptions.py +0 -0
  40. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/protocols.py +0 -0
  41. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/summary_printer.py +0 -0
  42. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/trace_fields.py +0 -0
  43. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/types.py +0 -0
  44. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/common/utils.py +0 -0
  45. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/config.py +0 -0
  46. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/__init__.py +0 -0
  47. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/middleware.py +0 -0
  48. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/google_ai/provider.py +0 -0
  49. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/prompt_extractor.py +0 -0
  50. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
  51. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
  52. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/google/vertex_ai/provider.py +0 -0
  53. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/__init__.py +0 -0
  54. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/__init__.py +0 -0
  55. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/config.py +0 -0
  56. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/context.py +0 -0
  57. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/decorators.py +0 -0
  58. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/hooks.py +0 -0
  59. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
  60. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
  61. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/middleware.py +0 -0
  62. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/summary_printer.py +0 -0
  63. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/trace_fields.py +0 -0
  64. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/client/validation.py +0 -0
  65. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/__init__.py +0 -0
  66. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/litellm/proxy/middleware.py +0 -0
  67. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/__init__.py +0 -0
  68. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/middleware.py +0 -0
  69. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/ollama/trace_fields.py +0 -0
  70. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_config.py +0 -0
  71. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/azure_model_resolver.py +0 -0
  72. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/config.py +0 -0
  73. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/__init__.py +0 -0
  74. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/_utils.py +0 -0
  75. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
  76. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/prompt_extractor.py +0 -0
  77. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/provider.py +0 -0
  78. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/summary_printer.py +0 -0
  79. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/openai/trace_fields.py +0 -0
  80. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/__init__.py +0 -0
  81. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/middleware.py +0 -0
  82. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
  83. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/provider.py +0 -0
  84. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_middleware/perplexity/trace_fields.py +0 -0
  85. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
  86. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/requires.txt +0 -0
  87. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/revenium_python_sdk.egg-info/top_level.txt +0 -0
  88. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/setup.cfg +0 -0
  89. {revenium_python_sdk-0.1.3 → revenium_python_sdk-0.1.5}/tests/test_metering.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revenium-python-sdk
3
- Version: 0.1.3
3
+ Version: 0.1.5
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
@@ -194,7 +194,7 @@ import revenium_middleware_openai # Auto-initializes on import
194
194
 
195
195
  client = openai.OpenAI()
196
196
  response = client.chat.completions.create(
197
- model="gpt-4o-mini",
197
+ model="gpt-5.5",
198
198
  messages=[{"role": "user", "content": "Hello!"}]
199
199
  )
200
200
  print(response.choices[0].message.content)
@@ -203,6 +203,30 @@ print(response.choices[0].message.content)
203
203
 
204
204
  ---
205
205
 
206
+ ## Agentic Outcomes (Outcome-Based Metering)
207
+
208
+ Emit per-agent terminal outcomes (`CONVERTED`, `DEFLECTED`, `ESCALATED`) alongside completion and tool-event records, so dashboards show business value next to AI cost.
209
+
210
+ ```python
211
+ from revenium_middleware.agentic_outcomes import AgenticOutcomeClient, AgenticOutcomeSettings
212
+
213
+ settings = AgenticOutcomeSettings(api_key="rev_sk_...")
214
+ client = AgenticOutcomeClient(settings)
215
+
216
+ client.emit_completion(...) # one per LLM call
217
+ client.emit_tool_event(...) # one per tool / step
218
+ client.report_outcome(job_id, {...}) # close the job with a terminal outcome
219
+ client.close()
220
+ ```
221
+
222
+ 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.
223
+
224
+ See [`examples/agentic_outcomes/`](examples/agentic_outcomes/) for runnable demos (sales / coding / support) with configurable failure rates and outcome distributions.
225
+
226
+ **API reference:** [docs.revenium.io](https://docs.revenium.io) · per-endpoint reference at [revenium.readme.io/reference/meter_ai_completion](https://revenium.readme.io/reference/meter_ai_completion).
227
+
228
+ ---
229
+
206
230
  ## Provider Usage Guides
207
231
 
208
232
  ### OpenAI
@@ -220,7 +244,7 @@ client = openai.OpenAI()
220
244
 
221
245
  # Basic chat completion
222
246
  response = client.chat.completions.create(
223
- model="gpt-4o-mini",
247
+ model="gpt-5.5",
224
248
  messages=[{"role": "user", "content": "Hello!"}],
225
249
  usage_metadata={
226
250
  "organizationName": "AcmeCorp",
@@ -232,7 +256,7 @@ response = client.chat.completions.create(
232
256
 
233
257
  # Streaming
234
258
  stream = client.chat.completions.create(
235
- model="gpt-4o-mini",
259
+ model="gpt-5.5",
236
260
  messages=[{"role": "user", "content": "Tell me a story"}],
237
261
  stream=True
238
262
  )
@@ -292,7 +316,7 @@ client = anthropic.Anthropic()
292
316
 
293
317
  # Basic message
294
318
  message = client.messages.create(
295
- model="claude-3-haiku-20240307",
319
+ model="claude-opus-4-7",
296
320
  max_tokens=100,
297
321
  messages=[{"role": "user", "content": "Hello!"}],
298
322
  usage_metadata={
@@ -304,7 +328,7 @@ message = client.messages.create(
304
328
 
305
329
  # Streaming
306
330
  with client.messages.stream(
307
- model="claude-3-haiku-20240307",
331
+ model="claude-opus-4-7",
308
332
  max_tokens=200,
309
333
  messages=[{"role": "user", "content": "Tell me a story"}],
310
334
  usage_metadata={"task_type": "creative"}
@@ -330,7 +354,7 @@ client = anthropic.AnthropicBedrock(
330
354
  )
331
355
 
332
356
  message = client.messages.create(
333
- model="anthropic.claude-3-haiku-20240307-v1:0",
357
+ model="claude-opus-4-7",
334
358
  max_tokens=100,
335
359
  messages=[{"role": "user", "content": "Hello from Bedrock!"}]
336
360
  )
@@ -354,6 +378,11 @@ message = client.messages.create(
354
378
 
355
379
  | Anthropic Model | Bedrock Model ID |
356
380
  |----------------|------------------|
381
+ | `claude-opus-4-7` | `anthropic.claude-opus-4-7` |
382
+ | `us.claude-opus-4-7` | `us.anthropic.claude-opus-4-7` |
383
+ | `eu.claude-opus-4-7` | `eu.anthropic.claude-opus-4-7` |
384
+ | `au.claude-opus-4-7` | `au.anthropic.claude-opus-4-7` |
385
+ | `global.claude-opus-4-7` | `global.anthropic.claude-opus-4-7` |
357
386
  | `claude-3-opus-20240229` | `anthropic.claude-3-opus-20240229-v1:0` |
358
387
  | `claude-3-sonnet-20240229` | `anthropic.claude-3-sonnet-20240229-v1:0` |
359
388
  | `claude-3-haiku-20240307` | `us.anthropic.claude-3-5-haiku-20241022-v1:0` |
@@ -518,7 +547,7 @@ litellm.api_base = os.getenv("LITELLM_PROXY_URL")
518
547
  litellm.api_key = os.getenv("LITELLM_API_KEY")
519
548
 
520
549
  response = litellm.completion(
521
- model="gpt-4o-mini",
550
+ model="gpt-5.5",
522
551
  messages=[{"role": "user", "content": "Hello!"}],
523
552
  usage_metadata={
524
553
  "organizationName": "AcmeCorp",
@@ -676,7 +705,7 @@ handler = ReveniumCallbackHandler(
676
705
  agent_name="support_agent"
677
706
  )
678
707
 
679
- llm = ChatOpenAI(model="gpt-4", callbacks=[handler])
708
+ llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
680
709
  response = llm.invoke("Hello!")
681
710
  ```
682
711
 
@@ -715,7 +744,7 @@ result = agent.invoke(
715
744
  from revenium_middleware_langchain import AsyncReveniumCallbackHandler
716
745
 
717
746
  handler = AsyncReveniumCallbackHandler(trace_id="async-session")
718
- llm = ChatOpenAI(model="gpt-4", callbacks=[handler])
747
+ llm = ChatOpenAI(model="gpt-5.5", callbacks=[handler])
719
748
  response = await llm.ainvoke("Hello!")
720
749
  ```
721
750
 
@@ -761,7 +790,7 @@ Add business context to any API call by passing a `usage_metadata` dictionary. A
761
790
 
762
791
  ```python
763
792
  response = client.chat.completions.create(
764
- model="gpt-4o-mini",
793
+ model="gpt-5.5",
765
794
  messages=[{"role": "user", "content": "Hello!"}],
766
795
  usage_metadata={
767
796
  "trace_id": "conv-28a7e9d4",
@@ -824,7 +853,7 @@ REVENIUM_TRACE_TYPE=customer-support
824
853
 
825
854
  ```python
826
855
  response = client.chat.completions.create(
827
- model="gpt-4o-mini",
856
+ model="gpt-5.5",
828
857
  messages=[{"role": "user", "content": "Hello!"}],
829
858
  usage_metadata={
830
859
  "environment": "production",
@@ -848,7 +877,7 @@ workflow_id = str(uuid.uuid4())
848
877
 
849
878
  # Step 1: Parent operation
850
879
  parent_response = client.chat.completions.create(
851
- model="gpt-4o-mini",
880
+ model="gpt-5.5",
852
881
  messages=[{"role": "user", "content": "Analyze this document"}],
853
882
  usage_metadata={
854
883
  "trace_id": "analysis-session-456",
@@ -859,7 +888,7 @@ parent_response = client.chat.completions.create(
859
888
 
860
889
  # Step 2: Child operation linked to parent
861
890
  child_response = client.chat.completions.create(
862
- model="gpt-4o-mini",
891
+ model="gpt-5.5",
863
892
  messages=[{"role": "user", "content": "Summarize findings"}],
864
893
  usage_metadata={
865
894
  "trace_id": "analysis-session-456",
@@ -890,7 +919,7 @@ from revenium_middleware import revenium_metadata
890
919
  def handle_customer_query(question: str) -> str:
891
920
  # All API calls automatically include the decorator metadata
892
921
  response = client.chat.completions.create(
893
- model="gpt-4o-mini",
922
+ model="gpt-5.5",
894
923
  messages=[{"role": "user", "content": question}]
895
924
  )
896
925
  return response.choices[0].message.content
@@ -929,13 +958,13 @@ def outer_function():
929
958
  def mixed_metadata():
930
959
  # Uses decorator metadata
931
960
  response1 = client.chat.completions.create(
932
- model="gpt-4o-mini",
961
+ model="gpt-5.5",
933
962
  messages=[{"role": "user", "content": "Hello"}]
934
963
  )
935
964
 
936
965
  # API-level metadata overrides decorator's task_type
937
966
  response2 = client.chat.completions.create(
938
- model="gpt-4o-mini",
967
+ model="gpt-5.5",
939
968
  messages=[{"role": "user", "content": "Hello"}],
940
969
  usage_metadata={
941
970
  "task_type": "special-override", # Overrides decorator
@@ -964,7 +993,7 @@ from revenium_middleware import revenium_meter, revenium_metadata
964
993
  def premium_feature(prompt: str) -> str:
965
994
  # This WILL be metered (decorated with @revenium_meter)
966
995
  response = client.chat.completions.create(
967
- model="gpt-4o",
996
+ model="gpt-5.5",
968
997
  messages=[{"role": "user", "content": prompt}]
969
998
  )
970
999
  return response.choices[0].message.content
@@ -972,7 +1001,7 @@ def premium_feature(prompt: str) -> str:
972
1001
  def free_feature(prompt: str) -> str:
973
1002
  # This will NOT be metered (no @revenium_meter decorator)
974
1003
  response = client.chat.completions.create(
975
- model="gpt-4o-mini",
1004
+ model="gpt-5.5",
976
1005
  messages=[{"role": "user", "content": prompt}]
977
1006
  )
978
1007
  return response.choices[0].message.content
@@ -1058,7 +1087,7 @@ from openai import OpenAI
1058
1087
 
1059
1088
  client = OpenAI()
1060
1089
  response = client.chat.completions.create(
1061
- model="gpt-4o-mini",
1090
+ model="gpt-5.5",
1062
1091
  messages=[
1063
1092
  {"role": "system", "content": "You are a helpful assistant."},
1064
1093
  {"role": "user", "content": "What is the capital of France?"}
@@ -1105,7 +1134,7 @@ export REVENIUM_TEAM_ID=your-team-id-here
1105
1134
  ============================================================
1106
1135
  REVENIUM USAGE SUMMARY
1107
1136
  ============================================================
1108
- Model: gpt-4o-mini
1137
+ Model: gpt-5.5
1109
1138
  Provider: OPENAI
1110
1139
  Duration: 1.23s
1111
1140
 
@@ -1123,7 +1152,7 @@ Trace ID: abc-123
1123
1152
  ### JSON Format
1124
1153
 
1125
1154
  ```json
1126
- {"model":"gpt-4o-mini","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
1155
+ {"model":"gpt-5.5","provider":"OPENAI","durationSeconds":1.23,"inputTokenCount":150,"outputTokenCount":250,"totalTokenCount":400,"cost":0.000045,"costStatus":"available","traceId":"abc-123"}
1127
1156
  ```
1128
1157
 
1129
1158
  ### Cost Status
@@ -1136,6 +1165,108 @@ Trace ID: abc-123
1136
1165
 
1137
1166
  ---
1138
1167
 
1168
+ ## Cost Controls / Enforcement
1169
+
1170
+ Block outbound provider requests client-side when a Revenium cost control trips. When the circuit breaker is enabled, the middleware polls compiled enforcement rules from the Revenium API in a background daemon thread and raises `BudgetExceededError` **before** the upstream call, preventing spend beyond the configured limit.
1171
+
1172
+ > **Terminology note:** The customer-facing entity is called a **cost control**, served by the backend at `/v2/api/ai/cost-controls`. This SDK polls a separate compiled-rules feed at `/v2/api/ai/enforcement-rules/{teamId}` and is unaffected by changes to the CRUD path — no SDK upgrade is required.
1173
+
1174
+ Currently wired for the OpenAI provider (other providers land via per-provider follow-on tickets).
1175
+
1176
+ ### Enable
1177
+
1178
+ ```bash
1179
+ pip install 'revenium-python-sdk[openai]'
1180
+ ```
1181
+
1182
+ ```env
1183
+ REVENIUM_CIRCUIT_BREAKER_ENABLED=true
1184
+ REVENIUM_METERING_API_KEY=hak_your_key_here
1185
+ REVENIUM_TEAM_ID=your_hashed_team_id
1186
+ REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream # optional
1187
+ ```
1188
+
1189
+ ### Environment Variables
1190
+
1191
+ | Variable | Default | Description |
1192
+ |----------|---------|-------------|
1193
+ | `REVENIUM_CIRCUIT_BREAKER_ENABLED` | `false` | Master switch. `true` / `1` / `yes` / `on` to enable. |
1194
+ | `REVENIUM_BYPASS` | `false` | When `true`, every `check_enforcement` call short-circuits to a no-op. Useful for incident response. |
1195
+ | `REVENIUM_TEAM_ID` | — | Hashed team ID. Path component on rule fetches; required when the breaker is enabled. |
1196
+ | `REVENIUM_ENFORCEMENT_BASE_URL` | origin of `REVENIUM_METERING_BASE_URL` | Base URL for the enforcement API. Set when the enforcement API lives behind a context-path. |
1197
+ | `REVENIUM_CB_POLL_INTERVAL_SECONDS` | `60` | Background poll interval for rule refreshes. |
1198
+ | `REVENIUM_CB_FAIL_MODE` | `open` | `open` (default) lets calls through when no cache exists; `closed` raises `BudgetExceededError` until rules are loaded. |
1199
+ | `REVENIUM_CACHE_DIR` | — | When set, the rule cache is mirrored to `<dir>/revenium_enforcement_rules.json` so a restarted process doesn't fail-closed on the very first call. |
1200
+
1201
+ ### Public API
1202
+
1203
+ Enforcement auto-initializes when the OpenAI middleware loads:
1204
+
1205
+ ```python
1206
+ import revenium_middleware.openai # auto-instruments openai
1207
+ import openai
1208
+
1209
+ client = openai.OpenAI()
1210
+ ```
1211
+
1212
+ The pre-call check fires before every chat / embeddings / responses call. When the circuit breaker is disabled, it is a no-op. When enabled:
1213
+
1214
+ 1. A daemon thread (`revenium-enforcement-poll`) starts on first use.
1215
+ 2. It polls `GET {REVENIUM_ENFORCEMENT_BASE_URL}/v2/api/ai/enforcement-rules/{REVENIUM_TEAM_ID}` every `REVENIUM_CB_POLL_INTERVAL_SECONDS` with the `x-api-key` header.
1216
+ 3. Rules are cached in-process (120 s TTL, refresh-on-stale with thundering-herd guard).
1217
+ 4. `204 No Content` is treated as "no rules configured" — the cache is cleared.
1218
+
1219
+ ### Exception Contract
1220
+
1221
+ ```python
1222
+ from revenium_middleware.openai import BudgetExceededError
1223
+ ```
1224
+
1225
+ When a tripped rule matches the current request, the middleware raises before the OpenAI call is made. All structured fields are populated when the server provides them:
1226
+
1227
+ | Attribute | Type | Description |
1228
+ |-----------|------|-------------|
1229
+ | `message` | `str` | Human-readable reason, e.g. `"Request blocked by Revenium enforcement rule: monthly-gpt4-cap"` |
1230
+ | `rule_name` | `str \| None` | Server-side rule name |
1231
+ | `current_value` | `float \| None` | Current metric value at the time of the block |
1232
+ | `threshold` | `float \| None` | Configured limit |
1233
+ | `resets_at` | `str \| None` | ISO-8601 timestamp the rule next resets |
1234
+ | `rule_id` | `str \| int \| None` | Server-side rule identifier |
1235
+
1236
+ `BudgetExceededError` does **not** inherit from `ReveniumMiddlewareError`, so the OpenAI middleware's `handle_exception_safely` decorator never swallows it — it always reaches your `except` block.
1237
+
1238
+ ```python
1239
+ from revenium_middleware.openai import BudgetExceededError
1240
+ import openai
1241
+
1242
+ client = openai.OpenAI()
1243
+
1244
+ try:
1245
+ response = client.chat.completions.create(
1246
+ model="gpt-5.5",
1247
+ messages=[{"role": "user", "content": "Summarize the meeting notes"}],
1248
+ )
1249
+ except BudgetExceededError as exc:
1250
+ print(f"Cost limit reached: {exc.message}")
1251
+ print(f"Rule {exc.rule_name}: {exc.current_value} / {exc.threshold}; resets {exc.resets_at}")
1252
+ ```
1253
+
1254
+ ### Fail-Open vs Fail-Closed
1255
+
1256
+ By default (`REVENIUM_CB_FAIL_MODE=open`) enforcement failures never propagate to user code. If the rule fetch errors (network, 5xx, auth), the previous in-memory cache is preserved and a debug log line is emitted. If there is no cache yet, enforcement behaves as if no rules are configured and the request continues.
1257
+
1258
+ Set `REVENIUM_CB_FAIL_MODE=closed` to refuse calls until at least one rule fetch (or `REVENIUM_CACHE_DIR` snapshot) succeeds. Pair with `REVENIUM_CACHE_DIR` so a process restart loads the last-known rules rather than blocking every call until the first poll completes.
1259
+
1260
+ ### Shadow Mode
1261
+
1262
+ Rules with `shadowMode: true` are observe-and-log: they are skipped by `check_enforcement`. Use shadow mode on the server side to audit a rule before flipping it to enforce.
1263
+
1264
+ ### End-to-End Example
1265
+
1266
+ See [`examples/openai/openai_blocking_demo.py`](examples/openai/openai_blocking_demo.py) for a runnable end-to-end demo using a seeded budget rule.
1267
+
1268
+ ---
1269
+
1139
1270
  ## Configuration Reference
1140
1271
 
1141
1272
  ### Required Environment Variables
@@ -1238,6 +1369,12 @@ Available log levels:
1238
1369
 
1239
1370
  For detailed documentation, visit [docs.revenium.io](https://docs.revenium.io)
1240
1371
 
1372
+ ### Server-Side Cost Controls
1373
+
1374
+ Cost controls (spend limits, throttling, alerts) are managed server-side in Revenium, not in this SDK. The SDK reports usage; Revenium evaluates it against your configured cost controls.
1375
+
1376
+ The cost-controls API endpoint is `/v2/api/ai/cost-controls`. This Python SDK does not call the endpoint directly — no SDK changes are required to use cost controls. If you manage cost controls via the Revenium API, HTTP client, or `curl`, see [docs.revenium.io](https://docs.revenium.io) for the current API reference.
1377
+
1241
1378
  ## Contributing
1242
1379
 
1243
1380
  See [CONTRIBUTING.md](./CONTRIBUTING.md)