{ "cells": [ { "cell_type": "markdown", "id": "b1b28232-b65d-41ce-88de-fd70b93a528d", "metadata": {}, "source": [ "# Imports" ] }, { "cell_type": "code", "execution_count": 1, "id": "abb5186b-ee67-4e1e-882d-3d8d5b4575d4", "metadata": { "tags": [] }, "outputs": [], "source": [ "import json\n", "from pathlib import Path\n", "import pickle\n", "from tqdm.auto import tqdm\n", "\n", "from haystack.nodes.preprocessor import PreProcessor" ] }, { "cell_type": "code", "execution_count": 2, "id": "c4b82ea2-8b30-4c2e-99f0-9a30f2f1bfb7", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/home/ec2-user/RAGDemo\n" ] } ], "source": [ "proj_dir = Path.cwd().parent\n", "print(proj_dir)" ] }, { "cell_type": "markdown", "id": "76119e74-f601-436d-a253-63c5a19d1c83", "metadata": {}, "source": [ "# Config" ] }, { "cell_type": "code", "execution_count": 13, "id": "f6f74545-54a7-4f41-9f02-96964e1417f0", "metadata": { "tags": [] }, "outputs": [], "source": [ "files_in = list((proj_dir / 'data/consolidated').glob('*.ndjson'))\n", "folder_out = proj_dir / 'data/processed'" ] }, { "cell_type": "markdown", "id": "6a643cf2-abce-48a9-b4e0-478bcbee28c3", "metadata": {}, "source": [ "# Preprocessing" ] }, { "cell_type": "markdown", "id": "a8f9630e-447e-423e-9f6c-e1dbc654f2dd", "metadata": {}, "source": [ "Its important to choose good pre-processing options. \n", "\n", "Clean whitespace helps each stage of RAG. It adds noise to the embeddings, and wastes space when we prompt with it.\n", "\n", "I chose to split by word as it would be tedious to tokenize here, and that doesnt scale well. The context length for most embedding models ends up being 512 tokens. This is ~400 words. \n", "\n", "I like to respect the sentence boundary, thats why I gave a ~50 word buffer." ] }, { "cell_type": "code", "execution_count": 4, "id": "18807aea-24e4-4d74-bf10-55b24f3cb52c", "metadata": { "tags": [] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[nltk_data] Downloading package punkt to /home/ec2-user/nltk_data...\n", "[nltk_data] Unzipping tokenizers/punkt.zip.\n" ] } ], "source": [ "pp = PreProcessor(clean_whitespace = True,\n", " clean_header_footer = False,\n", " clean_empty_lines = True,\n", " remove_substrings = None,\n", " split_by='word',\n", " split_length = 350,\n", " split_overlap = 50,\n", " split_respect_sentence_boundary = True,\n", " tokenizer_model_folder = None,\n", " language = \"en\",\n", " id_hash_keys = None,\n", " progress_bar = True,\n", " add_page_number = False,\n", " max_chars_check = 10_000)" ] }, { "cell_type": "code", "execution_count": 5, "id": "dab1658a-79a7-40f2-9a8c-1798e0d124bf", "metadata": { "tags": [] }, "outputs": [], "source": [ "with open(file_in, 'r', encoding='utf-8') as f:\n", " list_of_articles = json.load(f)" ] }, { "cell_type": "code", "execution_count": 6, "id": "4ca6e576-4b7d-4c1a-916f-41d1b82be647", "metadata": { "tags": [] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Preprocessing: 0%|▌ | 1551/332023 [00:02<09:44, 565.82docs/s]We found one or more sentences whose word count is higher than the split length.\n", "Preprocessing: 83%|████████████████████████████████████████████████████████████████████████████████████████████████▌ | 276427/332023 [02:12<00:20, 2652.57docs/s]Document 81972e5bc1997b1ed4fb86d17f061a41 is 21206 characters long after preprocessing, where the maximum length should be 10000. Something might be wrong with the splitting, check the document affected to prevent issues at query time. This document will be now hard-split at 10000 chars recursively.\n", "Document 5e63e848e42966ddc747257fb7cf4092 is 11206 characters long after preprocessing, where the maximum length should be 10000. Something might be wrong with the splitting, check the document affected to prevent issues at query time. This document will be now hard-split at 10000 chars recursively.\n", "Preprocessing: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 332023/332023 [02:29<00:00, 2219.16docs/s]\n" ] } ], "source": [ "documents = pp.process(list_of_articles)" ] }, { "cell_type": "markdown", "id": "f00dbdb2-906f-4d5a-a3f1-b0d84385d85a", "metadata": {}, "source": [ "When we break a wikipedia article up, we lose some of the context. The local context is somewhat preserved by the `split_overlap`. Im trying to preserve the global context by adding a prefix that has the article's title.\n", "\n", "You could enhance this with the summary as well. This is mostly to help the retrieval step of RAG. Note that the way Im doing it alters some of `haystack`'s features like the hash and the lengths, but those arent too necessary. \n", "\n", "A more advanced way for many business applications would be to summarize the document and add that as a prefix for sub-documents.\n", "\n", "One last thing to note, is that it would be prudent (in some use-cases) to preserve the original document without the summary to give to the reader (retrieve with the summary but prompt without), but since this is a simple use-case I wont be doing that." ] }, { "cell_type": "code", "execution_count": 7, "id": "076e115d-3e88-49d2-bc5d-f725a94e4964", "metadata": { "tags": [] }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ba764e7bf29f4202a74e08576a29f4e4", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/268980 [00:00" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "documents[0]" ] }, { "cell_type": "code", "execution_count": 9, "id": "b34890bf-9dba-459a-9b0d-aa4b5929cbe8", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "documents[1]" ] }, { "cell_type": "code", "execution_count": 10, "id": "e6f50c27-a486-47e9-ba60-d567f5e530db", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "documents[10102]" ] }, { "cell_type": "code", "execution_count": 11, "id": "5485cc27-3d3f-4b96-8884-accf5324da2d", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of Articles: 332023\n", "Number of processed articles: 237724\n", "Number of processed documents: 268980\n" ] } ], "source": [ "print(f'Number of Articles: {len(list_of_articles)}')\n", "processed_articles = len([d for d in documents if d.meta['_split_id'] == 0])\n", "print(f'Number of processed articles: {processed_articles}')\n", "print(f'Number of processed documents: {len(documents)}')" ] }, { "cell_type": "markdown", "id": "23ce57a8-d14e-426d-abc2-0ce5cdbc881a", "metadata": {}, "source": [ "# Write to file" ] }, { "cell_type": "code", "execution_count": 14, "id": "0d044870-7a30-4e09-aad2-42f24a52780d", "metadata": { "tags": [] }, "outputs": [], "source": [ "with open(file_out, 'wb') as handle:\n", " pickle.dump(documents, handle, protocol=pickle.HIGHEST_PROTOCOL)" ] }, { "cell_type": "code", "execution_count": null, "id": "c5833dba-1bf6-48aa-be6f-0d70c71e54aa", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 5 }