Back to Opik

Opik Dashboards — Python SDK

sdks/python/examples/dashboard_example.ipynb

2.0.67-7136-merge-228615.2 KB
Original Source

Opik Dashboards — Python SDK

A comprehensive walkthrough of the dashboard API on the opik.Opik client:

StepTopic
1Setup
2–3Create a MULTI_PROJECT dashboard scoped to a project
4Stats-card widgets (snapshot metrics)
5Time-series chart widgets
6Markdown / notes widget
7Update widgets
8Inspect and rearrange the grid layout
9Add sections and move widgets between sections
10Remove widgets
11–12Create an EXPERIMENTS dashboard with evaluation widgets
13Fetch and list dashboards
14Clean up

Project scopeproject_stats_card and project_metrics widgets are project-scoped. Pass project_name to create_dashboard once; the SDK automatically injects the project into every project-scoped widget added via add_widget.

Metric-ID namespaces — easy to mix up:

WidgetFieldNamespaceExample
project_stats_cardmetriclowercase-dottedtrace_count, duration.p50
project_metricsmetric_typeALL-CAPSTRACE_COUNT, DURATION

1 · Setup

python
%pip install opik --quiet
python
import copy

import opik
from opik import dashboard

client = opik.Opik()

PROJECT_NAME = "Default Project"

2 · Create a MULTI_PROJECT dashboard

MULTI_PROJECT dashboards support project_stats_card, project_metrics, and text_markdown widgets. A new dashboard starts with a single Overview section whose id we capture for adding widgets.

python
mp_dash = client.create_dashboard(
    name="SDK comprehensive demo",
    type=dashboard.DashboardType.MULTI_PROJECT,
    description="Created from the Python SDK walkthrough",
    project_name=PROJECT_NAME,
)
mp_section_id = mp_dash.sections[0].id
print(f"Dashboard id : {mp_dash.id}")
print(f"Type         : {mp_dash.type}")
print(f"Scope        : {mp_dash.scope}")
print(f"Section id   : {mp_section_id}")

3 · Project scope

The project_name passed to create_dashboard links the dashboard to a project. The SDK then automatically injects the project into every project-scoped widget (project_stats_card, project_metrics) when you call add_widget — you do not need to repeat the project in the widget config.

python
print(f"Dashboard linked to project: {PROJECT_NAME!r}")

4 · Stats-card widgets

project_stats_card shows a single current-value metric for a project. The metric field uses the lowercase-dotted namespace — see dashboard.StatsCardMetric for the full list (trace counts, duration percentiles, token usage, costs, …).

source selects whether the metric is computed over traces or spans.

python
# Total trace count
sc_trace_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_STATS_CARD,
        title="Traces",
        config=dashboard.ProjectStatsCardConfig(
            source=dashboard.TraceDataType.TRACES,
            metric=dashboard.StatsCardMetric.TRACE_COUNT,
        ),
    ),
)

# Estimated total cost
sc_cost_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_STATS_CARD,
        title="Total cost",
        config=dashboard.ProjectStatsCardConfig(
            source=dashboard.TraceDataType.TRACES,
            metric=dashboard.StatsCardMetric.TOTAL_ESTIMATED_COST_SUM,
        ),
    ),
)

# Median latency (p50)
sc_p50_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_STATS_CARD,
        title="Latency p50",
        config=dashboard.ProjectStatsCardConfig(
            source=dashboard.TraceDataType.TRACES,
            metric=dashboard.StatsCardMetric.DURATION_P50,
        ),
    ),
)

# LLM span count (source=SPANS to query span-level metrics)
sc_llm_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_STATS_CARD,
        title="LLM calls",
        config=dashboard.ProjectStatsCardConfig(
            source=dashboard.TraceDataType.SPANS,
            metric=dashboard.StatsCardMetric.LLM_SPAN_COUNT,
        ),
    ),
)

print(f"Stats cards added, total widgets: {len(mp_dash.sections[0].widgets)}")

5 · Time-series chart widgets

project_metrics renders a time-series for an aggregate metric. The metric_type field uses the ALL-CAPS namespace — see dashboard.ProjectMetricType.

Breakdowns split the series by a dimension: MODEL, PROVIDER, TAGS, NAME, etc. Available chart types: LINE (default), BAR, RADAR.

python
# Line chart: duration over time, broken down by model
chart_duration_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_METRICS,
        title="Duration by model",
        config=dashboard.ProjectMetricsConfig(
            metric_type=dashboard.ProjectMetricType.DURATION,
            chart_type=dashboard.ChartType.LINE,
            breakdown=dashboard.BreakdownConfig(field=dashboard.BreakdownField.MODEL),
        ),
    ),
)

# Bar chart: token usage over time
chart_tokens_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_METRICS,
        title="Token usage",
        config=dashboard.ProjectMetricsConfig(
            metric_type=dashboard.ProjectMetricType.TOKEN_USAGE,
            chart_type=dashboard.ChartType.BAR,
        ),
    ),
)

# Line chart: trace count broken down by tag
chart_count_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_METRICS,
        title="Trace count by tag",
        config=dashboard.ProjectMetricsConfig(
            metric_type=dashboard.ProjectMetricType.TRACE_COUNT,
            chart_type=dashboard.ChartType.LINE,
            breakdown=dashboard.BreakdownConfig(field=dashboard.BreakdownField.TAGS),
        ),
    ),
)

# Line chart: estimated cost broken down by provider
chart_cost_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.PROJECT_METRICS,
        title="Cost by provider",
        config=dashboard.ProjectMetricsConfig(
            metric_type=dashboard.ProjectMetricType.COST,
            chart_type=dashboard.ChartType.LINE,
            breakdown=dashboard.BreakdownConfig(
                field=dashboard.BreakdownField.PROVIDER
            ),
        ),
    ),
)

print(f"Total widgets: {len(mp_dash.sections[0].widgets)}")

6 · Markdown / notes widget

text_markdown renders freeform Markdown — useful for section headers, runbook links, or context notes. It is valid in both MULTI_PROJECT and EXPERIMENTS dashboards.

Widgets can also be created from a raw dict, which is the forward-compatible path for backend fields not yet modelled in the SDK.

python
# Typed config
notes_id = mp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.TEXT_MARKDOWN,
        title="",
        config=dashboard.TextMarkdownConfig(
            content=(
                "## Project overview\n"
                "This dashboard tracks **Default Project** metrics.\n\n"
                "- Duration p50 / p90\n"
                "- Token costs by provider\n"
                "- Error rate"
            )
        ),
    ),
)

# Raw-dict style: forward-compatible with new backend fields
raw_id = mp_dash.add_widget(
    {
        "type": dashboard.WidgetType.TEXT_MARKDOWN.value,
        "title": "Raw dict widget",
        "config": {"content": "Built with the `opik` Python SDK."},
    },
)

print(f"Total widgets: {len(mp_dash.sections[0].widgets)}")
print(f"Notes id : {notes_id}")
print(f"Raw id   : {raw_id}")

7 · Update widgets

update_widget patches only the fields you pass — omitted kwargs are left unchanged. Config is merged, not replaced, so you can change a single key without restating the whole config object.

python
# Change the chart title
mp_dash.update_widget(chart_duration_id, title="Duration by model (ms)")

# Swap the markdown note content (config merge)
mp_dash.update_widget(
    notes_id,
    config={"content": "## Project overview (updated)\nDashboard refreshed via SDK."},
)

# Add a subtitle to the trace-count stats card
mp_dash.update_widget(sc_trace_id, subtitle="last 7 days")

# Switch the token-usage chart from BAR to LINE
mp_dash.update_widget(
    chart_tokens_id,
    config=dashboard.ProjectMetricsConfig(
        metric_type=dashboard.ProjectMetricType.TOKEN_USAGE,
        chart_type=dashboard.ChartType.LINE,
    ),
)

# Rename the dashboard and update its description
mp_dash.rename("SDK comprehensive demo (v2)")
mp_dash.set_description("Updated via the Python SDK.")
print("Name:", mp_dash.name)

8 · Inspect and rearrange the grid layout

The grid is 6 columns wide with unlimited rows. Each widget has a DashboardLayoutItem with x (column), y (row), w (width in columns), h (height in rows).

replace_sections swaps the entire sections list in one call — use it to reposition widgets, resize them, or reorder sections. All other mutators persist immediately after each individual call.

python
section = mp_dash.sections[0]
by_id = {w.id: w for w in section.widgets}

print(f"{'title':35s}  x  y  w  h")
print("-" * 50)
for li in section.layout:
    title = by_id[li.id].title or "(no title)"
    print(f"{title:35s}  {li.x}  {li.y}  {li.w}  {li.h}")
python
# Rearrange: full-width notes banner at the top (row 0),
# four stats cards side-by-side below (row 2),
# charts below that (rows 4+).
new_section = copy.deepcopy(section)

stats_ids = [sc_trace_id, sc_cost_id, sc_p50_id, sc_llm_id]
chart_ids = [chart_duration_id, chart_tokens_id, chart_count_id, chart_cost_id]

for li in new_section.layout:
    if li.id == notes_id:
        # Full-width banner spanning all 6 columns
        li.x, li.y, li.w, li.h = 0, 0, 6, 2
    elif li.id == raw_id:
        # Small note pinned to the top-right
        li.x, li.y, li.w, li.h = 4, 2, 2, 2
    elif li.id in stats_ids:
        col = stats_ids.index(li.id)
        li.x, li.y, li.w, li.h = col, 2, 1, 2
    elif li.id in chart_ids:
        col = chart_ids.index(li.id)
        li.x, li.y, li.w, li.h = (col % 3) * 2, 4 + (col // 3) * 4, 2, 4

mp_dash.replace_sections([new_section])

print("Layout after rearrangement:")
section = mp_dash.sections[0]
by_id = {w.id: w for w in section.widgets}
print(f"{'title':35s}  x  y  w  h")
print("-" * 50)
for li in section.layout:
    title = by_id[li.id].title or "(no title)"
    print(f"{title:35s}  {li.x}  {li.y}  {li.w}  {li.h}")

9 · Add sections and move widgets

add_section appends a new empty section.
To move widgets between sections use replace_sections with the complete new state.

python
analytics_section_id = mp_dash.add_section("Analytics")
print("Sections:", [s.title for s in mp_dash.sections])
python
# Move the four chart widgets from Overview into the new Analytics section.
new_sections = [copy.deepcopy(s) for s in mp_dash.sections]
overview, analytics = new_sections

move_ids = {chart_duration_id, chart_tokens_id, chart_count_id, chart_cost_id}

# Extract chart widgets and their layout entries from Overview
moved_widgets = [w for w in overview.widgets if w.id in move_ids]
moved_layout = [li for li in overview.layout if li.id in move_ids]

overview.widgets = [w for w in overview.widgets if w.id not in move_ids]
overview.layout = [li for li in overview.layout if li.id not in move_ids]

# Re-position charts inside Analytics (2-wide, 4-tall, three per row)
for idx, li in enumerate(moved_layout):
    li.x, li.y, li.w, li.h = (idx % 3) * 2, (idx // 3) * 4, 2, 4

analytics.widgets.extend(moved_widgets)
analytics.layout.extend(moved_layout)

mp_dash.replace_sections(new_sections)

for s in mp_dash.sections:
    print(f"  [{s.title}]  {len(s.widgets)} widget(s)")

10 · Remove widgets

remove_widget removes a widget and its layout entry from whichever section contains it. Raises DashboardValidationError if the ID is not found.

python
# Remove the raw-dict markdown widget
mp_dash.remove_widget(raw_id)

total = sum(len(s.widgets) for s in mp_dash.sections)
print(f"Widgets after removal: {total}")

11 · EXPERIMENTS dashboard

EXPERIMENTS dashboards target evaluation results rather than live traces. Supported widgets: experiments_feedback_scores, experiment_leaderboard, text_markdown.

python
exp_dash = client.create_dashboard(
    name="SDK experiments demo",
    type=dashboard.DashboardType.EXPERIMENTS,
    description="Evaluation metrics overview",
)
exp_section_id = exp_dash.sections[0].id
print(f"Experiments dashboard id: {exp_dash.id}")

12 · Experiments evaluation widgets

experiments_feedback_scores plots feedback score distributions across experiments.
experiment_leaderboard shows a ranked table of runs against a chosen metric.

Pass max_experiments_count (1–100) to control how many recent experiments are included.

python
# Bar chart: feedback scores across the last 10 experiments
fb_bar_id = exp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.EXPERIMENTS_FEEDBACK_SCORES,
        title="Feedback scores (bar)",
        config=dashboard.ExperimentsFeedbackScoresConfig(
            chart_type=dashboard.ChartType.BAR,
            max_experiments_count=10,
        ),
    ),
)

# Radar chart: quality shape across the last 5 experiments
fb_radar_id = exp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.EXPERIMENTS_FEEDBACK_SCORES,
        title="Feedback scores (radar)",
        config=dashboard.ExperimentsFeedbackScoresConfig(
            chart_type=dashboard.ChartType.RADAR,
            max_experiments_count=5,
        ),
    ),
)

# Leaderboard with ranking enabled by a specific feedback-score metric
leaderboard_id = exp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.EXPERIMENT_LEADERBOARD,
        title="Experiment leaderboard",
        config=dashboard.ExperimentLeaderboardConfig(
            enable_ranking=True,
            ranking_metric="hallucination",  # name of the feedback score to rank by
            ranking_direction=True,  # True = descending (higher score is better)
            selected_columns=["dataset_id", "created_at", "duration.p50", "pass_rate"],
            max_rows=20,
        ),
    ),
)

# Context note
exp_dash.add_widget(
    dashboard.DashboardWidget(
        type=dashboard.WidgetType.TEXT_MARKDOWN,
        title="",
        config=dashboard.TextMarkdownConfig(
            content="### About\nTracks evaluation runs ranked by the **hallucination** metric."
        ),
    ),
)

print(f"Experiments dashboard widgets: {len(exp_dash.sections[0].widgets)}")

13 · Fetch and list dashboards

get_dashboard retrieves a single dashboard by ID (re-fetches from the backend).
get_dashboards pages through all dashboards with an optional name filter.

python
# Fetch the multi-project dashboard by id
fetched_mp = client.get_dashboard(mp_dash.id)
print(f"Fetched: {fetched_mp.name!r}  ({len(fetched_mp.sections)} section(s))")

# List all dashboards whose name contains "SDK"
found = client.get_dashboards(name="SDK", max_results=20)
print(f"\nDashboards matching 'SDK' ({len(found)} found):")
for d in found:
    print(f"  {d.id[:8]}{d.type:15s}  {d.name!r}")

14 · Clean up

python
mp_dash.delete()
exp_dash.delete()
print("Both dashboards deleted.")