LLMOpsThe LLMOps lifecycle · MLflow 3 · FastAPI · Docker · AWS/Azure
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
41 small wins to finish your path

Swapping the stand-ins for the real thing

Every runnable piece of this course is a stand-in, chosen so the lessons need no key, no GPU and no account. Each one has a real counterpart that drops into the same seam: a provider call, a vector store, a judge metric and a database-backed tracking store.

Last updated: 15 Oct, 2026 · LLMOps

Maintenance and drift after you ship closed the loop on an app that is live. This lesson is the hand-over from the course's version of that app to yours. Nothing about the shape changes: the config, the prompt library, the run record and the gate all stay where they are, and four pieces behind them get replaced one at a time.

The four stand-ins, and what replaces each

What you usedA real oneThe package
call_api, a keyword matcher over a handbook dictA provider call, or a gateway in front of severallitellm, or langchain-openai
HANDBOOK, a dict looked up by keywordA vector store the retriever querieslangchain-chroma, or faiss-cpu
faithfulness, the share of reply words found in the contextA judge metric, scored by a modeldeepeval, or ragas
file:./mlruns, a folder on your machineA database backend, or a hosted servermlflow with a SQLAlchemy URI
Four seams listed down the left, each showing what the course runs there and what replaces it. The model is a call_api keyword matcher over a dict, replaced by a provider call or a gateway, package litellm. Retrieval is a HANDBOOK dict looked up by keyword, replaced by a vector store the retriever queries, package langchain-chroma. The score is word overlap with the context, replaced by a judge metric scored by a model, package deepeval. The store is file colon slash mlruns, a folder on disk, replaced by a database backend and then a server, package mlflow, marked in green as the one swap that runs with no key. A closing note says to swap one at a time and re-run the evaluation set after each, because every swap changes what the numbers mean.

The first three need a key, so they are named here rather than run. The fourth needs nothing, so it is the one you can swap right now.

The swap you can run: a database-backed store

Syntax: the tracking URI is the only line that changes. A SQLAlchemy URI puts the same runs in a database file instead of a folder tree, which is what the MLflow project recommends for anything that outlives a lesson, and every logging call stays identical.

python
import os
os.environ["MLFLOW_DISABLE_AGENT_HINT"] = "1"    # hush MLflow's agent hint while you learn
import shutil
shutil.rmtree("mlruns", ignore_errors=True)      # start from an empty store
if os.path.exists("mlflow.db"):
    os.remove("mlflow.db")                       # and from an empty database

import mlflow
ExampleThe same run, written to a database instead of a folder
# the one line that changes: a database URI in place of file:./mlruns
mlflow.set_tracking_uri("sqlite:///mlflow.db")
mlflow.set_experiment("shop-assistant")

# every call below is the one you already wrote
with mlflow.start_run(run_name="on-sqlite"):
    mlflow.log_param("model", "shop-stand-in")
    mlflow.log_metric("answer_length", 8)

runs = mlflow.search_runs(experiment_names=["shop-assistant"])
print("backend :", mlflow.get_tracking_uri())
print("runs    :", len(runs))
print("db file :", os.path.exists("mlflow.db"))

What the swap showed

  • Only the URI moved. start_run, log_param, log_metric and search_runs are the same calls against a different backend, which is what makes this a seam rather than a rewrite.
  • The runs survive a folder delete, because they are rows in mlflow.db rather than files under mlruns/.
  • It is the step before a server. A hosted backend, the subject of A remote tracking server, is the same change again with an HTTPS URI and a token.

Swapping the other three

  • The model. call_api takes a prompt and returns a string, so a provider call replaces its body, and the call sites do not move. Routing it through a gateway buys retries, fallbacks and one place to see the spend: LiteLLM overview.
  • Retrieval. The handbook dict stands where a retriever goes. Replacing it means the indexing path from The RAG data pipeline built for real, with the embedding model pinned as part of the index.
  • The score. Word overlap is a stand-in for a judge. A real metric calls a model and returns a score with a reason: DeepEval overview and RAGAS overview both plug into the same gate.
  • Swap one at a time. Each replacement changes what the numbers mean, so change one, re-run the evaluation set, and keep the run that proves the new piece did not make things worse.
Watch out. The moment a real model answers, the output stops being the same twice, and a gate written against a fixed reply starts failing for reasons that have nothing to do with quality. Move to a judge metric and a threshold in the same change, not after it.
Try it yourself
  • Point the tracking URI at sqlite:///mlflow.db in the capstone and confirm its runs land in the database.
  • Replace call_api's body with a provider call behind a gateway and re-run the capstone's two questions.
  • Swap the word-overlap score for a judge metric and note which of the two questions changes verdict.

Every expert started right here.