Giskard: Testing and Evaluating ML Models plus LLM/RAG Apps So Nothing Leaks in Production

# Giskard: Testing dan Evaluasi Model ML plus Aplikasi LLM/RAG Biar Nggak Bocor di Produksi Halo temen-temen, ketemu lagi sama aku Ruby Abdullah. Kali ini aku mau ngajak kalian kenalan sama satu tool...

By Ruby Abdullah · · tutorial
giskardmachine-learningllm-evaluationragmodel-testing

Giskard: Testing and Evaluating ML Models plus LLM/RAG Apps So Nothing Leaks in Production

Hey friends, it is Ruby Abdullah again. This time I want to introduce you to a tool that I honestly think belongs in every serious ML or LLM builder's toolbox. It is called Giskard. It is an open source library for testing and evaluating ML models as well as LLM/RAG applications. So it is not just about checking accuracy, it is about hunting down the bugs that usually stay invisible in standard metrics. Bugs like bias, hallucination, and robustness issues.

Why am I so excited about this? Because honestly, a lot of us look at a model through a single number: accuracy. The model hits 92 percent, great, ship it. But behind that 92 percent, the model could quietly be discriminating against a specific group, or it could be trivially fooled just by changing one word in the input. Giskard is here to surface exactly those hidden problems automatically. Let us go through it slowly from zero until you can use it yourself.

Introduction

Giskard is essentially a quality assurance platform for AI models. I like to describe it as "unit tests but for models". Just like in regular software engineering where we write tests to make sure functions behave as expected, with Giskard we write (or auto generate) tests to make sure our model does not carry hidden weaknesses.

There are a few main problems Giskard helps you detect. The first is performance bias, which is when the model performs badly on a specific subset of data even though it looks great overall. The second is robustness issues, which is when the model easily flips its prediction just because of a small perturbation in the input, like a typo or a change in capitalization. The third is unrobustness to features that should not matter. The fourth, specific to LLMs, covers hallucination, prompt injection, leakage of sensitive information, and toxic or biased output.

What makes Giskard different from just writing plain asserts is its automatic scan. You simply hand over a model and a dataset, and Giskard runs hundreds of heuristic and machine learning based tests to hunt for vulnerabilities. The result is an interactive report that is easy to read. From there you can immediately generate a test suite that you can rerun every time the model gets updated. This is hugely important for a machine learning CI/CD pipeline.

For context, Giskard supports many types of models. From tabular (classification and regression), NLP, all the way to LLMs. It is framework agnostic, so whether you use scikit-learn, PyTorch, TensorFlow, HuggingFace, or even a custom model wrapped in a plain Python function, it all works. For LLMs and RAG, Giskard has a dedicated module called RAGET which we will cover in the advanced section later.

Okay, before we jump into code, I want to stress one thing. Giskard is not a replacement for your manual evaluation or your domain expertise. It is a tool to speed up and broaden the coverage of your testing. Think of it as a very diligent QA assistant who tries all sorts of scenarios you might have forgotten to check. The final decision still rests with you. Deal? Let us continue.

Installation

Installing Giskard is super easy, just use pip. I recommend creating a virtual environment first so things stay tidy and do not collide with your other project dependencies.

# Create a virtual environment first (optional but strongly recommended)

python -m venv venv

source venv/bin/activate (Linux/Mac)

venv\Scripts\activate (Windows)

Install the base Giskard package

pip install giskard

If you want to use the LLM and RAG evaluation features (RAGET), you need to install with the extra dependency. This is because the LLM module needs some additional libraries.

# pip install "giskard[llm]"

Once installed, you can check the version to make sure everything is in order.

import giskard

print(giskard.version)

For LLM features, Giskard uses LiteLLM by default to call language models. So you need to set the API key for the provider you use. If you use OpenAI for example, you set the environment variable first.

import os

Set the API key for the LLM provider you use

os.environ["OPENAIAPIKEY"] = "sk-..." # replace with your key

Configure the default model Giskard uses for LLM evaluation

import giskard

from giskard.llm.client.openai import OpenAIClient

giskard.llm.setllmmodel("gpt-4o-mini")

giskard.llm.setembeddingmodel("text-embedding-3-small")

Now that your environment is ready, we can start the fun part, which is using Giskard on a real model. I will start with a classic tabular case first so the concept sinks in, then we go up to LLMs.

Basic Usage

In this section we will practice using the legendary Titanic dataset. The goal is simple: predict whether a passenger survived. I deliberately picked this dataset because you are probably already familiar with it, which makes it easy to focus on the Giskard concepts rather than the data domain.

Preparing and Training a Model

First, we prepare the data and train a simple model with scikit-learn. Nothing weird here, this is the usual machine learning flow you already know.

import pandas as pd

from sklearn.compose import ColumnTransformer

from sklearn.pipeline import Pipeline

from sklearn.preprocessing import OneHotEncoder, StandardScaler

from sklearn.impute import SimpleImputer

from sklearn.linearmodel import LogisticRegression

from sklearn.modelselection import traintestsplit

Get the Titanic dataset

url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"

df = pd.readcsv(url)

Pick the columns we want to use

featurecols = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]

targetcol = "Survived"

df = df[featurecols + [targetcol]].copy()

Split the data

X = df[featurecols]

y = df[targetcol]

Xtrain, Xtest, ytrain, ytest = traintestsplit(

X, y, testsize=0.2, randomstate=42, stratify=y

)

Build preprocessing + model in one pipeline

numericfeatures = ["Age", "SibSp", "Parch", "Fare"]

categoricalfeatures = ["Pclass", "Sex", "Embarked"]

numerictransformer = Pipeline(steps=[

("imputer", SimpleImputer(strategy="median")),

("scaler", StandardScaler()),

])

categoricaltransformer = Pipeline(steps=[

("imputer", SimpleImputer(strategy="mostfrequent")),

("onehot", OneHotEncoder(handleunknown="ignore")),

])

preprocessor = ColumnTransformer(transformers=[

("num", numerictransformer, numericfeatures),

("cat", categoricaltransformer, categoricalfeatures),

])

model = Pipeline(steps=[

("preprocessor", preprocessor),

("classifier", LogisticRegression(maxiter=1000)),

])

model.fit(Xtrain, ytrain)

print("Test accuracy:", model.score(Xtest, ytest))

At this point you have a working model. If we stopped here, we would only know its accuracy. But we want more than that. We want to know where this model is fragile. This is where Giskard comes in.

Wrapping the Dataset with Giskard

The first step in Giskard is wrapping your dataset with a giskard.Dataset object. This matters because Giskard needs to know which column is the target and which columns are categorical so it can run relevant tests.

import giskard

Combine features and target for the Giskard dataset

rawtest = Xtest.copy()

rawtest[targetcol] = ytest

giskarddataset = giskard.Dataset(

df=rawtest,

target=targetcol, # the column we want to predict

name="Titanic test set",

catcolumns=categoricalfeatures, # categorical columns

)

print(giskarddataset)

Notice that I put the target column into the dataframe, then I tell Giskard the column name through the target parameter. This lets Giskard compare model predictions against ground truth while running the scan.

Wrapping the Model with Giskard

After the dataset, now it is the model's turn. We wrap the model with giskard.Model. The important thing here is that we provide a prediction function, the model type (classification or regression), the feature names, and the list of class labels.

def predictfn(dfinput):

# This function receives a raw dataframe and returns probabilities

return model.predictproba(dfinput)

giskardmodel = giskard.Model(

model=predictfn,

modeltype="classification",

name="Titanic survival classifier",

classificationlabels=[0, 1], # order must match predictproba output

featurenames=featurecols,

)

Quick validation: make sure the wrapper works correctly

print(giskardmodel.predict(giskarddataset).raw[:5])

There are two approaches to wrapping a model. The first uses a prediction function like above, which is the most flexible and the one I use most often since it works for any model. The second lets you pass a scikit-learn model object directly and Giskard auto detects it, but the function approach is more universal.

One important tip from me: make sure the order of classificationlabels matches exactly the column order of your predictproba output. If it is flipped, the scan results will be nonsense and confuse the heck out of you.

Running the Automatic Scan

Now this is my favorite part. With the wrapped model and dataset, we just call one function to run hundreds of automatic tests.

# Run the automatic scan to hunt for vulnerabilities

scanresults = giskard.scan(giskardmodel, giskarddataset)

Show the report (renders interactively in a notebook)

display(scanresults)

Outside a notebook, save it to HTML

scanresults.tohtml("titanicscanreport.html")

This scan checks many things at once. It checks performance bias across various data slices, for example whether the model performs worse for female passengers or third class passengers. It checks robustness by adding perturbations to the input. It checks overconfidence and underconfidence, meaning cases where the model is too sure while being wrong, or hesitant while the answer should be obvious. And much more.

The resulting report is interactive and tells you exactly where the problems are, how severe they are, plus sample data. Honestly the first time I ran this on an old production model, I was shocked because there was a data slice whose performance dropped drastically while the global metric looked perfectly healthy.

Generating a Test Suite from Scan Results

After the scan finds problems, Giskard can immediately turn those findings into a test suite that you can rerun. This is amazing for regression testing.

# Turn the scan results into an automatic test suite

testsuite = scanresults.generatetestsuite("Titanic test suite")

Run the test suite

suiteresults = testsuite.run()

print(suiteresults)

This test suite contains a collection of concrete tests with certain thresholds. For example "accuracy for the female passenger slice must be above 0.7" or "prediction must stay stable against capitalization changes in the Sex column". Every time you retrain the model, you just run this suite again to make sure there is no regression. This is what I meant earlier about integrating into CI/CD.

Advanced Usage

Okay now we level up. I split this section into two. First is about writing custom tests and tuning the scan for tabular data, second is about evaluating LLMs and RAG with RAGET. The LLM part is the one my friends have been asking me about the most lately, so I will go into a bit more detail.

Adding Custom Tests to a Test Suite

Sometimes Giskard's built in tests are not enough for your specific business needs. Luckily you can add your own custom tests. Giskard has plenty of ready to use tests in the giskard.testing module, and you can also write your own test functions.

from giskard import testing, Suite

Build a manual suite with Giskard's built in tests

customsuite = Suite(name="Custom Titanic Suite")

Test 1: minimum overall accuracy

customsuite.addtest(

testing.testaccuracy(

model=giskardmodel,

dataset=giskarddataset,

threshold=0.75,

)

)

Test 2: minimum F1 score

customsuite.addtest(

testing.testf1(

model=giskardmodel,

dataset=giskarddataset,

threshold=0.70,

)

)

results = customsuite.run()

print("All tests passed:", results.passed)

If you need truly custom test logic, you can use the @test decorator.

from giskard import test, TestResult

@test(name="Predictions must not all be identical")

def testpredictionvariety(model, dataset):

preds = model.predict(dataset).prediction

uniquepreds = set(preds)

passed = len(uniquepreds) > 1

return TestResult(

passed=passed,

metric=len(uniquepreds),

)

Add it to the suite

customsuite.addtest(

testpredictionvariety(model=giskardmodel, dataset=giskarddataset)

)

print(customsuite.run().passed)

Custom tests like this are super useful for encoding business rules that only you know. For example in a fraud detection case, you might want to make sure the model never assigns a very low risk score to a transaction above a certain amount. A rule like that will never be caught by a generic scan, so you have to write it yourself.

Evaluating LLMs and RAG with RAGET

Now we get into the LLM part. Say you build a RAG (Retrieval Augmented Generation) application, for example a chatbot that answers questions based on internal company documents. How do you know this chatbot is actually good? Testing it manually one by one is exhausting and does not scale. This is where Giskard's RAGET (RAG Evaluation Toolkit) shines.

RAGET has two main capabilities. First, it can automatically generate a test set from your knowledge base. It reads your documents, then builds questions along with their reference answers. Second, it can evaluate the answers of your RAG application and give scores per component, so you know which part of the RAG pipeline is failing.

First, we generate a test set from the knowledge base.

import pandas as pd

from giskard.rag import KnowledgeBase, generatetestset

Assume we have documents in the form of a dataframe

Each row is one chunk of text

documents = pd.DataFrame({

"text": [

"Giskard is an open source library for testing ML and LLM models.",

"RAGET is Giskard's module for evaluating RAG applications.",

"Giskard's automatic scan detects bias, robustness, and hallucination.",

"Giskard supports models from scikit-learn, PyTorch, and HuggingFace.",

# ... ideally hundreds to thousands of chunks from your real documents

]

})

Build the knowledge base

knowledgebase = KnowledgeBase(documents)

Generate a test set automatically

testset = generatetestset(

knowledgebase,

numquestions=30, # number of questions to generate

agentdescription="A chatbot that answers questions about Giskard",

)

Save the test set so it can be reused

testset.save("giskardragettestset.jsonl")

Look at sample generated questions

for sample in testset.topandas().head(3).itertuples():

print(sample)

The cool thing about RAGET is that it generates various question types. There are simple questions, complex questions that need reasoning, questions whose answers are spread across multiple documents, situational questions, and more. This matters because your RAG application must withstand testing from various angles, not just easy questions.

Once you have the test set, we evaluate our RAG application. We need to give RAGET a function that takes a question and returns an answer from our RAG application.

from giskard.rag import evaluate

def raganswerfn(question, history=None):

# Here you call your actual RAG application.

# Simple example: retrieval + generation.

# Replace this part with your RAG pipeline.

retrieved = retrieverelevantdocs(question) # your retrieval function

answer = generateanswer(question, retrieved) # your generation function

return answer

Run the evaluation using the test set from earlier

report = evaluate(

raganswerfn,

testset=testset,

knowledgebase=knowledgebase,

)

Show the report (interactive in a notebook)

display(report)

Save to HTML

report.tohtml("ragetreport.html")

Look at the overall score

print(report.correctness)

The RAGET report is truly informative. It gives scores per RAG component, namely the Generator (the LLM that produces answers), the Retriever (which fetches relevant documents), the Rewriter (if any), the Router, and the Knowledge Base itself. So if your score is bad, you can tell whether the problem is in retrieval fetching the wrong documents, or in the generator hallucinating even though the documents were correct. This is far more useful diagnostics than just a single accuracy number.

RAGET also classifies questions by topic and type, so you can see which topics your RAG application is weak at. For example it turns out your chatbot is great at product questions but weak at refund policy questions. Insight like this is pure gold for prioritizing improvements.

Running an LLM Scan Directly

Besides RAGET which focuses on RAG, Giskard also has a scan for LLMs in general. It is conceptually similar to the tabular scan earlier, but the tests are specific to LLM problems like prompt injection, hallucination, harmful output, and leakage of sensitive information.

import giskard

import pandas as pd

def llmpredict(dfinput):

# Function that calls your LLM for each input row

outputs = []

for question in dfinput["question"]:

answer = callyourllm(question) # replace with your LLM call

outputs.append(answer)

return outputs

Wrap as a Giskard model of type textgeneration

llmmodel = giskard.Model(

model=llmpredict,

modeltype="textgeneration",

name="Customer support assistant",

description="An assistant that answers customer questions about our product",

featurenames=["question"],

)

Sample dataset with a few questions

llmdataset = giskard.Dataset(pd.DataFrame({

"question": [

"How do I reset my password?",

"What is your refund policy?",

]

}))

Run the LLM scan

llmscan = giskard.scan(llmmodel, llmdataset)

llmscan.tohtml("llmscanreport.html")

Something important to remember in the LLM scan: the description parameter is not just documentation. Giskard uses that description to understand the intent and constraints of your application, then generates relevant tests. So write a clear and specific description. If the description is sloppy, the generated tests become less sharp too.

Best Practices

After using Giskard for quite a while across various projects, I have a few principles I want to share with you so you do not fall into the same holes I did.

First, do not rely on global metrics alone. This is the core message of this whole article. A 92 percent accuracy means nothing if that 8 percent of errors is concentrated in one group that matters, whether for business or ethical reasons. Always run the scan to see performance across various data slices. Giskard makes this easy, so there is no excuse not to do it.

Second, make the test suite part of your pipeline. Do not just run the scan once during development and forget it. Generate a test suite from the scan results, save it, and rerun it every time the model is retrained or the data changes. I usually put this in a CI step, so if a previously fixed weakness regresses, the build fails immediately and I get notified. This has saved me from many production incidents.

Third, write clear model descriptions especially for LLMs. Like I said, the quality of generated tests depends heavily on how well you describe what your model does and what its constraints are. Take the time to write a proper description. Imagine you are explaining it to a new QA engineer who knows nothing about your product yet.

Fourth, for RAG, evaluate per component, not just end to end. RAGET's strength is that it can pinpoint which component is failing. Take advantage of that. If the final score is bad, do not rush to swap the LLM. Check first, the problem might be in the retriever fetching the wrong documents. Often a fix in retrieval is far cheaper and more effective than swapping LLMs back and forth.

Fifth, start with a small test set then scale up. For RAGET, generating a test set from a knowledge base requires costly LLM calls. Start with a small number of questions, say 20 to 30, to validate that your setup works. Once you are confident, scale up to hundreds for a more thorough evaluation. This saves cost and time.

Sixth, review generated tests manually. Giskard is smart, but that does not mean it is perfect. Occasionally the generated test set can have odd questions or slightly incorrect reference answers. Take the time to read a sample of the test set, especially early on. Your domain expertise is still irreplaceable. Drop tests that do not make sense and fix the ones that need fixing.

Seventh, watch your API costs when using LLM features. LLM scans and RAGET test set generation send a lot of requests to your LLM provider. If you use an expensive model like GPT-4 for all of this, the bill can balloon. I usually use a cheaper model like gpt-4o-mini for routine evaluation, and reserve big models for cases that truly need high precision.

Eighth, document your findings. Every time a scan finds a vulnerability, note what the finding is and what your decision was. Sometimes you decide a "weakness" is actually acceptable for your business context. Document the reasoning, so your team does not get confused later and so you yourself do not forget why you made that decision.

Conclusion

Alright friends, we have gone pretty far from installation to a fairly deep RAG evaluation. I hope you now have a full picture of why Giskard is so valuable for anyone serious about bringing AI models to production.

The big point I want you to take home: a model that looks good on paper is not necessarily good in the real world. Global metrics can deceive. Giskard gives you a systematic way to uncover hidden weaknesses, whether that is bias, robustness issues, or hallucination in LLM applications. And more importantly, it makes this testing process repeatable and automatable, so you do not just test once and forget.

For your next step, I recommend you practice right away. Take one model you already have, wrap it with Giskard, and run the scan. I am almost certain you will find at least one thing that makes you think "wow, good thing I caught that now". Once you are comfortable with tabular, try moving up to LLM evaluation and RAGET if you are working on an LLM based application.

Remember, our goal is not to build a model that looks perfect in a demo, but to build a model that can truly be trusted when real people use it. Serious testing is an inseparable part of that. Giskard is just a tool, but the right tool can change the way you work into something far calmer and more professional.

That is it from me for now. If you try it and find an interesting insight, I would really love to hear the story. Happy hacking, and see you in the next tutorial. Keep up the learning, friends!

Related Articles

DSPy: Stop Hand-Tuning Prompts, Let the Compiler Optimize Them

DSPy: Berhenti Ngoprek Prompt Manual, Biarkan Compiler yang Optimasi Halo temen-temen, kali ini aku mau ngenalin satu li...

FastEmbed: Fast and Lightweight Embeddings Without Torch, by Qdrant

FastEmbed: Bikin Embedding Cepat dan Ringan Tanpa Torch dari Qdrant Halo temen-temen, balik lagi sama aku Ruby Abdullah....

RAGFlow: Build a RAG Engine That Actually Understands Documents, Complete With Citations

RAGFlow: Bikin RAG Engine yang Ngerti Dokumen Beneran, Lengkap dengan Sitasi Halo temen-temen, balik lagi sama aku. Kala...

Cognee: Build Memory and Knowledge Graphs for Your AI Agents

Cognee: Bangun Memory dan Knowledge Graph untuk AI Agent-mu Halo temen-temen, di tutorial kali ini aku mau ngajak kalian...