{
  "source_file": "0930",
  "source_platform": "个人博客",
  "author": "Philipp Schmid",
  "title_listed": "Fine-tune Embedding Model for RAG",
  "keywords": "嵌入模型,RAG,微调",
  "domain": "文本处理",
  "content_type": "教程",
  "is_paid_flag": "否",
  "url": "https://www.philschmid.de/fine-tune-embedding-model-for-rag",
  "publish_date": "2024-06",
  "corpus_stage": "预训练/SFT",
  "notes": "为RAG微调嵌入模型",
  "id": "b79a9af6043d",
  "collected_at": "2026-10-02T14:29:17",
  "title": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)",
  "body_markdown": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)\nJune 4, 2024\n11\nminute read\nView Code\nEmbedding models are crucial for successful RAG applications, but they're often trained on general knowledge, which limits their effectiveness for company or domain specific adoption. Customizing embedding for your domain specific data can significantly boost the retrieval performance of your RAG Application. With the new release of\nSentence Transformers 3\n, it's easier than ever to fine-tune embedding models.\nIn this blog, we'll show you how to fine-tune an embedding model for a financial RAG applications using a synthetic dataset from the\n2023_10 NVIDIA SEC Filing\n. We'll also leverage Matryoshka Representation Learning to boost efficiency. In the blog, we are going to:\nCreate & Prepare embedding dataset\nCreate baseline and evaluate pretrained model\nDefine loss function with Matryoshka Representation\nFine-tune embedding model with\nSentenceTransformersTrainer\nEvaluate fine-tuned model against baseline\n🪆 Matryoshka Embeddings\nMatryoshka Representation Learning (MRL)\nis a technique designed to create embeddings that can be truncated to various dimensions without significant loss of performance. This approach frontloads important information into earlier dimensions of the embedding, allowing for efficient storage and processing while maintaining high accuracy in downstream tasks such as retrieval, classification, and clustering.\nFor example, a Matryoshka model can preserve ~99.9% of its performance while needing 3x less storage. This is particularly useful for applications where storage and processing resources are limited, such as on-device applications or large-scale retrieval systems.\nNote: This blog was created to run on consumer size GPUs (24GB), e.g. NVIDIA A10G or RTX 4090/3090, but can be easily adapted to run on bigger GPUs.\nBefore we can get started we need to install Hugging Face Libraries and Pytorch, including Sentence Transformers, transformers and datasets.\nPython\n# Install Pytorch & other libraries\n!\npip install\n\"torch==2.1.2\"\ntensorboard\n# Install Hugging Face libraries\n!\npip install\n--\nupgrade \\\n\"sentence-transformers>=3\"\n\\\n\"datasets==2.19.1\"\n\\\n\"transformers==4.41.2\"\nWe will use the\nHugging Face Hub\nas a remote model versioning service. This means we will automatically push our model, logs and information to the Hub during training. You must register on the\nHugging Face\nfor this. After you have an account, we will use the\nlogin\nutil from the\nhuggingface_hub\npackage to log into our account and store our token (access key) on the disk.\nPython\nfrom\nhuggingface_hub\nimport\nlogin\nlogin\n(\ntoken\n=\n\"\"\n,\nadd_to_git_credential\n=\nTrue\n)\n# ADD YOUR TOKEN HERE\n1. Create & Prepare embedding dataset\nAn embedding dataset typically consists of text pairs (question, answer/context) or triplets that represent relationships or similarities between sentences. The dataset format you choose or have available will also impact the loss function you can use. Common formats for embedding datasets:\nPositive Pair\n: Text Pairs of related sentences (query, context | query, answer), suitable for tasks like similarity or semantic search, example datasets:\nsentence-transformers/sentence-compression\n,\nsentence-transformers/natural-questions\n.\nTriplets\n: Text triplets consisting of (anchor, positive, negative), example datasets\nsentence-transformers/quora-duplicates\n,\nnirantk/triplets\n.\nPair with Similarity Score\n: Sentence pairs with a similarity score indicating how related they are, example datasets:\nsentence-transformers/stsb\n,\nPhilipMay/stsb_multi_mt\nLearn more at\nDataset Overview\n.\nWe are going to use\nphilschmid/finanical-rag-embedding-dataset\n, which includes 7,000 positive text pairs of questions and corresponding context from the\n2023_10 NVIDIA SEC Filing\n.\nThe dataset has the following format\nJSON\n{\n\"question\"\n:\n\"<question>\"\n,\n\"context\"\n:\n\"<relevant context to answer>\"\n}\n{\n\"question\"\n:\n\"<question>\"\n,\n\"context\"\n:\n\"<relevant context to answer>\"\n}\n{\n\"question\"\n:\n\"<question>\"\n,\n\"context\"\n:\n\"<relevant context to answer>\"\n}\nWe are going to load our open-source dataset using the 🤗 Datasets library, rename the colums to match what\nsentence-transforemrs\nexpects and then split our dataset into a train and test spilt to be able to evaluate our model.\nPython\nfrom\ndatasets\nimport\nload_dataset\n# Load dataset from the hub\ndataset =\nload_dataset\n(\n\"philschmid/finanical-rag-embedding-dataset\"\n,\nsplit\n=\n\"train\"\n)\n# rename columns\ndataset = dataset.\nrename_column\n(\n\"question\"\n,\n\"anchor\"\n)\ndataset = dataset.\nrename_column\n(\n\"context\"\n,\n\"positive\"\n)\n# Add an id column to the dataset\ndataset = dataset.\nadd_column\n(\n\"id\"\n, range(len(dataset)))\n# split dataset into a 10% test set\ndataset = dataset.\ntrain_test_split\n(\ntest_size\n=\n0.1\n)\n# save datasets to disk\ndataset[\n\"train\"\n].\nto_json\n(\n\"train_dataset.json\"\n,\norient\n=\n\"records\"\n)\ndataset[\n\"test\"\n].\nto_json\n(\n\"test_dataset.json\"\n,\norient\n=\n\"records\"\n)\n2. Create baseline and evaluate pretrained model\nAfter we created our dataset we want to create a baseline. A baseline provides use reference point against which the performance of your customized model can be measured. By evaluating the performance of a pretrained model on our specific dataset, we gain insights into the initial effectiveness and can identify areas for improvement.\nFor our example, we will use the\nBAAI/bge-base-en-v1.5\nmodel as our starting point.\nBAAI/bge-base-en-v1.5\nis one of the strongest open embedding models for it size, with only 109M parameters and a hidden dimension of 768 it achieves\n63.55\non the\nMTEB Leaderboard\n.\nWe are going to use the\nInformationRetrievalEvaluator\nto evaluate the performance of our model on a given set of queries and corpus set. It will retrieve for each query the top-k most similar document. It measures Mean Reciprocal Rank (MRR), Recall@k, Mean Average Precision (MAP) and Normalized Discounted Cumulative Gain (NDCG).\nFor us the most important metric will be Normalized Discounted Cumulative Gain (NDCG) as it is a measure of ranking quality. It takes into account the position of the relevant document in the ranking and discounts it. The discounted value is logarithmic, which means that relevant documents are more important if they are ranked higher.\nFor our evaluation corpus we will use all \"documents\" for potential retrieval from the train and test split and the queries from the test set. As mentioned in the beginning we are going to use Matryoshka Representation Learning to boost efficiency. We are going to create a baseline for the following dimensions\n64\n,\n128\n,\n256\n,\n512\n,\n768\n. Since those are the dimensions we are going to use for our Matryoshka Representation Learning.\nPython\nimport\ntorch\nfrom\nsentence_transformers\nimport\nSentenceTransformer\nfrom\nsentence_transformers.evaluation\nimport\n(\nInformationRetrievalEvaluator,\nSequentialEvaluator,\n)\nfrom\nsentence_transformers.util\nimport\ncos_sim\nfrom\ndatasets\nimport\nload_dataset, concatenate_datasets\nmodel_id =\n\"BAAI/bge-base-en-v1.5\"\n# Hugging Face model ID\nmatryoshka_dimensions = [\n768\n,\n512\n,\n256\n,\n128\n,\n64\n]\n# Important: large to small\n# Load a model\nmodel =\nSentenceTransformer\n(\nmodel_id,\ndevice\n=\n\"cuda\"\nif\ntorch.cuda.\nis_available\n()\nelse\n\"cpu\"\n)\n# load test dataset\ntest_dataset =\nload_dataset\n(\n\"json\"\n,\ndata_files\n=\n\"test_dataset.json\"\n,\nsplit\n=\n\"train\"\n)\ntrain_dataset =\nload_dataset\n(\n\"json\"\n,\ndata_files\n=\n\"train_dataset.json\"\n,\nsplit\n=\n\"train\"\n)\ncorpus_dataset =\nconcatenate_datasets\n([train_dataset, test_dataset])\n# Convert the datasets to dictionaries\ncorpus = dict(\nzip(corpus_dataset[\n\"id\"\n], corpus_dataset[\n\"positive\"\n])\n)\n# Our corpus (cid => document)\nqueries = dict(\nzip(test_dataset[\n\"id\"\n], test_dataset[\n\"anchor\"\n])\n)\n# Our queries (qid => question)\n# Create a mapping of relevant document (1 in our case) for each query\nrelevant_docs = {}\n# Query ID to relevant documents (qid => set([relevant_cids])\nfor\nq_id\nin\nqueries:\nrelevant_docs[q_id] = [q_id]\nmatryoshka_evaluators = []\n# Iterate over the different dimensions\nfor\ndim\nin\nmatryoshka_dimensions:\nir_evaluator =\nInformationRetrievalEvaluator\n(\nqueries\n=queries,\ncorpus\n=corpus,\nrelevant_docs\n=relevant_docs,\nname\n=\nf\n\"dim_\n{\ndim\n}\n\"\n,\ntruncate_dim\n=dim,\n# Truncate the embeddings to a certain dimension\nscore_functions\n={\n\"cosine\"\n: cos_sim},\n)\nmatryoshka_evaluators.\nappend\n(ir_evaluator)\n# Create a sequential evaluator\nevaluator =\nSequentialEvaluator\n(matryoshka_evaluators)\nWe are going to use the\nevaluator\nalso for evaluation during training. But first lets create our baseline.\nPython\n# Evaluate the model\nresults =\nevaluator\n(model)\n# # COMMENT IN for full results\n# print(results)\n# Print the main score\nfor\ndim\nin\nmatryoshka_dimensions:\nkey =\nf\n\"dim_\n{\ndim\n}\n_cosine_ndcg@10\"\nprint\nprint(\nf\n\"\n{\nkey\n}\n:\n{\nresults[key]\n}\n\"\n)\nGreat our default baseline with the\nBAAI/bge-base-en-v1.5\nare:\ndim 768:\n0.7683576219287339\ndim 512:\n0.7642500951356254\ndim 256:\n0.7546468566985919\ndim 128:\n0.7233663127615272\ndim 64:\n0.6439999058834552\nNow, let's see if we can improve this score by fine-tuning the model on our specific dataset.\n3. Define loss function with Matryoshka Representation\nWhen fine-tuning embedding models we select a\nloss function based on our dataset format\n. For Positive Text pairs we can use the\nMultipleNegativesRankingLoss\nin combination with the\nMatryoshkaLoss\n. This approach allows us to leverage the efficiency and flexibility of Matryoshka embeddings, enabling different embedding dimensions to be utilized without significant performance trade-offs. The\nMultipleNegativesRankingLoss\nis a great loss function if you only have positive pairs as it adds in batch negative samples to the loss function to have per sample n-1 negative samples.\nLets reload our model using\nSDPA\nor Flash Attention 2 as\nattn_implementation\nand define a model card.\nPython\nfrom\nsentence_transformers\nimport\nSentenceTransformerModelCardData, SentenceTransformer\n# Hugging Face model ID: https://huggingface.co/BAAI/bge-base-en-v1.5\nmodel_id =\n\"BAAI/bge-base-en-v1.5\"\n# load model with SDPA for using Flash Attention 2\nmodel =\nSentenceTransformer\n(\nmodel_id,\nmodel_kwargs\n={\n\"attn_implementation\"\n:\n\"sdpa\"\n},\nmodel_card_data\n=\nSentenceTransformerModelCardData\n(\nlanguage\n=\n\"en\"\n,\nlicense\n=\n\"apache-2.0\"\n,\nmodel_name\n=\n\"BGE base Financial Matryoshka\"\n,\n),\n)\nAfter we loaded our model we can initialize our loss function.\nPython\nfrom\nsentence_transformers.losses\nimport\nMatryoshkaLoss, MultipleNegativesRankingLoss\nmatryoshka_dimensions = [\n768\n,\n512\n,\n256\n,\n128\n,\n64\n]\n# Important: large to small\ninner_train_loss =\nMultipleNegativesRankingLoss\n(model)\ntrain_loss =\nMatryoshkaLoss\n(\nmodel, inner_train_loss,\nmatryoshka_dims\n=matryoshka_dimensions\n)\n4. Fine-tune embedding model with\nSentenceTransformersTrainer\nWe are now ready to fine-tune our model. We will use the\nSentenceTransformersTrainer\na subclass of the\nTrainer\nfrom the\ntransformers\nlibrary, which supports all the same features, including logging, evaluation, and checkpointing.\nIn addition to this there is a\nSentenceTransformerTrainingArguments\nclass that allows us to specify all the training parameters.\nPython\nfrom\nsentence_transformers\nimport\nSentenceTransformerTrainingArguments\nfrom\nsentence_transformers.training_args\nimport\nBatchSamplers\n# load train dataset again\ntrain_dataset =\nload_dataset\n(\n\"json\"\n,\ndata_files\n=\n\"train_dataset.json\"\n,\nsplit\n=\n\"train\"\n)\n# define training arguments\nargs =\nSentenceTransformerTrainingArguments\n(\noutput_dir\n=\n\"bge-base-financial-matryoshka\"\n,\n# output directory and hugging face model ID\nnum_train_epochs\n=\n4\n,\n# number of epochs\nper_device_train_batch_size\n=\n32\n,\n# train batch size\ngradient_accumulation_steps\n=\n16\n,\n# for a global batch size of 512\nper_device_eval_batch_size\n=\n16\n,\n# evaluation batch size\nwarmup_ratio\n=\n0.1\n,\n# warmup ratio\nlearning_rate\n=\n2e-5\n,\n# learning rate, 2e-5 is a good value\nlr_scheduler_type\n=\n\"cosine\"\n,\n# use constant learning rate scheduler\noptim\n=\n\"adamw_torch_fused\"\n,\n# use fused adamw optimizer\ntf32\n=\nTrue\n,\n# use tf32 precision\nbf16\n=\nTrue\n,\n# use bf16 precision\nbatch_sampler\n=BatchSamplers.\nNO_DUPLICATES\n,\n# MultipleNegativesRankingLoss benefits from no duplicate samples in a batch\neval_strategy\n=\n\"epoch\"\n,\n# evaluate after each epoch\nsave_strategy\n=\n\"epoch\"\n,\n# save after each epoch\nlogging_steps\n=\n10\n,\n# log every 10 steps\nsave_total_limit\n=\n3\n,\n# save only the last 3 models\nload_best_model_at_end\n=\nTrue\n,\n# load the best model when training ends\nmetric_for_best_model\n=\n\"eval_dim_128_cosine_ndcg@10\"\n,\n# Optimizing for the best ndcg@10 score for the 128 dimension\n)\nWe now have every building block we need to create our\nSentenceTransformersTrainer\nto start then training our model.\nPython\nfrom\nsentence_transformers\nimport\nSentenceTransformerTrainer\ntrainer =\nSentenceTransformerTrainer\n(\nmodel\n=model,\n# bg-base-en-v1\nargs\n=args,\n# training arguments\ntrain_dataset\n=train_dataset.\nselect_columns\n(\n[\n\"positive\"\n,\n\"anchor\"\n]\n),\n# training dataset\nloss\n=train_loss,\nevaluator\n=evaluator,\n)\nStart training our model by calling the\ntrain()\nmethod on our\nSentenceTransformerTrainer\ninstance. This will start the training loop and train our model for 4 epochs.\nPython\n# start training, the model will be automatically saved to the hub and the output directory\ntrainer.\ntrain\n()\n# save the best model\ntrainer.\nsave_model\n()\n# push model to hub\ntrainer.model.\npush_to_hub\n(\n\"bge-base-financial-matryoshka\"\n)\nThe training with Flash Attention (SDPA) for 4 epochs on 6.3k samples took ~00:03:26 on a\ng5.2xlarge\n. The instance costs\n1.212$/h\nwhich brings us to a total cost of only\n0.07$\nfor the training.\n5. Evaluate fine-tuned model against baseline\nWe evaluated our model during training, but we also want to evaluate it against our baseline at the end. We use the same\nInformationRetrievalEvaluator\nto evaluate the performance of our model on a given set of queries and corpus set.\nPython\nfrom\nsentence_transformers\nimport\nSentenceTransformer\nfine_tuned_model =\nSentenceTransformer\n(\nargs.output_dir,\ndevice\n=\n\"cuda\"\nif\ntorch.cuda.\nis_available\n()\nelse\n\"cpu\"\n)\n# Evaluate the model\nresults =\nevaluator\n(fine_tuned_model)\n# # COMMENT IN for full results\n# print(results)\n# Print the main score\nfor\ndim\nin\nmatryoshka_dimensions:\nkey =\nf\n\"dim_\n{\ndim\n}\n_cosine_ndcg@10\"\nprint(\nf\n\"\n{\nkey\n}\n:\n{\nresults[key]\n}\n\"\n)\nOur fine-tuned model achieves:\ndim 768:\n0.8253652077429072\ndim 512:\n0.8274643684492735\ndim 256:\n0.8230326345640168\ndim 128:\n0.8184049256658124\ndim 64:\n0.7892150398663841\nLets put this into a table and compare the performance of our fine-tuned model against the baseline.\nDimension\nBaseline\nFine-tuned\nImprovement\n768\n0.7684\n0.8254\n7.42%\n512\n0.7643\n0.8275\n8.27%\n256\n0.7546\n0.8230\n9.06%\n128\n0.7234\n0.8184\n13.13%\n64\n0.6440\n0.7892\n22.55%\nLets try to understand this:\nFine-tuned model outperforms the baseline model in all dimensions.\nBGE base is already a very strong base model but fine-tuning it on only 6.3k samples still improves performance by ~7.4%.\nMatryoshka representation learning works and keeps 95% performance at 64 dimensions (12x size reduction), 99% at 128 dimensions (6x size reduction), >99.5% at 256 dimensions (3x size reduction).\nThe Fine-tuned model with lowest dimension (64) outperforms the baseline model with highest dimensions (768).\nOptimizing the model dimension of 128 allows us to keep 99% of the performance of the 768 dimension model but reducing the storing cost by 6x and improving the cosine similarity search.\nThe fine-tuned model with 128 dimensions outperforms the baseline model with 768 dimensions by 6.51%, while being 6x smaller.\nConclusion\nEmbedding models are crucial for successfull RAG applications, since if you don't retrieve the right context you can't generate the right answer. Customizing embedding models for domain-specific data can improve retrieval performance significantly compared to using general knowledge models. Fine-tuning embedding models has become highly accessible, and using synthetic data generated by LLMs, one can easily customize models for specific needs, resulting in substantial improvements.\nOur results show that fine-tuning can boost performance by ~7% with only 6.3k sample. The training took 3 minutes on a consumer size GPU and by leveraging modern techniques like Matryoshka Representation Learning we achieved over 99% performance retention with 6x storage reduction and efficiency gains.\nThanks for reading! If you have any questions or feedback, please let me know on\nTwitter\nor\nLinkedIn\n.",
  "metadata": {
    "title": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)",
    "description": "Customizing embedding models for domain-specific data can significantly boost the retrieval performance of your RAG Application.",
    "keywords": null,
    "author": "Philipp Schmid",
    "og:title": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)",
    "og:description": "Customizing embedding models for domain-specific data can significantly boost the retrieval performance of your RAG Application.",
    "og:url": "https://www.philschmid.de/fine-tune-embedding-model-for-rag",
    "og:image": "https://www.philschmid.de/static/blog/fine-tune-embedding-model-for-rag/thumbnail.jpg",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:image:alt": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:title": "Fine-tune Embedding models for Retrieval Augmented Generation (RAG)",
    "twitter:description": "Customizing embedding models for domain-specific data can significantly boost the retrieval performance of your RAG Application.",
    "twitter:image": "https://www.philschmid.de/static/blog/fine-tune-embedding-model-for-rag/thumbnail.jpg",
    "article:published_time": "2024-06-04"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/b79a9af6043d/code/body-000.txt",
      "chars": 218,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-001.txt",
      "chars": 102,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-002.txt",
      "chars": 209,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-003.txt",
      "chars": 609,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-004.txt",
      "chars": 1782,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-005.txt",
      "chars": 241,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-006.txt",
      "chars": 508,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-007.txt",
      "chars": 318,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-008.txt",
      "chars": 1782,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-009.txt",
      "chars": 324,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-010.txt",
      "chars": 233,
      "language": "text"
    },
    {
      "file": "attachments/b79a9af6043d/code/body-011.txt",
      "chars": 415,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://github.com/philschmid/deep-learning-pytorch-huggingface/blob/main/training/fine-tune-embedding-model-for-rag.ipynb",
      "label": "View Code",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/blog/train-sentence-transformers",
      "label": "Sentence Transformers 3",
      "type": "a"
    },
    {
      "href": "https://stocklight.com/stocks/us/nasdaq-nvda/nvidia/annual-reports/nasdaq-nvda-2023-10K-23668751.pdf",
      "label": "2023_10 NVIDIA SEC Filing",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/blog/matryoshka",
      "label": "Matryoshka Representation Learning (MRL)",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/models",
      "label": "Hugging Face Hub",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/join",
      "label": "Hugging Face",
      "type": "a"
    },
    {
      "href": "https://sbert.net/docs/sentence_transformer/dataset_overview.html",
      "label": "Dataset Overview",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/datasets/philschmid/finanical-rag-embedding-dataset",
      "label": "philschmid/finanical-rag-embedding-dataset",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/BAAI/bge-base-en-v1.5",
      "label": "BAAI/bge-base-en-v1.5",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/spaces/mteb/leaderboard",
      "label": "MTEB Leaderboard",
      "type": "a"
    },
    {
      "href": "https://sbert.net/docs/package_reference/sentence_transformer/evaluation.html",
      "label": "InformationRetrievalEvaluator",
      "type": "a"
    },
    {
      "href": "https://sbert.net/docs/sentence_transformer/loss_overview.html",
      "label": "loss function based on our dataset format",
      "type": "a"
    },
    {
      "href": "https://sbert.net/docs/package_reference/sentence_transformer/trainer.html",
      "label": "SentenceTransformersTrainer",
      "type": "a"
    },
    {
      "href": "https://twitter.com/_philschmid",
      "label": "Twitter",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/philipp-schmid-a6a2bb196/",
      "label": "LinkedIn",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:52:53Z",
  "source_url": "https://www.philschmid.de/fine-tune-embedding-model-for-rag",
  "papers": [
    {
      "status": "ok",
      "url": "https://stocklight.com/stocks/us/nasdaq-nvda/nvidia/annual-reports/nasdaq-nvda-2023-10K-23668751.pdf",
      "bytes": 947040,
      "reused": true,
      "local_path": "papers/repair/67117afb0c69ce82fa5a.pdf"
    }
  ],
  "actual_video_files": [],
  "body_chars": 16518,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/b79a9af6043d/pages/source.html",
  "local_html": "attachments/b79a9af6043d/pages/article.html",
  "body_base_path": "attachments/b79a9af6043d/pages/",
  "body_markdown_path": "attachments/b79a9af6043d/pages/article.md"
}