bento.begin(...) or the @bento.interaction decorator. Inside it sit spans. A span is one step within a trajectory: an LLM call, a tool call, or anything you choose to time. LLM calls come from bento.track_ai(...); tool calls come from bento.tool_span(...) or the @bento.tool decorator.
An attribute is a typed key/value pair on a span, and it becomes a column or filter in the dashboard. You set attributes through the kwargs on the calls above, plus properties={...} for anything custom. A session is every trajectory that shares a convo_id. It’s a server-side grouping, not an SDK object: pass the same convo_id on every call and Bento ties the runs together for you.
Under the hood these are OpenTelemetry spans, carrying OpenInference and GenAI semantic conventions. You can ignore that unless you’re wiring Bento into an existing OTel pipeline.
How they nest
A session holds the trajectories from one conversation, and each trajectory holds its spans:track_ai call with no surrounding begin is its own one-span trajectory. You don’t have to open a trajectory to record an LLM call.
What gets emitted
Every manual call produces one OTel span. Spans captured by the Google ADK integration (bento.instrument()) land in the same columns through OpenInference semantic conventions, and you write none of the per-call code in that case. The attributes below land in dashboard columns. Everything else lives in the per-span attributes JSON.
See Attributes for the per-kwarg breakdown.
Span kinds
Theopeninference.span.kind attribute drives icons, colors, and filters in the dashboard.
OpenInference defines other kinds (
retriever, embedding, agent, chain). Set them by passing openinference.span.kind in properties.
Sessions are server-side
There is noSession object in the SDK. A session is whatever trajectories share the same convo_id string.
user_id works the same way: a free-form string passed on every call. We don’t store profile data.
See Sessions and users for ID-choosing rules.