SuperAPI AI Directory

opik-explain

Agents & models

View repository

Updated 10-15 20:14

About this skill

Explain — Root-Cause an Opik Trace and Ground It in the Code

Definition of done: a grounded root cause for the requested trace (or pattern), tied to specific evidence spans and paired with exactly one suggested next step. "Grounded" means the explanation names the failing/anomalous span and connects it to the code or data that produced it — not a restatement of the trace. If the target can't be fetched or read, stop at the first genuine blocker and return one concrete next step. A trace dump is not an explanation.

Operate: investigate over the real trace data, reason against the repo, commit to a single most-likely root cause with its evidence — and change no code. This skill is read-only by design.

Inputs

The entry point is /opik-explain <trace-id> (one trace) or /opik-explain <describe the behavior> (a pattern to find and explain). Infer the rest; treat these as optional overrides:

  • project name (default: inferred from config/repo) · time window for a pattern (default: recent) · a known-good trace to compare against.

Ask only at a genuine, non-inferable blocker (see Blockers).

Activation — the only in-scope work

1. Resolve the target

  • A trace id (uuid-shaped): explain that one trace.
  • A behavior/pattern ("hallucinations since the prompt change", "slow responses"): search for the matching set (below), then explain the shared cause.
  • Confirm Opik is reachable: if ~/.opik.config exists or OPIK_API_KEY is set, use it. Otherwise → Blocker ("run opik configure, then rerun").

2. Fetch the trace and spans — MCP first, SDK fallback

Check whether the hosted Opik MCP is connected and prefer it; fall back to SDK scripting when it isn't.

  • MCP connected: use the MCP to read the trace and list/read its spans.
  • No MCP: fall back to the SDK.

Either way, read every span's input/output/error/duration.

import opik
client = opik.Opik()

tid = "<trace_id>"
trace = client.get_trace_content(tid)       # TracePublic: exposes project_id, input, output, error info — NOT project_name (accessing .project_name raises)
project = client.rest_client.projects.get_project_by_id(trace.project_id).name
spans = client.search_spans(project_name=project, trace_id=tid)   # spans come from a SEPARATE call, not from the trace object
# ALWAYS pass project_name: without it the SDK searches the configured default project, which
# returns an empty list (or a 404 if that project doesn't exist) even for a valid trace id.
# Reconstruct the tree via each span's parent_span_id (the root span has none).
# Your anchor is the first span that errored, returned wrong output, or dominates the duration.

For a pattern, pull the matching set scoped to the project, then look for the shared failing span across them. With the MCP, one list call does the filtering and ordering server-side — filters is an OQL string, sort is "<field> [asc|desc]", since takes "1h" / "7d":

list(entity_type="trace", project_name="<project>", since="7d",
     filters="error_info is_not_empty", sort="start_time desc")        # error traces
list(entity_type="trace", project_name="<project>", since="7d",
     sort="duration desc")                                             # slow traces (duration in ms)
list(entity_type="trace", project_name="<project>", since="7d",
     filters="feedback_scores.hallucination > 0.5")                    # low-scored traces
list(entity_type="span",  project_name="<project>", since="7d",
     filters='name = "<span name>" AND error_info is_not_empty')       # the shared failing span, across traces

The applied filter is echoed on the first line; a rejected one comes back with what fixes it (schema("list.trace") is the full field reference). Without the MCP, the SDK takes the same grammar:

traces = client.search_traces(project_name="<project>", filter_string="error_info is_not_empty")

Traces are asynchronous; if you just produced the trace, allow a few seconds and confirm the flush ran.

3. Root-cause it

The coding agent root-causes over the fetched data, against the repo — it has the one thing a generic reasoner lacks: the code. Whether the trace came from the MCP or the SDK, the analysis is the same: find the anchor span (error / wrong output / latency dominator), read its input and output, connect it to the code (grep the repo for the span name / function), and state the single most-likely root cause with its evidence spans. Prefer one well-evidenced cause over a list of maybes.

4. Report

Return the root cause, the evidence spans as clickable Opik UI links (the trace redirect URL Opik emits, e.g. .../session/redirect/...?trace_id=THE_ID — never a bare id), and one next step (see Output). If a fix is obvious, name it as the next step; do not apply it (this skill changes no code — handing off to opik-instrument/opik-test or the developer is the next step).

Blockers

Stop at the earliest blocker and return exactly one next step:

  • "Run opik configure, then rerun /opik-explain <trace-id>."
  • "No trace found for <id> in project <name> — confirm the id and project, then rerun."
  • "This environment can't reach Opik — open the trace in the UI and paste its error/output, or run where Opik is configured."
  • "Which behavior should I explain? Give a trace id or describe what went wrong."

Output

User-facing: a short human message — the root cause in one or two sentences, the evidence spans as clickable Opik UI links (name + why each matters), and the single next step. Not a raw trace dump, not JSON.

Underneath (for composition / evals), one shape whether the MCP or the SDK path produced it:

  • status: explained | blocked | not_found
  • target: trace_id + trace_url (the Opik UI link; for a pattern, the trace_ids + trace_urls sampled)
  • root_cause: one grounded statement
  • evidence: spans (each: name, type, a trace_url deep-link where available, why it's evidence)
  • next_step: exactly one
  • reasoner: agent (the coding agent root-causes over the trace data and the repo)

Invariants: explained must carry a root_cause and at least one evidence span; blocked/not_found carry exactly one next_step; every path leaves the codebase unchanged.

Examples

Single trace — tool failure. /opik-explain 019fd8a7-.... Fetch trace + spans; the retrieve (tool) span returned empty and the llm span then hallucinated. Open retrieve() in the repo: the query filter is wrong. Root cause = the retrieval filter, evidence = the empty tool span feeding the llm span; next step = "fix the filter in retrieve() (or /opik-test it)". → explained.

Pattern — slowness. /opik-explain why responses got slow this week. list('trace', since="7d", sort="duration desc") (or search_traces without the MCP) for the slowest traces; the same external tool span dominates each. Root cause = that call's latency; evidence = the shared slow span across N traces; next step = "add a timeout/cache around it". → explained.

Blocked — bad id. /opik-explain 123. get_trace_content finds nothing. → not_found: "No trace 123 in project X — confirm the id/project and rerun." (No code touched.)

Anti-patterns

Dumping the span tree without naming a cause; guessing a cause without reading the anchor span's input/output; listing five maybes instead of the one best-evidenced cause; returning bare span/trace ids instead of clickable Opik UI links; editing code (this skill explains; opik-instrument/opik-test or the developer make changes); calling .project_name on a TracePublic (it raises — use project_id); calling search_spans(trace_id=…) without project_name (it searches the default project and comes back empty).

References

SDK and observability detail live in the opik skill, installed beside this one. Read the files directly — paths are relative to this file: ../opik/SKILL.md (Searching traces — the OQL filter grammar shared by the MCP list tool and search_traces), ../opik/references/production.md (search_traces, error/latency/cost analysis), ../opik/references/tracing-python.md (SDK read APIs), ../opik/references/observability.md (span-type model). If your host lays skills out differently, locate the opik skill's references/ directory.

If the opik skill isn't installed, say so in the report and use https://www.comet.com/docs/opik/ rather than working from memory.