Opik Tutorial: LLM Evaluation and Observability Platform

# Tutorial Opik: Platform Evaluasi dan Observabilitas LLM Opik adalah platform open-source yang dikembangkan oleh Comet untuk evaluasi, testing, dan monitoring aplikasi berbasis Large Language Model...

By Ruby Abdullah · · tutorial
OpikLLM EvaluationObservabilityCometPython

Opik Tutorial: LLM Evaluation and Observability Platform

Opik is an open-source platform developed by Comet for evaluating, testing, and monitoring Large Language Model (LLM) applications. In the modern AI ecosystem, building LLM applications is only half the battle. You need to ensure that model responses are consistent, accurate, and meet expectations. Opik provides a complete toolkit for tracing, automated evaluation, and production monitoring that helps AI teams develop LLM applications with confidence.

This tutorial covers how to use Opik from installation, tracing LLM requests, creating automated evaluations, to monitoring applications in production. All code examples can be run directly in your local Python environment.

Why Opik?

When building LLM applications, there are several key challenges:

  • Difficult debugging: It is hard to trace why a model gave a particular response, especially in multi-step pipelines like RAG
  • Inconsistent evaluation: Manually evaluating LLM output quality is time-consuming and not scalable
  • Production monitoring: No visibility into model performance after deployment
  • Regression testing: Prompt or model changes can cause undetected degradation
  • Opik addresses all these challenges by providing:

    • Tracing: Records every step in your LLM pipeline in detail
    • Evaluation: Automated evaluation framework with built-in and custom metrics
    • Dataset Management: Manage test datasets for reproducible evaluations
    • Monitoring Dashboard: Real-time visualization of LLM application performance

    Installation

    Install via pip

    pip install opik
    

    Configure Opik

    There are two ways to use Opik: using Comet Cloud (hosted) or self-hosted.

    Option 1: Comet Cloud (Recommended to get started)

    opik configure
    

    This command will prompt for your Comet API key. Sign up for free at comet.com if you do not have an account yet.

    Option 2: Self-Hosted with Docker

    # Clone the repository
    

    git clone https://github.com/comet-ml/opik.git

    cd opik

    Run with Docker Compose

    docker compose up -d

    Once running, the Opik dashboard is available at http://localhost:5173.

    Configure the client for self-hosted:

    import opik
    
    

    opik.configure(uselocal=True)

    Verify Installation

    import opik
    
    

    client = opik.Opik()

    print("Opik connected successfully!")

    print(f"Workspace: {client.workspace}")

    Tracing: Recording Every Step of Your LLM Pipeline

    Tracing is Opik's core feature that allows you to record every step in your LLM pipeline. This is invaluable for debugging and performance analysis.

    Basic Tracing with Decorators

    The easiest way to use tracing is with the @opik.track decorator:

    import opik
    

    from openai import OpenAI

    client = OpenAI()

    @opik.track

    def generateresponse(prompt: str) -> str:

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[{"role": "user", "content": prompt}]

    )

    return response.choices[0].message.content

    @opik.track

    def processquery(userquery: str) -> dict:

    enhancedprompt = f"Answer the following question clearly and concisely:\n{userquery}"

    response = generateresponse(enhancedprompt)

    return {

    "query": userquery,

    "response": response

    }

    result = processquery("What is machine learning?")

    print(result["response"])

    Every function call decorated with @opik.track is automatically recorded as a span in the trace. Nested functions automatically form a parent-child hierarchy.

    Tracing with Metadata

    You can add additional metadata to traces for richer context:

    import opik
    

    from opik import opikcontext

    @opik.track

    def classifytext(text: str, categories: list) -> str:

    opikcontext.updatecurrenttrace(

    tags=["classification", "production"],

    metadata={

    "model": "gpt-4o-mini",

    "numcategories": len(categories),

    "textlength": len(text)

    }

    )

    prompt = f"""Classify the following text into one of these categories: {', '.join(categories)}

    Text: {text}

    Category:"""

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[{"role": "user", "content": prompt}]

    )

    result = response.choices[0].message.content.strip()

    opikcontext.updatecurrentspan(

    metadata={"resultcategory": result}

    )

    return result

    result = classifytext(

    "BBCA stock price rose 2% today",

    ["finance", "technology", "sports", "politics"]

    )

    print(f"Category: {result}")

    OpenAI Integration

    Opik provides direct integration with OpenAI so all API calls are automatically traced:

    from opik.integrations.openai import trackopenai
    

    from openai import OpenAI

    client = OpenAI()

    client = trackopenai(client)

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[

    {"role": "system", "content": "You are a helpful assistant."},

    {"role": "user", "content": "Explain the concept of RAG in 2 sentences."}

    ]

    )

    print(response.choices[0].message.content)

    LangChain Integration

    If you use LangChain, Opik provides a callback handler:

    from opik.integrations.langchain import OpikTracer
    

    from langchainopenai import ChatOpenAI

    from langchaincore.prompts import ChatPromptTemplate

    opiktracer = OpikTracer()

    llm = ChatOpenAI(model="gpt-4o-mini")

    prompt = ChatPromptTemplate.frommessages([

    ("system", "You are an expert in {topic}."),

    ("user", "{question}")

    ])

    chain = prompt | llm

    response = chain.invoke(

    {"topic": "data science", "question": "What is the difference between supervised and unsupervised learning?"},

    config={"callbacks": [opiktracer]}

    )

    print(response.content)

    Tracing a RAG Pipeline

    One of the most common use cases is tracing a RAG (Retrieval-Augmented Generation) pipeline:

    import opik
    

    from openai import OpenAI

    client = OpenAI()

    @opik.track(name="embedquery")

    def embedquery(query: str) -> list:

    response = client.embeddings.create(

    model="text-embedding-3-small",

    input=query

    )

    return response.data[0].embedding

    @opik.track(name="retrievedocuments")

    def retrievedocuments(queryembedding: list, topk: int = 3) -> list:

    documents = [

    {"id": 1, "text": "Python is a popular programming language for data science.", "score": 0.92},

    {"id": 2, "text": "Machine learning is a branch of artificial intelligence.", "score": 0.87},

    {"id": 3, "text": "Data preprocessing is an important step before training a model.", "score": 0.83},

    ]

    return documents[:topk]

    @opik.track(name="generateanswer")

    def generateanswer(query: str, contextdocs: list) -> str:

    context = "\n".join([doc["text"] for doc in contextdocs])

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[

    {"role": "system", "content": f"Answer based on the following context:\n{context}"},

    {"role": "user", "content": query}

    ]

    )

    return response.choices[0].message.content

    @opik.track(name="ragpipeline")

    def ragpipeline(query: str) -> dict:

    embedding = embedquery(query)

    documents = retrievedocuments(embedding)

    answer = generateanswer(query, documents)

    return {

    "query": query,

    "numdocsretrieved": len(documents),

    "answer": answer

    }

    result = ragpipeline("How do I get started with data science?")

    print(result["answer"])

    In the Opik dashboard, you will see the complete trace with hierarchy: ragpipeline -> embedquery -> retrievedocuments -> generateanswer, complete with inputs and outputs for each step.

    Automated Evaluation

    Evaluation is the feature that makes Opik truly powerful. You can automatically evaluate LLM outputs using built-in or custom metrics.

    Creating a Dataset

    First, create a dataset for evaluation:

    import opik
    
    

    client = opik.Opik()

    dataset = client.getorcreatedataset(name="qa-evaluation-dataset")

    dataset.insert([

    {

    "input": {"question": "What is Python?"},

    "expectedoutput": "Python is a high-level programming language popular for data science, web development, and scripting."

    },

    {

    "input": {"question": "What is pandas used for?"},

    "expectedoutput": "Pandas is a Python library for data manipulation and analysis, especially tabular data."

    },

    {

    "input": {"question": "Explain what a neural network is."},

    "expectedoutput": "A neural network is a computational model inspired by the human brain, consisting of interconnected layers of neurons that process information."

    },

    {

    "input": {"question": "What is the difference between SQL and NoSQL?"},

    "expectedoutput": "SQL uses a relational schema with structured tables, while NoSQL uses flexible data models like documents, key-value pairs, or graphs."

    },

    {

    "input": {"question": "What is an API?"},

    "expectedoutput": "An API (Application Programming Interface) is an interface that allows two applications to communicate with each other."

    }

    ])

    print(f"Dataset '{dataset.name}' created with {len(dataset)} items")

    Using Built-in Metrics

    Opik provides several built-in metrics for LLM evaluation:

    from opik.evaluation.metrics import Hallucination, AnswerRelevance, ContextRecall
    

    from opik.evaluation import evaluate

    @opik.track

    def qatask(inputdata: dict) -> dict:

    question = inputdata["question"]

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[{"role": "user", "content": question}]

    )

    answer = response.choices[0].message.content

    return {

    "output": answer,

    "context": [f"Related question: {question}"]

    }

    hallucinationmetric = Hallucination()

    relevancemetric = AnswerRelevance()

    results = evaluate(

    dataset=dataset,

    task=qatask,

    scoringmetrics=[hallucinationmetric, relevancemetric],

    experimentname="qa-evaluation-v1"

    )

    print(f"Evaluation complete!")

    print(f"Average Hallucination Score: {results.averagescores.get('hallucination', 'N/A')}")

    print(f"Average Relevance Score: {results.averagescores.get('answerrelevance', 'N/A')}")

    Creating Custom Metrics

    You can also create your own evaluation metrics:

    from opik.evaluation.metrics import basemetric, scoreresult
    
    

    class ResponseLengthMetric(basemetric.BaseMetric):

    def init(self, minlength: int = 50, maxlength: int = 500):

    self.name = "responselength"

    self.minlength = minlength

    self.maxlength = maxlength

    def score(self, output: str, kwargs) -> scoreresult.ScoreResult:

    length = len(output)

    if self.minlength <= length <= self.maxlength:

    scorevalue = 1.0

    reason = f"Response length ({length}) is within acceptable range [{self.minlength}, {self.maxlength}]"

    elif length < self.minlength:

    scorevalue = length / self.minlength

    reason = f"Response too short ({length} < {self.minlength})"

    else:

    scorevalue = max(0, 1 - (length - self.maxlength) / self.maxlength)

    reason = f"Response too long ({length} > {self.maxlength})"

    return scoreresult.ScoreResult(

    name=self.name,

    value=scorevalue,

    reason=reason

    )

    class KeywordPresenceMetric(basemetric.BaseMetric):

    def init(self, requiredkeywords: list):

    self.name = "keywordpresence"

    self.requiredkeywords = [kw.lower() for kw in requiredkeywords]

    def score(self, output: str, kwargs) -> scoreresult.ScoreResult:

    outputlower = output.lower()

    found = [kw for kw in self.requiredkeywords if kw in outputlower]

    scorevalue = len(found) / len(self.requiredkeywords) if self.requiredkeywords else 1.0

    return scoreresult.ScoreResult(

    name=self.name,

    value=scorevalue,

    reason=f"Found {len(found)}/{len(self.requiredkeywords)} keywords: {found}"

    )

    lengthmetric = ResponseLengthMetric(minlength=30, maxlength=300)

    keywordmetric = KeywordPresenceMetric(requiredkeywords=["python", "programming"])

    results = evaluate(

    dataset=dataset,

    task=qatask,

    scoringmetrics=[lengthmetric, keywordmetric],

    experimentname="qa-custom-metrics-v1"

    )

    Evaluation with LLM-as-Judge

    Opik supports using an LLM as an evaluator, which is very useful for semantically assessing response quality:

    from opik.evaluation.metrics import LLMJudge
    
    

    judgemetric = LLMJudge(

    name="qualityjudge",

    model="gpt-4o-mini",

    prompttemplate="""Evaluate the quality of the following answer based on the given question.

    Question: {input}

    Answer: {output}

    Expected Answer: {expectedoutput}

    Score from 0-1 based on these criteria:

    • Accuracy: Is the answer correct?
    • Completeness: Does the answer cover key points?
    • Clarity: Is the answer easy to understand?

    Score (0-1):""",

    )

    results = evaluate(

    dataset=dataset,

    task=qatask,

    scoringmetrics=[judgemetric],

    experimentname="qa-llm-judge-v1"

    )

    Comparing Experiments

    One of Opik's strengths is the ability to compare results across experiments:

    @opik.track
    

    def qataskdetailed(inputdata: dict) -> dict:

    question = inputdata["question"]

    response = client.chat.completions.create(

    model="gpt-4o-mini",

    messages=[

    {"role": "system", "content": "Answer in detail and provide examples when possible. Use clear and accessible language."},

    {"role": "user", "content": question}

    ],

    temperature=0.3

    )

    return {"output": response.choices[0].message.content}

    resultsv2 = evaluate(

    dataset=dataset,

    task=qataskdetailed,

    scoringmetrics=[hallucinationmetric, relevancemetric, lengthmetric],

    experimentname="qa-evaluation-v2-detailed"

    )

    In the Opik dashboard, you can compare qa-evaluation-v1 and qa-evaluation-v2-detailed side-by-side to see the impact of prompt changes.

    Production Monitoring

    Opik can also be used for monitoring LLM applications in production.

    Logging User Feedback

    import opik
    
    

    client = opik.Opik()

    @opik.track(name="productionquery", projectname="my-llm-app-production")

    def handleuserquery(query: str, userid: str) -> str:

    from opik import opikcontext

    opikcontext.updatecurrenttrace(

    tags=["production"],

    metadata={

    "userid": userid,

    "environment": "production"

    }

    )

    response = openaiclient.chat.completions.create(

    model="gpt-4o-mini",

    messages=[{"role": "user", "content": query}]

    )

    answer = response.choices[0].message.content

    traceid = opikcontext.getcurrenttracedata().id

    return answer, traceid

    def submitfeedback(traceid: str, score: float, reason: str = ""):

    client.logtracesfeedback(

    traceids=[traceid],

    scores=[score],

    reasons=[reason],

    name="usersatisfaction"

    )

    answer, traceid = handleuserquery(

    query="How do I use Docker?",

    userid="user123"

    )

    submitfeedback(

    traceid=traceid,

    score=0.9,

    reason="Complete and easy to understand answer"

    )

    Online Scoring Rules

    You can set up scoring rules that run automatically for every trace in production:

    client.createonlinescoringrule(
    

    projectname="my-llm-app-production",

    name="auto-hallucination-check",

    metric=Hallucination(),

    samplingrate=0.1

    )

    This will automatically evaluate 10% of all production traces using the hallucination metric.

    Prompt Management

    Opik also provides prompt management features for versioning and tracking prompts:

    import opik
    
    

    client = opik.Opik()

    prompt = client.createprompt(

    name="qa-system-prompt",

    prompt="You are an AI assistant that helps answer questions about {topic}. "

    "Answer clearly, accurately, and concisely. "

    "If you do not know the answer, say so honestly."

    )

    print(f"Prompt version: {prompt.commit}")

    retrievedprompt = client.getprompt(name="qa-system-prompt")

    formatted = retrievedprompt.format(topic="machine learning")

    print(formatted)

    Prompt Versioning

    promptv2 = client.createprompt(
    

    name="qa-system-prompt",

    prompt="You are an AI assistant specializing in {topic}. "

    "Provide structured answers with bullet points when appropriate. "

    "Include practical examples when possible. "

    "If you do not know the answer, direct the user to relevant resources."

    )

    print(f"Updated prompt version: {promptv2.commit}")

    history = client.getpromptversions(name="qa-system-prompt")

    for version in history:

    print(f"Version {version.commit}: created at {version.createdat}")

    Best Practices

    1. Good Tracing Structure

    Give descriptive names to every trace and span:

    @opik.track(name="customersupportpipeline")
    

    def handlesupportticket(ticket: dict):

    intent = classifyintent(ticket["message"])

    context = retrieveknowledge(intent, ticket["message"])

    response = generateresponse(ticket["message"], context)

    return response

    2. Use Tags for Filtering

    @opik.track
    

    def processrequest(request: dict):

    from opik import opikcontext

    opikcontext.updatecurrenttrace(

    tags=[

    f"model:{request.get('model', 'default')}",

    f"env:{request.get('environment', 'dev')}",

    f"priority:{request.get('priority', 'normal')}"

    ]

    )

    3. Evaluate Regularly

    Run evaluations regularly, especially when:

    • Changing prompts or system messages
    • Upgrading model versions
    • Adding new data to the knowledge base
    • Before and after deploying changes

    4. Separate Projects per Environment

    @opik.track(projectname="my-app-development")
    

    def devpipeline(query: str):

    pass

    @opik.track(projectname="my-app-staging")

    def stagingpipeline(query: str):

    pass

    @opik.track(projectname="my-app-production")

    def prodpipeline(query: str):

    pass

    5. Save Evaluation Results as Baselines

    Whenever you achieve good evaluation results, save them as a baseline:

    results = evaluate(
    

    dataset=dataset,

    task=qatask,

    scoringmetrics=[hallucinationmetric, relevancemetric],

    experimentname=f"baseline-v1-{datetime.now().strftime('%Y%m%d')}"

    )

    Integration with Other Frameworks

    In addition to OpenAI and LangChain, Opik also supports integration with:

    • LlamaIndex: from opik.integrations.llamaindex import LlamaIndexCallbackHandler
    • Anthropic: from opik.integrations.anthropic import trackanthropic
    • Google Gemini: from opik.integrations.gemini import trackgemini
    • Ollama: Through OpenAI-compatible integration
    • LiteLLM: from opik.integrations.litellm import tracklitellm
    • Haystack: from opik.integrations.haystack import OpikConnector
    • CrewAI: from opik.integrations.crewai import trackcrewai

    Example integration with LlamaIndex:

    from opik.integrations.llamaindex import LlamaIndexCallbackHandler
    

    from llamaindex.core import Settings, VectorStoreIndex, SimpleDirectoryReader

    from llamaindex.core.callbacks import CallbackManager

    opikhandler = LlamaIndexCallbackHandler()

    Settings.callbackmanager = CallbackManager([opikhandler])

    documents = SimpleDirectoryReader("./data").loaddata()

    index = VectorStoreIndex.fromdocuments(documents)

    queryengine = index.asqueryengine()

    response = queryengine.query("What is a vector database?")

    print(response)

    Conclusion

    Opik provides a complete toolkit for managing the lifecycle of LLM applications from development to production. With tracing, you gain full visibility into your LLM pipeline. The evaluation features enable consistent and reproducible automated testing. And monitoring ensures your application continues to perform as expected in production.

    The main advantages of Opik compared to similar tools are its open-source nature, easy setup (can be self-hosted or cloud-based), and extensive integrations with various popular AI frameworks. For teams seriously building production-grade LLM applications, Opik is a worthy addition to your AI observability stack.

    Recommended next steps:

    • Explore the Opik dashboard for trace and evaluation visualization
    • Implement tracing in your existing LLM pipelines
    • Create evaluation datasets from real production data
    • Set up monitoring rules for early detection of quality issues

    Related Articles

    Complete Guide to TruLens: LLM Application Evaluation and Observability

    Panduan Lengkap TruLens: Evaluasi dan Observabilitas Aplikasi LLM Penulis: Ruby Abdullah - PT RUBYTHALIB DATA KONSULTA D...

    Logfire Tutorial: Observability Platform for Python and AI Applications

    Tutorial Logfire: Platform Observability untuk Aplikasi Python dan AI Logfire adalah platform observability buatan tim P...

    Portkey: One AI Gateway to Manage Every LLM Across Many Providers

    Portkey: Satu Gateway AI buat Ngatur Semua LLM dari Banyak Provider Temen-temen, kalau kamu udah pernah bikin aplikasi y...

    Weave Tutorial: LLM Application Observability and Evaluation with Weights & Biases

    Tutorial Weave: Observabilitas dan Evaluasi Aplikasi LLM dengan Weights & Biases Membangun aplikasi berbasis Large Langu...