📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

I Built the ULTIMATE n8n RAG AI Agent Template

Cole Medin34:01

Transcription

Retrieval Augmented Generation (RAG) is the most popular tool for giving AI agents access to your knowledge base, essentially making them domain experts for your documents. It's really easy to implement RAG, and no-code tools like n8n make it even simpler because of its wide adoption and support.

But I'm going to be honest: a lot of times RAG sucks for me. The reason is mostly because it relies on a lookup that often misses key context and related information. Good luck trying to analyze trends in a spreadsheet when the RAG lookup only pulls a fourth of the chunks for the table—and you need the whole thing! I find it so frustrating when I ask it to summarize a meeting, but it pulls the meeting notes from the wrong date. The date for the meeting is right there in the title of the document—why can't it get the right one? Not to mention that RAG often struggles to connect different documents together to give you that often necessary broader context.

Really, this boils down to two things: first, RAG isn't able to "zoom out" to entire documents or sets of documents unless the context is small enough; and second, RAG has no concept of proper data analysis.

So now the question is: how do we overcome these limitations? There are a couple of ways to do it, but my absolute favorite is Agentic RAG. In this video, I'll show you what that is, why it solves our problems, and exactly how to build an Agentic RAG agent in n8n. Plus, this workflow that I walk you through will be available as a downloadable template, allowing you to bring it into your own n8n instance in minutes.

This is a bird's-eye view of the Agentic RAG agent in n8n that I'm going to walk you through right now. I'll be the first to admit there's a good amount going on here, but don't worry, I will walk you through everything. This is what it takes to make a good Agentic RAG setup. I'll be walking through the RAG pipeline as well, so you understand everything—from our files in Google Drive all the way to extracting from the different file types and adding them into our Supabase knowledge base. I do want to make a local version of this as well using the local AI package, so let me know in the comments if you'd be interested in that.

This entire workflow is sort of version three of the n8n RAG agent that I've been working on. This is a much simpler implementation than the last version I covered on my channel—a good starting point that handles some different file formats. However, it doesn't work with tabular data very well, as we'll see later in this video. You need to add tables differently from CSVs and Excel files to the knowledge base compared to other things; you can't just treat it as a text document. You have to be able to query that table. Also, this agent only has RAG for its tool. You can see, based on this tool node here, there's nothing else it has. So if the RAG lookup fails to get the information it needs, the agent has no other way to explore the knowledge in Supabase; it's just stuck and has to tell the user that it doesn't have the answer, even though there are other ways you can look at these documents to potentially get the answer differently. That is what we're doing in this workflow right here.

Let me zoom in on this a little bit so you can see the tools we have for our RAG agent. We still have the RAG lookup tool, just like we had in our previous example (this is an improved version that can cite its sources). But we also have all these other Postgres tools for our RAG agents that can do other things to look at our knowledge. That gets into our definition for Agentic RAG. All Agentic RAG is is giving agents the ability to reason about how they explore the knowledge base. Instead of just giving it a single tool, it also includes agents being able to improve the RAG lookup queries and choose different tools to answer different user questions.

In the old version of our Agentic workflow, we do have the ability to improve the RAG lookup because we have RAG as a tool; the agent could decide to invoke it a second time with a better query. So we have that part at least, but there's no way for it to explore the knowledge base in different ways or figure out, based on a user's question, "Oh, I need to look at the data in this way." But with these three Postgres tools, we are giving that to our Agentic RAG agent in this upgraded version. It can list out all the documents available in the knowledge base, and then it can get the file contents of specific ones. So if a RAG lookup fails, for whatever reason, then instead of doing that lookup, we can look at the files available to us and then reason about which document or documents we might want to look at to get that answer. If we ask it to summarize the meeting notes for February 23rd and the RAG lookup fails (maybe it pulls from the wrong date, whatever it might be), then we can instead just look at the documents and see, "Oh yeah, the title of this document is literally 'February 23rd Meeting Note,' so I'm going to pull that, get the contents for it, and then use that to answer the user's question." You can already see how it's able to look at the knowledge base in different ways: use RAG, look at the entire document—it has all of that in its tool belt. Then we also have this tool to query Excel and CSV files as if they are SQL tables—super, super cool! This is a bit of a fancier part of the implementation, but it makes it so powerful to be able to get things like sums and maximums over tables that you typically can't get with RAG because it's never just pulling in an entire CSV file unless it's really small.

Now it is time to take our agent for a spin. We'll ask it some tougher questions that maybe the old version could have answered with just RAG, maybe not. But the most important thing I want to show you is it using these different tools to explore the knowledge base in different ways depending on the question I ask. I have these six documents in my Google Drive—some that are spreadsheets, some that are regular documents—and I already have this all in my Supabase knowledge base. I'll walk through getting this all set up later as well. We have our `documents` table that includes things like our embeddings for RAG, metadata, and the contents for each of these chunks. Then we have the `document_metadata` table (I'll explain more later); this has the high-level information for our documents, like the URLs for citing sources and the titles as well. Then we have our `document_rows` table. This is how we take our CSV and Excel files and store them in Supabase where they can be queried with SQL queries, even though we don't actually have to create a dedicated SQL table for each CSV or Excel file. It's very, very neat.

Let's go ahead and ask it a question for one of our documents. Actually, first I'll open one of these; I'll just show you the data and the question I'm going to ask. We'll go into the "2024 Revenue Metrics by Month." This is all fake data generated by Claude, by the way. We'll ask it a simple question like, "Which month do we get the most new customers?" Maybe you could pull in this entire table with RAG because it is small enough, but we want to see our agent write a SQL query to fetch this. If this table was big enough, we wouldn't be able to pull the entire thing in with RAG because it would just take the number of chunks that it would accept, and that might only be like a fourth of the table, and so we might not even pull in the record that has the most number of new customers, and then it would give us the wrong answer. So let's go back and actually ask that question here. I'll say, "Which month did we get the most new customers?" My goal here is to see it invoke the tool to write a SQL query. Yep, there we go, it did! I'll even click into this so you can see this is the query that it decided to write—a little bit more complex, we won't get into that right now—but yeah, look at that: 129 new customers in the month of December, and that is the right answer. So we got everything back, and yep, right here it says the correct answer.

All right, so next question. I'm going to have a blank slate for the conversation for each one, so I cleared that. Let's go back over to Google Drive and open up a text document this time: "Areas for Improvement." This is a customer feedback survey. I'm going to ask it, "How can we improve?" and then see if it can pull this from this document specifically without me calling it out explicitly. So I'll go back here and I'll just ask, "What are areas we could do better with?" I specifically don't want to use the word "improvement" because I want to make sure that this lookup isn't just relying on the fact that we kind of just say word for word "areas for improvement." So I'll just say, "Areas we can do better with," and it—yeah, it used RAG for this time around—"mobile access, integration capabilities, and reporting customizations"—and that is exactly right. So boom, we got the right answer.

So now I'll go back, clear the conversation again, and this time I wanted to explicitly look at the contents of a file instead of performing RAG. Surprisingly, this can be tricky to get it to do when you're in a test environment with only this much data; it's hard to make RAG fail, so it needs to actually pull the contents of an entire file. So I'm going to be explicit here; I'm going to tell it to use this tool just so you can at least see it in action, but trust me, from my experience with RAG in general, this kind of functionality certainly is necessary because RAG isn't always reliable because of what we were talking about earlier on. Let me open up this "Product Team Meeting Minutes." I'll tell it to look at this file specifically to then pull the action items that we have. So let me go back over to n8n for this test. I'll also have it cite its source, but first let's ask it to get the file contents of the product meeting minutes and then tell me the action item. I'm explicitly asking it, and there we go. Yep, it called the tool to get the file contents; it returned the entire document here, and yeah, this answer looks good—"Marcus to provide a timeline"—that looks good; everything else matches up. Yep, that looks perfect. And then I'll also ask it to cite its source: "Cite your source." So I want a link to the document because I want to maybe go and check then to make sure I had the right answer if I don't have the document pulled up already, and there we go—gives us a link right here. I can click on this, and then boom, we have the document open right from our agent. Look at that!

The sponsor of today's video is Unra, an open-source, no-code LLM platform to create APIs and ETL pipelines to turn unstructured documents into structured data. This is so important for AI agents, especially with RAG, because you're not always going to have simple CSV and text documents for your knowledge base. So you can't easily just extract all the text and dump it in. Sometimes you're going to have PDFs where you have to pull specific tables from, or you're going to have images that you need to extract information from, for something like a receipt. That's what Unra can help you with. You could even turn it into an API endpoint to put into something like an n8n workflow to handle your more complicated documents.

Here is the GitHub repository for Unra. You can think of this platform as being three distinct parts: first, you have the Prompt Studio; this is where you can engineer your prompts to work with the LLMs and make sure they know how to extract the information from your unstructured documents. Then you take those prompts and you add them into workflows; this is where you build these flows to automatically extract the information from your documents. Then you can deploy the workflows as data APIs and ETL pipelines. They have fantastic documentation (I'll have it linked in the description) for how to work with all these different components for things like API deployments and ETL pipelines. I also just want to call out the Prompt Studio really quick because it is just fantastic how easy it is to upload a file (like this receipt that I just pulled off Google) and then define prompts to extract all the different key pieces of information you want (like the line items and the tax amount and the dollar amount at the bottom), and it just does so well extracting all this. You define your prompts here, figure out exactly what you need, and then go on to build your workflows. If you have more than just simple CSV and text documents that you could extract with a single node in n8n, I would highly recommend checking out Unra; it just solves so many problems that we have working with our more complex documents, and it's so important for a huge variety of use cases, including RAG agents. I'll have a link in the description below to Unra; definitely recommend checking them out if you want to work with all your data and not just what's simple.

So now you know at a high level how this Agentic RAG setup works. I want to drill down now into the different components so that you have what it takes to take my template and extend it to your specific use case. This is a very good starting point, but I don't expect it to be an out-of-the-box solution for you. I do want you to work on the prompting, the tools, and the pipeline—change things up to work with your specific knowledge base.

Zooming in here, I'm going to show the first part of this workflow: running all the nodes in this red box to set up your Supabase database. We have these three different tables here, and we have to create each one of them. The first node is to create our `documents` table. If you've set up RAG with n8n before, this query probably looks very familiar to you; this is in the setup instructions for Supabase, so you might already have this. You could always just use what you already made or rename the `documents` table and the query here, but this builds our `documents` table where we store the embeddings for RAG, the metadata, and all the contents of each file as well. Then we have the second node to create the `metadata` table. This table stores the higher-level information for our documents so that our agent is able to look at things at a higher level compared to just a dead RAG lookup—decide based on the title if it wants to analyze an entire file (like the revenue metrics, for example)—and it also has the URLs so that it can cite its sources—both RAG and the entire file lookups here on these tools for the agents cite its sources when it calls those. Then the last thing we have (I'll explain more later) is the schema. For just the spreadsheet type files, we define the schema here, and that tells it what fields there are when it queries the data for that table in the `document_rows` table. Speaking of which, that is the third node here—creating the `document_rows`. All the data for each row is stored in `jsonb` in this `row_data` column. That is how we're able to essentially create SQL queries for our table data but not have to create a brand new SQL table for every file that we ingest because it's all done within this `jsonb`, which is flexible; we can have any kind of schema stored in the `row_data` right here. So that is kind of what we see right here; for example, this file we have "cohort," "initial customers," all these different things, but then for this spreadsheet we have "CAC," "LTV," "MRR"—all that different data is stored in `row_data`, and then the schema right here tells the agent how to query it—what columns are available to it. It's not the perfect setup because it doesn't tell you things like the type of the data, so it might try to do a sum over something that actually has dollar signs for each number (so it's a string), so not a perfect implementation. Again, this is just a template to get you started, but it does show the concept—very powerful in a simple way—and that's the main thing I'm trying to do with this agent.

The second part to our workflow is our RAG pipeline—that's everything in this blue box—where we're taking documents from something like Google Drive and bringing it into our Supabase knowledge base. Obviously, we have to do that before we're creating our actual AI agent because we need a way to test to make sure the tools we're giving it to explore the knowledge base are working. So I'll walk you through the pipeline right now. I won't cover creating all the different credentials for things like Google Drive and Supabase because I have done that before in other videos on my channel, like for this version of the workflow. If you go to create new credentials, there's always going to be an "open doc" button that n8n gives you; it brings you to their documentation page that makes it super easy to set up your credentials. The one thing I will say for the Postgres nodes, though, and the credentials for that, is that the n8n documentation is not very clear. You need to use the Transaction Pooler method for connecting to Postgres. So you go into your dashboard for Supabase, click on "Connect" in the top middle; this will save you a huge headache, by the way—it did for me. You don't want to use the direct connection parameters; these won't work. You want to use the Transaction Pooler ones where the port is 6543. So this will give you everything you need, obviously except for the database password, which hopefully you should have.

With that out of the way, let's dive into the start of this pipeline, which is our Google Drive trigger. Clicking into this node, all we're doing in Google Drive is polling every minute for new files that are created. You can swap this out for a Dropbox or a local file trigger (I'll show you that when I make the local AI version of this). This is just an example using Google Drive. So it's watching every minute for files created in a specific folder that I give it in my drive, and then I have a similar trigger for files that are updated as well. So this workflow will handle both files being created and updated. There isn't a trigger to watch for files that are deleted, unfortunately—it's kind of a big bummer; I hope they add that in n8n just so that you could clear your knowledge base when you delete a file in Google Drive as well. So currently that is not supported.

One thing that was really missing from my old version of this workflow is it didn't handle properly when multiple files came into the trigger at the exact same time; it would just send one file through this entire workflow and skip the rest of them. But in this version, I am handling that for you. I know that was a big piece of feedback that I got. I added this loop in, so now it can handle when you dump in multiple files within the same polling minute or update multiple, and I even show this here because in my pin data for my Google Drive trigger I have two items—I'm sending in two files and handling that in this loop. So what it'll do here is send one file through this entire flow, just like we saw before, but then it'll loop all the way back and do the same thing for the next file and the next file until it gets through everything that the trigger gave into the loop. So I hope that makes sense; definitely wanted to improve that for you all.

So now we're zooming in on just a single file level; the rest of this happens for just one file at a time. First of all, I ran everything here already, so we'll see the inputs and outputs because I have a test execution that I went through; that's why you see the green boxes for all these. So in this first node here, we're setting the stage for the rest of the workflow with all our important information: the file IDs for our queries, the file type to determine how we want to extract the content, and then the title and URL as well, which is going to be going into the database now. The next thing you want to do is clear out all the old data for this file in Supabase; that is, if we are updating the file, we're just going to do it every time, just in case. The reason we want to do this: we want a blank slate because we want no chance that there's any data from an old version of our file left in the knowledge base for our agent to query when it shouldn't be available. To give you a very clear example of this, let's say you have a file that is initially 10 chunks because it's something like 10 paragraphs, but then you delete the last paragraph; now it's only nine chunks. If you try to just update the existing chunks in the database instead of clearing them and inserting them new, you're only going to update the first nine, and then that 10th chunk (because the file used to be longer) is going to remain in the knowledge base even though it's from an old version of the file. So the most surefire way that's generally recommended is just to delete everything. So we're deleting all of the `document_rows` specifically for this file ID using that `metadata` field that I'll show you later on, and then doing the same thing for the data rows as well for all of our tabular files, and again, just based on the file ID that we already set, we're just going to delete all of those records in the Supabase table.

Then we want to do our first insert (or this is actually an upsert too because if the file already exists we're just going to update the metadata, and then if it doesn't exist we will insert the metadata). This is just setting the initial stage for our document here with things like the title and the URL, and then later for the tables we'll be setting the schema (I'll show you that in a bit as well). We can set this here because this table doesn't rely on having the content for the file yet because we're going to extract that later, and that's when we'll be able to populate the `documents` table because we'll have that content extracted to add in the `content` column here, create our embeddings, and add those—all that good stuff. The reason I'm using Postgres here and then Supabase here—they're kind of interchangeable—but Postgres offers some better nodes for things like running SQL queries, doing upserts (like perform an update if it's not there, insert it); you don't have those options for the Supabase nodes, but I do want to use Supabase for deleting because it has this filter option that I didn't see with Postgres. So that's just a little aside—why I'm kind of mixing and mingling the Postgres and Supabase nodes in this workflow.

Okay, at this point we have a blank slate and we inserted the initial metadata. Now we want to extract the content for the rest of this pipeline. So we download the file from Google Drive; so this `data` field that output is the file itself—like I could download it or view it. So we don't have the content of the file yet; we have the file itself stored in our n8n instance now, so we can extract from it. And then we go on to this switch node. Based on the type of file, there needs to be a different way for us to extract the content from it because the way that you take content from a PDF or a spreadsheet or a Google Doc—those are all different. And so we have these different branches that are all determined based on this switch right here. If it's a CSV file (like it is in this test run), then we go down output two—that third branch—otherwise, if it's a Google Doc (or also the default is the output 3 as well), then we go down this bottom branch right here. For my test, we see the green line going to "Extract from CSV" because this test is working with a CSV file that I uploaded to Google Drive. It's actually quite simple. If we're extracting from just a PDF or a text document, we just have a single node here, and I'll even show you: if you go to add a new node and search for "extract," all these different file types are supported. So if you want to extend this to work with JSON files or extracting from HTML files, you can add these extract nodes in, and then you just need to add another branch into the switch statement here. So it's very easy to extend this for other file types as well. If it's something that's not supported within those options that you saw there, you can always create a custom n8n workflow to extract from different file types too. So the world is your oyster here; the different possibilities are endless for how you can work with really any file type that you want. And then other file types like Markdowns and text documents can also be handled by just extracting from the text document; like this node covers a lot of different file types as well. What I do want to focus on, though, is extracting from CSVs because this is where it gets a little bit more complicated, and we want to populate the schema in the metadata and the rows as well. So let's go back into our n8n workflow; I'll show you how this all works for CSV and Excel files.

In this demo, I'm just running this CSV path right here, but for Excel it's exactly the same; the rest of these nodes are the same; we just have to have a different extract node. So first we are taking the contents from the CSV file and turning it into rows in our n8n workflow, and then we want to do two things at once because we want the data from our table file to be available in RAG, so we want to turn it into a text document and chunk it just like the rest of our documents, but we also want it to live in the `document_rows` because we want to be able to query it as if it is a SQL table; we're giving our agent the ability to do that. So we have two different paths that we are going down here. The first one—for all 15 records that it pulled from the CSV—we are inserting each of those into the `document_rows` table; so like this is all one file, for example—everything right here—we're inserting all these records within this node right here. And then in parallel we also want to start to turn it into a text document, so we're going to aggregate everything together, so instead of there being multiple records it's just a single item, which is an array of all of our rows, and then we want to summarize it, which essentially just turns it into a string because now we have a text document that we can chunk up just like if we extracted from a PDF or a markdown file, whatever you might have in the top and bottom branches right here. And then all of that goes into Supabase, which I'll cover in a second here. So in the end, the tables are treated just like any other text document, but also we have this route right here where we're setting the schema. Using this fancy JavaScript that I'm not going to explain in detail right here, we're taking the headers from the CSV and defining that as our schema and then updating our metadata record so that the agent can access that schema as well. So this is where we set this piece of information; we tell it that this CSV file has these headers, and that's how the agent knows—it'll read this record first to get the metadata for the customer cohort analysis; it'll see that this is the schema, so then it knows how to write the SQL queries to query the rows here. So I hope that makes sense. So the agent has to look here first, understand the schema, then go and query the rows, and we're making that all possible right here—adding in the schema to that metadata record.

And then the last part right here is our Supabase, and this one is pretty simple because n8n takes care of so much for us with all these nodes. It is quite complicated to chunk everything and add it into Supabase, but it's just done with four nodes here. So first of all, we have our "Insert into Supabase Vector Store" node where we just define the table and the query that we are using for RAG, and then we have our embedding (I'm just using OpenAI, by the way; I'm using `text-embedding-ada-002` for my embedding model), and then for all of the LLMs I'm just using GPT-4-0-mini, which isn't the most powerful LLM; I just wanted something cheap and fast here, but depending on your use case you might want something more powerful as well, like GPT-4 or Claude 3.5. So anyway, that's our embedding model, and then we are just using a default data loader; this is what's responsible for chunking our documents, getting them ready to insert into Supabase, and defining the metadata as well, which that is very, very important. For the metadata here, I have the file ID and the file title, and the reason this is so important is because the metadata is how we can query to delete only the records for a specific file when we're at the start of the flow when we want that blank slate for inserting for RAG, and then the file title itself—we also want in the metadata because the agent is going to reference this to know what file it's actually looking at when it performs RAG so that it can cite its source, and we'll get into that later. But in the RAG tool, we actually have it so that the metadata is returned as well, so I have this ticked right here; that's how the agent is able to know what it is looking at. And then finally, for the text splitter, I just have something very simple with a character text splitter; didn't put a ton of thought into this because it will depend a lot on your use case how you want to chunk your documents, so just keeping it very, very simple there. But that is everything for our RAG pipeline, and you also saw as I was going through this workflow I showed you the inputs and outputs for everything, so you basically saw a whole run of this, and I've already done that, so all of the files that I have in my Google Drive for both my spreadsheets and my documents, I have that all ingested already. I just used the trigger, dumped in my files, and it handled that.

With the RAG pipeline created and all of our knowledge ready, we can now move on to setting up our agent. Luckily, creating our AI agent is simpler than our RAG pipeline because we do all the work getting everything set up in our knowledge base and Supabase, and then our agent only needs a few rather simple tools to leverage it. So let's go through that right here. So first I have a couple of triggers for this workflow: I have a webhook, so we can turn our agent into an API endpoint, and then I also have a chat trigger, so you can chat with it right here in the n8n workflow; that's what gives us this chat button in the bottom middle here. And then these two nodes output slightly different formats, and so I have this "Edit Fields" right here—just a little bit of JavaScript to handle both of these different triggers so that we have a consistent output for our agent node.

So going into that right here, our agent is quite simple overall. I just have this system prompt right here that describes to it the different tools that it has to explore the knowledge base, and I give it some instructions for how to leverage these tools; for example, I tell it to start with RAG and then use some of the other tools if RAG doesn't give you the right answer. And so you can certainly tweak this system prompt; I think that there is a lot of opportunity to make this a much better system prompt; I just have this as an example for you. One thing that helps a lot with these kind of RAG agents is ask it to be honest: if you don't find the answer from RAG and the other tools that you have, just tell the user instead of trying to make up something yourself. And this alone can reduce a good amount of hallucinations. And then for our model, we just have GPT-4-0-mini, like I showed earlier, setting up a simple Postgres conversation history right here, which, by the way, this table is created automatically if you don't have it already in the first conversation, which is why I don't create this as a fourth node in the red box—so nice and simple and n8n just makes it easy for you. And then we go on to our tools. The first tool that we have is RAG, and you'll see here—looking at the old version of the workflow—this is a much simpler version, and it's because n8n has had a lot of really awesome updates for AI agents since I made that last video. And so this tool for the Supabase vector store is much simpler, and there's the option now to include the metadata—so the file ID and the file title that we inserted in the metadata for each record—like I'll show that here; I go to `documents`, and then click in on the `metadata` field; we'll see here that we have the file ID and the file title. All of this is brought into the results for RAG, and so the agent has access to that information to cite its sources; that is super, super important. And then we are using the exact same embedding model that we use when we insert things into Supabase; that's super important because you just have to make sure that your model has the same number of dimensions for both the inserts and the retrievals.

And then going to our other tools here, the first one we have is to list our documents—just using a simple Postgres query here; we're pulling all our documents from the `document_metadata` table so the agent can read all these and then reason about which files it wants to look at and the IDs of each of them, and also for the table files the schemas as well, so it knows how to query the `document_rows` table. I am returning every single document here, so keep that in mind: if you have a large, large corpus of documents, you might want to not return everything and find some way to filter the documents that you are pulling—maybe based on a date or having the AI write a query, something like that—but also keep in mind the LLMs can manage very long context lengths right now, so even if you have like a thousand documents in your knowledge base, you still could pull the files and the titles and IDs for every single one of them and dump that into the prompt for the LLM, so that might even still work. And then after we list the documents, then the agent might want to pull the contents of specific files, and so I just have this query right here that is essentially—given a file ID that it pulls from the `metadata` table—it can use that to pull the content of all the chunks for the document and combine that together to give us the full text for that document. The reason that I'm using the `content` column in the `documents` table instead of just having a `content` column in the `metadata` table that would have all the content for the file is just because n8n includes this `content` column by default; it's something that I can't control, so if it's already here I don't want to duplicate the information by having the content of the file stored in the `metadata` as well. So I just pull all the chunks together for the content and combine that with this query right here, and the one parameter that the AI decides is the file ID, so it picks that out from the `metadata` table and then passes that into this tool. So you'll see the agent—every single time that calls "get file contents"—it always called "list documents" first because it needs to do that to actually know what file ID to pass into the tool to get the contents for it. And then the last tool that we have here is the one to write SQL queries to query our tabular data, and this is a little bit of a fancier implementation here; it's pretty bare bones as well; there's a lot of room for improvement for the prompt here because what we have right here for the tool description is given—given as a part of the prompt to the LLM—so it knows when and how to use this tool (I mean, same thing for all the other tools as well), but I have to be a lot more explicit here because I have to help it understand the `document_rows` table; it needs to know how it's structured, how it needs to use the `row_data` JSON…

B to write these SQL queries for these different files. I give it some examples as well, and these examples are pretty barebones. You probably want to improve this more for your specific use case and how you want it to query your tabular data. But I give it examples of how to use the row data JSON B to select certain columns, to do group buys. You could have it understand filtering better; all of that. And then I have it write the full query. The single parameter here is the entire SQL query that it wants to write, something like this to query the contents of a specific file. Because the data set ID is the file ID, that's how it specifies the single file that it wants to query. Then it uses the row data JSON B to query and group by specific columns and do all that filtering as well. So that is the last tool, and that is everything.

I know that the tools got a little bit more complex there, so yeah, just let me know in the comments if you have any questions on that. But this is it for our agent. So we now have everything to do what I did at the start of this video. Just like a quick example here, I can say like, "what employees are at the company?" Just something super generic. I mean, this is the kind of thing that, like, again, maybe RAG could do, but I'm just showing a random example here, just to show it using all the different tools here.

So yeah, in this case, it performed RAG and decided that it didn't get what it needed, so it decided to list a few of the documents as well. And so it told us, "Oh, this document didn't give us what we wanted; this document didn't give us what we wanted." But then the product team meeting minutes did. Here are the team members. So look at that, it's so cool! Like, I actually just pulled this example out of my butt just now, but it worked really well because it showed performing RAG and that not working, which kind of makes sense. Like, you wouldn't really know how to find specific names using a vector search just because you're asking for employees. So then it decided to search the files, which is really cool. So yeah, good example. I hope that this template can get you started super fast with an agent, RAG, and an N8N. Of course, let me know in the comments if you have any questions as you build out this workflow. It is getting into the more advanced RAG topics. Also, a lot more similar content coming out soon, including a completely local version of this agentive RAG agent built with the local AI package. So stay tuned for that. If you appreciated this content and you're looking forward to more things AI agents and N8N, I would really appreciate a like and subscribe. And with that, I will see you in the next video.