Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bentolabs.ai/llms.txt

Use this file to discover all available pages before exploring further.

import bentolabs_sdk.analytics as bento

bento.track_ai(
    event="user_message",
    user_id="user_42",
    input="What's the capital of France?",
    output="Paris.",
    model="claude-3-5-sonnet-20241022",
    provider="anthropic",
    convo_id="conv_abc",
)
Each call ships one OTel span to BentoLabs. The span lands in your dashboard within seconds.

Signature

event
str
required
Free-form event name. Becomes span.name in OTel and the row title in the dashboard.
user_id
str
Your stable user identifier. Pass-through string only, no profile data is stored.
convo_id
str
Conversation or session ID. Same value across all turns in one conversation links them in the timeline view.
model
str
Model identifier, e.g. claude-3-5-sonnet-20241022 or gpt-4o. Required for cost view and model breakdowns.
provider
str
Provider key. One of openai, anthropic, google, aws_bedrock, etc. Not auto-inferred from model name.
input
str | dict | list
The prompt or input. Strings pass through; dicts and lists are JSON-serialized.
output
str | dict | list
The model output. Same serialization rules as input.
properties
dict
Arbitrary custom dimensions. See Properties.
Returns: the span ID as a 16-character hex string. Useful for logging or correlating with downstream systems.

Multi-turn conversations

Pass the same convo_id on every turn:
convo = "conv_2026-05-10_user42"

bento.track_ai(event="user_msg", input="Hi", convo_id=convo, user_id="u1")
bento.track_ai(event="assistant_msg", output="Hello!", convo_id=convo, user_id="u1")
bento.track_ai(event="user_msg", input="And then?", convo_id=convo, user_id="u1")

Structured chat messages

Dicts and lists are JSON-serialized into input.value / output.value:
bento.track_ai(
    event="chat",
    input=[
        {"role": "system", "content": "You are helpful."},
        {"role": "user", "content": "Hi"},
    ],
    output={"role": "assistant", "content": "Hello!"},
    user_id="u1",
)

Parenting

track_ai calls are root spans by default. They detach from any caller’s OTel context, so a track_ai inside a customer’s FastAPI or Django span doesn’t get pulled into that trace. When you call track_ai inside a trajectory opened with bento.begin(...), the call becomes a child of the trajectory span instead.
This isolation is intentional. Without it, a track_ai inside a customer’s instrumented stack would ship a “child-only” OTLP batch and the ingest mapper would drop trace-level fields.