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.

Attributes are how you slice everything in the dashboard: filter by user, break down spend by model, segment by your own experiment ID. Some are built into the SDK (user_id, model, convo_id); the rest you define yourself with properties={...}. If you skip a kwarg, the dashboard column behind it stays empty for that call.

The built-in fields

KwargOTel attributeColumnSkip it and…
event (required)span.nameSpan name(required)
user_idgen_ai.user.idUserNo user filter
convo_idgen_ai.conversation.idSessionTurns look standalone
modelgen_ai.request.modelModelNo cost or model breakdown
providergen_ai.systemProviderNo provider filter
inputinput.valueInputEmpty input column
outputoutput.valueOutputEmpty output column
properties={...}each key, as-isAttributes JSONNo custom dimensions
The mapping is the same for track_ai, begin, interaction.update, interaction.finish, and tool_span. Unpassed kwargs are omitted from the span, not set to null.

Why each one matters

A model name like claude-3-5-sonnet is not auto-tagged as Anthropic. Bedrock model IDs (anthropic.claude-3-...) are ambiguous on purpose. Pass provider= explicitly: "openai", "anthropic", "google", "aws_bedrock", etc.
Without convo_id, multi-turn conversations look like N independent requests. See Sessions and users.
Required to filter the trace list by user. We do not store profile data (no email, no name, no traits). user_id is a pass-through string only.
The cost view and per-model breakdowns key off gen_ai.request.model. Without it, spend rolls up under “Unknown”.

Custom attributes via properties

Use properties for anything you want to filter or aggregate on that isn’t covered above.
bento.track_ai(
    event="answer",
    user_id="user_42",
    model="gpt-4o",
    provider="openai",
    input=question,
    output=answer,
    properties={
        "experiment_id": 17,
        "is_premium": True,
        "feature_flags": ["new_planner", "fast_path"],
        "latency_budget_ms": 1500,
    },
)
Each key in properties becomes a top-level OTel attribute with the same name. No namespace is added.
Don’t put gen_ai.*, input.value, output.value, or openinference.span.kind in properties. SDK kwargs are written after properties and will silently overwrite them. Use the dedicated kwargs.

Type fidelity

Property values keep their type when possible, so downstream filters and aggregates work.
You passSpan seesNotes
"foo" (str)string
42 (int)intfilterable: experiment_id > 100
3.14 (float)float
True (bool)boolfilterable: is_premium = true
[1, 2, 3] (homogeneous)array
{"nested": "x"} (dict)JSON stringOTel attributes are flat
[1, "two"] (mixed list)JSON stringOTel can’t store heterogeneous arrays
The JSON-string fallback is lossless but won’t be filterable as a number or bool. Flatten dicts to multiple keys (user.tier, user.region) if you need numeric filters.

Span kind

The kind of a span lives in openinference.span.kind. The SDK sets it for you:
APIKind
bento.track_ai(...)(unset, treated as LLM call)
bento.tool_span(...), @bento.tool"tool"
interaction.tool_span(...)"tool"
OpenInference defines other kinds (retriever, embedding, agent, chain). Set them through properties:
bento.track_ai(
    event="vector_search",
    input=query,
    output=results,
    properties={"openinference.span.kind": "retriever"},
)

What an emitted span looks like

The OTel JSON for a track_ai call with properties={"experiment_id": 17}:
{
  "name": "answer",
  "attributes": {
    "gen_ai.user.id": "user_42",
    "gen_ai.conversation.id": "chat_99",
    "gen_ai.request.model": "gpt-4o",
    "gen_ai.system": "openai",
    "input.value": "...",
    "output.value": "...",
    "experiment_id": 17
  }
}
The same attributes show up in any OTel backend you wire this into. Nothing about your code is BentoLabs-specific.

Common mistakes

1

Forgetting provider

Half your dashboard filters go unused. Pass it on every call.
2

Forgetting convo_id

Conversations fragment into single-turn rows.
3

Putting gen_ai.* keys in properties

SDK kwargs overwrite them after properties are applied.
4

Bedrock model IDs with provider=anthropic

Bedrock routes through AWS. Use provider="aws_bedrock".
5

No flush() in short scripts

The exporter batches. Call bento.flush() (or bento.shutdown()) before a short-lived process exits.