Groups
Bundle related Python trace steps - tables, charts and other spans - into a named, collapsible section with recount.add_group.
A group span bundles related child spans into a named, collapsible section in the trace view. Use groups when you want to display multiple tables, charts, or other spans together under a single heading.
Basic usage
def build_group(g):
g.span("table", "Current plan", {
"output": recount.table_from_object(
{"plan": "Standard", "rate": "$0.28/kWh", "dailySupply": "$1.10"}
),
})
g.span("table", "Recommended plan", {
"output": recount.table_from_object(
{"plan": "GreenSaver", "rate": "$0.24/kWh", "dailySupply": "$0.95"}
),
})
recount.add_group("Energy plan comparison", build_group)The group renders as a collapsible accordion with its child spans indented inside. The group itself does not count against your span quota - only its child spans do.
recount.add_group(name, builder, options=None)
Creates a group span on the active trace. Must be called inside a start_trace callback.
| Parameter | Type | Description |
|---|---|---|
name | str | Display name for the group |
builder | Callable[[SpanGroup], None] | Callback to add child spans to the group |
options | GroupSpanOptions | Optional group-level options |
A builder that raises still flushes whatever spans it buffered before the error - the exception itself is swallowed.
Options
| Option | Type | Description |
|---|---|---|
description | str | Short description shown when the group is expanded in the dashboard (max 1,000 chars) |
limit | int | Keep only the first N child spans |
sample | int | Randomly sample down to N child spans (reservoir sampling, preserves relative order) |
sampleThreshold | int | Only apply sample if the child count exceeds this value. Defaults to sample |
limit and sample are mutually exclusive - if both are set, sample takes precedence.
Limiting child spans
Use limit to keep only the first N child spans. Useful when iterating over a list but only the leading results matter.
def build_group(g):
for result in results:
g.span("json", result["title"], {"output": result})
recount.add_group("Search results", build_group, {"limit": 10})Sampling child spans
Use sample to keep a random subset of N child spans. Selected spans preserve their original relative order.
def build_group(g):
for item in items:
g.span("json", item["id"], {"output": item})
recount.add_group("Processed items", build_group, {"sample": 20})Use sampleThreshold to only apply sampling when the child count exceeds a given value - useful when the list size varies and you only want to cut down large batches.
# Sample to 20 only if more than 100 items - otherwise keep all
recount.add_group(
"Processed items",
build_group,
{"sample": 20, "sampleThreshold": 100},
)SpanGroup
The g object passed to the builder callback is a SpanGroup. It exposes the same span(), add_message(), and alert() methods as the top-level trace - except you cannot nest groups within groups.
g.span(type_, name, options=None)
Add a child span to the group. Supports all non-group span types.
def build_group(g):
g.span("llm", "Classify intent", {
"input": {"messages": [{"role": "user", "content": "My bill seems too high"}]},
"output": {"response": "billing_query"},
})
g.span("db", "Fetch account history", {
"metadata": {"table": "transactions", "queryType": "sql"},
"input": {
"query": "SELECT * FROM transactions WHERE account_id = $1",
"params": [42],
},
})
g.span("json", "Build response", {
"output": {"recommendation": "Switch to GreenSaver", "savings": "$18.40/mo"},
})
recount.add_group("LLM pipeline", build_group)g.add_message(name, text=None, opts=None)
Shorthand for adding a message child span.
def build_group(g):
g.add_message("Policy check", "Policy is active and in-force")
g.add_message("Coverage check", "Comprehensive cover confirmed")
g.add_message("Excess check", "Excess of $500 applies", {"level": "warn"})
recount.add_group("Validation", build_group)g.alert(label, level, options=None)
Flags the most recently added child span with a warn or error badge. See Alerts for the full API.
def build_group(g):
g.span("json", "Credit check", {"output": {"score": 610}})
if credit_score < 650:
g.alert("Below minimum credit score", "error", {"checks": [{"value": 610}]})
recount.add_group("Risk checks", build_group)Using the SpanGroup class directly
If you need to pass the group builder around or construct it separately, import SpanGroup:
from recount import SpanGroup
def build_comparison_group(g: SpanGroup, plans: list[dict]) -> None:
for plan in plans:
g.span("table", plan["name"], {"output": recount.table_from_object(plan)})
async def trace_fn():
recount.add_group("Plan options", lambda g: build_comparison_group(g, plans))
await recount.start_trace("Compare plans", trace_fn)