An off-the-shelf embedding model like text-embedding-3-small or a good open-weights sentence-transformer will get you a working semantic search in an afternoon. It also has a ceiling: it was trained on general web text, not on your domain's vocabulary, and it has no idea that in your product "cancel" and "churn" and "downgrade" cluster together while in a general model they don't. Fine-tuning closes that gap by nudging the model's embedding space so that the pairs you care about — a support query and the article that actually answers it, a product name and its correct SKU — end up closer together than they would by default.
When fine-tuning is worth it
Fine-tuning an embedding model is not the first thing to try. It's the thing to try after you've measured that retrieval quality on a general model is actually the bottleneck — not your chunking strategy, not your reranker, not a missing metadata filter. If you haven't built an eval set of realistic queries with labeled relevant documents and measured recall@k against it, fine-tuning is guessing. Once you have that eval set and a general model plateaus below the quality bar you need, fine-tuning on your own domain pairs is usually the highest-leverage next step, because it directly targets the thing you measured as broken.
Fine-tuning needs (query, positive document) pairs at minimum, and ideally (query, positive, hard negative) triplets. Historical search logs with click-through, support tickets linked to the KB article that resolved them, or FAQ question/answer pairs are the usual sources — you're rarely starting from zero if the product has been running for a while.
Contrastive loss and hard negatives
The standard training objective is a contrastive loss — most commonly MultipleNegativesRankingLoss in the sentence-transformers library — which pulls a query's embedding toward its known positive document and pushes it away from the other documents in the batch, treated as negatives. This works reasonably well with random in-batch negatives, but the real quality jump comes from hard negatives: documents that are topically similar to the correct answer but wrong, mined by running your current model and picking high-scoring non-matches. Training against hard negatives teaches the model the fine-grained distinctions that actually cause search failures, instead of the easy distinctions it already gets right.
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
model = SentenceTransformer("BAAI/bge-base-en-v1.5")
train_examples = [
InputExample(texts=[query, positive_doc]) for query, positive_doc in training_pairs
]
train_loader = DataLoader(train_examples, shuffle=True, batch_size=32)
train_loss = losses.MultipleNegativesRankingLoss(model)
model.fit(
train_objectives=[(train_loader, train_loss)],
epochs=3,
warmup_steps=100,
output_path="./embedding-model-finetuned",
)
Evaluating the result, not just the loss curve
Training loss going down tells you the model fit the training pairs, not that retrieval improved on queries it hasn't seen. Hold out a slice of your labeled pairs as a validation set and track recall@k or NDCG on it during training, and keep a separate, never-touched test set for the final go/no-go decision. It's common for a fine-tuned model to overfit to the phrasing patterns in the training set and quietly regress on paraphrased or out-of-distribution queries — the only way to catch that is a held-out eval, not a lower loss number.
Aggressive fine-tuning on a narrow domain can degrade the model's general-purpose embedding quality, which matters if the same model serves other retrieval use cases. Keep learning rate low (1e-5 to 2e-5 is typical), use warmup, and re-run your general benchmark (or a broader eval slice) after fine-tuning, not just your domain eval.
Serving the fine-tuned model
A fine-tuned open-weights model is just a modified checkpoint — you serve it the same way you'd serve the base model, whether that's a local inference server, a managed endpoint, or baked into a batch embedding job. The one operational catch is versioning: every document already embedded with the old model needs to be re-embedded with the new one, because embeddings from different model versions are not comparable in the same vector index. Plan the re-embedding job and the cutover before you plan the fine-tuning run, not after.
Wrapping up
Fine-tuning an embedding model is worth the effort when you've already measured that a general model's retrieval quality is the actual bottleneck, and you have real query-document pairs — not synthetic ones — to train on. Use a contrastive loss with hard negatives, evaluate on a held-out set with a real ranking metric instead of trusting the training loss, and budget for re-embedding your whole corpus once the new model ships.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.