Record business outcomes to enable cost-per-outcome analysis.
Outcomes connect infrastructure costs to business value. By recording what was achieved (tickets resolved, documents processed, leads qualified), you can calculate the true ROI of your AI workflows.
from botanu import botanu_use_case, emit_outcome
@botanu_use_case("Customer Support")
async def handle_ticket(ticket_id: str):
# ... process ticket ...
# Record the business outcome
emit_outcome("success", value_type="tickets_resolved", value_amount=1)emit_outcome(
status: str, # Required: "success", "partial", "failed"
value_type: str = None, # What was achieved
value_amount: float = None, # How much
confidence: float = None, # Confidence score (0.0-1.0)
reason: str = None, # Why (especially for failures)
)The outcome status:
| Status | Description | Use Case |
|---|---|---|
success |
Fully achieved goal | Ticket resolved, document processed |
partial |
Partially achieved | 3 of 5 items processed |
failed |
Did not achieve goal | Error, timeout, rejection |
A descriptive label for what was achieved:
emit_outcome("success", value_type="tickets_resolved", value_amount=1)
emit_outcome("success", value_type="documents_processed", value_amount=5)
emit_outcome("success", value_type="leads_qualified", value_amount=1)
emit_outcome("success", value_type="revenue_generated", value_amount=499.99)The quantified value:
# Count
emit_outcome("success", value_type="emails_sent", value_amount=100)
# Revenue
emit_outcome("success", value_type="order_value", value_amount=1299.99)
# Score
emit_outcome("success", value_type="satisfaction_score", value_amount=4.5)For probabilistic outcomes:
emit_outcome(
"success",
value_type="intent_classified",
value_amount=1,
confidence=0.92,
)Explain the outcome (especially for failures):
emit_outcome("failed", reason="rate_limit_exceeded")
emit_outcome("failed", reason="invalid_input")
emit_outcome("partial", reason="timeout_partial_results", value_amount=3)@botanu_use_case("Order Processing")
async def process_order(order_id: str):
order = await fetch_order(order_id)
await fulfill_order(order)
emit_outcome(
"success",
value_type="orders_fulfilled",
value_amount=1,
)@botanu_use_case("Sales Bot")
async def handle_inquiry(inquiry_id: str):
result = await process_sale(inquiry_id)
if result.sale_completed:
emit_outcome(
"success",
value_type="revenue_generated",
value_amount=result.order_total,
)
else:
emit_outcome(
"partial",
value_type="leads_qualified",
value_amount=1,
)@botanu_use_case("Batch Processing")
async def process_batch(items: list):
processed = 0
for item in items:
try:
await process_item(item)
processed += 1
except Exception:
continue
if processed == len(items):
emit_outcome("success", value_type="items_processed", value_amount=processed)
elif processed > 0:
emit_outcome(
"partial",
value_type="items_processed",
value_amount=processed,
reason=f"processed_{processed}_of_{len(items)}",
)
else:
emit_outcome("failed", reason="no_items_processed")@botanu_use_case("Document Analysis")
async def analyze_document(doc_id: str):
try:
document = await fetch_document(doc_id)
if not document:
emit_outcome("failed", reason="document_not_found")
return None
result = await analyze(document)
emit_outcome("success", value_type="documents_analyzed", value_amount=1)
return result
except RateLimitError:
emit_outcome("failed", reason="rate_limit_exceeded")
raise
except TimeoutError:
emit_outcome("failed", reason="analysis_timeout")
raise@botanu_use_case("Intent Classification")
async def classify_intent(message: str):
result = await classifier.predict(message)
emit_outcome(
"success",
value_type="intents_classified",
value_amount=1,
confidence=result.confidence,
)
return result.intentThe @botanu_use_case decorator automatically emits outcomes:
@botanu_use_case("My Use Case", auto_outcome_on_success=True) # Default
async def my_function():
# If no exception and no explicit emit_outcome, emits "success"
return resultIf an exception is raised, it automatically emits "failed" with the exception class as the reason.
To disable:
@botanu_use_case("My Use Case", auto_outcome_on_success=False)
async def my_function():
# Must call emit_outcome explicitly
emit_outcome("success")For sub-functions within a use case:
from botanu import botanu_use_case, botanu_outcome
@botanu_use_case("Data Pipeline")
async def run_pipeline():
await step_one()
await step_two()
@botanu_outcome()
async def step_one():
# Emits "success" on completion, "failed" on exception
await process_data()
@botanu_outcome(success="data_extracted", failed="extraction_failed")
async def step_two():
# Custom outcome labels
await extract_data()Outcomes are recorded as span attributes:
| Attribute | Description |
|---|---|
botanu.outcome |
Status (success/partial/failed) |
botanu.outcome.value_type |
What was achieved |
botanu.outcome.value_amount |
Quantified value |
botanu.outcome.confidence |
Confidence score |
botanu.outcome.reason |
Reason for outcome |
An event is also emitted for timeline visibility:
# Event: botanu.outcome_emitted
# Attributes:
# status: "success"
# value_type: "tickets_resolved"
# value_amount: 1With outcomes recorded, you can calculate:
-- Cost per successful ticket resolution
SELECT
AVG(total_cost) as avg_cost_per_resolution
FROM runs
WHERE use_case = 'Customer Support'
AND outcome_status = 'success'
AND outcome_value_type = 'tickets_resolved';
-- ROI by use case
SELECT
use_case,
SUM(outcome_value_amount * value_per_unit) as total_value,
SUM(total_cost) as total_cost,
(SUM(outcome_value_amount * value_per_unit) - SUM(total_cost)) / SUM(total_cost) as roi
FROM runs
GROUP BY use_case;Every use case should emit an outcome:
@botanu_use_case("My Use Case")
async def my_function():
try:
result = await do_work()
emit_outcome("success", value_type="items_processed", value_amount=result.count)
return result
except Exception as e:
emit_outcome("failed", reason=type(e).__name__)
raiseDefine standard value types for your organization:
# Good - consistent naming
emit_outcome("success", value_type="tickets_resolved", value_amount=1)
emit_outcome("success", value_type="documents_processed", value_amount=1)
# Bad - inconsistent
emit_outcome("success", value_type="ticket_done", value_amount=1)
emit_outcome("success", value_type="doc processed", value_amount=1)Include amounts for better analysis:
# Good - quantified
emit_outcome("success", value_type="emails_sent", value_amount=50)
# Less useful - no amount
emit_outcome("success")Always explain why something failed:
emit_outcome("failed", reason="api_rate_limit")
emit_outcome("failed", reason="invalid_input_format")
emit_outcome("failed", reason="model_unavailable")Emit only one outcome per use case execution:
@botanu_use_case("Process Items")
async def process_items(items):
successful = 0
for item in items:
if await process(item):
successful += 1
# One outcome at the end
emit_outcome("success", value_type="items_processed", value_amount=successful)- Run Context - Understanding runs
- LLM Tracking - Tracking LLM costs
- Best Practices - More patterns