Documentation Index
Fetch the complete documentation index at: https://braintrust.dev/docs/llms.txt
Use this file to discover all available pages before exploring further.
Applies to:
- Plan -
- Deployment -
Summary
Goal: Query per-user and per-prompt token distribution across traces using the two-level GROUP BY pattern. Features:project_logs(), any_value(), estimated_cost(), shape => 'traces', timestamp filters.
Configuration steps
Step 1: Understand span data layout
Token metrics (metrics.prompt_tokens, metrics.completion_tokens) live on LLM-type spans. User and prompt metadata (e.g., metadata.user_id, metadata.model) may live on the root span. A single span row cannot see both. The two-level query below bridges them.
Step 2: Write the inner query (group by trace)
Group byroot_span_id to collapse all spans in a trace. Use any_value() to surface root-span metadata fields within that group.
Step 3: Wrap with outer query (group by user or model)
Step 4: Use the correct shape parameter
| Shape | Use case |
|---|---|
| (default) | One row per span |
shape => 'traces' | All spans returned when any span matches a filter; enables inner GROUP BY on root_span_id |
shape => 'summary' | Pre-aggregated trace-level metrics; useful for rollups without a subquery |
Step 5: Use estimated_cost() not metrics.estimated_cost
estimated_cost() is a function that computes cost from token counts and the model registry. Use it in span-level queries. Aggregating the pre-computed metrics.estimated_cost field at a second level of grouping is a known bug.
Step 6: Add a timestamp filter to avoid timeouts
Queries without acreated filter scan the full table and will time out on large projects.
Step 7: Work around cross-span JOIN limitations
JOIN, UNION, and subqueries across different project_logs() calls are unsupported. Use these alternatives instead:
shape => 'traces'— returns all spans in a trace when any span matches; useany_value()to pull fields from other span typesshape => 'summary'— returns one pre-aggregated row per trace with total token counts and cost
Step 8: Handle Loop query generation limitations
Loop does not know aboutany_value() or the two-level GROUP BY pattern. For nuanced schemas:
- Run Loop with a 1-hour window to generate a starting query.
- Paste the output into the SQL sandbox.
- Edit by hand — add
any_value(), the inner/outer GROUP BY, and a tightcreatedfilter.