00:00Hello all, my name is Krishna and I am super excited to announce this amazing crash course on RAG, that is Retrieval-Augmented Generation
00:07Uh, in this specific crash course, it'll be around 2.5 to 3 hours, but we are going to discuss everything related to RAG completely from scratch
00:17Uh, we'll be talking about the entire pipeline, from data injection to retrieval pipeline to output generation
00:24How to use LLM models, how to use embedding models in this, along with what should be the right strategy for using chunking and many more things
00:35Right, so we will be deep diving into both the theoretical understanding along with practical implementation, and we will initially go step by step
00:44Start with the basic implementation, and then, as we go ahead into the advanced section, we'll also implement modular coding
00:51The main aim of modular coding is to link the entire pipeline in a way that you should be able to understand how RAG (Retrieval-Augmented Generation) actually works and also implement it in your company's use cases
01:01 Let me tell you one very important thing: 90% of the use cases that are currently being worked on in all the companies are specifically related to RAG
01:07So, this crash course will be an amazing opportunity for all of you
01:11We'll set a simple target of completing it as soon as possible, aiming for around 1,000 completions, and also a target of 500 comments
01:20So, please try to complete it, and yes, go ahead
01:22Enjoy this particular crash course
01:24Thank you
01:26So, this is a simple definition that I have put up over here, and in this definition, first of all, we'll try to understand RAG
01:35Okay, so first of all, let's go through the definition and then I will give you a brief idea of what exactly RAG is all about
01:41You know, so here you can clearly see that RAG is the process of optimizing the output of a large language model
01:51Okay, so it references an authoritative knowledge base outside of its training data set before generating a response
02:01LLMs are trained on a vast volume of data, as we all know, and use billions of parameters to generate original output for tasks like question answering, translating, and completing sentences
02:15RAG It extends the already powerful capabilities of LLMs to a specific domain or an organization's internal knowledge base, all without the need to retrain the model
02:28Okay, it is a cost-effective approach to improve LLM output
02:30So, it's relevant, accurate, and useful in various contexts
02:35So, this is just a basic definition
02:36You can refer to this particular definition
02:38So, guys, now let's go ahead and understand more about RAG
02:40 Let's consider that I have a generative AI application
02:42And as you all know, in a generative AI application, usually, let's say that I have an LLM
02:50So, this is my LLM
02:52Now, usually, whenever we have an LLM, what happens is that—let's consider that I have a user
02:58A user is asking a query
03:01So, this is my query from the user, and Before it is sent to the LLM, we do add a prompt, right? We do add a prompt, and this prompt is just like an instruction to the LLM, telling it how it should work
03:16Based on this, we actually get an output
03:22Now, this is a simple generative AI application where the LLM is used to generate content
03:27So, by using this specific technique, we give a query, and this LLM—which has been trained with billions of data, different kinds of data available on the internet—will be able to generate the output
03:52One of the disadvantages of this, let me talk about the disadvantage of this particular approach
03:58As you know, every LLM that is trained..
04:02We need to be trained on a specific set of data
04:04So, let's say right now it is 31st August
04:07Okay, 31st August
04:09Let's assume this is my LLM model, and this is basically GPT-5, the recent model from OpenAI
04:19Now, as you know, when this model was launched, it may have been trained with data up to 1st August
04:27Okay, so this LLM will not have any idea what has happened in the world between 1st and 31st August
04:38Right? And let's say if I go ahead and ask a specific question to the LLM regarding events between these dates, the LLM will start hallucinating
04:49So, one of the major disadvantages of only using the LLM is that it will hallucinate
04:59Okay, when we say hallucinating, what does this mean? Basically, it means that even if the model does not have knowledge of what happened between 1st August and 31st August, any events, even when we ask any question, the LLM will try to generate its own answer because it does not want to look like a fool
05:19Okay, that is the best example
05:22It does not want to look like a fool
05:24So it will try to generate some answers and make sure that it shows you an answer that you may also have to believe
05:31That is how it will be reflected in the output we get
05:33Usually, this condition is called hallucinating
05:38So, this is one of the major disadvantages
05:42The second disadvantage is that, let's say, I am using this LLM and you know, this LLM has..
05:47We've been trained with a huge amount of data
05:51Now, what happens is that I'm running a startup
05:55Let's say, in my startup, I'm solving a specific use case, and I have some data that I need to use along with my large language model (LLM)
06:07Okay, so let's say I have some other data, like policies—policies of my company, HR policies, finance policies
06:20These policies are not available publicly because it is my startup, so all this data is protected
06:29Now, I also want to use this specific data and probably create a chatbot
06:34Okay, now how do I do this? One way is that many people will say, "Hey, Kish, we can take this particular data and fine-tune the..." The model, right? We can simply fine-tune the model
06:48Yes, this is a very good solution, but understand that fine-tuning a model is a very expensive and tedious process because this LLM, whichever LLM we are using, has billions of parameters, and tweaking these billions of parameters usually takes a lot of time
07:06Right? So, obviously, this is a solution, but it is a very expensive one
07:12Okay
07:17Now, do we have any other way? Any other approach? And remember, all these policies and data will also keep getting updated as we run the startup
07:23Right? So every time, we cannot just go ahead and fine-tune it daily
07:28We don't fine-tune it every day, right? So, we should try to find a solution—like, how do we prevent this? So this can again be..
07:35Prevented with the help of RAG right now
07:38How will it be prevented with the help of RAG? I will talk about it, okay? So here, instead of fine-tuning, I'm saying that I will go ahead and implement RAG now
07:50You'll understand only when we understand the pipeline of RAG, which I will discuss in this specific video
07:56Okay? Now, these are the major two disadvantages that you see right over here, and yes, there are some more disadvantages, which we'll just deep dive into more as we go ahead
08:09Okay? Now, what happens if we use RAG and how we are preventing it? See, RAG is nothing but a process of optimizing the output of a large language model, so it references an authoritative knowledge base outside of its training data
08:24Now, how do we solve this hallucinating problem that we have? Okay, so let me just go ahead and draw the diagram again
08:35Okay, so here is my LLM, and here is my query
08:37So let's say I am coming up with a user query, so let's consider it over here
08:44Okay, and here I am drawing a user
08:46Okay, and this user will first of all give a query
08:55Okay, now what happens is that there will be two important pipelines that will be created
09:03As I said before, we are trying to optimize the output of a large language model, so it references an authoritative knowledge base outside of its training data sources
09:18So, as you all know, this is my LLM, right? This LLM is already trained with a huge amount of data
09:23Now, along with this, I will be..
09:24Having an external database, and this database we basically call it a vector database, okay? External vector database
09:33Now, you know that this LLM is already trained with some amount of data, and any additional data—let's say my startup data, my policies, HR, finance, whatever data is there—we will try to create a data injection pipeline over here, a data injection pipeline
09:54Now, what will this data injection pipeline be? So, let's say I have my data; from this data, we will do some kind of parsing, and from this parsing, we will generate embeddings, and then we finally store it into the vector store
10:20Okay? Now, whenever we talk about specific data, this data can be in any format
10:24It can be in PDF format, it can be in HTML, It can be in Excel format
10:29It can even be in SQL database format or unstructured format—any format
10:34So, what we do initially is take this data and perform data parsing
10:39Now, data parsing is a very important step
10:44I think if you master this step, developing a RAG application becomes very easy
10:52Data parsing is all about how you read the unstructured data or the structured data that is present inside this, and how you chunk this data
11:03How do you divide the specific data into chunks? Chunking is very important because you need to store this data inside some kind of vector store
11:13This is nothing but a vector store or vector database
11:15Okay
11:18Now, the vector store or vector database is nothing but it will actually..
11:21Help you to save vectors inside this
11:23Okay, so once you do the chunking, after doing the chunking, you pass it to the embedding models
11:29Now, here in the embedding models, you basically convert text to vectors
11:33Okay, vectors are just like a numerical representation of text so that you will be able to apply algorithms like similarity search, cosine similarity techniques that are already available, right? Wherein similar kinds of results based on a specific query can be retrieved from this particular database
11:58Okay, so here, whenever I talk about vector database, this is my vector database or vector store
12:03Here, we are storing embeddings
12:06Okay, and these embeddings will get applied to every chunk
12:10Embeddings are nothing but we..
12:13Basically, we convert text into vectors
12:15Here, we can use different embeddings, such as Google Gemini models
12:20We can also use OpenAI embedding models
12:23Additionally, we can use Hugging Face embedding models, each with different costs, and there are open-source embedding models that help you convert text into vectors
12:33Now, this is a specific pipeline called the data injection pipeline
12:41At the end of the data injection pipeline, you are able to store the text as vectors inside your vector database
12:46Now, how is RAG different from the previous method? Initially, you had this data injection pipeline where you convert all your data into..
12:54Vectors, right? And this data is specifically for this particular startup
13:00And now I have created a knowledge base
13:02So, this is my knowledge base—an external knowledge base or internal knowledge base, whatever knowledge base I have
13:12And this knowledge base does not exist within this LLM
13:14Right? Yes, some amount of information may be available, but not the entire part
13:18Now, see the definition: it is a process of optimizing the output of a large language model so that it references an authoritative knowledge base outside of its training data
13:29Now, what will happen when the user gives a query? Instead of the query going directly to the LLM, it will go to this vector database
13:39And before going here, we also need to apply..
13:43Embedding, right? Because this query will be converted into vectors
13:48Why do we need to convert into vectors? So that when we send this query to the vector database, this similarity search is basically applied, and based on this, we get some kind of context; we get some information from the vector database
14:11And now, whatever query I'm asking—okay, if I ask, "What is the leave policy of my company right now?"—what will happen? First of all, it will go to the vector store, gather all the related information available there, and that information, when it is sent to the language model, is called the context
14:31Now, we use this context along with a specific prompt
14:35This prompt is an instruction to the large language model, and it says: That you can use this context to answer the question, and finally you get an output
14:49This is the entire pipeline
14:53This pipeline is basically called a retrieval pipeline
14:56Retrieval pipeline
14:58And this is a very good example of a traditional RAG
15:03Now you may be thinking, "Kish, what about other types of RAG?" Don't worry, don't worry, I will explain it completely from basic to advanced, with implementation, everything, because later on we'll be discussing about agentic RAGs
15:18We'll be discussing how agentic RAGs actually work, everything
15:20But I hope you got an idea with respect to this
15:24Now here, you will even not be seeing this particular problem, like you'll not completely eliminate hallucination, but some..
15:31The amount of hallucination, if any, in queries related to the data present in the vector database, I will definitely get some kind of context, and my LLM will give me the output
15:43Let's say that if that data is not present here, then the LLM can hallucinate, right? But here we are doing this
15:51See, one of the best examples you can do is that you can use PerfectPerplexity
15:54Perplexity is nothing but it is based on RAG
15:58It is completely developed based on RAG applications
16:00Okay? RAG, it is a kind of RAG application
16:09In Perplexity, you are connected to various retrievers, to tools, and to web search, and then it is summarizing the output and giving it to the LLM
16:14It also uses..
16:19Various LLMs themselves
16:25I'm also planning to start a startup soon, within a couple of weeks, I guess, and the kind of application I am developing is a RAG application only, and it solves a very good problem for a developer
16:38Okay, so that is the reason I am not even able to upload a lot of videos because I am pretty much involved in those startups, working and developing a product that India can remember
16:49Okay, and this is how, you know, this is how things are, and you can basically see how good, you know, the pipeline actually works
17:00This is basically a traditional RAG
17:04Now, you may be thinking, what all things will we be discussing? Okay, fine, we have discussed about a traditional RAG in
17:09The future classes, what coding we'll be doing
17:11Okay, so let's go ahead and talk about it
17:13As I said, two important pipelines we'll go ahead and create: one is a data injection pipeline, and the other is a retrieval pipeline
17:18Okay
17:23Now, in the data injection pipeline, you'll see that we will be performing data injection
17:28Along with the data injection, we will also do data parsing
17:30Then, we'll perform embeddings
17:32After that, we will store everything into the vector store
17:40Then, we will create a retriever for this, and whenever a user asks any queries, it will be able to give the context to the LLM
17:47And finally, we will generate the output
17:50So, this is retrieval
17:53This is augmentation, right? This is augmentation
17:56Over here, augmentation basically means what? You're giving a context to the LLM along with the prompt to generate the output, right? So, this is basically called augmentation, and finally you're generating the output, which is nothing but generation
18:07So here, you are basically generating
18:09Now, in the next session, we are going to implement it
18:16First of all, I will show you how to perform these two steps in a very efficient way
18:20Okay, sorry, not these two steps
18:24I will show you how we can perform all these steps—data parsing and embedding
18:29Here, we are going to consider different files like PDFs, HTML, okay? PDF, HTML—you can consider Excel, SQL databases, you can consider
18:42Any kind of files
18:44Then we'll do document parsing and we will try to convert this into a document
18:49So, a document is an amazing data structure which you can basically use, and you can even parse this, do the chunking, and store it in the vector store
18:58Then we'll perform embeddings here; we will use both open-source and paid embeddings for the same
19:06Okay, and then finally, we go to the vector store
19:10Based on a user query, how do we go ahead and apply the same embeddings? We are going to see that, okay? And then finally, we'll be developing this
19:17So, mostly I really want—I'm focusing more on making bigger videos so that you don't just follow a playlist
19:24Okay? I want to basically cover a lot of stuff
19:28In one video, so that you should also be able to cover it efficiently instead of covering 50 different videos
19:34Right now, when we are doing data injection and data parsing, there are various techniques
19:39See, we are going to talk about optimization
19:41We will discuss various chunking strategies, context engineering—these all kinds of topics will come up when we talk about data parsing
19:50You know, what is a semantic chunker, how do we go ahead and do the chunking with those strategies, and everything else
19:57We'll try to discuss as we go along, but I hope you got a very super cool idea about what exactly RAG is
20:03Hello guys, so we are going to continue the discussion regarding RAG
20:07Up until now, we have… I understood what RAG is, then what the main drawbacks we are fixing with RAG, and along with that, we have also understood how the RAG pipeline works
20:19It usually consists of two important parts: one is the data injection pipeline, and the other is the retrieval pipeline, which includes these two boxes
20:26Now, we are going to go ahead with some practical implementation
20:30The major thing that usually comes to my mind whenever we start any new series is how to cover a specific topic, so that we can understand the coding from basics and move towards modular coding
20:46That is how I am going to implement this entire pipeline
20:52Initially, we will go ahead with some basic code and try to..
20:56Understand the fundamentals, and then we will start writing more complex code
21:01We'll be using modular coding as well, so initially, we will write all the code in a Jupyter Notebook
21:06Then, we'll increase the complexity by writing code in terms of classes, reusability, and usability, and then we'll try to see how we can actually create the pipeline
21:15So, that is how the agenda will probably go as we progress
21:20 Two important things that we'll consider are: the first is to understand the document structure
21:26Now, whenever we work with any external knowledge database, any data that needs to be fed into the vector database, you definitely need to know about this document structure
21:38Why? Because inside this..
21:39The data injection pipeline's first step is data injection
21:43Now, whenever we talk about data injection, we can have any kind of files, right? We can have PDF files, HTML files, DB files, Excel files
21:50Our main aim is to read all this file content and probably convert it into a structure where we can additionally apply strategies like chunking, embedding, and store it into the vector database
22:06That is what this entire pipeline is all about
22:08 So, for that, you really need to understand the document structure
22:10If you see this diagram, these two are the main topics that we are going to cover in this particular video
22:19Initially, we will go ahead with understanding the document structure, and then proceed further
22:23We'll try to build our complete RAG pipeline
22:27In our complete RAG pipeline, we have two important steps: one is the data injection pipeline and the other is the query retrieval pipeline
22:31Now, whenever we talk about the data injection pipeline, let's discuss this in complete depth
22:38Initially, you have this data injection pipeline
22:40The first step in the data injection pipeline is data injection itself
22:45That basically means, let's say you have different kinds of files like PDF, HTML, Excel, or a database file
22:51You may also have unstructured files, any kind of file format
22:59So, in data injection, our main strategy is to determine how to proceed with reading this particular file and how to..
23:06Perform data parsing, how to perform data parsing, and then finally how to convert this into a document structure
23:16Document structure
23:25So, that is the reason in this video—right, as I said—we're going to first of all understand about document structure, how to build this document structure, what is metadata? Now, inside this document structure, you will be learning about important components like metadata
23:35You'll be learning about content, and how the structure of the metadata exists, everything, right? So, we will be covering this completely in depth, like how these things actually work
23:50Okay? Once you understand this, data parsing is a really, really important step because Of this, you know, later in the retrieval pipeline, that is the query retrieval pipeline, based on this parsing, it can become much more efficient, right? You'll be able to get the results with much more accuracy, so that is the reason you need to really focus on the data parsing
24:14Now, after doing the data parsing, the next step usually is something called chunking
24:18So here, in chunking, we convert this entire data into multiple chunks
24:24So these chunks are like, let's say, this is my Chunk 1, this is my Chunk 2, this is my Chunk 3, this is my Chunk 4
24:35Okay? Then, as we go ahead after applying chunking, so chunking basically means—and why do we apply chunking? The chunking strategy is very simple: whatever..
24:50Documents we have, we are just dividing these into smaller parts or smaller chunks
24:54The reason we do this is because, with respect to any LLM model or any embedding models, the next step is all about embeddings
25:05Okay? In embedding, with respect to every LLM model, there is a fixed context size
25:14Let's say I take a complete 100-page PDF and try to give it directly to an LLM for performing the embeddings
25:28Embedding basically means converting text to vectors
25:32It will not be possible to give the entire document at once; it will say, "Hey, you are providing data more than the context size," and that will not be feasible
25:42In order to convert the text into vectors, you need to give the data within the limit of the context size
25:51This applies to both embedding models and even in later stages whenever we use any kind of LLM model, because for every LLM model, there is a fixed context size
25:57Different LLM models may have different context sizes
25:59That is the reason why it is always a good strategy to divide our data into chunks, so that we fit them in a way that, in the later stages, we'll be able to efficiently store them into the vector database, which is this
26:13After chunking, for every chunk, we go ahead and apply embeddings
26:17So, we apply embeddings to each chunk, and from these embeddings, we finally store that data into the vector database
26:22Our vector database
26:26Now, inside this vector database, all this data will be stored in the form of vectors
26:30Like, let's say this is my record one, record two, record three, record four, and so on
26:34Right? So, this is my first record, second record, third record, then fourth record, fifth record
26:40You have it now
26:42From this particular vector database, you will definitely be able to apply any kind of similarity search
26:50Now, in this specific video, what we are going to do is that I will be using one of these files and create this entire pipeline
26:58Okay? I will just create this entire pipeline, and you also need to probably work along with me later on
27:07For any other files, I will give you an assignment
27:14Okay? I will show you with a couple of files
27:16Let's say I'll take a PDF file and I'll show you this entire data injection
27:20Then, what you do is that, as an assignment, you use any of the other file formats, let's say Excel, CSV, or whatever file format you want, and you try to complete the same pipeline
27:29Okay? So that is my strategy, and please make sure to complete the assignment as well
27:36We will go step by step completely from scratch so that everybody will be able to follow
27:40First of all, I will go ahead and open my empty folder, and in this, I will be using LangChain
27:46This is just a traditional RAG right now; later, we will move towards active RAG
27:51From this particular command, I will just go ahead and open my command prompt
27:55I will open my VS Code
27:59Let me quickly go ahead and open Visual Studio Code
28:03Now, from Visual Studio Code, the next step will be to open my terminal
28:05Let me just go ahead and write `uv`
28:15I'll just go ahead and initialize this particular workspace as my repository
28:17So, `yt-rag` is my workspace
28:20Now, I will also go ahead and create my environment
28:24If you're using the `uv` package, you can just write `uv env`
28:31So, my Python 3.13.2 will be the recent Python version I am specifically using for this project
28:37Then, I will go ahead and activate this particular environment
28:41Okay, perfect
28:44So far, we are good
28:45Now, I will go ahead and create my `requirements.txt` file
28:48From this `requirements.txt`, let me..
28:50Let's quickly proceed and install some of the packages like Langchain, Langchain Core, the Langchain Community, and other related packages
28:58I'll quickly go ahead and install these packages using `pip install -r requirements.txt`
29:11Okay, that's done
29:13Along with this, I will also install some other libraries like PyPDF, PyMuPDF, and PDFplumber
29:26These are the libraries I will be using
29:27I'll explain why I'm using PyPDF, PyMuPDF, and PDFplumber
29:34Specifically, these are for reading PDF documents
29:36The example I will show you is related to PDFs, but you should also try to create the same pipeline with other data types or formats, such as..
29:48JSON, it can be anything like that
29:53So, my requirements.txt is filled
29:55Now, what I will do is go ahead and create my data folder, and here I will also create my notebooks folder quickly so that I can start working on it
30:11Along with this, I will also go ahead and add the Jupyter Notebook kernel, so that I will be able to work with my Jupyter Notebook
30:17The IPython kernel has been executed
30:23Now, I will quickly start my Jupyter Notebook, and the first thing I want to discuss is related to the document data structure
30:30Right, what is a document and how can it be very helpful if you are using it in a data injection pipeline
30:36Okay, so I will quickly select my..
30:40Kernel and all these things, you really need to be good at Python programming language
30:45See, there cannot be anything that you can skip—Python programming language
30:49So my suggestion would be never to do that
30:53Okay? So Python is a must, and this time I'm just going to use some more advanced coding, and it will not be possible for me to write line by line
31:01So I'll definitely go a little bit faster to explain it to you
31:05Okay? Now, as I told you, if I go back over here, in the data injection, our main aim is to load some data, apply some chunking, then convert into embeddings, and finally store it into the vector database
31:17That is what my entire data injection pipeline is all about
31:21Right? For understanding this, we need to… Understanding a document structure is important because all this chunking that is done, you know, the final output will be documents
31:29Now, what exactly is a document data structure? So here I will go ahead and write what exactly a document data structure is
31:37For this, I will go ahead and import from LangChain or probably show you this
31:42I will be showing you some kind of file so that you'll be able to understand it
31:50Okay, let me put this file over here
31:54I have some file over here, and then we'll try to understand what exactly is a document structure
32:02See, LangChain document structure
32:04So, LangChain documents are a kind of data structure that can store data in a specific format, where we have two important things
32:08One is the page content, and one is the metadata
32:18The page content will basically have the content that is present inside that particular file
32:27Okay, so if you are reading the file, inside my page content, all those details— all those contents that are present inside the file— will be available here
32:37Metadata will be some additional information about the file, like it can be the file name, how many pages are there, what the timestamp of the file is— everything
32:47So, this way, whenever you read any kind of data and convert it into a document data structure, this format will be very, very important because, at the end of the day, we will be doing the embedding on this particular data and pushing it
33:00Into the vector database, and when we do that specific task of pushing it to the vector database, we will be able to apply different algorithms like similarity search, cosine similarity, and we will be able to retrieve the results
33:15So here, you can see that all the information regarding this is given over here
33:20Usually, LangChain's document structure has two core components: one is page_content and the other is metadata
33:26The page_content will be the actual text content, which is very useful in research papers, if you want to create a RAG application, or in products like manuals
33:39You can specifically use this in LangChain, and it definitely has different loaders
33:43Okay, loaders like you have something like a PDF loader, a CSV loader, a web-based loader, and a directory loader
33:53Now, all these loaders, what they do is that, for example, the PDF loader will be used to load PDF files, and once it loads the PDF file, it will give you the output of the documents in the form of a document structure
34:04Okay, I will show you practically also why I'm specifically saying and stressing on this
34:11It will definitely give you all the output in the form of a document structure
34:14Similarly, in the case of the CSV loader, here we give the CSV file, but it will try to convert the entire content that is present inside that CSV into a document data structure
34:18Similarly, with respect to..
34:22Web-based loader, clarity loader
34:26Similarly, there are so many different loaders over here, right? You can use any of these loaders to load the data, and at the end of the day, this loader will finally give you the output in the form of a document structure
34:41Okay, I hope you now have an idea of what exactly the document structure is
34:47So, now, quickly, what I will do is start explaining how we can work with the document structure
34:53 For that, we need to import from LangChain
34:58There is something called a text splitter, and sorry, from LangChain core, it is present inside core, in the documents module, import Document
35:11Okay, now, with this, you will be able to see the document
35:15That if you just hover over here, you'll be able to see the class for storing a piece of text and associated metadata
35:23Okay, now, if you really want to understand a document's structure, so first of all, I will go ahead and create one document, let's say manually
35:31I'll go ahead and create it, so I will use this document, and inside this, we will be using two parameters: one is the page content
35:39Let's say this page content, I'm writing this as the main text content, which I'm using to create RAG
35:43So I’ve just basically written some basic content over here
35:50Let's consider that this particular content is coming from a TXT file
36:00Okay, but along with this content, if you really want to improve the search query retrieval from the vector database, you..
36:07You need to also go ahead and write metadata
36:09So the second parameter that you'll be able to see is something called metadata
36:13Now, inside this metadata, you can write different pieces of information because, at the end of the day, this is text
36:20You can write, for example, okay, fine, this is my source
36:21The source is basically coming from the example.txt file
36:23Okay, then let's say the number of pages is equal to one
36:27The total number of pages is one
36:30I can also go ahead and write some more information, like okay, who is the author for this? The author is none other than Krishnaik06
36:40So these are the additional details that you'll be able to see
36:42Okay, fine, let's go ahead and write the date created
36:46So, date created, and..
36:50Here, I can go ahead and write 24-01-2024, like it's for the year 2024 or 2025
36:54Now, why all this metadata will be really, really important is because once we consider this document, once we do the chunking, once we do the embedding, and once we store it into the vector database, when you're doing the similarity search, you can also apply filters
37:11That's the most important thing about this
37:12And when you apply filters, let's say I am applying a filter—I'm searching for the main text content for building the RAG
37:20If some information is there, let's say there's some information related to the RAG, and I ask that particular question with a filter, such as specifying the author Krishnaik, then it knows from which document to probably fetch the answer
37:34Pick up because it is going to apply a filter by using the name of the author, right? And that is why this metadata will definitely play a very important role
37:44Now, if I just go ahead and execute this doc, you'll be able to see that
37:48Fine, I'm getting this particular document here
37:51You can see metadata is there, and as you go ahead, you'll also be able to see page_content
37:54Right? So these are the two main important parameters with respect to this, which everybody can probably go ahead and use
38:01Okay
38:03Now, I hope you got a very clear idea about it
38:05Now, what I'll do is just go ahead and create a simple text file
38:11Okay? Now, for creating a simple text file, I will just go ahead and import OS
38:21Okay? And I'm saying OS
38:23To create the directory, I will make a folder named "data/text file" inside this folder
38:29If the folder already exists, I will say that do nothing
38:31So, as soon as I execute it, you will see that it is going inside the notebook file
38:39I'll remove this command and then type `..` to go up one directory
38:43Now, you can see here, the text file is present
38:46I just created that text file inside this folder
38:50Next, I will manually create a text file using Python code
38:53Okay, I will go ahead and use Python code to do this
38:57These are all basic Python commands
39:02I don't want to write each line of code and make it very long
39:06The main aim should be to understand concepts quickly, show you multiple use cases, and then try to implement this
39:10Okay
39:14So now you will be able to see I have created this simple text
39:18I've given the file a name like this
39:19So let me go ahead and write this to it
39:21Data text files, Python intro.txt
39:23And this is some content that is present inside that particular key name
39:25Okay
39:29So this is my file name
39:31You can see this key is my file name
39:33And then here I have specifically my Python content
39:35Okay
39:37Here I'm saying, for file content in sample text, do items
39:40I'm telling it to open the file name
39:43I'm saying to write the content
39:44Okay
39:46So this file path is nothing but my file name
39:48Okay
39:50So if the file is not there, it will try to create it
39:53Python intro.txt
39:56So now, if I go ahead and execute this, it is saying me no directory
39:59Okay, let me just go ahead and create one file
40:01Okay, Python intro.txt file
40:04Okay, I have to give the path because there are two files that are over here
40:11One is, okay, one file is also over here
40:13Okay, so I'll just go ahead and write dot
40:16Okay
40:18So now, here you can see my sample file has been created: machine_learning.txt and python intro.txt
40:21Now, what I will do, see, I've created some sample files
40:28I could have also manually created them instead of doing it through code
40:31Okay, but I really wanted to show you all the things
40:35Now, what I will do, I will show you how to read this particular text using the text loader
40:38So, one of the loaders that is present inside LangChain
40:40There is something called a text loader
40:43So here I will go ahead and write: from langchain.docstore.document_loaders import TextLoader
40:50Okay, TextLoader
40:56So here we have imported TextLoader, and along with this, see, if you don't want to also use this, if I execute this—this is also available
41:02Before, when I talk about it, right, since langchain keeps changing its library here and there, we used to use langchain's community documentation loaders
41:12This is also something we used to use: import TextLoader
41:18So, any of them you can actually use unless and until you get a deprecated warning
41:22Okay? Now, the question is, how do we go ahead and read the text? So, I'll write: loader is equal to—I will initialize TextLoader
41:28Let's give the path
41:31The path is..
41:33Nothing but parent folder
41:35We go to the parent folder, data /ext files / python intro.txt
41:40So here I have actually given my file name, whatever file name we have created, and we can also go ahead and use encoding UTF-8
41:49Okay, encoding UTF-8
41:57So once I do this, okay, and now once I go ahead and read this with the loader, now what it is giving me is an object of the text loader
42:06Right now, in order to get the content inside this, I will be using loader.load(), okay? And here you'll be able to see that I will be getting the document, okay? Now let's go ahead and print the document
42:19So I will write print(document)
42:24So let's say this is my document
42:26I'm going to print it
42:28So here, you can see in the document you are getting metadata
42:30You're..
42:31Getting the entire information, and this is your page content
42:33Now, this is what it is doing, right? This text loader is by default giving you the data in the document structure as soon as it is reading
42:42And here, the best part is that you can also see some of the metadata information has also been updated, like what the source is
42:48You can still go ahead and manually change more information inside the metadata, but by default, the best part is that whenever you're using this library, it will also be able to give you the content in the document structure, which is really, really good
43:05Because in the document structure, you have two important things: one is the metadata and one is the page content
43:10So, this is it
43:13Respect to the text loader, right? I have just read the text loader and I'm able to get this in this way
43:17Okay
43:19Now, one more way I will do—I will show you with the help of the Directory Loader, like if I have all the important files in my directory
43:26Can I read it like that also or not? Okay
43:31So, for doing this, let's use one more library which is called Directory Loader
43:35Right
43:38So, here you can see from langchain.document_loaders import DirectoryLoader
43:42Now, inside my DirectoryLoader, you can see that I'm giving this particular folder
43:48Again, this folder should be the parent folder
43:49And here, I have given the pattern to match
43:51See, this function basically allows you to give a pattern to match all the files, then you can use the loader class
43:59The loader class basically means which file you are planning to load
44:03If it is a PDF, you can directly go ahead and use PDF
44:06Okay, so what I can actually do is that I can also go ahead and insert PDF files over here
44:12I can also provide this in the form of a list so that it will be able to read both the contents
44:17Okay, so once I go ahead and execute this, you can see here I am also using the encoding and all these things
44:23And here you can see, once I go ahead and write `directory_loader.load()`, and here you will be able to see the documents
44:27Okay, and then now if you just go ahead and print the documents, you should be able to see this
44:43Okay, I’m getting an error: "To log the progress, please install pip install tdk." Okay, so here we have enabled the… Parameter show progress is equal to true
44:49Let me make it false so that I probably don't need to go ahead and install this
44:56Now, here you can clearly see that there were two text files
44:57I got two documents
45:00Yes
45:03Now, further, you can do chunking and all that, based on the number of documents over there, I was able to get it
45:05Right
45:07So, this is the most amazing part about this
45:08Now, what I will do quickly is that let me go ahead and create a PDF file also
45:12Okay
45:16So, here I have some examples of the PDF file
45:19Okay
45:22So, let me quickly copy this and paste it over here
45:24Reveal explorer data
45:26I have text files and PDF files
45:28Now, inside this PDF file, my main aim is to read both the text and PDF files
45:31Let's see
45:34So, here I have
45:37Attention PDF, this PDF, this PDF
45:39Okay, so this is my one document
45:42Okay, let me go ahead and write the same code, copy and paste it over here
45:47And this will basically be for the PDFs
45:49So for PDF, I will be using from langchain.document_loaders import PyPDFLoader
45:51I think PyPDFLoader is not available here
45:54Let's see where this specific library is
46:06I'm just checking the documentation
46:07Uh, PyPDFLoader
46:09Oh yeah, it should be there
46:10So it should be here inside my community data directory, in the document_loaders
46:15I have two different types of libraries, PyPDFLoader and Pymupdf
46:19Pymupdf is better when compared to PyPDFLoader
46:22You can see, PyPDFLoader loads and parses a PDF file using the PyPDF2 library
46:25And similarly, if you go ahead and see Pymupdf, it loads..
46:33And parse PDF files using this method to load the data
46:35You can see all the information, and compare which one is better or not in the later stages
46:41Okay, now what I’m doing is that I will give the path here
46:47So, from data / data, and you can see the path is nothing but PDF
46:53Here, I will go ahead and write PDF instead of writing TextLoader
46:57I will go ahead and write Pi Mu PDF
47:00Let’s go ahead and use Pi Mu PDF
47:02I can also include encoding in this
47:04Here, what I will do is quickly write PDFDocuments = DirectoryLoader.load()
47:07And then, if I just go ahead and see PDFDocuments, you should be able to see there are so many different PDFs
47:23Okay, I’m getting an error
47:24Uh, get_text() got an..
47:26Unexpected argument
47:27Okay, let's remove this
47:29I will not be requiring anything
47:30We don't need to apply any encoding by default
47:35Okay, so here you can see I have got all my documents
47:38Yes
47:41So, how many different files were there inside the PDF folder? One is Attention PDF, Embedding PDF, Object Detection
47:44These are some of the research papers, and with respect to these, we are able to see this
47:49Now, the best part is that when you're using PyMuPDF here, the metadata information is completely different
47:53See the creation date, source file path, total pages, right? Total pages is 15 for the first one, then 27, then 21
48:04See, you can see it so beautifully
48:06It is there
48:08See, I have also created some of the PDFs
48:10There, you will also be able to see
48:13Some kind of author's name also, right? It tries to bring up all the entire source information, and this is your page content
48:21So beautifully, you are able to see the entire content quickly
48:24So that is what this PDF is all about
48:27And here, at the end of the day, even though we use this specific library, we are getting this in the form of a document structure
48:36It is a list of documents
48:39So, if I go ahead and say, "What is the type of the PDF document at index zero?" you'll be able to see, okay, it is a document type
48:44That is the most important thing
48:50If you now see that we have understood how to work with document structure, we know how to read PDF and text
48:55Now, don't you think you can easily figure out how to go ahead and read Excel files as well? Kind of files, and this is the task that you really need to do
49:08How you'll do it? Just go to LangChain document loaders, right, and you will be able to find out everything over here
49:11Just go ahead and try it out
49:15Try it out
49:17Try it out
49:18Try to see if the document structure that you're getting is good or not
49:20So here there are so many different things you can go through, just go ahead and try it out
49:25If you want to load from an AWS S3 bucket, you want from an AWS S3 directory, go ahead and just install this particular library
49:29But before that, you have to do the authentication and all right
49:36Once you do this and once you're able to do it, you can use any kind of document loaders you want
49:40But at the end of the day, what is the best thing about this, at the end of...? The day you are able to convert everything into a document data structure, right now if you see with respect to data injection, here you have actually completed it
49:56Now, the next step is that I will move towards chunking
49:58Okay, I’ll move and show you how the chunking can be specifically done, what are the different ways of chunking that you can actually do, and then finally, we’ll see how we can even convert it into embeddings
50:10We’ll try to use an open-source embedding for this, and then finally a vector database
50:14So, yes, I hope you have understood the data injection part
50:15Now, let's move towards the chunking part, where we will understand how we can actually perform chunking, and I have also told you what the importance of it is
50:25Of chunking, so guys, till now we have already discussed the entire document structure, and I've also shown you how, with the help of the PI PDF Loader, PI Mud PDF Loader, and how, with the help of the Text Loader, you will be able to read the .txt file and PDF file
50:42All the other files, again, you can go ahead and see the LangChain documentation; you have different document loaders, which I have already discussed, and these are some of the document loaders that you can specifically use, which I have already shown you from the documentation page
50:55Now, we are going to go ahead one step further because we have just started with this, we understood about data parsing, and we were able to create the document structure
51:07I want to go ahead and do the chunking, and then after the chunking, I also want to do the embedding
51:15Finally, whatever text to vectors is, basically the converted vectors will be stored in some kind of vector store database
51:26Okay, so let's start building this entire pipeline
51:28This pipeline will initially be built from the basics, since this entire RAG series is learning from the fundamental stuff
51:36You'll love it, and you'll enjoy the explanation of what I'm doing
51:43So, I will go ahead and create one more file quickly, and I'll name it "PDF Loader.ipynb," and here I will proceed with..
51:50Select my kernel
51:57This is my kernel, and let's go ahead and start the entire RAG pipeline, and this pipeline is nothing but data injection into the vector database pipeline
52:04Okay, the vector DB pipeline
52:07We are going to go ahead and build this quickly
52:11So, the first step, as you know, is that I already have a data folder over here
52:16So, this is my data folder, and I definitely have a lot of PDF files inside this PDF folder itself
52:26So, first things first, what I will do is go ahead and create a function, you know, saying that I will try to read all the documents from this, and I will try to read the data inside these particular documents, which are PDF files, and then we may use the PyPDF loader and finally convert that into a..
52:36Document
52:46Okay, so for this, I will quickly go ahead and create a function, and this function will be nothing but—uh, this is a markdown
52:56Let me just go ahead and make a code cell
53:01So, before I go ahead, I want to import all the important libraries that are available
53:05Some of the libraries that I will be noting down here are nothing but import os, then you have something called langchain document loaders, langchain community document loaders
53:15I'm using the PDF loader and all
53:20Then you also have the langchain text splitter and recursive character text splitter
53:25Okay, so otherwise, instead of writing in a new file, I will go ahead and use—okay, this file is fine, so I will just go ahead and execute this
53:29I don't require the Path library
53:34So once I execute this, all these libraries will get imported
53:38Now we will be able to use them
53:44Now, since my first step is related to data injection, whenever I want to specifically do data injection, I will try to read all the PDFs
53:53We will read all the PDFs inside the directory
53:59Okay, directory
54:05Now, guys, uh, you need to have some knowledge of coding, because otherwise, if I keep writing line by line, it will definitely take a lot of time
54:11So here, we are going to create a function called "process_all_pdfs." Here, we need to specify the PDF directory
54:14Once you give the PDF directory, we will probably go ahead and take the path
54:18So, for this also, I..
54:23We will be requiring the Path library here
54:27So, once we get the path based on the workspace location, we are going to get the PDF directory path
54:34Then, we'll list all the files and apply this regular expression to get all the PDF files
54:42Next, I am printing the number of PDF files, and we are processing each PDF file
54:45As you can see, I’m using the PyPDFLoader, passing in the file name
54:49Then, I’m assigning the loaded documents to the variable 'documents'
54:51Here, I load the document
54:53Additionally, I’m adding some metadata information, such as the source file
54:55So, here you can see the document metadata, including the file name and the file type
55:02This is my updated version
55:06Keys inside my metadata to add some more additional information, and finally you get a PDF
55:13I'm just mentioning some more metadata information, so along with this, I've added this metadata information like file type, source file
55:19Now, you can keep adding any number of metadata information you want, right? And once we read these entire documents, we are going to store them in this particular variable called 'all documents,' which is nothing but a list
55:33It is an empty list, okay? So once we do this, we'll be able to see that it is returning this 'all documents.' This function reads all the PDF files inside a folder, extracts the content from them, and adds it
55:48This kind of metadata information, and finally, it is basically stored in this particular variable
55:51Okay
55:55Now, I call this particular function `process_all_PDFs`
55:58I'm giving the data folder over here
56:00So, once I execute this, you'll be able to see that it has found four PDF files
56:06Attention: PDF had 15 pages, my embedding PDF had 27 pages, and Object Detection PDF had 21 pages
56:11And this is Proposal one page
56:13Okay
56:16So, all the information I have is over here
56:18Now, if I go ahead and check my all documents
56:21So, if I go ahead and check just this particular `v` variable, all PDF documents, you should be able to see that this is my list of documents, right? And the best part is that, for every PDF, you'll be able to see, by default, some of the..
56:38Metadata information, along with this, you can see there is an author, metadata, keywords, mode, date, all this modified date, right? All these pieces of information are basically present in the metadata
56:48Now, what we have added is the source; along with the source, you can see we have also added the total pages, the source file is also included, and these are the texts which are present inside my page content
57:00So, for every PDF, regardless of the size of the document, we are able to read it
57:08This is the step that we have completed
57:10Now, we need to go to the next step and perform chunking
57:12How do I go ahead and perform chunking? Now I have my entire list of documents, so what I will do is just go ahead and..
57:21Quickly create a function, and this will be specifically for text splitting into chunks
57:26Okay, chunks I have over here
57:29Right
57:32So, first of all, I will go ahead and create a function called split_documents
57:35Inside this function, I will be passing my parameters
57:40The first parameter is nothing but documents
57:43Then I have my chunk size, which is equal to 1000
57:47Then I have my chunk_overlap, which is equal to 200
57:50Okay, so I have given all these parameters
57:56Now you know how to do the chunking
58:02It is very simple
58:08You go ahead and directly use the RecursiveCharacterTextSplitter, and for this, we definitely require RecursiveCharacterTextSplitter, which we have already imported, I think, right? So, on the top, you'll be able to see..
58:13To see that we have imported this, which is present in langchain.text_splitter
58:16So inside, we are taking this text splitter, which is nothing but a RecursiveCharacterTextSplitter
58:20Now, this recursively splits all the document sizes based on the chunk size, which is 1,000, with a chunk overlap of 200
58:28Chunk overlap basically means some number of tokens will be overlapped between two different documents when we are doing the splitting
58:35And here, you can see we are also using separators
58:38This is just like an empty space, like a blank
58:41Sorry, this is an empty space
58:43This is one more separator; this is a new line separator
58:48Now, you tell me in the comment section, what separator is this? Okay, so we can use different separators
58:51Separators, you can also use commas
58:53Um, we'll be seeing different types of chunking strategies in the later stages, but let's start creating this one pipeline, then you'll get a clear idea of how this entire pipeline works
59:04Okay, then you have this text splitter
59:06Uh, once you have this text splitter, you can actually use it to do the splitting
59:13Right
59:14So now what I will do, I will create a variable inside this, and I will write `text_splitter.split_documents`
59:18So we are using `split_documents`, and we are giving the documents
59:23And these all are the default parameters that we are providing here
59:26Now, once we do the split, you'll also be able to see what the page content is
59:29I'll just try to..
59:31Display 200 characters from the page content, and you can also see the metadata right
59:35So once we go ahead and execute this, it is going to return the entire split documents
59:39Now, let's go ahead and use this split; let's say here, I'm just going to get all my chunks
59:47I will be using this function, split documents, and let's give the documents here
59:49We are going to give the list of documents, right? Like, what are the list of documents? So, the list of documents is nothing but all the PDF documents
01:00:00So, I will give it over here and let's see the chunks
01:00:04Okay
01:00:07So, now if I go ahead and just print the chunks, you should be able to see that all my data is basically chunked
01:00:11And you can see that we have split 64 documents
01:00:16Into 359 chunks
01:00:22So these are all my chunks that we have done, right? That basically means we have converted all our text into smaller chunks, based on the chunk size and the overlap
01:00:27So, like these kinds of chunks, we have—how many? 359, I guess
01:00:31How much is it? 359
01:00:34Initially, we had only 64 documents, right? For each page, there will be a separate document structure
01:00:37Perfect
01:00:40So, we have done this, and we have completed the splitting part
01:00:42Now, let's go to the next step
01:00:46The next step will be quite interesting because now, if you see from this particular pipeline, what are we doing? Right? So, here we have done the chunking, but these two are the most important steps
01:00:58One is the embedding, right? We need to perform some kind of..
01:01:02Embeddings over here, right? Embedding, uh, generation, embedding generation, and vector store database, right? Embedding—you can use any kind of model, but I will try to focus on using open-source models so that everybody will be able to just try it out, you know? For this, what I will do is I will try to use some kind of modular coding, so I will try to create some classes, you know, for embedding
01:01:28I will create a separate class, and inside this, we will try to define different functions because in embedding, you know, that you are converting text into vectors, right? So, for converting text into vectors, I may define different functions like loading the model, generating embeddings, you know, that kind of thing, and in vector database, again, we'll..
01:01:42Try to create this as a separate class
01:01:44So, let's go ahead and probably discuss this, wherein we work on the embedding part quickly
01:01:49Let's go ahead and see the embedding part
01:01:54So, for the embedding, I will just go ahead and write a markdown
01:01:59So, let me quickly write "Embedding and Vector Store DB." We are going to specifically implement these two important modules
01:02:07Now, first of all, what I do is that I definitely require some kind of libraries here for embeddings
01:02:13For embedding, we are going to use Sentence Transformers
01:02:19We are going to use a model that is available on Hugging Face, and for that, I will be using the Sentence Transformers library, along with this, I also want..
01:02:27To use some kind of, you know, vector store, so this is the vector store I may use, which is FAISS
01:02:36You can use FAISS, or you can also go ahead and use Chroma
01:02:39So these are some very good open-source vector stores that are available
01:02:44Um, now, these all libraries will be more than sufficient to get started with
01:02:50So, quickly, let me go ahead and install it
01:02:51So I will write `pip install -r requirements.txt`
01:02:53Once I do the installation, you'll be able to see that
01:03:00Okay, the installation will get completed
01:03:02So once the installation is finished, it will take some time because we are loading the entire Transformers library
01:03:10So here you can see that it has been installed quickly
01:03:14Now I'll go back again to over here
01:03:16Now, once I go over here, what is the..
01:03:18The first step that I'm actually going to do is to quickly import some of the libraries I require, like this
01:03:22So, I'm importing NumPy, and from SentenceTransformers, I'm importing the SentenceTransformer model
01:03:28My embedding model will be available inside this
01:03:30Then, I'm importing chromadb
01:03:32We are also importing the settings from chromadb
01:03:35We are importing UQI D; the reason for creating this UQI D is that every record we specifically insert into the vector database will have some kind of ID associated with it
01:03:41We will generate that ID along with the record
01:03:47Additionally, we will be importing list, dictionary, and numpy
01:03:54Since we are going to apply cosine similarity when doing retrieval from the vector database, I also..
01:03:59We will be importing this, and this is available in SkyItler, so let's quickly execute this
01:04:02Okay, and till then, I will go ahead and create more cells
01:04:06Now, as I said, for embedding, I will go ahead and write a different class
01:04:11So I will say EmbeddingManager
01:04:15This will be responsible for doing the embedding part
01:04:19The first thing is that once I create this, for every class that we specifically create, we need to write an __init__ function
01:04:27Okay, so __init__
01:04:29This is my constructor
01:04:32You'll see that it handles document embedding generation using transformers
01:04:35Here, we are initializing the EmbeddingManager, and the model name that we are giving is 'all-Mini-L6-v2'
01:04:40This is available in Hugging Face
01:04:43The specific model, all Mini L6 V2, is responsible for converting text into vectors, and you get somewhere around 384 dimensions
01:04:48Okay
01:04:52Then, we initialize the embedding manager
01:04:57The model name is nothing but the Hugging Face model name for sentence embeddings
01:05:01We are going to use this
01:05:04Okay
01:05:07So, here we are initializing the model name
01:05:08We are setting self.domodel to None because later on, we will initialize this value
01:05:13This function is very important—load model
01:05:15That basically means my next function will be load_model
01:05:19And this model work is very simple
01:05:21This function's work is very simple
01:05:23It is going to load this model, which is all Mini L6 V2
01:05:25Okay
01:05:27So, I will create another..
01:05:29The function, which is nothing but _load_model
01:05:31Why do we write underscore? Uh, this is just like a protected function
01:05:33Uh, if you know about classes, we use something called a protected function
01:05:37And within this protected function, within this class only, it'll be accessible
01:05:42So here, uh, what we are doing is using the sentence transformer, and whatever model name we have, we are loading it
01:05:49Okay, we are loading it
01:05:51So, self._model of sentence transformer model, self.model_name, then this will be loaded, and here you'll also be able to get the dimension
01:05:58For that, we use a function called get_sentence_embedding_dimension, and by default, it will be somewhere around 384 dimensions
01:06:02Okay, that basically means
01:06:06Every text will be converted into 384 dimensions
01:06:10So once we have this init function, we have the load model
01:06:15Now, one more function that we require is generate embeddings, right? So here, you'll be able to see that I will be explaining this generate_embeddings function
01:06:20Okay
01:06:26So generate_embeddings is nothing but it takes the text, which is a list of strings, and it returns a NumPy array
01:06:30Okay
01:06:33So here, it generates the embeddings for a list of texts
01:06:39Very simple
01:06:45So here, what we are doing is we are basically using this self.domodel.encode function, which we have to use on the text—whatever text or list of texts we give—and we are also setting show_progress_bar=True so that we can see the progress bar, and then we return the embeddings
01:06:51The embeddings
01:06:52Okay
01:06:53Now, generate embedding is one function, load model is another function
01:06:53We have also used get sentence embedding dimension just to get the dimension
01:06:58Okay
01:07:02Now, for this, you can either create this particular function or you can also remove this; it is not necessary
01:07:07But what I did is that, to show you more clearly, we will create this function get sentence embedding dimension
01:07:10So, here is my get embedding dimension self
01:07:15So, here what we are doing is just calling model.get sentence embedding dimension
01:07:19See, instead of doing it like this, I can also write it like this only over here
01:07:22Okay
01:07:24I can just quickly write this particular function over here
01:07:26Okay
01:07:27So, sometimes it is not..
01:07:29You can also do that if required
01:07:32So I will just go ahead and remove it if you want
01:07:34Okay, I will just remove it
01:07:35Perfect
01:07:37So I have these two or three important functions
01:07:38Now we can initialize the embedding manager
01:07:42Okay
01:07:46Sorry, we can initialize the embedding manager
01:07:49So here I will write embedding_manager = EmbeddingManager
01:08:00So I hope the class name should not have an underscore; it should be like this
01:08:04Okay
01:08:07Now, once I go ahead and write this and execute it, this will just go ahead and initialize the constructor
01:08:09Right
01:08:12So here you can see it is loading the embedding model
01:08:15All MiniLMv2 62 model loaded successfully, and here you can see the dimension is 384
01:08:18So it has been loaded, so when we call this particular function, This is basically getting loaded right, so my embedding manager now has the model information over here
01:08:28Great, so I have my model ready
01:08:32If you see from this particular graph, this entire class has been created
01:08:36Now we go to the next step and create this specific class, which basically means over here we have our model embedding ready; we just need to use it
01:08:44Now, similarly, we'll go ahead and create it for the vector store also
01:08:48Okay, the vector store is just like a vector database, where you can store all the vectors that have been converted by the embedding layer inside it, so that you can apply any kind of similarity search into it
01:08:58Right? So, first of all, let me quickly go ahead and define a class for this also
01:09:02So, here I will go..
01:09:05Ahead and write the vector store
01:09:09Okay, vector store
01:09:13Remember, guys, the code that I'm showing you is very simple
01:09:17If you just see, you need to have some coding knowledge if you really want to become better at RAG
01:09:24Okay, now we'll go to the next step with respect to the vector store
01:09:26 Now, in the vector store, we are creating a class called VectorStore
01:09:30Again, here we are using an __init__ method
01:09:32We are providing a collection name
01:09:34What should be the collection name for the vector store itself? And here, we are giving it as "PDF documents." We are also specifying the persistent directory, which will be this particular directory inside my data folder
01:09:45Persistent directory means, whatever vector store is basically created..
01:09:49Going to save it on the hard disk
01:09:52So here, first of all, I'm giving the collection name
01:09:54I'm setting the person directory collection to none
01:09:56The self.collection is equal to none
01:09:58Okay
01:10:00And then we are initializing the store
01:10:01Now, whenever we initialize the store, that basically means this function will be initializing the vector store itself
01:10:05Right
01:10:07So, for this, we need to create another function again and see the code
01:10:11Okay, just observe the code
01:10:13Here, we are initializing the chroma client and collection
01:10:16So, here we have written os.makedirs to create the directory of self.persistent directory, whatever directory path is there
01:10:19If it already exists, we are just going to keep it like that; otherwise, it is going to create a new directory
01:10:26Then We create a client, self.client, wherein we are using the chromadb.PersistentClient function, and we provide the persistent directory here
01:10:33So, what it is going to do is basically create a client that will have a reference to the Chroma vector store
01:10:38Okay
01:10:40Then we go ahead and create a collection
01:10:42So, here we write self.collection
01:10:44Then self.client.get_or_create_collection
01:10:46We're giving the collection name and some metadata information, like what the collection is about
01:10:52And here, we basically create a collection
01:10:55A collection basically means a storage space where we are going to store the vectors inside my vector store
01:10:59So, it will..
01:11:03We store the documents inside this particular collection name
01:11:07Then, we initialize it with the collection name dot collection count
01:11:11Okay, so as soon as we execute this, that basically means my Chroma client will be ready, and my collection will be created
01:11:17Okay
01:11:23Now, the next step is that usually, whenever we create a collection, we need to add the documents, right? So, for adding documents, we will create another function
01:11:28So, let's go ahead and create this quickly, because whenever I have a document, I will go ahead and use this particular function
01:11:32Okay
01:11:35So, here you can see I've created another function called add_documents
01:11:39Here, we pass the list of documents, apply the embeddings—very simple, add documents
01:11:43and the embeddings to the vector store
01:11:47And here, you can see if the length of documents is not equal to the length of embeddings
01:11:52Here, you can actually see this
01:11:53Now, we are preparing the data for Chroma
01:11:55We require IDs, metadata, document text, and embedding list
01:11:57So now, whatever documents I have here, whatever documents I'm getting, I will be zipping them, meaning I am creating a tuple with embeddings, and then I am creating a UUID
01:12:11Why do I require UUID? Because it's just like an ID for a specific record, right? And that will be my document ID
01:12:17Okay, document ID variable, and I’m appending it over there
01:12:21Then we are preparing the metadata
01:12:24Whatever document metadata we get, remember we are iterating through these documents
01:12:28So we have all the..
01:12:31Information
01:12:35So, all the metadata we are putting over here, such as document index content length, we are just adding some more metadata information to store inside my vector database
01:12:39Then, we get the document content from doc.page_content, and we also get the embedding, where we are converting this embedding to a list
01:12:44Okay
01:12:48See, two pieces of information are basically required right here
01:12:53If you see, from this particular function, one is the embedding, which is my NumPy ND array, and this embedding is coming from where? From the previous function, right? Generate embeddings, where we have done it
01:12:59So, it's all interconnected
01:13:05The reason for creating this particular structure in the form of a class is because I want to link each and every pipeline
01:13:08So here we..
01:13:10We are writing: embedding list.append(embedding)
01:13:12Two list
01:13:15So, we have the page content and this list
01:13:17What I'm doing is adding that entirely to the collection
01:13:19For this, we require IDs, we require the embedding list, we require metadata, we require document text
01:13:26So, whatever we have prepared, we're just adding it here based on the parameters, right? And finally, you'll be able to see how many documents have been inserted
01:13:35Now, let's quickly go ahead and initialize
01:13:38Let's go ahead and initialize my vector store
01:13:40So, I'll write: vector store is equal to, uh, vector store, and I'll initialize this
01:13:46Okay
01:13:52So, quickly, I will go ahead and write the vector store
01:13:57So, now, this is basically going to initialize the
01:14:01The entire vector store itself
01:14:05Right
01:14:08So here you can see this is my collection name, and the existing document count in the collection is zero since we did not add any records
01:14:13Okay
01:14:17Now, if we want to add any number of records, we have to call this function, add_documents, right? So, let's go ahead and do that and call it
01:14:22Okay
01:14:28Now, first of all, you know that I have already done the splitting of the chunks, right? So, here if you go ahead and see this, this is my split chunks, right? Sorry, that was the variable
01:14:33Let's see which variable it has been saved in
01:14:36Okay, it should be chunks, right? So, these are my chunks
01:14:40What I am going to do now is extract all the text from each of those chunks
01:14:45We'll generate an embedding
01:14:49Okay, so for that, I will use a list comprehension
01:14:54Now let's convert the text to embeddings
01:14:56Okay, we're going to do this step by step
01:15:02Here, we are basically going to write chunks
01:15:08First of all, I'll iterate—I'll say, for doc in chunks
01:15:13And we are just going to take this doc.page_content
01:15:21So, we'll take all this page content and go ahead and create our text variable
01:15:31Once I do this, you should see that this is my text, all the text I have
01:15:39I will pass this text to my embedding manager, which I have already created
01:15:44So, what I will do next is quickly proceed with that
01:15:47Just go ahead and execute this once again
01:15:50I have all my text
01:15:53Okay, I have all my text
01:15:55Now, from this, we will generate the embeddings
01:15:58Now, once we generate the embeddings, how do we generate the embeddings? Very simple: we use this EmbeddingManager object, which we have actually created
01:16:07What object have we created earlier? If you see over here, this is my EmbeddingManager, right? So, we are using this EmbeddingManager.generate_embedding, and here I have to give the text in the form of a list—list of strings
01:16:22So, here, quickly, I will call this particular function, `generate_embeddings`
01:16:23And here, you will see that I will be giving my text
01:16:29Then, let's store
01:16:40The vector database
01:16:51So, after we convert that into an embedding, we store everything in the vector database, right? So here I will use the vector store — the variable that we have created — and call dot add_documents
01:17:02And this is a small letter, add_documents; this is a function that we have used
01:17:08Inside this, if you remember, we have to give our entire chunks, okay? Whatever embeddings we are specifically applying
01:17:16So, once we do this, you can see the embeddings, whatever we have got, and the chunks, the documents, the entire documents
01:17:33We're going to do this, okay? So, let's quickly execute this, and I think now my embedding will happen
01:17:38Now you can see that for 359 texts, this is happening, and it has been converted into
01:17:43So many numbers of batches, uh, the vector store is not defined
01:17:48Why is it not defined? Let's see what I have defined over there, okay? It should be 'vector_store,' so this should be the spelling of my 'vector_store' instead of that
01:17:52Okay
01:17:56So now, let me quickly go ahead and execute this
01:18:00Now, inside that same 'vector_store,' it'll get executed
01:18:04Okay, perfect
01:18:09Now, you can see that the total number of documents in the collection is 359
01:18:13So, if you see over here, uh, inside my Jupyter Notebook file, inside my data file, there is something called 'vector_store,' and we have made it persistent over here, right? So, 'persistent' basically means that now, it is saved on this particular hard disk
01:18:27We can just load this hard disk and probably go ahead and..
01:18:32Execute anything as such
01:18:34Okay
01:18:35Now, perfect
01:18:37Now you can see that we have completed this entire pipeline
01:18:38Now we have all the data available here in the vector store database, right in the form of vectors
01:18:43But now the main thing is, how do we perform the retrieval? Because in retrieval, what happens is that whenever we have a user query, we have to take this query, convert it into embeddings again, okay, and then we basically go ahead and hit the vector store in the form of a retriever, and then only we get the context
01:19:10So, in our example, first of all, we'll try to get till here, okay? We have a user query
01:19:15We convert that query into embeddings
01:19:17Then we hit this particular vector store, and we get the context
01:19:19So
01:19:22Let's go ahead and create this specific pipeline now
01:19:24Okay
01:19:26And for this pipeline, we will try to create a RAG retriever
01:19:28Okay
01:19:29So we will try to create a RAG retriever
01:19:31So let's quickly go ahead and do that particular thing
01:19:33Up until now, we have created all the amazing pipelines
01:19:38We have created this embedding manager
01:19:40Now we also have this vector store
01:19:42Now, what I will do is that I'll create another pipeline, which will be a RAG retriever, just to get the specific context
01:19:48So, let's go ahead and discuss that
01:19:50So, guys, now let's go ahead and create the RAG retriever pipeline
01:19:54So, first of all, what we are going to do is that I will go ahead and create a class called RAG retriever
01:19:58Now, this RAG retriever class
01:20:03You will be able to see that it handles query-based retrieval from the vector store
01:20:07So, inside the constructor, we will be giving two important parameters: one is the vector store and one is the embedding manager
01:20:14And if you remember, we have created both of these
01:20:19We have created the embedding manager and the vector store manager
01:20:21Right now, after providing these, we will be initializing two class variables, which are the vector store and the embedding manager, and we will be assigning them accordingly
01:20:32Now, whenever we create a retriever, one thing you really need to understand is that this retriever is actually built on top of a vector store, and a retriever is nothing but a simple interface that, based on whatever query we get, retrieves the relevant information
01:20:45It's just going to give you the response back
01:20:47Okay, and this retriever is basically a kind of interface which is connected to the vector store and chart
01:20:51Okay
01:20:56Now, the next step that we are going to create is another function, which will be called the retrieve function
01:21:00Now, this is really important because this retrieve function's main work is to retrieve based on a specific query
01:21:05So, let me go ahead and define the specific function
01:21:11Now, this function again, you see, will definitely take a lot of time to write, so we will try to understand this particular function
01:21:17Okay
01:21:22So, here, a retrieve function, you can see, we are giving the query, we are specifying the number of top key results we want, and there is also a threshold value
01:21:27By, By default, it is 0.0, and this function is basically going to return a list of results
01:21:31Okay, so here you can see it retrieves relevant documents for a query
01:21:35The arguments are the search query, top K documents, and score threshold, and it returns a list of dictionaries containing the retrieved documents and metadata
01:21:45At the end of the day, this function actually helps us get this specific context
01:21:48So, you'll be able to see here that we are using the same self-embedding manager and calling this generate embedding function
01:21:59Now, if you remember, this generate embedding function is already defined in my embedding manager
01:22:03So, if I go to the top, here is my generate embedding function, and this is—nothing but this is
01:22:09Basically, you're just using model.encode and you're giving the text, and it is converting into embeddings
01:22:15Yeah
01:22:21So, that is the reason we are basically using this, because at the end of the day, first of all, whenever we get a query, right? So, let me go down over here inside this retrieval
01:22:27Whenever we give this query, first, the query needs to be converted into an embedding, right? So, this query that is given needs to have embeddings applied to it as well, so that we can do a similarity search in the retriever itself, right? So, first, the query is basically converted into a vector with the help of `EmbeddingManager.generate_embeddings`
01:22:49Then, we are going to use `VectorStoreCollection`, and we are going to use this
01:22:57Dot query, and here we are going to give our query embedding, which is nothing but this embedding in the form of a list
01:23:04Then, we are also going to give the top results
01:23:07So, by using this, this is basically going to hit the vector database, whichever vector database we have initialized, and it is going to give you the results
01:23:14Once you get the results, internally there will be a key called 'documents.' Okay, you can get document information, the metadata information, the distance information, and some of the IDs information
01:23:28So, all this specific information we are using
01:23:31And here, you can see, very similarly, what we are doing is using all these parameters like ID, documents, metadata, and distance
01:23:37We are zipping..
01:23:42It, Zipping it, basically means we are just trying to create a pivot over here, and then for every value, we are just trying to calculate the distance
01:23:48Right, one minus the distance will basically give you the similarity score, like how similar those text data are, basically coming up outside this vector store
01:23:58So, we are creating the similarity score, and if the similarity score is greater than the threshold, then what we do is we basically add this inside my text context documents
01:24:10And context documents are basically created in this particular variable, which is nothing but retrieve docs, which we have kept empty over here
01:24:18Okay, so all the information, we are just trying to add it over here so that we'll be able to see
01:24:21It, okay
01:24:24And finally, we return that retrieved docs
01:24:27So, if you say step by step, we're not doing anything very complex
01:24:31We are getting the user query, converting this into embeddings, hitting the vector store, then getting the response
01:24:36Once we get the specific response, that context, we put it in the form of a list
01:24:44If you just go ahead and see the code, that is how things are happening
01:24:49Okay, so this is one of the very important functions that you'll be able to see now
01:24:53Here, what I can do is that I can quickly create a variable called rag retriever, and I can call this the same class
01:25:00So, if you see over here, I will use this same rag retriever, and let's give it our vector
01:25:09Store vector store, which I've defined earlier, is my vector store manager and my embedding manager
01:25:18Once I do this, I should be able to see this
01:25:22Okay, uh, it should be the vector store file, right? So now you'll be able to see this is my RAG retriever
01:25:30RAG retriever, it is an object of this
01:25:34Now, if I call this particular function with a query, right? I can call dot retrieve with a query
01:25:38So let's go ahead and do this
01:25:41Okay, so here I will write RAG retriever dot query—sorry, dot retrieve, which is my function
01:25:48Okay
01:25:55So here, you can see, quickly, this is my retrieve function, and I need to give a query
01:26:02Now, let's test it with a specific query
01:26:04I'll say, "Hey, what is *Attention is All You Need*," because I know inside my data there is a PDF file which is called..
01:26:11As attention, or I have also created some kind of proposal over here with embeddings of some files
01:26:19So, we'll try to execute this
01:26:26Here you can see that as soon as I asked, “What is ‘Attention Is All You Need’?”, it is giving me the top K results, printing all the information, and it has generated embeddings for one text
01:26:34Right? And the text shape is 1, 384 because I have used the embedding called All Mini LMV6, which creates a 384-dimensional vector
01:26:40Now, once we go ahead and apply this particular function—this function—is basically retrieving the results, and we are printing those same results
01:26:51At the end of the day, we can also go ahead and return these retrieved documents
01:26:57Okay, so in short, this is basically it
01:27:00The function is going to give me all the retrieved documents
01:27:05So, these are the retrieved documents; you can see content, metadata, author, so these are my context information
01:27:07 So, here you can see, the attention function can be described as a mapping from a query to a set of this one, and this entire thing is basically the context
01:27:16 So, from this particular diagram, you can see easily we are able to get the context, right? And this is nothing but, this is your context
01:27:20 Now, let's try some more things
01:27:24Okay, I will just go ahead and open some PDF
01:27:26Okay
01:27:29Um, this is a very new research paper, an embedding technical report
01:27:31Okay
01:27:33Uh, we'll search for any topic here—uh, embedding model training
01:27:36I'll just go ahead and search for Unified Multi-Task Learning
01:27:39Framework
01:27:43Okay, because this information is also stored over there
01:27:46So here, I'll go ahead and create one more, and I will copy this entire code
01:27:51Okay, quickly, and this is the query that I'm actually going to give, which is nothing but the unified multi-task learning framework
01:28:02So, if I go ahead and execute this, you can see that I’m able to get this, and then you can see content benchmark ranking over both the leaders, effective of our approach
01:28:14So, we are able to get the response very, very quickly
01:28:19And this response is basically coming from the vector store, in a very similar way, very easily
01:28:25We are able to get the specific response over here, and let me tell you, this is the easiest method
01:28:33Way, like how things are basically happening over here right now
01:28:39What we can do is, see, if you have created all these things so far, the next step is to integrate the Large Language Model (LLM) with this specific context
01:28:45Okay
01:28:52Now, for this LLM with this specific context, what you can do is directly take this particular context and provide it to the LLM
01:28:58That is what we are going to see in the next video
01:29:02But in this video, we covered the entire RAG pipeline, from data injection to the vector database pipeline
01:29:07Right now, you can go ahead and write any kind of queries, and, with all this information, you can see the similarity score is..
01:29:15Also, coming up, the distance is also basically coming up
01:29:18All the information you're putting over here, and we have also used modular coding
01:29:23Now, in the next step, what I will do is take this vector store and we will go ahead with the next integration, which is LLM and output, and I will call it a retrieval pipeline
01:29:32This entire data injection pipeline, with this query retrieval, we have actually created
01:29:36Now, the next two steps will be this one, and after doing this, we will try to convert the same code—whatever code we have written here—into the form of modular coding
01:29:48We will see how we can put this inside our source folder
01:29:54So here, what I will do is quickly create a source folder
01:29:56In the folder and inside the source folder, I will show you how we can take this entire pipeline and how we can actually create it in such a way that we have a kind of pipeline over here
01:30:09Right, a pipeline basically means from data injection to vector embedding, how in a sequential way we can actually go ahead and call it
01:30:19Hello guys, so we are going to continue the discussion with respect to RAG
01:30:21Until now, we have already discussed the entire data injection pipeline, and with the help of a user query, we are also able to retrieve the context
01:30:32We have completely implemented this first pipeline, which is called the data injection pipeline, where we did the data injection, the chunking, and then we converted the text
01:30:41Into vectors, and after that, you know, we were able to probably store everything inside a vector database, and we also persisted it in the local directory so that we can always read it whenever we definitely want to, based on a specific query
01:30:56Now, we are going to move towards the second pipeline, which is the query retrieval pipeline, wherein we are also going to use LLMs with it
01:31:01Okay
01:31:06So here we are going to specifically use LLM models, and these LLM models will actually help us to generate a summarized output, okay, in RAG
01:31:11So the entire pipeline will look something like this
01:31:17And when we talk about this query retrieval pipeline, we are specifically referring to something called augmented generation
01:31:22Okay, see, in retrieval..
01:31:25Retrieval-Augmented Generation, or RAG, basically means retrieval-augmented generation
01:31:30And this augmented generation, how does it specifically work? Okay
01:31:36So let's consider that this vector database is already ready, and you know how I created this particular vector database
01:31:43By following this specific pipeline, right? Now, once we follow this pipeline, the data is stored inside the vector database
01:31:54Now, whenever a user gives a new query, okay, it has a new query related to the documents already ingested inside the vector database
01:32:04Then what we do: we take this query, apply the same embedding, and in this particular embedding, what we do is convert the query to vectors
01:32:13Right? And then, from this embedding, we query the vector database to get the context
01:32:20And then..
01:32:26Whatever context we get, along with prompt engineering, is basically giving a simple prompt that instructs the LLM how it should work
01:32:32Now, once we are doing this, right, this step is called augmentation
01:32:39This step involves taking the context and combining it with a specific prompt
01:32:50Finally, you'll see that we generate the output from the LLM, and this step is called generation
01:32:59This is the retrieval step, where we give a query, convert it into vectors, and then..
01:33:07Hitting the vector database
01:33:10So you really need to understand the entire concepts with respect to RAG
01:33:15Okay, so let's go ahead and implement this entire retrieval query pipeline along with the LLMs
01:33:21Now, here we are also going to set up the LLM
01:33:26So, guys, now let's go ahead and implement this with the help of practical implementation
01:33:30Here, we are going to integrate the vector database context pipeline with the LLM output
01:33:35As suggested, we are going to implement augmented generation
01:33:36Now, first of all, what we are going to do is use the myGRO API key
01:33:44Okay, so I have updated the GRO API key here in the environment file, and here we are probably going to go ahead and create a
01:33:53Simple RAG pipeline
01:34:02Okay, with the GPT-3.5 or GPT-4 GPT model, so first of all, what we are going to do is that, again, if you remember in our requirements.txt, we will go ahead and import these two libraries, which are called langchain-gpt and then you have Python-dotenv
01:34:11Okay, and after this, we will go ahead and, you know, quickly initialize from langchain.chat_models import ChatOpenAI, along with this, I'm also going to import os
01:34:23Then, from dotenv, I'm going to use load_dotenv so that we import or load the entire environment variables
01:34:31Then the next thing is that we will go ahead and initialize the GPT model and set your environment API key inside this
01:34:42Okay, and in order to do this, again, you'll be able to see that I'm using the OpenAI API
01:34:49Key API.get_env, something like this
01:34:52Okay
01:34:54If you just go ahead and call this, my suggestion would be to not call from get_env initially
01:34:57You can directly test it by pasting the environment keys directly here
01:35:00Okay
01:35:03So here, I will go ahead and paste it
01:35:06Otherwise, you can go ahead and replace it
01:35:09Just for testing purposes, I am doing this
01:35:11Now, we will go ahead and initialize our LLM model, ChatGPT, and here I will use my GPT API key, which is equal to API, sorry, GPT API key
01:35:17Okay
01:35:23And then, the model name is GPT-3.5-turbo, temperature I will set to 0.1, and the maximum number of tokens it will generate is 1024
01:35:29Okay, so this is my LLM
01:35:31We have initialized the GPT model now
01:35:34The second thing is that we will quickly go ahead and create a..
01:35:38Simple RAG class function, and this is going to integrate everything—retrieve context plus generate response
01:35:44If you remember, guys, here is my retriever from the previous session; we have already seen how this RAG retriever was actually created
01:35:54We created a class for that
01:35:59Okay, so here, we are probably going to take two different parameters
01:36:02First, we will define a function called RAG simple, and then we will input our query
01:36:07Then, we will proceed to set our retriever, with LLM top_k equal to three
01:36:14Okay, and then, over here, let's quickly go ahead and retrieve the context first
01:36:24Yeah, so we are going to retrieve the context, so here, I'm going to..
01:36:28The write results is equal to retriever.retrieve(query)
01:36:33So here, you have this query and top_k is equal to k
01:36:38Okay
01:36:43And then, we are just going to get the context or I'll go ahead and define my context
01:36:48Inside this context, I will say that, hey, whatever information I'm getting from my results, just go ahead and combine everything and put it inside this
01:36:56Right? So here, I'm saying that, hey, for doc in results, whatever content I'm getting, I'm going to join it with a double newline over here
01:37:05If results are empty, we are just going to keep it as empty
01:37:09So this is my context over here, right? Then, I can still go ahead and write one more condition, saying that, hey, if not context, okay, we just going to go ahead and return, saying that
01:37:21No relevant context found
01:37:25Okay, to the answer, question, and then we are going to generate the answer using Grok LM
01:37:32Okay, and now I'm just going to go ahead and define a prompt
01:37:38Obviously, I require a prompt
01:37:44If you remember, here I can again use a prompt template, or I can directly use a prompt here
01:37:50So here, with respect to the prompt, I will give a query saying, "Hey, this is what you really need to do
01:37:58You need to go ahead and answer this specific question," and you should probably get a response for that
01:38:03Right? So here, what I will do is I will quickly go ahead and paste it: "Use the following context to answer the question concisely." Okay
01:38:10And here, what we can basically do is..
01:38:15That we can just go ahead and, um, do one thing over here quickly
01:38:21I'll say just put a tab
01:38:24Okay
01:38:27So, use the following context to answer the question precisely or concisely
01:38:29So, here I have given the context
01:38:31Here I've given the query
01:38:33Okay
01:38:35Now, the next step is that we will go ahead and create a response
01:38:38So, response is equal to this time we are going to use llm.invoke
01:38:40Okay
01:38:42And here, let's go ahead and put something like prompt.format
01:38:44And here we are going to write: context = context, and query = query—whatever query I have
01:38:49Okay
01:38:54Then we go ahead and return response.content
01:38:59So, once we do this, then we can specifically call this particular function
01:39:08Okay
01:39:12So, now what? We are going to do is that I will just go ahead and write answer equals RAG simple, and let's say I go ahead and ask a question: "What is the attention mechanism?" Okay
01:39:28And here I need to give my RAG retriever along with the LLM, and then we can go ahead and print the answer
01:39:34Okay
01:39:41So here you can see, the attention mechanism is a function that maps a query, and in this case, we are able to get the answer over here
01:39:47This is really good
01:39:49See, a very simple pipeline where I have initialized my LLM model, defined a function, and then this function, what it is doing first of all, is hitting the RAG retriever's retrieve function, getting the context, and then combining the context with the prompt to hit the LLM
01:40:04So if you Remember, we are just following this entire process and generating a proper output
01:40:07If that particular output is available inside the vector database right now, guys, what we are going to do is enhance the RAG pipeline—the simple RAG pipeline that we have created over here
01:40:21We will enhance it in such a way that it will have more amazing features
01:40:26So now, we're going to go ahead and create an enhanced RAG pipeline, and this is the code
01:40:31You can see here we have a function called RAG Advanced
01:40:36I'm giving a query retriever, LM top-k elements—like how many we want, minimum scores, return context is set to false
01:40:41So here, you can see that, previously, we were simply..
01:40:46Just by combining the context, we are putting the information in the prompt and probably generating the response
01:40:53In this, what we will do is that here we are going to generate this entire pipeline with some more additional features, like what all additional features we'll require
01:41:01See, here we are directly getting the answers, right, but we do not have much information about the source or the context over here, right? So, what we are doing here is that we will return answers, sources, confidence scores, optionally full context, or the entire context
01:41:16Okay, so first of all, again, the code will be similar, where we are retrieving the context, so this becomes my context when we retrieve it from the retriever
01:41:24Retrieve, and then uh, I have..
01:41:26Written, if not results; if results are empty, we are saying that no relevant context was found, and here we are giving sources which are blank, confidence is 0.0, zero, and the context is blank
01:41:36This context is basically coming from the vector database
01:41:38Let's say that if we are getting some kind of results over here, we are combining all those results and preparing the context here, and then we are adding sources
01:41:46See, this source list includes the metadata information, source file, along with that you can see the metadata page number from which page number you are able to get, then what is the similarity score? And here, what I will do is I will just try to go ahead and, you know, display at least 300 units length of the..
01:42:04Content right, so up to 300 characters, we'll try to display, and then we are going through each and every document that is available inside these results
01:42:15Then we are going to calculate the confidence; we are actually getting that information in this document similarity score
01:42:18Here is my prompt
01:42:19In this prompt, we are giving context, query, and everything, and we are invoking it
01:42:23The output will be in this format
01:42:29So, let's now go ahead and execute this RAG advanced function
01:42:31Here, I've provided all the information, like asking what is the attention mechanism? What is RAG retrieval? I’ve set RAG retrieval here, LLM return context is set to true, minimum score, and all these things are configured
01:42:44So now, I'll proceed with the..
01:42:48Execute this
01:42:53Now, as soon as I ask what the attention mechanism is, you'll be able to see that I'm getting this particular information correctly, and it is also giving me the source information, which includes the page number, the score, and the preview information
01:42:57Along with that, here is my final information that you can see—right where we are displaying the first 300 characters
01:43:02Let's say that I go ahead and change my question
01:43:07Okay, I ask something else
01:43:09I'll say, "Hey, what's the attention mechanism?" which was one of the topics
01:43:15But if I go ahead and see my data, my PDFs—okay, I will go ahead and ask something else
01:43:19Okay, let's see what I can ask
01:43:22So, I'll go to the embeddings PDF
01:43:25I'll say, "Okay," and then let me search for something else, right? I will say..
01:43:28Hard negative
01:43:30I'll ask this question: hard negative mining techniques
01:43:33Okay, so I will go to my question over here
01:43:38Hard negative mining techniques
01:43:41Okay, and I'll go ahead and search this thing from my vector retriever
01:43:53So here, you can see that I'm able to get this entire information
01:44:03The test includes several hard negative embeddings in the retriever
01:44:05All these pieces of information, and again, you can see that embedding.pdf, PDF page 4
01:44:10I'm able to see all the information along with the context, right? So this is really amazing
01:44:17And here, we have just created an RAG (Retrieval-Augmented Generation) pipeline
01:44:18Why do we say this is an RAG pipeline? Because here, we are providing information related to answers, we are providing information related to confidence scores, and each..
01:44:27And everything now, let me just show you one more amazing way, and this is also an advanced RAG pipeline, but this time I will suggest you probably go through this particular code and tell me
01:44:39So here, what are we doing? We're doing streaming, citation, history, and summarization
01:44:42So all these things are included over here, and you can just go and search for this, and you can see the answer
01:44:49Okay, final answer, relevant context found, because that question may not be there
01:44:52Okay, I will just— or let me just change this minimum score to 0.1
01:44:56I think we should be able to get something
01:44:58Still nothing
01:44:59Uh, let me change the question
01:45:01Let's say, hard negative mining techniques
01:45:03And here, we are just going to go ahead and display this
01:45:09Particular output
01:45:10Okay
01:45:12So now you just go ahead and explore this
01:45:15Okay
01:45:17I'll keep this for you; at least see some kind of coding
01:45:18Okay
01:45:20So here, we are not able to get anything as such
01:45:21Uh, let's see, advanced RAG query, hard query, top querying, summarize equal to true
01:45:25Uh, no relevant, this one
01:45:28Let's see, I go ahead and ask, "What is 'Attention Is All You Need'?" Okay, I'll go ahead and execute it
01:45:36So here, you can see that I'm able to see all these particular answers over here
01:45:41Right
01:45:46Yeah, for some of the queries, this will not, it is not giving
01:45:51There may be some problem with respect to the context size, but it's okay
01:45:54You can try out different things
01:45:57If something is not coming, then we'll try to optimize
01:46:01That also, as we go ahead, we'll try to see this
01:46:04So here we have seen three amazing RAG pipelines
01:46:06One was a simple RAG pipeline, here was an enhanced RAG pipeline, and here, in the last one, we made sure to include streaming citations, history, and summarization with all this kind of information over here
01:46:16You just go ahead and check out all the information and look at the code
01:46:19I think you should be able to understand it
01:46:23So overall, I hope you were able to understand this particular video, and yeah, this was about the RAG pipeline
01:46:30Now, in the upcoming videos, what we will do is try to create some modular coding because, see, here, everything is basically created in one IPython file
01:46:36So, guys, now..
01:46:42It's time that we implement the entire RAG pipeline in the form of a modular structure
01:46:48Already in our notebook, we have seen about PDF loader, PiPInB, and B, wherein we discussed how to go ahead and create the entire data injection process and how to store all the information into the vector database
01:47:00Finally, you're also able to make queries along with that
01:47:04I have also shown you how to work with Typesense, which is an open-source vector store itself and is excellent for searching anything quickly
01:47:11Now, all the implementations we have done, what we are going to do next is demonstrate how, in a modular way, you can go ahead and integrate this into a cohesive system
01:47:16Pipeline
01:47:21Okay, so already we have this source folder
01:47:27Now, inside this source folder, what I am actually going to do is that I'll go ahead and create my __init__.py file
01:47:32After creating this particular file, the next step is that I will go ahead and create all my important components required to build your RAG pipeline
01:47:44The first important component is nothing but the data loader, right? Data loader.py file
01:47:51This will be my first component because initially, we need to load the documents, perform chunking, and then probably store it into the vector store
01:48:02So inside my data loader, I will just try to read all the..
01:48:07Documents, uh, that is actually required
01:48:10Okay
01:48:13Then, uh, after this, uh, the next step should be your vector store
01:48:16Right? Now, the vector store—what vector store are we basically going to use? Uh, so for that, I will be creating my another file
01:48:23So here, inside my source, I will go ahead and create one more file, which is called vector_store.py
01:48:26Okay
01:48:29So, this is my next file that is basically created
01:48:35Okay
01:48:40Uh, along with this, uh, while actually inserting anything into the vector store, I also need to probably go ahead and do some kind of embeddings, right? And, uh, I will try to show you some open-source embeddings that we are going to use
01:48:46So, for that, I'll be creating my embedding.py file, and finally, uh, the last file that I really want to..
01:48:52Create is something called a search.py
01:48:55Now my entire RAG pipeline needs to be integrated in such a way that there is a linkage between all the specific files
01:49:03The first step is that I will go ahead and start working on the data loader
01:49:07Now, you know, the data loader's work is nothing but it should be reading this particular data
01:49:12Okay, it can be from any source itself
01:49:14We will try to read this specific data
01:49:16Right? So, for this, what I’m actually going to do is that I’ll go ahead and import some of the libraries
01:49:23So, quickly, I will go ahead and import all these libraries like PiPDF Loader, Text Loader, and others
01:49:27Okay
01:49:31So, I’ll start working on this because I need to form a pipeline itself, right? So, inside this particular..
01:49:36My main code should be in such a way that I will go ahead and read all the documents, whether it be a PDF text loader or CSV
01:49:42Okay, here I'm also going to give you some of the assignments because, in this entire series of videos, we have discussed this
01:49:47Okay
01:49:53So quickly, what I am actually going to do is that I will go ahead and create one function, which is basically called "load_all_documents." Now, see this
01:49:57Okay
01:50:01So here, I am just going to go ahead and write this function
01:50:04Now, please have a look at this particular function
01:50:06This function definition is "load_all_documents." I am given the data directory
01:50:09This should be in the form of a string, and it is returning a list, right? A list of anything, right? Of any kind of
01:50:20The main important thing about this function is that it loads all supported files from the data dictionary and converts them into a LangChain data structure, because as soon as we read any kind of data like PDF, CSV, TXT, we need to go ahead and convert that into a LangChain document structure
01:50:36Only then will we be able to apply chunking
01:50:40So here, you can see that I have used the data path of the data directory itself
01:50:44The data directory will be specified during runtime, and obviously, just by looking at this, the data directory is nothing but the data folder itself
01:50:49Okay
01:50:53Now, this is the code specifically to read all the PDF files
01:50:58So here, I have created a list called 'documents,' which will be storing all the..
01:51:03Documents itself
01:51:09Uh, here we have used the glob function, and here I have used this pattern, this kind of regular expression, to match all the PDF files
01:51:15So, what it will do is that inside this data directory, it will start looking for all the PDF files
01:51:19So, inside this, you know, that in the PDF folder, there are some PDF files
01:51:24So, it is going to go ahead and read all these particular PDF files
01:51:27Okay
01:51:30So, once it reads the PDF files, uh, we will have those PDF files over here in the form of a list
01:51:33Okay
01:51:35Then, what we are doing— we are writing for PDF and PDF files
01:51:38We are going through every PDF, and then we are using the PyPDF loader to read the content inside these PDFs, and we are using loader.load, and finally, I get all the..
01:51:48Information over here, and we are going to extend that with documents
01:51:50Now, this is just an example with PDF files
01:51:52The same thing can also be done with text files
01:51:54Okay, text files
01:51:57You can also do it with CSV files
01:51:59Right? Similar kind of code is basically suggested by GitHub Copilot
01:52:05But I really want to give you an assignment
01:52:07Okay
01:52:10So, this will be for CSV files
01:52:12It can also be for SQL files—any kind of files you want to work with
01:52:14You can go ahead and write that particular code and keep appending inside these documents
01:52:19Okay? So, as soon as you do that, you'll be able to perform this specific task and retrieve all the documents
01:52:28Okay? Now, what I will do just to test it out
01:52:32Whether my PDF files are working fine or not, I will just go ahead and create a `app.py` file here
01:52:35Okay
01:52:38Now, inside this `app.py` file, let me go ahead and import some of the libraries
01:52:41So first of all, I need to read everything here, right? So I have written `from source.data_loader import load_all_documents`
01:52:49So, this `load_all_documents` is nothing but the same function that is present inside my `data_loader.py`
01:52:53Okay
01:52:56And then, from `source.dove.vector_store` and `source.rag_search`, I will create in the later stages
01:52:59So right now, I'll remove this
01:53:02Okay
01:53:05Now, let's try to test the example
01:53:08So, example usage, I will write `if __name__ == "__main__":` and then here, I will go ahead and write `documents = load_all_documents`
01:53:13Documents, and I'll give my data folder, okay? Data folder
01:53:22Then what I can actually do is that I can just go ahead and print my documents
01:53:28Okay, if you see inside this data loader, what this is returning right now, it is not returning anything
01:53:37So, what you can actually do is that from here
01:53:39So, here, what we are going to do is that we are going to return the specific documents over here so that we should be able to print those particular documents
01:53:47Right now, what I am quickly going to do is that I will just go ahead and open the command prompt
01:53:51Okay, and here, I'm going to go ahead and write `python app.py`
01:53:56Now, let's see whether it will be able to read the PDF files or not
01:54:03Now, here, you can see it has found four PDF files; all the PDF file URLs are over here
01:54:09And you are able to see that it is also able to see all the content that is available inside that particular document, which is good, right? And this is basically in the form of a document data structure, I guess
01:54:19Yeah, so all the information is basically happening, so that basically means—so clearly, I can see something really amazing over here is that my entire data, the PDF code that we have written, is working absolutely fine
01:54:28Okay, now, comes the next step
01:54:37Now, the next step, you should probably start thinking whether we should basically go ahead and work with embeddings, so that we can do the chunking and all
01:54:46Right, so here, I will go ahead and start working on embeddings
01:54:47Now, inside my embeddings, what we are going to do is that I'll be… Importing these libraries now
01:54:55These all are the same thing repeated, but here I'm using classes and function definitions
01:54:59So, here you can see that after reading all the documents, after loading all the documents, I'm going to use SentenceTransformer, Recursive Character Text Splitter, and here you can see I've defined a function, a class called EmbeddingPipeline
01:55:11The model that I'm going to use is all mini V6, Llama V6, V2
01:55:16Chunk size is nothing but 1,000, and chunk overlap is 200
01:55:18Then, here we are setting self.chunk_size, self.overlap, and we are also initializing the SentenceTransformer
01:55:30Now, in the next function that we are going to work on, we are going to go ahead and..
01:55:36Create a function called "chunkDocuments." Inside this function, we pass the documents, which can be a list of any documents
01:55:42We apply a recursive character text splitter based on all the values that we have initialized
01:55:49Additionally, we use different separators; if you're interested, you can also use this blank separator
01:55:54Then, you can see that I am also using splitter.split to split the documents, and you will be able to see the remaining chunks right here
01:56:06This approach is for any document that I pass into this particular function
01:56:10But one very important point is that after the chunking is done, because of the way the process is designed..
01:56:20We need to also convert that chunking into vectors with the help of this particular model
01:56:23So, for that, I will be creating one more function called embedding chunks
01:56:26Here, what I will be doing is creating this specific function called embed chunks
01:56:33In this, we will take these chunks
01:56:39What happens is that first, the load all documents function will be called, and after that, the chunk documents function will be called, wherein all these documents will be chunked
01:56:45Then, all the chunks will be passed through our model to convert them into vector embeddings
01:56:47Right? So, here you'll be able to see self dot model.encode, with show progress bar set to true
01:56:55So, what we are doing here is reading all the..
01:56:59Page content, and we are performing the embeddings, and finally, we return the embeddings over here
01:57:03So, this is what we are actually doing
01:57:05Two important functions are: one is chunk documents, and the other is embed chunks inside a class called EmbeddingPipeline
01:57:11Now, the same thing you can go ahead and test in your app.py
01:57:15In the app.py, what you are going to do is, here—I will just go ahead and, a okay, let me go ahead and initialize—just a second—the embedding pipeline
01:57:25Okay, so here, what I will do, I will go ahead and write `from src.embedding import EmbeddingPipeline`
01:57:36Right? And once you do this, I will go ahead and initialize the embedding pipeline
01:57:46Okay? And then I will just go ahead
01:57:48and give this the correct way
01:57:52So this basically becomes my vectors, sorry, embed chunks
01:57:56It is there, right? So embed chunks
01:57:59Before that, I need to chunk the documents
01:58:03I also did not call the chunk documents, so let's first of all call the chunk documents here
01:58:08Okay, and then this will basically be my chunks
01:58:10Finally, you can also go ahead and define here as my chunk vectors
01:58:16So, chunk vectors is equal to, and here, uh, you can go ahead and use the same embedding pipeline, dot embed_chunks
01:58:30Right, and finally, you can go ahead and print the chunk vectors
01:58:37So once you do this, that basically means you'll be able to understand whether the chunking is happening or not
01:58:47So let's quickly run this particular file again
01:58:49And now, you should be able to see
01:58:50The chunking that may be happening over here, okay? So it'll take some amount of time because it is going to load all the documents again, okay? And then the chunk document function is going to get applied over here
01:59:04The chunk documents function, what it does, is that it is just going to apply a recursive character text splitter on every document that we specifically give
01:59:10Right? And once we do that, you'll be able to see that it is loading
01:59:16You can see all the things happening over here: 21 PDFs, one PDF—like 21 pages PDFs—is over here with respect to this proposal
01:59:22Load embeddings, all models split into 64 documents, I got into 359 chunks, you know, and then we basically go ahead and store this
01:59:31Now, the next step is that after this, I will… I try to create a vector store, and we will try to save those embeddings also
01:59:35Okay
01:59:40So here you can see all the chunks and vectors are visible over here, right
01:59:45So this is really, really good
01:59:49Just imagine, in a pipeline, it is specifically working one by one, right? It is working over here, and that's the best part
01:59:58Now, the next step is that I will try to create some more functions, which can be for saving and loading
02:00:07Like, if I want to save this entire set of chunks, how do I go ahead and save it? What do I save? Each and every piece of information that you'll be able to see over here
02:00:14Okay
02:00:20Now, this was about the two important pipelines, which are basically load all documents and..
02:00:24Embedding pipelines with two important functions
02:00:28One is chunk documents, and one is embed chunks
02:00:32So, guys, now the next step is that we have already created this embedding pipeline, right? Now let me do one thing, because after performing the embedding, we also need to store it in some kind of vector store, and it should be persistent in any kind of directory or in the cloud
02:00:49Right? So, for this, I will start working on this vector_store.py file, and here I’m going to use some code
02:00:53Now, you can see what all things I am actually using
02:00:58So, I’m using the SentenceTransformer and embedding pipeline here
02:01:00The first vector store is the class name that we are going to use
02:01:04Uh, I’m going to specifically use Fis
02:01:06Here we are going to use the same model
02:01:09All mini L6V2 chunk size, everything is over here
02:01:13And we are also creating some directories, the persistent directories like Firestore should be named accordingly
02:01:17Here, you'll see that I am initializing the embedding model, SentenceTransformer, and all that
02:01:22Now, the first step is to build from the documents
02:01:25Now, see here, we will go ahead and write the same code we used in the embedding pipeline
02:01:32So, here we are initializing the embedding pipeline model, including self-embedding model, chunk size, and other parameters
02:01:36I’ve given the chunk documents, embed documents, embed chunks, and added the metadata
02:01:39I am adding all these embeddings inside my vector store, and once done, I will save the data using `self.save`
02:01:46What is this self dot save? Save is a function that is going to save all the vectors inside this index, the pickle files
02:01:51Right? So, metadata is basically getting saved in the pickle file, and files.index will basically be my vector store, which will be in the persistent directory
02:01:59So, that is the reason I have written files, right? Index, self.index, files path, right? With open metadata, and all the information is there, right? So, this same method is basically there; add embedding method is over here
02:02:13Add embedding is nothing but it is basically taking it and adding it as an index flat array
02:02:18So, these are some basic things when you actually work on this
02:02:20Along with that, I've also created two more functions: load and search
02:02:25Load and..
02:02:30Search what it does is that it will actually allow you to load the files, index the vector store, okay, and it will load it in read byte mode
02:02:34Then, with the help of search and query, you should be able to ask any kind of questions you have
02:02:37Right
02:02:41You can also use this query method
02:02:44Here, you can see we have written self.doc_model.encode with respect to the query test, as type float32, and with the help of query search, you'll be able to get the output
02:02:48Okay
02:02:52So, this was about my vector store
02:02:56Now, in the app.py, what I am actually going to do, I will just go ahead and make some changes
02:02:59Okay
02:03:01Now, what are the changes I will be making? Okay
02:03:03Instead of calling this two, I will just go ahead and write store equals first
02:03:09Let me go ahead and initialize this file's vector store
02:03:11So, source dot embeddings, files vector store here, okay? And here I will go ahead and initialize this and let me give the path name
02:03:23The path name is "firestore." Okay? Now, initially, if this path exists, then it is fine
02:03:30Otherwise, it will go ahead, and I’ll just go ahead and write store.build from documents of all the docs
02:03:35That's it
02:03:39Now, if I do this, it is just going to build it for the first time
02:03:43Okay, it is going to build it
02:03:49So, let's see whether it will be able to build it or not
02:03:51Here, I am going to clear the screen
02:03:52Python app.py, let's quickly see
02:03:54Now, it is going to read
02:03:55First of all, it is going to read it, then this is fine
02:04:00Loading
02:04:04Perfect, load all the PDF files
02:04:08Perfect
02:04:12Now the chunking will happen automatically, and it'll save them in the vector store inside that particular folder called 'files'
02:04:14Let's see now
02:04:16It is generating 359 chunks
02:04:18All the steps are almost the same as what we discussed from the beginning, but this is a very super cool way of building something
02:04:28Right? Now, you can see it saves files, index, metadata to Firestore, vector store also
02:04:32So here you can see Firestore is there, Firestore.index and metadata.pickle
02:04:34Right now, we need not run it every time because once we have this, from the next time, instead of always building, unless and until you have new documents, I can also go ahead and write `store.load`
02:04:49Okay, if I go..
02:04:51Okay, I should be able to print anything that I want, right? Let's say I will go ahead and print something like this
02:05:02I can use the same query method that we had: "What is attention mechanism?" Top K is equal to three, right? So once I do this, you should get the results
02:05:11And this time, I don't think we need to read any documents over here, so I'll comment it out
02:05:16You can also uncomment it if you really want to, or you can give other conditions
02:05:24Now, what it'll do is directly read from the vector store, which it will get from the persistent directory, and then it will give you the output
02:05:27Let's see
02:05:30So, from the vector store, it'll go ahead and pick it up
02:05:33And here you go
02:05:36Here you get the answer clearly, right? See, loading embedding models
02:05:38This is loading the FAISS index and metadata
02:05:40What is the attention mechanism? All the information is over here
02:05:45And this is the output that you are able to get
02:05:47Right
02:05:49Perfect
02:05:50This is exactly what I was actually talking about
02:05:52But the best part is that we have created this in the form of a pipeline
02:05:58You have a data loader, you have embeddings, you have a vector store
02:06:02Now, for search, what you can do is integrate any LLMs over here
02:06:04Right? So for this, I also have written the code
02:06:06Again, I don't want to discuss it step by step line by line, so that it doesn't take too much time to complete this
02:06:12Right? So here I have my load_env file
02:06:15You..
02:06:17Just go ahead and load all these things
02:06:20The API key for Google Cloud is given over here
02:06:23You can use it or you can use your own Google Cloud API key
02:06:24It's fine
02:06:29Okay
02:06:33And then we are doing the search, right? Wherein we are using this vector store `do.query` to get all the documents, get all the metadata, and then we're giving some prompt and invoking it along with the LLM
02:06:37So once we do this, it is superbly easy to execute this
02:06:40Anyhow, you can do the research because I have discussed all these things in my Jupyter notebook, right? Now, what I will do in my `app.py`, I will see what changes need to be added
02:06:51And what I will do is that I will first of all import RAGSearch again from `search.search`, and then I will go ahead and..
02:06:59Initialize like this, right? And now I don't even require this
02:07:04Okay, now let's see whether it'll be able to give the summary or not
02:07:10It is loading from the vector store
02:07:14Now, I'm asking the question, "search and summarize." This is the function here
02:07:20What we do? We first of all query the vector store, which we were usually doing before
02:07:24Then we provide a prompt, and finally, the LLM will be able to generate the output
02:07:30So, here you can see, if my LLM is functioning properly, then I think I should be able to get an answer
02:07:35So, here you can see all the output is basically sitting over here
02:07:38This was a complete idea or a kind of crash course that I really wanted to give on the entire RAG
02:07:44RAG is one of the most important use cases, and that is what I always believe
02:07:49Most of the..
02:07:52Companies are specifically building RAG applications
02:07:55So I think this is really, really important and a super cool topic
02:07:58I hope you like this particular video
02:07:59This was it from my side
02:08:01I'll see you in the next video
02:08:04Thank you
02:08:05Take care.