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
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,17 @@ Botanu adds **runs** on top of distributed tracing. A run represents a single bu
## Quick Start

```python
from botanu import enable, botanu_use_case, emit_outcome
from botanu import enable, botanu_use_case

enable(service_name="my-app")

@botanu_use_case(name="Customer Support")
async def handle_ticket(ticket_id: str):
# All LLM calls, DB queries, and HTTP requests inside
# are automatically instrumented and linked to this run
context = await fetch_context(ticket_id)
response = await generate_response(context)
emit_outcome("success", value_type="tickets_resolved", value_amount=1)
return response
@botanu_use_case(name="process_order")
def process_order(order_id: str):
order = db.get_order(order_id)
result = llm.analyze(order)
return result
```

That's it. All operations within the use case are automatically tracked.

## Installation

```bash
Expand Down Expand Up @@ -57,14 +52,22 @@ No manual instrumentation required.

## Kubernetes Deployment

For large-scale deployments, use zero-code instrumentation via OTel Operator:
For large-scale deployments (2000+ services):

| Service Type | Code Change | Kubernetes Config |
|--------------|-------------|-------------------|
| Entry point | `@botanu_use_case` decorator | Annotation |
| Intermediate | None | Annotation only |

```yaml
# Intermediate services - annotation only, no code changes
metadata:
annotations:
instrumentation.opentelemetry.io/inject-python: "true"
```

Auto-instrumentation captures all HTTP calls including retries (requests, httpx, aiohttp, urllib3).

See [Kubernetes Deployment Guide](./docs/integration/kubernetes.md) for details.

## Documentation
Expand Down
180 changes: 36 additions & 144 deletions docs/api/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,82 +10,41 @@ from botanu import botanu_use_case
@botanu_use_case(
name: str,
workflow: Optional[str] = None,
*,
environment: Optional[str] = None,
tenant_id: Optional[str] = None,
auto_outcome_on_success: bool = True,
span_kind: SpanKind = SpanKind.SERVER,
)
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | Required | Use case name (e.g., "Customer Support"). Low cardinality for grouping. |
| `workflow` | `str` | Function name | Workflow identifier. Defaults to the decorated function's qualified name. |
| `environment` | `str` | From env | Deployment environment (production, staging, etc.). |
| `tenant_id` | `str` | `None` | Tenant identifier for multi-tenant systems. |
| `auto_outcome_on_success` | `bool` | `True` | Automatically emit "success" outcome if function completes without exception. |
| `span_kind` | `SpanKind` | `SERVER` | OpenTelemetry span kind. |
| `name` | `str` | Required | Use case name for grouping |
| `workflow` | `str` | Function name | Workflow identifier |
| `environment` | `str` | From env | Deployment environment |
| `tenant_id` | `str` | `None` | Tenant identifier for multi-tenant systems |

### Behavior

1. **Generates UUIDv7 `run_id`** - Sortable, globally unique identifier
2. **Creates root span** - Named `botanu.run/{name}`
3. **Emits events** - `botanu.run.started` and `botanu.run.completed`
4. **Sets baggage** - Propagates context via W3C Baggage
5. **Records outcome** - On completion or exception

### Examples

#### Basic Usage

```python
@botanu_use_case("Customer Support")
async def handle_ticket(ticket_id: str):
result = await process_ticket(ticket_id)
emit_outcome("success", value_type="tickets_resolved", value_amount=1)
return result
```

#### With All Parameters
### Example

```python
@botanu_use_case(
name="Document Processing",
workflow="pdf_extraction",
environment="production",
tenant_id="acme-corp",
auto_outcome_on_success=False,
span_kind=SpanKind.CONSUMER,
)
async def process_document(doc_id: str):
...
```

#### Sync Functions
from botanu import botanu_use_case

```python
@botanu_use_case("Batch Processing")
def process_batch(batch_id: str):
# Works with sync functions too
return process_items(batch_id)
@botanu_use_case(name="process_order")
def process_order(order_id: str):
order = db.get_order(order_id)
result = llm.analyze(order)
return result
```

### Span Attributes

The decorator sets these span attributes:

| Attribute | Source |
|-----------|--------|
| Attribute | Description |
|-----------|-------------|
| `botanu.run_id` | Generated UUIDv7 |
| `botanu.use_case` | `name` parameter |
| `botanu.workflow` | `workflow` parameter or function name |
| `botanu.workflow_version` | SHA256 hash of function source |
| `botanu.environment` | `environment` parameter or env var |
| `botanu.tenant_id` | `tenant_id` parameter (if provided) |
| `botanu.parent_run_id` | Parent run ID (if nested) |
| `botanu.environment` | Deployment environment |
| `botanu.tenant_id` | Tenant identifier (if provided) |

### Alias

Expand All @@ -94,115 +53,48 @@ The decorator sets these span attributes:
```python
from botanu import use_case

@use_case("My Use Case")
async def my_function():
...
@use_case(name="process_order")
def process_order(order_id: str):
return db.get_order(order_id)
```

---

## @botanu_outcome

Convenience decorator for sub-functions to emit outcomes based on success/failure.
Decorator for sub-functions to emit outcomes based on success/failure.

```python
from botanu import botanu_outcome

@botanu_outcome(
success: Optional[str] = None,
partial: Optional[str] = None,
failed: Optional[str] = None,
)
@botanu_outcome()
def extract_data():
return fetch_from_source()
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `success` | `str` | `None` | Custom label for success outcome (reserved for future use). |
| `partial` | `str` | `None` | Custom label for partial outcome (reserved for future use). |
| `failed` | `str` | `None` | Custom label for failed outcome (reserved for future use). |

### Behavior

- **Does NOT create a new run** - Works within an existing run
- **Emits "success"** if function completes without exception
- **Emits "failed"** with exception class name if exception raised
- **Skips emission** if outcome already set on current span
- Emits "success" on completion
- Emits "failed" with exception class name if exception raised
- Does NOT create a new run

### Example

```python
from botanu import botanu_use_case, botanu_outcome

@botanu_use_case("Data Pipeline")
async def run_pipeline():
await extract_data()
await transform_data()
await load_data()
@botanu_use_case(name="data_pipeline")
def run_pipeline():
extract_data()
transform_data()
load_data()

@botanu_outcome()
async def extract_data():
# Emits "success" on completion
return await fetch_from_source()
def extract_data():
return fetch_from_source()

@botanu_outcome()
async def transform_data():
# Emits "failed" with reason if exception
return await apply_transformations()
```

---

## Function Signatures

### Async Support

Both decorators support async and sync functions:

```python
# Async
@botanu_use_case("Async Use Case")
async def async_handler():
await do_work()

# Sync
@botanu_use_case("Sync Use Case")
def sync_handler():
do_work()
```

### Return Values

Decorated functions preserve their return values:

```python
@botanu_use_case("Processing")
async def process(data) -> ProcessResult:
return ProcessResult(status="complete", items=100)

result = await process(data)
assert isinstance(result, ProcessResult)
```

### Exception Handling

Exceptions are recorded and re-raised:

```python
@botanu_use_case("Risky Operation")
async def risky():
raise ValueError("Something went wrong")

try:
await risky()
except ValueError:
# Exception is re-raised after recording
pass
def transform_data():
return apply_transformations()
```

## See Also

- [Quickstart](../getting-started/quickstart.md) - Getting started
- [Run Context](../concepts/run-context.md) - Understanding runs
- [Outcomes](../tracking/outcomes.md) - Recording outcomes
- [Quickstart](../getting-started/quickstart.md)
- [Run Context](../concepts/run-context.md)
Loading
Loading