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.
- {botanu-0.1.dev63 → botanu-0.1.dev77}/PKG-INFO +70 -46
- botanu-0.1.dev77/README.md +126 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/pyproject.toml +8 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/__init__.py +27 -14
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/models/run_context.py +18 -10
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/processors/__init__.py +3 -1
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/processors/enricher.py +15 -22
- botanu-0.1.dev77/src/botanu/processors/resource_enricher.py +179 -0
- botanu-0.1.dev77/src/botanu/processors/sampled.py +86 -0
- botanu-0.1.dev77/src/botanu/register.py +50 -0
- botanu-0.1.dev77/src/botanu/sampling/__init__.py +8 -0
- botanu-0.1.dev77/src/botanu/sampling/content_sampler.py +39 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/__init__.py +9 -6
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/bootstrap.py +207 -20
- botanu-0.1.dev77/src/botanu/sdk/config.py +515 -0
- botanu-0.1.dev77/src/botanu/sdk/decorators.py +459 -0
- {botanu-0.1.dev63 → 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.dev77/src/botanu/sdk/span_helpers.py +170 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/tracking/data.py +54 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/tracking/llm.py +50 -0
- botanu-0.1.dev63/README.md +0 -105
- botanu-0.1.dev63/src/botanu/sdk/config.py +0 -330
- botanu-0.1.dev63/src/botanu/sdk/decorators.py +0 -407
- botanu-0.1.dev63/src/botanu/sdk/span_helpers.py +0 -143
- {botanu-0.1.dev63 → botanu-0.1.dev77}/.gitignore +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/LICENSE +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/NOTICE +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/_version.py +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/integrations/__init__.py +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/integrations/tenacity.py +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/models/__init__.py +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/py.typed +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/resources/__init__.py +0 -0
- {botanu-0.1.dev63 → botanu-0.1.dev77}/src/botanu/sdk/context.py +0 -0
- {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.
|
|
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
|
-
|
|
131
|
+
export BOTANU_API_KEY=<your-api-key>
|
|
121
132
|
```
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
50+ libraries.
|
|
134
|
+
Wrap your agent:
|
|
125
135
|
|
|
126
136
|
```python
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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 [
|
|
171
|
+
See the [Quickstart](./docs/getting-started/quickstart.md) for the full five-minute walkthrough.
|
|
142
172
|
|
|
143
173
|
## Documentation
|
|
144
174
|
|
|
145
|
-
| Topic |
|
|
146
|
-
|
|
147
|
-
| [Installation](./docs/getting-started/installation.md) | Install and configure
|
|
148
|
-
| [
|
|
149
|
-
| [Configuration](./docs/getting-started/configuration.md) |
|
|
150
|
-
| [
|
|
151
|
-
| [
|
|
152
|
-
| [
|
|
153
|
-
| [
|
|
154
|
-
| [
|
|
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
|
-
| [
|
|
157
|
-
| [
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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?
|
|
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) —
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
|
@@ -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
|
-
#
|
|
46
|
-
from botanu.sdk.decorators import
|
|
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
|
-
#
|
|
60
|
-
"
|
|
61
|
-
"
|
|
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
|
|
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
|
-
|
|
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:
|