botanu 0.1.dev63__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 (37) hide show
  1. {botanu-0.1.dev63 → botanu-0.1.dev77}/PKG-INFO +70 -46
  2. botanu-0.1.dev77/README.md +126 -0
  3. {botanu-0.1.dev63 → botanu-0.1.dev77}/pyproject.toml +8 -0
  4. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/__init__.py +27 -14
  5. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/models/run_context.py +18 -10
  6. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/processors/__init__.py +3 -1
  7. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/processors/enricher.py +15 -22
  8. botanu-0.1.dev77/src/botanu/processors/resource_enricher.py +179 -0
  9. botanu-0.1.dev77/src/botanu/processors/sampled.py +86 -0
  10. botanu-0.1.dev77/src/botanu/register.py +50 -0
  11. botanu-0.1.dev77/src/botanu/sampling/__init__.py +8 -0
  12. botanu-0.1.dev77/src/botanu/sampling/content_sampler.py +39 -0
  13. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/__init__.py +9 -6
  14. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/bootstrap.py +207 -20
  15. botanu-0.1.dev77/src/botanu/sdk/config.py +515 -0
  16. botanu-0.1.dev77/src/botanu/sdk/decorators.py +459 -0
  17. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/middleware.py +0 -1
  18. botanu-0.1.dev77/src/botanu/sdk/pii.py +179 -0
  19. botanu-0.1.dev77/src/botanu/sdk/pii_presidio.py +98 -0
  20. botanu-0.1.dev77/src/botanu/sdk/span_helpers.py +170 -0
  21. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/tracking/data.py +54 -0
  22. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/tracking/llm.py +50 -0
  23. botanu-0.1.dev63/README.md +0 -105
  24. botanu-0.1.dev63/src/botanu/sdk/config.py +0 -330
  25. botanu-0.1.dev63/src/botanu/sdk/decorators.py +0 -407
  26. botanu-0.1.dev63/src/botanu/sdk/span_helpers.py +0 -143
  27. {botanu-0.1.dev63 → botanu-0.1.dev77}/.gitignore +0 -0
  28. {botanu-0.1.dev63 → botanu-0.1.dev77}/LICENSE +0 -0
  29. {botanu-0.1.dev63 → botanu-0.1.dev77}/NOTICE +0 -0
  30. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/_version.py +0 -0
  31. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/integrations/__init__.py +0 -0
  32. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/integrations/tenacity.py +0 -0
  33. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/models/__init__.py +0 -0
  34. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/py.typed +0 -0
  35. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/resources/__init__.py +0 -0
  36. {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/context.py +0 -0
  37. {botanu-0.1.dev63 → 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.dev63
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,6 +113,14 @@ 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.
117
+
118
+ ## Install
119
+
120
+ ```bash
121
+ pip install botanu
122
+ ```
123
+
113
124
  An **event** is one business transaction — resolving a support ticket, processing
114
125
  an order, generating a report. Each event may involve multiple **runs** (LLM calls,
115
126
  retries, sub-workflows) across multiple services. By correlating every run to a
@@ -117,91 +128,104 @@ stable `event_id`, botanu gives you per-event cost attribution and outcome
117
128
  tracking without sampling artifacts.
118
129
 
119
130
  ```bash
120
- pip install botanu
131
+ export BOTANU_API_KEY=<your-api-key>
121
132
  ```
122
133
 
123
- One install. Includes OTel SDK, OTLP exporter, and auto-instrumentation for
124
- 50+ libraries.
134
+ Wrap your agent:
125
135
 
126
136
  ```python
127
- 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
+ ```
128
142
 
129
- enable() # reads config from environment variables
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
- @botanu_workflow("my-workflow", event_id="evt-001", customer_id="cust-42")
132
- async def do_work():
133
- result = await do_something()
134
- emit_outcome("success")
135
- return result
145
+ ### Decorator form
146
+
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)
136
157
  ```
137
158
 
138
- Entry points use `@botanu_workflow`. Every other service only needs `enable()`.
139
- 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
162
+
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
+ ```
140
170
 
141
- See the [Quick Start](./docs/getting-started/quickstart.md) guide for a full walkthrough.
171
+ See the [Quickstart](./docs/getting-started/quickstart.md) for the full five-minute walkthrough.
142
172
 
143
173
  ## Documentation
144
174
 
145
- | Topic | Description |
146
- |-------|-------------|
147
- | [Installation](./docs/getting-started/installation.md) | Install and configure the SDK |
148
- | [Quick Start](./docs/getting-started/quickstart.md) | Get up and running in 5 minutes |
149
- | [Configuration](./docs/getting-started/configuration.md) | Environment variables and options |
150
- | [Core Concepts](./docs/concepts/) | Events, runs, context propagation, architecture |
151
- | [LLM Tracking](./docs/tracking/llm-tracking.md) | Track model calls and token usage |
152
- | [Data Tracking](./docs/tracking/data-tracking.md) | Database, storage, and messaging |
153
- | [Outcomes](./docs/tracking/outcomes.md) | Record business outcomes for ROI |
154
- | [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 |
155
188
  | [Kubernetes](./docs/integration/kubernetes.md) | Zero-code instrumentation at scale |
156
- | [API Reference](./docs/api/) | Decorators, tracking API, configuration |
157
- | [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 |
158
193
 
159
194
  ## Requirements
160
195
 
161
- - Python 3.9+
162
- - 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)
163
198
 
164
199
  ## Contributing
165
200
 
166
- We welcome contributions from the community. Please read our
167
- [Contributing Guide](./CONTRIBUTING.md) before submitting a pull request.
201
+ Contributions are welcome. Read the [Contributing Guide](./CONTRIBUTING.md) before opening a pull request.
168
202
 
169
- This project requires [DCO sign-off](https://developercertificate.org/) on all
170
- commits:
203
+ All commits require [DCO sign-off](https://developercertificate.org/):
171
204
 
172
205
  ```bash
173
206
  git commit -s -m "Your commit message"
174
207
  ```
175
208
 
176
- Looking for a place to start? Check the
177
- [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).
178
210
 
179
211
  ## Community
180
212
 
181
213
  - [GitHub Discussions](https://github.com/botanu-ai/botanu-sdk-python/discussions) — questions, ideas, show & tell
182
- - [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
183
215
 
184
216
  ## Governance
185
217
 
186
- See [GOVERNANCE.md](./GOVERNANCE.md) for details on roles, decision-making,
187
- and the contributor ladder.
188
-
189
- 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).
190
219
 
191
220
  ## Security
192
221
 
193
- To report a security vulnerability, please use
194
- [GitHub Security Advisories](https://github.com/botanu-ai/botanu-sdk-python/security/advisories/new)
195
- 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.**
196
223
 
197
224
 
198
225
  ## Code of Conduct
199
226
 
200
- This project follows the
201
- [LF Projects Code of Conduct](https://lfprojects.org/policies/code-of-conduct/).
202
- 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).
203
228
 
204
229
  ## License
205
230
 
206
231
  [Apache License 2.0](./LICENSE)
207
-
@@ -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
@@ -23,6 +30,9 @@ from botanu._version import __version__
23
30
  # Run context model
24
31
  from botanu.models.run_context import RunContext, RunOutcome, RunStatus
25
32
 
33
+ # Processors
34
+ from botanu.processors import RunContextEnricher, SampledSpanProcessor
35
+
26
36
  # Bootstrap
27
37
  from botanu.sdk.bootstrap import (
28
38
  disable,
@@ -42,11 +52,11 @@ from botanu.sdk.context import (
42
52
  set_baggage,
43
53
  )
44
54
 
45
- # Decorators (primary integration point)
46
- from botanu.sdk.decorators import botanu_workflow, run_botanu, workflow
55
+ # Primary integration API
56
+ from botanu.sdk.decorators import event, step
47
57
 
48
58
  # Span helpers
49
- 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
50
60
 
51
61
  __all__ = [
52
62
  "__version__",
@@ -56,13 +66,13 @@ __all__ = [
56
66
  "is_enabled",
57
67
  # Configuration
58
68
  "BotanuConfig",
59
- # Decorators / context managers
60
- "botanu_workflow",
61
- "run_botanu",
62
- "workflow",
69
+ # Primary API
70
+ "event",
71
+ "step",
63
72
  # Span helpers
64
73
  "emit_outcome",
65
74
  "set_business_context",
75
+ "set_correlation",
66
76
  "get_current_span",
67
77
  # Context
68
78
  "get_run_id",
@@ -73,4 +83,7 @@ __all__ = [
73
83
  "RunContext",
74
84
  "RunStatus",
75
85
  "RunOutcome",
86
+ # Processors
87
+ "RunContextEnricher",
88
+ "SampledSpanProcessor",
76
89
  ]
@@ -89,6 +89,7 @@ class RunContext:
89
89
  event_id: str
90
90
  customer_id: str
91
91
  environment: str
92
+ step: Optional[str] = None
92
93
  workflow_version: Optional[str] = None
93
94
  tenant_id: Optional[str] = None
94
95
  parent_run_id: Optional[str] = None
@@ -211,22 +212,30 @@ class RunContext:
211
212
  # Serialisation
212
213
  # ------------------------------------------------------------------
213
214
 
214
- def to_baggage_dict(self, lean_mode: Optional[bool] = None) -> Dict[str, str]:
215
- """Convert to dict for W3C Baggage propagation."""
216
- if lean_mode is None:
217
- env_mode = os.getenv("BOTANU_PROPAGATION_MODE", "lean")
218
- lean_mode = env_mode != "full"
215
+ def to_baggage_dict(self) -> Dict[str, str]:
216
+ """Convert to dict for W3C Baggage propagation.
219
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
+ """
220
232
  baggage: Dict[str, str] = {
221
233
  "botanu.run_id": self.run_id,
222
234
  "botanu.workflow": self.workflow,
223
235
  "botanu.event_id": self.event_id,
224
236
  "botanu.customer_id": self.customer_id,
237
+ "botanu.environment": self.environment,
225
238
  }
226
- if lean_mode:
227
- return baggage
228
-
229
- baggage["botanu.environment"] = self.environment
230
239
  if self.tenant_id:
231
240
  baggage["botanu.tenant_id"] = self.tenant_id
232
241
  if self.parent_run_id:
@@ -270,7 +279,6 @@ class RunContext:
270
279
  if self.cancelled_at:
271
280
  attrs["botanu.run.cancelled_at"] = self.cancelled_at
272
281
  if self.outcome:
273
- attrs["botanu.outcome.status"] = self.outcome.status.value
274
282
  if self.outcome.reason_code:
275
283
  attrs["botanu.outcome.reason_code"] = self.outcome.reason_code
276
284
  if self.outcome.error_class:
@@ -8,5 +8,7 @@ All other processing should happen in the OTel Collector.
8
8
  """
9
9
 
10
10
  from botanu.processors.enricher import RunContextEnricher
11
+ from botanu.processors.resource_enricher import ResourceEnricher
12
+ from botanu.processors.sampled import SampledSpanProcessor
11
13
 
12
- __all__ = ["RunContextEnricher"]
14
+ __all__ = ["RunContextEnricher", "ResourceEnricher", "SampledSpanProcessor"]
@@ -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: