Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: pip install ruff
Expand All @@ -33,8 +33,8 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: pip install -e ".[dev]"
Expand All @@ -50,11 +50,11 @@ jobs:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # hatch-vcs needs full history

- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -66,7 +66,7 @@ jobs:

- name: Upload coverage
if: matrix.python-version == '3.12'
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
file: coverage.xml
fail_ci_if_error: false
Expand All @@ -77,15 +77,15 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: pip install build
- run: python -m build
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist
path: dist/
Expand All @@ -97,10 +97,10 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: DCO check
uses: christophebedard/dco-check@0.5.0
uses: christophebedard/dco-check@7b0205d25ead0f898e0b706b58227dd5fa7e3f55 # 0.5.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ jobs:
matrix:
language: [python]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
with:
category: "/language:${{ matrix.language }}"
18 changes: 9 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # hatch-vcs needs full history

- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

Expand All @@ -48,7 +48,7 @@ jobs:
- name: List build artifacts
run: ls -la dist/

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist
path: dist/
Expand All @@ -71,13 +71,13 @@ jobs:
permissions:
id-token: write # required for OIDC trusted publishing
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist/

- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
Expand All @@ -99,13 +99,13 @@ jobs:
permissions:
id-token: write # required for OIDC trusted publishing
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1

# -------------------------------------------------------------------
# Create GitHub Release with auto-generated notes
Expand All @@ -117,11 +117,11 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist/
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/repolinter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Run Repolinter
uses: todogroup/repolinter-action@v1
uses: todogroup/repolinter-action@4d478dcd860571382da7d512d6dc6dd5f554fbb2 # v1
with:
config_file: .github/repolinter.json
6 changes: 3 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ jobs:
security-events: write # upload SARIF
id-token: write # publish results
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Run OpenSSF Scorecard
uses: ossf/scorecard-action@v2
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
with:
sarif_file: results.sarif
120 changes: 56 additions & 64 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.0] - 2026-02-05

### Added

- Initial open-source release under Apache-2.0 license
- **Core SDK**
- `enable()` / `disable()` bootstrap functions for SDK initialization
- `@botanu_workflow` decorator with UUIDv7 run_id generation
- `@botanu_outcome` decorator for sub-function outcome tracking
- `emit_outcome()` helper for recording business outcomes
- `set_business_context()` for cost attribution dimensions
- `RunContextEnricher` span processor for automatic run_id propagation

- **LLM Tracking** (aligned with OTel GenAI semantic conventions)
- `track_llm_call()` context manager for LLM/model operations
- `track_tool_call()` context manager for tool/function calls
- Token usage tracking (input, output, cached)
- Provider normalization for 15+ LLM providers
- Support for all GenAI operations (chat, embeddings, etc.)

- **Data Tracking**
- `track_db_operation()` for database operations
- `track_storage_operation()` for object storage (S3, GCS, Azure Blob)
- `track_messaging_operation()` for message queues (SQS, Kafka, Pub/Sub)
- System normalization for 30+ database/storage systems

- **Context Propagation**
- W3C Baggage propagation for cross-service run_id correlation
- Lean mode (default) and full mode propagation options
- `RunContext` model with retry tracking and deadline support

- **Resource Detection**
- Kubernetes (pod, namespace, container)
- AWS (EC2, ECS, Lambda, Fargate)
- GCP (GCE, Cloud Run, Cloud Functions)
- Azure (VM, Container Apps, Functions)

- **Auto-Instrumentation Support**
- HTTP clients: requests, httpx, urllib3, aiohttp
- Web frameworks: FastAPI, Flask, Django, Starlette
- Databases: SQLAlchemy, psycopg2, asyncpg, pymongo, Redis
- Messaging: Celery, Kafka
- GenAI: OpenAI, Anthropic, Vertex AI, Google GenAI, LangChain

- **Optional Extras**
- `[sdk]` - OTel SDK + OTLP exporter
- `[instruments]` - Common library instrumentation
- `[genai]` - GenAI provider instrumentation
- `[carriers]` - Cross-service propagation helpers
- `[all]` - Everything included
- `[dev]` - Development and testing tools

- **Documentation**
- Comprehensive docs in `/docs` following LF format
- Getting started guides
- API reference
- Best practices and anti-patterns

### Dependencies

- Core: `opentelemetry-api >= 1.20.0`
- SDK extra: `opentelemetry-sdk`, `opentelemetry-exporter-otlp-proto-http`
- Python: `>= 3.9`

[Unreleased]: https://github.com/botanu-ai/botanu-sdk-python/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/botanu-ai/botanu-sdk-python/releases/tag/v0.1.0
- **Security**
- OTLP bearer token is attached only when the endpoint host is botanu-owned
(`*.botanu.ai`) or a local dev host, preventing tenant API-key leakage
via a customer-supplied `OTEL_EXPORTER_OTLP_ENDPOINT`.
- Authorization / `x-api-key` / `botanu-api-key` headers and `user:pass@`
URL credentials are redacted in logs.
- **Brownfield OTel coexistence**
- `SampledSpanProcessor` preserves the host app's existing TracerProvider
sampling ratio when botanu is bootstrapped into a project that already
has OTel wired up.
- `register.py` entry point for explicit opt-in without decorator-side
provider mutation.
- Bootstrap detects a pre-configured provider and hands off instead of
overriding it.
- **Content capture for eval**
- Workflow-level input/output capture gated by `content_capture_rate`
config and a shared `content_sampler`. Writes
`botanu.eval.input_content` / `botanu.eval.output_content`.
- `set_input_content()` / `set_output_content()` on `LLMTracker` with the
same gate, plus matching helpers on data-tracking helpers.
- **Multi-step workflows**
- `@botanu_workflow(..., step=...)` parameter (stored in `RunContext`,
not yet emitted to span attributes — kept backward compatible until the
collector servicegraph work lands).
- **Resources**
- `ResourceEnricher` span processor for deployment attributes.
- **Release tooling**
- `scripts/pre_publish_check.py` red/green gate: builds sdist + wheel,
runs `twine check`, installs into a fresh venv, validates the public
API surface, runs an end-to-end decorator + `emit_outcome` smoke test.

### Fixed

- `SampledSpanProcessor.on_start` now gates on the same ratio decision as
`on_end`; forwarding `on_start` unconditionally while gating `on_end`
leaked span bookkeeping inside wrapped exporters (QUAL-C1).

### Initial release contents

Carried forward from the pre-tag scaffolding (never published):

- `enable()` / `disable()` bootstrap, `@botanu_workflow`,
`@botanu_outcome`, `emit_outcome()`, `set_business_context()`,
`RunContextEnricher` — with UUIDv7 run_ids.
- LLM tracking aligned with OTel GenAI semconv: `track_llm_call()`,
`track_tool_call()`, token accounting, 15+ provider normalization.
- Data tracking: `track_db_operation()`, `track_storage_operation()`,
`track_messaging_operation()`; 30+ system normalizations.
- W3C Baggage propagation with `RunContext` (retry tracking + deadline).
- Cloud resource detectors via optional extras (`aws`, `gcp`, `azure`,
`container`, `cloud`).
- Auto-instrumentation bundled in the base install — HTTP clients, web
frameworks, databases, messaging, and GenAI providers; instrumentation
packages no-op when their target library is not installed.

[Unreleased]: https://github.com/botanu-ai/botanu-sdk-python/commits/main
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Botanu SDK for Python

[![CI](https://github.com/botanu-ai/botanu-sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/botanu-ai/botanu-sdk-python/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/botanu)](https://pypi.org/project/botanu/)
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)


Event-level cost attribution for AI workflows, built on [OpenTelemetry](https://opentelemetry.io/).



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
Expand Down
28 changes: 11 additions & 17 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
## Requirements

- Python 3.9 or later
- OpenTelemetry Collector (recommended for production)

## Install

Expand All @@ -18,6 +17,16 @@ One install gives you everything:

Instrumentation packages are lightweight shims that silently no-op when the target library is not installed. Zero bloat.

## Configure

Set your API key as an environment variable. The SDK auto-configures the OTLP endpoint to `ingest.botanu.ai` — no other configuration needed.

```bash
export BOTANU_API_KEY="btnu_live_..."
```

That's it. No collector to run, no infrastructure to deploy. Botanu hosts everything.

## Verify

```python
Expand Down Expand Up @@ -47,6 +56,7 @@ FROM python:3.12-slim
WORKDIR /app
RUN pip install botanu
COPY . .
ENV BOTANU_API_KEY="btnu_live_..."
CMD ["python", "app.py"]
```

Expand All @@ -58,22 +68,6 @@ For running tests and linting:
pip install "botanu[dev]"
```

## Collector Setup

The SDK sends traces to an OpenTelemetry Collector via OTLP HTTP (port 4318). Configure the endpoint via environment variable:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
```

Quick start with Docker:

```bash
docker run -p 4318:4318 otel/opentelemetry-collector:latest
```

See [Collector Configuration](../integration/collector.md) for production setup.

## Next Steps

- [Quickstart](quickstart.md) - Your first instrumented application
Expand Down
Loading
Loading