--- name: opik description: Reference for the Opik SDK — tracing, span types, framework integrations, threads, and the prompt library (Python, TypeScript, REST). Use for "what span types exist", "how do I flush", "track_openai", "add OpikTracer", "version a prompt". To instrument a repo end to end, use the `opik-instrument` skill. compatibility: Tested with Claude Code; works with any Agent Skills-compatible host (Cursor, VS Code Copilot, Codex). A reference — needs no Opik connection to read; the snippets assume the `opik` Python or TypeScript SDK 2.x. The task-shaped skills (opik-instrument, opik-diagnose, opik-explain, opik-test, opik-compare, opik-evaluate, opik-online-eval, opik-optimize, opik-verify) read this skill's references and expect it installed beside them. allowed-tools: - Read - Grep - Glob metadata: last_updated: "2026-09-17" source_commit: "2.0.0" --- # Opik SDK Reference Opik is an open-source LLM observability platform. This skill is a **reference** for the SDK. To instrument a codebase step by step (detect frameworks, add config, emit and verify a trace), use the task-shaped `opik-instrument` skill. ## Core concepts A trace is one execution path (one request → one response). Spans are the operations inside it and form a hierarchy. ### Span types — the ONLY valid values | Type | Use for | |------|---------| | `general` | orchestration, agent entry points | | `llm` | model calls | | `tool` | tools, retrieval, API / DB calls | | `guardrail` | safety / validation checks | Do NOT use `retrieval` or any other value. ## Python — tracing ```python import opik @opik.track(name="agent", type="general") def agent(query: str) -> str: return generate(retrieve(query)) @opik.track(type="tool") def retrieve(query): ... @opik.track(type="llm") def generate(ctx): ... opik.flush_tracker() # required in scripts ``` ## TypeScript — tracing ```typescript import { Opik } from "opik"; const client = new Opik({ projectName: "my-project" }); const trace = client.trace({ name: "agent", input: { query } }); const span = trace.span({ name: "llm-call", type: "llm" }); span.end({ output }); trace.end({ output }); await client.flush(); ``` ## Framework integrations Prefer an integration over manual `@opik.track` — integrations capture tokens, model, and cost automatically. Patterns (full list in `references/integrations.md`): - **wrap-the-client** — `track_openai(OpenAI())`, `track_anthropic(...)` - **global-enable** — `track_crewai(crew=crew)` - **callback** — `dspy.configure(callbacks=[OpikCallback()])` - **tracer** — `OpikTracer()` for LangChain / LangGraph / LlamaIndex - **agent-specific** — `track_adk_agent_recursive(agent, OpikTracer())` ### LiteLLM inside `@opik.track` (common trap) If code uses `litellm` **and** you add `@opik.track`, pass `current_span_data` via metadata on every completion call — otherwise `OpikLogger` emits **orphaned** top-level traces instead of nesting under your span. ```python from opik.opik_context import get_current_span_data @opik.track def call_llm(messages): return litellm.completion( model="gpt-4o", messages=messages, metadata={"opik": {"current_span_data": get_current_span_data()}}, ) ``` ## Threads (conversations) Group turns with `thread_id` — one turn = one trace, shared `thread_id` = one thread. Use for chat / multi-turn; skip for single-shot. ```python @opik.track(entrypoint=True) def handle(session_id: str, message: str) -> str: opik.update_current_trace(thread_id=session_id) return reply(message) ``` ## Prompt library Version prompts with `client.get_prompt` / `create_prompt` (chat variants: `get_chat_prompt` / `create_chat_prompt`). Store model + temperature in the prompt `metadata` so they version with the text. Call `get_prompt` **inside** a `@opik.track` function so the version links to the trace. ```python @opik.track(entrypoint=True) def run(question: str) -> str: p = client.get_prompt(name="system") or client.create_prompt( name="system", prompt="You help with {{product}}.", metadata={"model": "gpt-4o", "temperature": 0.7}, ) return llm(p.format(product="Opik"), model=p.metadata["model"]) ``` ## How a project is doing With the MCP connected, start at the project, not at its traces: ``` read("project", "") ``` One call returns the last 7 days against the 7 before — trace count, error rate, average duration, total cost, SDK traffic only, which is what the Logs page's four cards show — plus the score names and usage keys the project actually records, and the freshest experiment, dataset, prompt version and optimization run in it. `since`/`until` pick another window; `since="30d"` is what the UI opens on. A rate or an average over a window with no traces comes back `null` rather than `0`, because a rate over no samples is undefined and "0% errors" is advice someone may act on. Then attribute the change rather than restating it: ``` list("project_metric", project_name="", metric_type="trace_cost") list("project_metric", project_name="", metric_type="span_count", breakdown="model", since="30d") ``` Rows are time buckets, not records — `interval` is `hourly`/`daily`/`weekly`/ `total`, and `page`/`size`/`sort` do not apply. `schema("list.project_metric")` is the metric list, what each is about, and which groupings each accepts; seven of them accept none. The score names the overview returned are the ones worth filtering on, and `list("score_name", project_name=…)` has the rest. ## Searching traces One filter grammar, OQL, serves both the hosted MCP's `list` tool and the SDK's `search_traces` / `search_spans` / `search_threads`: ``` [.] [AND ...] ops: = != > >= < <= contains not_contains starts_with ends_with is_empty is_not_empty in not_in ``` Strings in double quotes, numbers bare, `duration` in **milliseconds**, dates as ISO-8601 instants with a timezone (`"2026-09-08T10:00:00Z"`). Scores and dictionaries take a key: `feedback_scores.accuracy < 0.5`, `metadata.environment = "prod"`. `AND` is the only connector. ``` error_info is_not_empty AND duration > 5000 type = "llm" AND usage.total_tokens > 10000 # spans feedback_scores.hallucination > 0.5 AND start_time >= "2026-09-08T00:00:00Z" ``` With the MCP connected, prefer `list` — it also sorts (`sort="duration desc"`), windows (`since="1h"`, `"7d"`), and searches free text (`search="order-42"`): ``` list(entity_type="trace", project_name="", since="1h", filters="error_info is_not_empty", sort="duration desc") ``` Trace, span and thread lists add `source = "sdk"` unless you name `source`, so evaluator, playground and experiment traces stay out of the way. A rejected filter comes back with what fixes it; `schema("list.trace")` (or `list.span`, `list.thread`, `list.experiment`) is the full field and operator reference. Without the MCP, the same string goes to the SDK: ```python client.search_traces(project_name="", filter_string="error_info is_not_empty") ``` ## Anti-patterns | Anti-pattern | Fix | |--------------|-----| | span type `retrieval` / custom | use `tool` (or `general`) | | `get_prompt` outside `@opik.track` | fetch inside — else no trace link | | deprecated `opik.Prompt` / `opik.Config` | use `client.get_prompt` / config file | | `litellm` without `current_span_data` | pass it — else orphaned traces | | no flush in scripts | `opik.flush_tracker()` / `await client.flush()` | ## References | Topic | File | |-------|------| | Python SDK (async, distributed, context) | `references/tracing-python.md` | | TypeScript SDK | `references/tracing-typescript.md` | | REST API | `references/tracing-rest-api.md` | | All integrations | `references/integrations.md` | | Core concepts (traces, spans, threads) | `references/observability.md` | | Best practices (lifecycle, monitoring, anti-patterns) | `references/best-practices.md` | | Agent architecture, reliability, security | `references/agent-patterns.md` | | Production monitoring, alerts, guardrails | `references/production.md` | | Evaluation datasets & test suites (reference) | `references/evaluation-datasets.md`, `references/evaluation-test-suites.md` | To build and run an evaluation, use the `opik-evaluate` skill. For repo instrumentation and config, use the `opik-instrument` skill.