opik
Original:🇺🇸 English
Translated
Opik observability for LLM agents — Agent Configuration, Local Runner (opik connect), Evaluation Suites, threads, integrations. Use for "configure my agent", "connect my agent", "evaluate my agent" or "integrate with Opik".
18installs
Sourcecomet-ml/opik-skills
Added on
NPX Install
npx skill4agent add comet-ml/opik-skills opikTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Opik — Observability for LLM Agents
Integrating with Opik always means adding all three components unless the user explicitly asks for only one:
- Tracing — instrument LLM calls with the appropriate integration or
@opik.track - Entrypoint — mark the top-level function with for Local Runner and UI integration
entrypoint=True - Agent Configuration — externalize all tunable parameters into : model names, temperatures, top_p, max_tokens, all prompts and prompt templates, and any other runtime parameters the user may want to compare or optimize
AgentConfig
Setup
Environment Config Decision Tree
Before adding Opik config, inspect the project's existing config approach. Follow this decision tree exactly:
-
Check for existing/
.envfiles and.env.localusage in code.dotenv- If the project loads a file (via
.env,python-dotenv, or framework auto-loading): appenddotenvandOPIK_API_KEYto that same file. Do NOT create a separate config file.OPIK_WORKSPACE - If there is a or
.env.example: also update it with the new Opik vars (using placeholder values) so future developers know which vars are needed..env.sample
- If the project loads a
-
If nofile exists:
.env- Python: create or update (INI format). This is the SDK's native config file.
~/.opik.config - TypeScript/JavaScript: create (or
.envif the project uses Next.js or similar)..env.local
- Python: create or update
-
Never introduce a second config mechanism. If the project already usesfor API keys, do NOT also create
.env. If it uses~/.opik.config, do NOT add Opik vars to~/.opik.config..env -
Never overwrite existing values. Ifis already set in
OPIK_API_KEY, leave it. Only add vars that are missing..env -
Prefer settingin code, not in env files — one machine may log to many projects.
project_name -
If the user provides an API key and workspace in the prompt, use those values directly. If they provide only an API key, ask for the workspace or default tofor local OSS.
"default"
Config Formats
Python (INI):
~/.opik.configini
[opik]
api_key=your-api-key
url_override=https://www.comet.com/opik/api
workspace=your-workspaceEnvironment variables (append to existing ):
.envbash
# Opik
OPIK_API_KEY=your-api-key
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api
OPIK_WORKSPACE=your-workspaceTypeScript uses as the env var and in .
OPIK_WORKSPACEworkspaceNamenew Opik({...})Standard Deployments
- Cloud: — requires
https://www.comet.com/opik/api+api_keyworkspace - Local OSS: — usually workspace
http://localhost:5173/apidefault - Self-hosted: use the deployment's custom URL, following the project's existing config style
Interactive Config (optional)
bash
opik configure
opik configure --use_local
npx opik-ts configure
npx opik-ts configure --use-localSet the project name in code:
python
@opik.track(project_name="my-project")
def run():
...typescript
const client = new Opik({ projectName: "my-project" });Python Instrumentation
python
import opik
@opik.track(entrypoint=True, name="my-agent")
def agent(query: str) -> str:
context = retrieve(query)
return generate(query, context)
@opik.track(type="tool")
def retrieve(query: str) -> list:
return search_db(query)
@opik.track(type="llm")
def generate(query: str, context: list) -> str:
return llm_call(query, context)
result = agent("What is ML?")
opik.flush_tracker() # required in scriptsValid span types for manual instrumentation: , , , .
generalllmtoolguardrailFramework integrations — these capture tokens, model, and cost automatically:
python
from opik.integrations.openai import track_openai # OpenAI
from opik.integrations.anthropic import track_anthropic # Anthropic
from opik.integrations.langchain import OpikTracer # LangChain
from opik.integrations.crewai import track_crewai # CrewAI
from opik.integrations.dspy import OpikCallback # DSPy
from opik.integrations.adk import track_adk_agent_recursive # Google ADKCRITICAL — LiteLLM inside :
OpikLogger@opik.trackIf the codebase uses AND you are adding decorators, you MUST pass via the metadata parameter on every / call. This tells the callback to nest under the active trace. Without it, creates orphaned top-level traces that are separate from your hierarchy.
litellm@opik.trackcurrent_span_datalitellm.completion()litellm.acompletion()OpikLoggerOpikLogger@opik.trackpython
from opik import track
from opik.opik_context import get_current_span_data
from litellm.integrations.opik.opik import OpikLogger
import litellm
litellm.callbacks = [OpikLogger()]
@track
def call_llm(messages, model="gpt-4o"):
return litellm.completion(
model=model,
messages=messages,
metadata={
"opik": {
"current_span_data": get_current_span_data(),
"tags": ["litellm"],
},
},
)
@track(entrypoint=True)
def agent(query: str) -> str:
return call_llm([{"role": "user", "content": query}])This pattern applies whenever you see or in existing code that you are instrumenting with .
litellm.completionlitellm.acompletion@opik.trackTypeScript Instrumentation
typescript
import { Opik } from "opik";
const client = new Opik({ projectName: "my-project" });
const trace = client.trace({
name: "my-agent",
input: { query: "What is ML?" },
});
const toolSpan = trace.span({
name: "retrieve-context",
type: "tool",
input: { query: "What is ML?" },
});
// retrieval logic
toolSpan.end({ output: { documents: [] } });
const llmSpan = trace.span({
name: "generate-response",
type: "llm",
input: { prompt: "What is ML?" },
});
// model call
llmSpan.end({ output: { response: "Machine learning is..." } });
trace.end({ output: { response: "Machine learning is..." } });
await client.flush();Prefer the client-based path in TypeScript. Use in code rather than machine-wide config when possible.
projectNameFor framework-specific integrations such as Vercel AI SDK or LangChain.js, see .
references/tracing-typescript.mdAlways before exit.
await client.flush()Valid span types for manual instrumentation: , , , .
generalllmtoolguardrailThreads (Conversations)
Group conversation turns via . Each turn = one trace; shared = one thread.
thread_idthread_idpython
@opik.track(entrypoint=True)
def handle_message(session_id: str, message: str) -> str:
opik.update_current_trace(thread_id=session_id)
return generate_response(session_id, message)Thread metrics:
python
from opik.evaluation import evaluate_threads
from opik.evaluation.metrics.conversation import (
SessionCompletenessMetric, UserFrustrationMetric, ConversationalCoherenceMetric,
)
results = evaluate_threads(project_name="chat-agent", metrics=[
SessionCompletenessMetric(), UserFrustrationMetric(), ConversationalCoherenceMetric(),
])Use for chat agents, support bots, multi-step assistants. Skip for single-shot agents or batch processing.
Pitfalls: Missing → turns appear as unrelated traces. Shared across users → conversations get mixed.
thread_idthread_idAgent Configuration
Externalize the parts of your agent you expect to tune over time into versioned, immutable config snapshots. This includes prompts, models, temperatures, token limits, and other runtime parameters you may want to compare, optimize, or roll out gradually.
CRITICAL — Search for existing config classes first. Before creating a new , search the codebase for existing classes that hold tunable parameters (model names, temperatures, prompts, token limits, etc.). Look for names like , , , , , or any /Pydantic model with fields like , , , . An existing config class is a migration target, not a reason to skip this step. If found, convert it to inherit from :
AgentConfigAgentConfigConfigSettingsAgentSettingsModelConfig@dataclassmodeltemperaturesystem_promptmax_tokensopik.AgentConfig- Replace the existing base (,
@dataclass, plain class) withBaseModelopik.AgentConfig - Add type hints with descriptions to each field
Annotated - Convert plain prompt fields to
stropik.Prompt - Wire up at startup and
client.create_agent_config_version()inside the entrypointclient.get_agent_config() - Update all call sites that reference the old config to use the new Opik-managed config
python
from typing import Annotated
import opik
class AgentConfig(opik.AgentConfig):
model: Annotated[str, "LLM model"] # NO defaults
temperature: Annotated[float, "Sampling temperature"]
system_prompt: Annotated[opik.Prompt, "Managed system prompt"]
DEFAULT_AGENT_CONFIG = AgentConfig(
model="gpt-4o",
temperature=0.7,
system_prompt=opik.Prompt(
name="agent-system-prompt",
prompt="You are a helpful assistant for {{product}}.",
),
)
client = opik.Opik()
client.create_agent_config_version(
AgentConfig(
model="gpt-4o",
temperature=0.7,
system_prompt=opik.Prompt(
name="agent-system-prompt",
prompt="You are a helpful assistant for {{product}}.",
),
),
project_name="my-agent",
)
# Identical values → same version (dedup). Different values → new version.
@opik.track(entrypoint=True, project_name="my-agent")
def run_agent(question: str) -> str:
cfg = client.get_agent_config(
fallback=DEFAULT_AGENT_CONFIG,
project_name="my-agent",
# optional: latest=True | env="staging" | version="v1" (default: prod)
)
return llm_call(
model=cfg.model,
temperature=cfg.temperature,
system_prompt=cfg.system_prompt.format(product="Opik"),
question=question,
)- must be inside
get_agent_config()— raises error otherwise@opik.track - Deploy: — tags a version with an environment
cfg.deploy_to("prod") - Prompt fields: use (from
Prompt) /opik.api_objects.prompt.text.prompt(fromChatPrompt) typed config fields for managed promptsopik.api_objects.prompt.chat.chat_prompt - Extract: model, temperature, top_p, max_tokens, system prompt, tunable params
- Don't extract: API keys, structural logic, true constants
Local Runner (opik connect)
Pair your local agent with the Opik browser UI. Get a pairing code from the UI, then:
bash
opik connect --pair <CODE> python3 app.py # Python
opik connect --pair <CODE> npx tsx app.ts # TypeScriptReplace or with the normal command you use to start your app locally.
python3 app.pynpx tsx app.tsPython: + type-hinted parameters for schema discovery.
TypeScript: .
@track(entrypoint=True)track({ entrypoint: true, params: [{name, type}] }, fn)After pairing: entrypoint registered as agent, UI shows input form, jobs from UI or Optimizer trigger runs.
| Issue | Fix |
|---|---|
| No entrypoint found | Add |
| Invalid pair code | Codes expire — get a new one |
| Connection refused | Check Opik server (OSS) or API key (Cloud) |
Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
Existing config class left unconverted (e.g., | Convert to |
| Hardcoded config | Use |
| Missing entrypoint | Add |
| No thread_id on conversational agent | Wire |
| Must be inside decorated function |
TS missing | Add explicit |
Missing | Call before exit |
References
| Topic | File |
|---|---|
| Python SDK (decorators, async, distributed, config, entrypoint) | |
| TypeScript SDK (client, decorators, entrypoint, params) | |
| REST API | |
| All integrations | |
| Core concepts (traces, spans, threads, metadata) | |
| Evaluation (suites, 41 built-in metrics, trajectory) | |