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.
- {botanu-0.1.dev68 → botanu-0.1.dev77}/PKG-INFO +68 -46
- botanu-0.1.dev77/README.md +126 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/pyproject.toml +8 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/__init__.py +21 -14
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/models/run_context.py +18 -14
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/enricher.py +15 -22
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/register.py +1 -1
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sampling/content_sampler.py +1 -1
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/__init__.py +3 -5
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/bootstrap.py +17 -7
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/config.py +75 -28
- botanu-0.1.dev77/src/botanu/sdk/decorators.py +459 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/middleware.py +0 -1
- botanu-0.1.dev77/src/botanu/sdk/pii.py +179 -0
- botanu-0.1.dev77/src/botanu/sdk/pii_presidio.py +98 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/span_helpers.py +9 -56
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/tracking/data.py +7 -4
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/tracking/llm.py +15 -10
- botanu-0.1.dev68/README.md +0 -107
- botanu-0.1.dev68/src/botanu/sdk/decorators.py +0 -491
- {botanu-0.1.dev68 → botanu-0.1.dev77}/.gitignore +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/LICENSE +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/NOTICE +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/_version.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/integrations/__init__.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/integrations/tenacity.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/models/__init__.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/__init__.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/resource_enricher.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/processors/sampled.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/py.typed +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/resources/__init__.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sampling/__init__.py +0 -0
- {botanu-0.1.dev68 → botanu-0.1.dev77}/src/botanu/sdk/context.py +0 -0
- {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.
|
|
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
|
-
|
|
131
|
+
export BOTANU_API_KEY=<your-api-key>
|
|
123
132
|
```
|
|
124
133
|
|
|
125
|
-
|
|
126
|
-
50+ libraries.
|
|
134
|
+
Wrap your agent:
|
|
127
135
|
|
|
128
136
|
```python
|
|
129
|
-
|
|
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
|
-
|
|
145
|
+
### Decorator form
|
|
132
146
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
|
|
159
|
+
Works for both sync and `async def` functions.
|
|
160
|
+
|
|
161
|
+
### Multi-phase workflows
|
|
142
162
|
|
|
143
|
-
|
|
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 |
|
|
148
|
-
|
|
149
|
-
| [Installation](./docs/getting-started/installation.md) | Install and configure
|
|
150
|
-
| [
|
|
151
|
-
| [Configuration](./docs/getting-started/configuration.md) |
|
|
152
|
-
| [
|
|
153
|
-
| [
|
|
154
|
-
| [
|
|
155
|
-
| [
|
|
156
|
-
| [
|
|
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
|
-
| [
|
|
159
|
-
| [
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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?
|
|
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) —
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
8
|
+
import botanu
|
|
9
9
|
|
|
10
|
-
enable() # reads
|
|
10
|
+
botanu.enable() # reads OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT env vars
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
#
|
|
49
|
-
from botanu.sdk.decorators import
|
|
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
|
-
#
|
|
63
|
-
"
|
|
64
|
-
"
|
|
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
|
|
216
|
-
"""Convert to dict for W3C Baggage propagation.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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
|
-
"
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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"):
|