botanu 0.1.dev68__tar.gz → 0.1.dev77__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 (35) hide show
  1. {botanu-0.1.dev68 → botanu-0.1.dev77}/PKG-INFO +68 -46
  2. botanu-0.1.dev77/README.md +126 -0
  3. {botanu-0.1.dev68 → botanu-0.1.dev77}/pyproject.toml +8 -0
  4. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/__init__.py +21 -14
  5. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/models/run_context.py +18 -14
  6. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/enricher.py +15 -22
  7. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/register.py +1 -1
  8. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sampling/content_sampler.py +1 -1
  9. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/__init__.py +3 -5
  10. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/bootstrap.py +17 -7
  11. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/config.py +75 -28
  12. botanu-0.1.dev77/src/botanu/sdk/decorators.py +459 -0
  13. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/middleware.py +0 -1
  14. botanu-0.1.dev77/src/botanu/sdk/pii.py +179 -0
  15. botanu-0.1.dev77/src/botanu/sdk/pii_presidio.py +98 -0
  16. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/span_helpers.py +9 -56
  17. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/tracking/data.py +7 -4
  18. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/tracking/llm.py +15 -10
  19. botanu-0.1.dev68/README.md +0 -107
  20. botanu-0.1.dev68/src/botanu/sdk/decorators.py +0 -491
  21. {botanu-0.1.dev68 → botanu-0.1.dev77}/.gitignore +0 -0
  22. {botanu-0.1.dev68 → botanu-0.1.dev77}/LICENSE +0 -0
  23. {botanu-0.1.dev68 → botanu-0.1.dev77}/NOTICE +0 -0
  24. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/_version.py +0 -0
  25. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/integrations/__init__.py +0 -0
  26. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/integrations/tenacity.py +0 -0
  27. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/models/__init__.py +0 -0
  28. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/__init__.py +0 -0
  29. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/resource_enricher.py +0 -0
  30. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/sampled.py +0 -0
  31. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/py.typed +0 -0
  32. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/resources/__init__.py +0 -0
  33. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sampling/__init__.py +0 -0
  34. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/context.py +0 -0
  35. {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/tracking/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: botanu
3
- Version: 0.1.dev68
3
+ Version: 0.1.dev77
4
4
  Summary: OpenTelemetry-native run-level cost attribution for AI workflows
5
5
  Project-URL: Homepage, https://github.com/botanu-ai/botanu-sdk-python
6
6
  Project-URL: Documentation, https://docs.botanu.ai
@@ -98,6 +98,9 @@ Requires-Dist: ruff>=0.4.0; extra == 'dev'
98
98
  Requires-Dist: starlette<0.30.0,>=0.27.0; extra == 'dev'
99
99
  Provides-Extra: gcp
100
100
  Requires-Dist: opentelemetry-resource-detector-gcp>=0.1b0; extra == 'gcp'
101
+ Provides-Extra: pii-nlp
102
+ Requires-Dist: presidio-analyzer>=2.2; extra == 'pii-nlp'
103
+ Requires-Dist: presidio-anonymizer>=2.2; extra == 'pii-nlp'
101
104
  Description-Content-Type: text/markdown
102
105
 
103
106
  # botanu SDK for Python
@@ -110,7 +113,13 @@ This SDK is built on [OpenTelemetry](https://opentelemetry.io/) for event-level
110
113
 
111
114
  ## Getting Started
112
115
 
116
+ An **event** is one business transaction — resolving a support ticket, processing an order, generating a report. Each event may involve multiple **runs** (LLM calls, retries, sub-workflows) across multiple services. By correlating every run to a stable `event_id`, Botanu gives you per-event cost attribution and outcome tracking without sampling artefacts.
113
117
 
118
+ ## Install
119
+
120
+ ```bash
121
+ pip install botanu
122
+ ```
114
123
 
115
124
  An **event** is one business transaction — resolving a support ticket, processing
116
125
  an order, generating a report. Each event may involve multiple **runs** (LLM calls,
@@ -119,91 +128,104 @@ stable `event_id`, botanu gives you per-event cost attribution and outcome
119
128
  tracking without sampling artifacts.
120
129
 
121
130
  ```bash
122
- pip install botanu
131
+ export BOTANU_API_KEY=<your-api-key>
123
132
  ```
124
133
 
125
- One install. Includes OTel SDK, OTLP exporter, and auto-instrumentation for
126
- 50+ libraries.
134
+ Wrap your agent:
127
135
 
128
136
  ```python
129
- from botanu import enable, botanu_workflow, emit_outcome
137
+ import botanu
138
+
139
+ with botanu.event(event_id=ticket.id, customer_id=user.id, workflow="Support"):
140
+ agent.run(ticket)
141
+ ```
142
+
143
+ That single wrap captures every LLM call, HTTP call, and DB call inside and stamps them with `event_id`, `customer_id`, and `workflow`.
130
144
 
131
- enable() # reads config from environment variables
145
+ ### Decorator form
132
146
 
133
- @botanu_workflow("my-workflow", event_id="evt-001", customer_id="cust-42")
134
- async def do_work():
135
- result = await do_something()
136
- emit_outcome("success")
137
- return result
147
+ ```python
148
+ import botanu
149
+
150
+ @botanu.event(
151
+ workflow="Support",
152
+ event_id=lambda ticket: ticket.id,
153
+ customer_id=lambda ticket: ticket.user_id,
154
+ )
155
+ def handle_ticket(ticket):
156
+ return agent.run(ticket)
138
157
  ```
139
158
 
140
- Entry points use `@botanu_workflow`. Every other service only needs `enable()`.
141
- All configuration is via environment variables — zero hardcoded values in code.
159
+ Works for both sync and `async def` functions.
160
+
161
+ ### Multi-phase workflows
142
162
 
143
- See the [Quick Start](./docs/getting-started/quickstart.md) guide for a full walkthrough.
163
+ ```python
164
+ with botanu.event(event_id=ticket.id, customer_id=user.id, workflow="Support"):
165
+ with botanu.step("retrieval"):
166
+ docs = vector_db.query(ticket.query)
167
+ with botanu.step("generation"):
168
+ response = llm.complete(docs)
169
+ ```
170
+
171
+ See the [Quickstart](./docs/getting-started/quickstart.md) for the full five-minute walkthrough.
144
172
 
145
173
  ## Documentation
146
174
 
147
- | Topic | Description |
148
- |-------|-------------|
149
- | [Installation](./docs/getting-started/installation.md) | Install and configure the SDK |
150
- | [Quick Start](./docs/getting-started/quickstart.md) | Get up and running in 5 minutes |
151
- | [Configuration](./docs/getting-started/configuration.md) | Environment variables and options |
152
- | [Core Concepts](./docs/concepts/) | Events, runs, context propagation, architecture |
153
- | [LLM Tracking](./docs/tracking/llm-tracking.md) | Track model calls and token usage |
154
- | [Data Tracking](./docs/tracking/data-tracking.md) | Database, storage, and messaging |
155
- | [Outcomes](./docs/tracking/outcomes.md) | Record business outcomes for ROI |
156
- | [Auto-Instrumentation](./docs/integration/auto-instrumentation.md) | Supported libraries and frameworks |
175
+ | Topic | |
176
+ | --- | --- |
177
+ | [Installation](./docs/getting-started/installation.md) | Install and configure |
178
+ | [Quickstart](./docs/getting-started/quickstart.md) | Zero-to-first-trace in five minutes |
179
+ | [Configuration](./docs/getting-started/configuration.md) | Env vars, YAML, trusted-host auth |
180
+ | [Run Context](./docs/concepts/run-context.md) | Events, runs, retries, baggage |
181
+ | [Context Propagation](./docs/concepts/context-propagation.md) | Cross-service and queue propagation |
182
+ | [Architecture](./docs/concepts/architecture.md) | SDK + collector split |
183
+ | [LLM Tracking](./docs/tracking/llm-tracking.md) | Manual LLM instrumentation (usually not needed) |
184
+ | [Data Tracking](./docs/tracking/data-tracking.md) | DB, storage, messaging (usually not needed) |
185
+ | [Content Capture](./docs/tracking/content-capture.md) | Prompt/response capture for eval, with PII scrubbing |
186
+ | [Outcomes](./docs/tracking/outcomes.md) | Diagnostic annotations and server-side resolution |
187
+ | [Auto-Instrumentation](./docs/integration/auto-instrumentation.md) | Supported libraries |
157
188
  | [Kubernetes](./docs/integration/kubernetes.md) | Zero-code instrumentation at scale |
158
- | [API Reference](./docs/api/) | Decorators, tracking API, configuration |
159
- | [Best Practices](./docs/patterns/best-practices.md) | Recommended patterns |
189
+ | [Existing OTel / Datadog](./docs/integration/existing-otel.md) | Brownfield coexistence |
190
+ | [`event` / `step` API](./docs/api/event.md) | Primary API reference |
191
+ | [Best Practices](./docs/patterns/best-practices.md) | Patterns that work |
192
+ | [Anti-Patterns](./docs/patterns/anti-patterns.md) | Patterns that break cost attribution |
160
193
 
161
194
  ## Requirements
162
195
 
163
- - Python 3.9+
164
- - OpenTelemetry Collector (recommended for production)
196
+ - Python 3.9 or newer
197
+ - An OpenTelemetry Collector (Botanu Cloud runs one for you; self-hosted is supported too)
165
198
 
166
199
  ## Contributing
167
200
 
168
- We welcome contributions from the community. Please read our
169
- [Contributing Guide](./CONTRIBUTING.md) before submitting a pull request.
201
+ Contributions are welcome. Read the [Contributing Guide](./CONTRIBUTING.md) before opening a pull request.
170
202
 
171
- This project requires [DCO sign-off](https://developercertificate.org/) on all
172
- commits:
203
+ All commits require [DCO sign-off](https://developercertificate.org/):
173
204
 
174
205
  ```bash
175
206
  git commit -s -m "Your commit message"
176
207
  ```
177
208
 
178
- Looking for a place to start? Check the
179
- [good first issues](https://github.com/botanu-ai/botanu-sdk-python/labels/good%20first%20issue).
209
+ Looking for a place to start? See the [good first issues](https://github.com/botanu-ai/botanu-sdk-python/labels/good%20first%20issue).
180
210
 
181
211
  ## Community
182
212
 
183
213
  - [GitHub Discussions](https://github.com/botanu-ai/botanu-sdk-python/discussions) — questions, ideas, show & tell
184
- - [GitHub Issues](https://github.com/botanu-ai/botanu-sdk-python/issues) — bug reports and feature requests
214
+ - [GitHub Issues](https://github.com/botanu-ai/botanu-sdk-python/issues) — bugs and feature requests
185
215
 
186
216
  ## Governance
187
217
 
188
- See [GOVERNANCE.md](./GOVERNANCE.md) for details on roles, decision-making,
189
- and the contributor ladder.
190
-
191
- Current maintainers are listed in [MAINTAINERS.md](./MAINTAINERS.md).
218
+ See [GOVERNANCE.md](./GOVERNANCE.md) for roles, decision-making, and the contributor ladder. Current maintainers are in [MAINTAINERS.md](./MAINTAINERS.md).
192
219
 
193
220
  ## Security
194
221
 
195
- To report a security vulnerability, please use
196
- [GitHub Security Advisories](https://github.com/botanu-ai/botanu-sdk-python/security/advisories/new)
197
- or see [SECURITY.md](./SECURITY.md) for full details. **Do not file a public issue.**
222
+ Report security vulnerabilities via [GitHub Security Advisories](https://github.com/botanu-ai/botanu-sdk-python/security/advisories/new) or see [SECURITY.md](./SECURITY.md). **Do not file a public issue.**
198
223
 
199
224
 
200
225
  ## Code of Conduct
201
226
 
202
- This project follows the
203
- [LF Projects Code of Conduct](https://lfprojects.org/policies/code-of-conduct/).
204
- See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
227
+ This project follows the [LF Projects Code of Conduct](https://lfprojects.org/policies/code-of-conduct/). See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
205
228
 
206
229
  ## License
207
230
 
208
231
  [Apache License 2.0](./LICENSE)
209
-
@@ -0,0 +1,126 @@
1
+ # botanu SDK for Python
2
+
3
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
4
+
5
+ [botanu](https://botanu.ai/) is platform that helps AI companies understand the real cost of their AI features per customer, enabling outcome-based pricing and smarter scaling.
6
+ This SDK is built on [OpenTelemetry](https://opentelemetry.io/) for event-level cost attribution for AI workflow. For more email- deborah@botanu.ai
7
+
8
+
9
+ ## Getting Started
10
+
11
+ An **event** is one business transaction — resolving a support ticket, processing an order, generating a report. Each event may involve multiple **runs** (LLM calls, retries, sub-workflows) across multiple services. By correlating every run to a stable `event_id`, Botanu gives you per-event cost attribution and outcome tracking without sampling artefacts.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install botanu
17
+ ```
18
+
19
+ An **event** is one business transaction — resolving a support ticket, processing
20
+ an order, generating a report. Each event may involve multiple **runs** (LLM calls,
21
+ retries, sub-workflows) across multiple services. By correlating every run to a
22
+ stable `event_id`, botanu gives you per-event cost attribution and outcome
23
+ tracking without sampling artifacts.
24
+
25
+ ```bash
26
+ export BOTANU_API_KEY=<your-api-key>
27
+ ```
28
+
29
+ Wrap your agent:
30
+
31
+ ```python
32
+ import botanu
33
+
34
+ with botanu.event(event_id=ticket.id, customer_id=user.id, workflow="Support"):
35
+ agent.run(ticket)
36
+ ```
37
+
38
+ That single wrap captures every LLM call, HTTP call, and DB call inside and stamps them with `event_id`, `customer_id`, and `workflow`.
39
+
40
+ ### Decorator form
41
+
42
+ ```python
43
+ import botanu
44
+
45
+ @botanu.event(
46
+ workflow="Support",
47
+ event_id=lambda ticket: ticket.id,
48
+ customer_id=lambda ticket: ticket.user_id,
49
+ )
50
+ def handle_ticket(ticket):
51
+ return agent.run(ticket)
52
+ ```
53
+
54
+ Works for both sync and `async def` functions.
55
+
56
+ ### Multi-phase workflows
57
+
58
+ ```python
59
+ with botanu.event(event_id=ticket.id, customer_id=user.id, workflow="Support"):
60
+ with botanu.step("retrieval"):
61
+ docs = vector_db.query(ticket.query)
62
+ with botanu.step("generation"):
63
+ response = llm.complete(docs)
64
+ ```
65
+
66
+ See the [Quickstart](./docs/getting-started/quickstart.md) for the full five-minute walkthrough.
67
+
68
+ ## Documentation
69
+
70
+ | Topic | |
71
+ | --- | --- |
72
+ | [Installation](./docs/getting-started/installation.md) | Install and configure |
73
+ | [Quickstart](./docs/getting-started/quickstart.md) | Zero-to-first-trace in five minutes |
74
+ | [Configuration](./docs/getting-started/configuration.md) | Env vars, YAML, trusted-host auth |
75
+ | [Run Context](./docs/concepts/run-context.md) | Events, runs, retries, baggage |
76
+ | [Context Propagation](./docs/concepts/context-propagation.md) | Cross-service and queue propagation |
77
+ | [Architecture](./docs/concepts/architecture.md) | SDK + collector split |
78
+ | [LLM Tracking](./docs/tracking/llm-tracking.md) | Manual LLM instrumentation (usually not needed) |
79
+ | [Data Tracking](./docs/tracking/data-tracking.md) | DB, storage, messaging (usually not needed) |
80
+ | [Content Capture](./docs/tracking/content-capture.md) | Prompt/response capture for eval, with PII scrubbing |
81
+ | [Outcomes](./docs/tracking/outcomes.md) | Diagnostic annotations and server-side resolution |
82
+ | [Auto-Instrumentation](./docs/integration/auto-instrumentation.md) | Supported libraries |
83
+ | [Kubernetes](./docs/integration/kubernetes.md) | Zero-code instrumentation at scale |
84
+ | [Existing OTel / Datadog](./docs/integration/existing-otel.md) | Brownfield coexistence |
85
+ | [`event` / `step` API](./docs/api/event.md) | Primary API reference |
86
+ | [Best Practices](./docs/patterns/best-practices.md) | Patterns that work |
87
+ | [Anti-Patterns](./docs/patterns/anti-patterns.md) | Patterns that break cost attribution |
88
+
89
+ ## Requirements
90
+
91
+ - Python 3.9 or newer
92
+ - An OpenTelemetry Collector (Botanu Cloud runs one for you; self-hosted is supported too)
93
+
94
+ ## Contributing
95
+
96
+ Contributions are welcome. Read the [Contributing Guide](./CONTRIBUTING.md) before opening a pull request.
97
+
98
+ All commits require [DCO sign-off](https://developercertificate.org/):
99
+
100
+ ```bash
101
+ git commit -s -m "Your commit message"
102
+ ```
103
+
104
+ Looking for a place to start? See the [good first issues](https://github.com/botanu-ai/botanu-sdk-python/labels/good%20first%20issue).
105
+
106
+ ## Community
107
+
108
+ - [GitHub Discussions](https://github.com/botanu-ai/botanu-sdk-python/discussions) — questions, ideas, show & tell
109
+ - [GitHub Issues](https://github.com/botanu-ai/botanu-sdk-python/issues) — bugs and feature requests
110
+
111
+ ## Governance
112
+
113
+ See [GOVERNANCE.md](./GOVERNANCE.md) for roles, decision-making, and the contributor ladder. Current maintainers are in [MAINTAINERS.md](./MAINTAINERS.md).
114
+
115
+ ## Security
116
+
117
+ Report security vulnerabilities via [GitHub Security Advisories](https://github.com/botanu-ai/botanu-sdk-python/security/advisories/new) or see [SECURITY.md](./SECURITY.md). **Do not file a public issue.**
118
+
119
+
120
+ ## Code of Conduct
121
+
122
+ This project follows the [LF Projects Code of Conduct](https://lfprojects.org/policies/code-of-conduct/). See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
123
+
124
+ ## License
125
+
126
+ [Apache License 2.0](./LICENSE)
@@ -139,6 +139,14 @@ cloud = [
139
139
  "opentelemetry-resource-detector-azure >= 0.1b0",
140
140
  "opentelemetry-resource-detector-container >= 0.1b0",
141
141
  ]
142
+
143
+ # NER-based PII scrubbing on top of the built-in regex pass. Regex covers
144
+ # structured tokens (emails, card numbers, API keys); Presidio adds names,
145
+ # addresses, and medical terms. Heavy (~200MB with spaCy model) — opt-in only.
146
+ pii-nlp = [
147
+ "presidio-analyzer >= 2.2",
148
+ "presidio-anonymizer >= 2.2",
149
+ ]
142
150
  dev = [
143
151
  "pytest >= 7.4.0",
144
152
  "pytest-asyncio >= 0.21.0",
@@ -5,15 +5,22 @@
5
5
 
6
6
  Quick Start::
7
7
 
8
- from botanu import enable, botanu_workflow, emit_outcome
8
+ import botanu
9
9
 
10
- enable() # reads config from OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT env vars
10
+ botanu.enable() # reads OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT env vars
11
11
 
12
- @botanu_workflow(name="Customer Support")
13
- async def handle_request(data):
14
- result = await process(data)
15
- emit_outcome("success", value_type="tickets_resolved", value_amount=1)
16
- return result
12
+ # One wrap around the agent entrypoint captures every LLM/HTTP/DB call.
13
+ with botanu.event(event_id=ticket.id, customer_id=user.id, workflow="Support"):
14
+ agent.run(ticket)
15
+
16
+ # Or as a decorator, with lambda extractors from the function args:
17
+ @botanu.event(
18
+ workflow="Support",
19
+ event_id=lambda t: t.id,
20
+ customer_id=lambda t: t.user_id,
21
+ )
22
+ def handle_ticket(ticket):
23
+ ...
17
24
  """
18
25
 
19
26
  from __future__ import annotations
@@ -45,11 +52,11 @@ from botanu.sdk.context import (
45
52
  set_baggage,
46
53
  )
47
54
 
48
- # Decorators (primary integration point)
49
- from botanu.sdk.decorators import botanu_workflow, run_botanu, workflow
55
+ # Primary integration API
56
+ from botanu.sdk.decorators import event, step
50
57
 
51
58
  # Span helpers
52
- from botanu.sdk.span_helpers import emit_outcome, set_business_context
59
+ from botanu.sdk.span_helpers import emit_outcome, set_business_context, set_correlation
53
60
 
54
61
  __all__ = [
55
62
  "__version__",
@@ -59,13 +66,13 @@ __all__ = [
59
66
  "is_enabled",
60
67
  # Configuration
61
68
  "BotanuConfig",
62
- # Decorators / context managers
63
- "botanu_workflow",
64
- "run_botanu",
65
- "workflow",
69
+ # Primary API
70
+ "event",
71
+ "step",
66
72
  # Span helpers
67
73
  "emit_outcome",
68
74
  "set_business_context",
75
+ "set_correlation",
69
76
  "get_current_span",
70
77
  # Context
71
78
  "get_run_id",
@@ -212,22 +212,30 @@ class RunContext:
212
212
  # Serialisation
213
213
  # ------------------------------------------------------------------
214
214
 
215
- def to_baggage_dict(self, lean_mode: Optional[bool] = None) -> Dict[str, str]:
216
- """Convert to dict for W3C Baggage propagation."""
217
- if lean_mode is None:
218
- env_mode = os.getenv("BOTANU_PROPAGATION_MODE", "lean")
219
- lean_mode = env_mode != "full"
220
-
215
+ def to_baggage_dict(self) -> Dict[str, str]:
216
+ """Convert to dict for W3C Baggage propagation.
217
+
218
+ Always present: ``botanu.run_id``, ``botanu.workflow``,
219
+ ``botanu.event_id``, ``botanu.customer_id``, ``botanu.environment``.
220
+
221
+ Included when set on the context: ``botanu.tenant_id``,
222
+ ``botanu.parent_run_id``, ``botanu.root_run_id`` (if non-root),
223
+ ``botanu.attempt`` (if > 1), ``botanu.retry_of_run_id``,
224
+ ``botanu.deadline``, ``botanu.cancelled``.
225
+
226
+ The :class:`RunContextEnricher` stamps only the first seven
227
+ (run_id, workflow, event_id, customer_id, environment, tenant_id,
228
+ parent_run_id) on downstream spans. The remaining keys are for
229
+ :meth:`from_baggage` to reconstruct retry/deadline state on the
230
+ receiving side of cross-process propagation (e.g. message queues).
231
+ """
221
232
  baggage: Dict[str, str] = {
222
233
  "botanu.run_id": self.run_id,
223
234
  "botanu.workflow": self.workflow,
224
235
  "botanu.event_id": self.event_id,
225
236
  "botanu.customer_id": self.customer_id,
237
+ "botanu.environment": self.environment,
226
238
  }
227
- if lean_mode:
228
- return baggage
229
-
230
- baggage["botanu.environment"] = self.environment
231
239
  if self.tenant_id:
232
240
  baggage["botanu.tenant_id"] = self.tenant_id
233
241
  if self.parent_run_id:
@@ -271,10 +279,6 @@ class RunContext:
271
279
  if self.cancelled_at:
272
280
  attrs["botanu.run.cancelled_at"] = self.cancelled_at
273
281
  if self.outcome:
274
- # `botanu.outcome.status` is NOT emitted (removed 2026-04-16):
275
- # customer-reported outcome is trivially fakeable. Event outcome
276
- # is derived from eval verdict rollup / HITL / SoR instead.
277
- # Remaining fields are diagnostic only and stay for debugging.
278
282
  if self.outcome.reason_code:
279
283
  attrs["botanu.outcome.reason_code"] = self.outcome.reason_code
280
284
  if self.outcome.error_class:
@@ -8,10 +8,13 @@ Why this MUST be in SDK (not collector):
8
8
  - Only the SDK can read baggage and write it to span attributes.
9
9
  - The collector only sees spans after they're exported.
10
10
 
11
- All heavy processing should happen in the OTel Collector:
12
- - PII redaction → ``redactionprocessor``
11
+ Heavy non-content processing happens in the OTel Collector:
13
12
  - Cardinality limits → ``attributesprocessor``
14
13
  - Vendor detection → ``transformprocessor``
14
+ - Belt-and-suspenders PII regex → ``redactionprocessor``
15
+
16
+ In-process PII scrubbing of content-capture attributes is handled by
17
+ :mod:`botanu.sdk.pii` at the tracker methods, not by a span processor.
15
18
  """
16
19
 
17
20
  from __future__ import annotations
@@ -29,17 +32,18 @@ logger = logging.getLogger(__name__)
29
32
  class RunContextEnricher(SpanProcessor):
30
33
  """Enriches ALL spans with run context from baggage.
31
34
 
32
- This ensures that every span (including auto-instrumented ones)
33
- gets ``botanu.run_id``, ``botanu.workflow``, etc. attributes.
34
-
35
- Without this processor, only the root ``botanu.run`` span would
36
- have these attributes.
35
+ This ensures that every span (including auto-instrumented ones) gets
36
+ ``botanu.run_id``, ``botanu.workflow``, ``botanu.event_id``,
37
+ ``botanu.customer_id``, ``botanu.environment``, ``botanu.tenant_id``,
38
+ and ``botanu.parent_run_id`` attributes when those baggage keys are
39
+ present on the active OTel context.
37
40
 
38
- In ``lean_mode`` (default), only ``run_id`` and ``workflow`` are
39
- propagated to minimise per-span overhead.
41
+ Without this processor, only the root ``botanu.run`` span would carry
42
+ these attributes; downstream auto-instrumented spans (LLM, HTTP, DB)
43
+ would not.
40
44
  """
41
45
 
42
- BAGGAGE_KEYS_FULL: ClassVar[List[str]] = [
46
+ BAGGAGE_KEYS: ClassVar[List[str]] = [
43
47
  "botanu.run_id",
44
48
  "botanu.workflow",
45
49
  "botanu.event_id",
@@ -49,17 +53,6 @@ class RunContextEnricher(SpanProcessor):
49
53
  "botanu.parent_run_id",
50
54
  ]
51
55
 
52
- BAGGAGE_KEYS_LEAN: ClassVar[List[str]] = [
53
- "botanu.run_id",
54
- "botanu.workflow",
55
- "botanu.event_id",
56
- "botanu.customer_id",
57
- ]
58
-
59
- def __init__(self, lean_mode: bool = True) -> None:
60
- self._lean_mode = lean_mode
61
- self._baggage_keys = self.BAGGAGE_KEYS_LEAN if lean_mode else self.BAGGAGE_KEYS_FULL
62
-
63
56
  def on_start(
64
57
  self,
65
58
  span: Span,
@@ -68,7 +61,7 @@ class RunContextEnricher(SpanProcessor):
68
61
  """Called when a span starts — enrich with run context from baggage."""
69
62
  ctx = parent_context or context.get_current()
70
63
 
71
- for key in self._baggage_keys:
64
+ for key in self.BAGGAGE_KEYS:
72
65
  value = baggage.get_baggage(key, ctx)
73
66
  if value:
74
67
  if not span.attributes or key not in span.attributes:
@@ -23,7 +23,7 @@ Usage::
23
23
  uvicorn app:app --env-file .env
24
24
 
25
25
  # Or in Dockerfile
26
- ENV BOTANU_API_KEY=btnu_live_...
26
+ ENV BOTANU_API_KEY=<your key from app.botanu.ai>
27
27
  ENV BOTANU_SERVICE_NAME=my-service
28
28
  CMD ["python", "-c", "import botanu.register; import uvicorn; uvicorn.run('app:app')"]
29
29
 
@@ -36,4 +36,4 @@ def should_capture_content(rate: float, event_id: Optional[str] = None) -> bool:
36
36
  return False
37
37
  if rate >= 1.0:
38
38
  return True
39
- return random.random() < rate
39
+ return random.random() < rate # noqa: S311
@@ -14,7 +14,7 @@ from botanu.sdk.context import (
14
14
  get_workflow,
15
15
  set_baggage,
16
16
  )
17
- from botanu.sdk.decorators import botanu_outcome, botanu_workflow, run_botanu, workflow
17
+ from botanu.sdk.decorators import event, step
18
18
  from botanu.sdk.span_helpers import (
19
19
  emit_outcome,
20
20
  set_business_context,
@@ -23,20 +23,18 @@ from botanu.sdk.span_helpers import (
23
23
 
24
24
  __all__ = [
25
25
  "BotanuConfig",
26
- "botanu_outcome",
27
- "botanu_workflow",
28
26
  "disable",
29
27
  "emit_outcome",
30
28
  "enable",
29
+ "event",
31
30
  "get_baggage",
32
31
  "get_config",
33
32
  "get_current_span",
34
33
  "get_run_id",
35
34
  "get_workflow",
36
35
  "is_enabled",
37
- "run_botanu",
38
36
  "set_baggage",
39
37
  "set_business_context",
40
38
  "set_correlation",
41
- "workflow",
39
+ "step",
42
40
  ]
@@ -163,11 +163,22 @@ def enable(
163
163
  from botanu.sdk.config import _redact_url_credentials
164
164
 
165
165
  logger.info(
166
- "Initializing Botanu SDK: service=%s, env=%s, endpoint=%s",
166
+ "Initializing Botanu SDK: service=%s, env=%s, endpoint=%s, content_capture_rate=%s",
167
167
  cfg.service_name,
168
168
  cfg.deployment_environment,
169
169
  _redact_url_credentials(traces_endpoint),
170
+ cfg.content_capture_rate,
170
171
  )
172
+ if cfg.content_capture_rate <= 0.0:
173
+ # Louder signal when the customer explicitly turned capture off —
174
+ # evaluator judge will no-op on every span. Not a failure, just a
175
+ # disable-by-choice worth flagging once at startup so a bug hunt
176
+ # for "why are eval rollups empty" ends here (Codex 2026-04-24 P0 #1).
177
+ logger.warning(
178
+ "Botanu content_capture_rate=0.0 — set_input_content / set_output_content "
179
+ "will not write span attributes, and the L2 evaluator judge will return "
180
+ "no test case for any span. See docs/tracking/content-capture.md to enable."
181
+ )
171
182
 
172
183
  try:
173
184
  from opentelemetry import trace
@@ -216,9 +227,8 @@ def enable(
216
227
  resource = Resource.create(resource_attrs)
217
228
 
218
229
  from opentelemetry.trace import ProxyTracerProvider
219
- from botanu.processors import ResourceEnricher, SampledSpanProcessor
220
230
 
221
- lean_mode = cfg.propagation_mode == "lean"
231
+ from botanu.processors import ResourceEnricher, SampledSpanProcessor
222
232
 
223
233
  botanu_exporter = OTLPSpanExporter(
224
234
  endpoint=traces_endpoint,
@@ -287,7 +297,7 @@ def enable(
287
297
  else:
288
298
  provider.add_span_processor(proc)
289
299
 
290
- provider.add_span_processor(RunContextEnricher(lean_mode=lean_mode))
300
+ provider.add_span_processor(RunContextEnricher())
291
301
  if cfg.auto_instrument_resources:
292
302
  provider.add_span_processor(ResourceEnricher())
293
303
  provider.add_span_processor(botanu_batch)
@@ -312,7 +322,7 @@ def enable(
312
322
  elif isinstance(existing, ProxyTracerProvider):
313
323
  # GREENFIELD: no real provider — create fresh
314
324
  provider = TracerProvider(resource=resource, sampler=ALWAYS_ON)
315
- provider.add_span_processor(RunContextEnricher(lean_mode=lean_mode))
325
+ provider.add_span_processor(RunContextEnricher())
316
326
  if cfg.auto_instrument_resources:
317
327
  provider.add_span_processor(ResourceEnricher())
318
328
  provider.add_span_processor(botanu_batch)
@@ -330,7 +340,7 @@ def enable(
330
340
  type(existing).__name__,
331
341
  )
332
342
  provider = TracerProvider(resource=resource, sampler=ALWAYS_ON)
333
- provider.add_span_processor(RunContextEnricher(lean_mode=lean_mode))
343
+ provider.add_span_processor(RunContextEnricher())
334
344
  if cfg.auto_instrument_resources:
335
345
  provider.add_span_processor(ResourceEnricher())
336
346
  provider.add_span_processor(botanu_batch)
@@ -350,9 +360,9 @@ def enable(
350
360
  # Set up LoggerProvider for outcome event emission
351
361
  try:
352
362
  from opentelemetry._logs import set_logger_provider
363
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
353
364
  from opentelemetry.sdk._logs import LoggerProvider as _LoggerProvider
354
365
  from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
355
- from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
356
366
 
357
367
  logs_endpoint = cfg.otlp_endpoint
358
368
  if logs_endpoint and not logs_endpoint.endswith("/v1/logs"):