Chat with CSV files using LangChain Agent [GPT-4o]

Build a LangChain agent that lets you query CSV files with natural language using GPT-4o. The agent converts your questions into Pandas code, runs it, and returns answers. The tutorial uses a 5.6 million-row consumer-complaint dataset and shows how to generate charts automatically.
Chapters
In this tutorial, I'll walk you through the process of using LangChain agents to query your CSV data using natural language. If you've never heard of LangChain, LangChain is a framework for developing applications using large language models. And so a little bit about the environment we'll be using today so you can follow along. I'm using Google Colab. And if you have a Gmail account, you can just go to colab.research.google.com. And you'll be able to provision yourself a notebook that you can follow along, or you can just kind of clone this from my repo and run it as well. And so before we get started, I just want to show you the documentation where I'm adapting the code for this tutorial from, which is from LangChain documentation. And if you go to their toolkits and under CSV, you'll be able to see an example of this dataset here, although it needs to be updated, especially when it comes to this section here. If you follow this tutorial, it will fail because the run is deprecated. Or at least it would throw an error telling you that this is deprecated. Now it uses invoke and you'll see that down the road as well. And so first of all, let's just kind of walk through the entire flow of what this looks like, right? What are we building? How the pipeline is going to look like. So first of all, the first place you start, think of it as a user at both ends. So you have the user in the beginning and the user at the end. So the user in the beginning is the one you basically asking a question of your dataset. and sending that to the agent. And then at the end, as well as the user, you're receiving the output of what the LLM has generated as a response using the context from the dataset. And so in the middle, on the first section here, you have the LLM selection. So when you ask a question, in order to build the CSV agent itself, you will need an LLM first of all and a dataset. And so for the LLM, you can use, and in this case we're using GPT-4o, which should be very robust in this case. And then you'll need a CSV file or at least multiple CSV files. If you want to like maybe, I guess, compares, if you have related CSV datasets that maybe you want to have the agent kind of run through all of them and generate like insightful analysis from both of them in this case, more of like joins if you can think of a database that way. And so once you have those elements, the LLM and the datasets, then the agent itself is going to convert the CSV files and load them into data frames, into Pandas data frames. And then the LLM and the data frames form the CSV agent itself. And so it will take your question and the LLM portion of that is going to process your question and maybe look at the semantics and think, what is this person asking? The user is asking for this specific question. And from their dataset in the data frame, it infers what columns or what specific code it would need to be able to generate a response or an answer for you based on your query, if that makes sense. And so once it generates the code, it sends it to a REPL, a Python REPL, and the REPL itself is going to run or execute that code and it's going to return a response, an answer, which then gets passed to the LLM, in our case GPT-4o, to generate a final response, which then gets sent to you as a user to be able to consume and figure out maybe if it made a right choice there. Now that we've seen how that looks like, let's go back to our code and just walk through the code itself. And so here I have structured this pretty well. So in the beginning here, we have the dependencies that you need to install. So you'd install, we would need LangChain, we need LangChain OpenAI for the OpenAI portion. And then we have LangChain Experimental, which for the CSV agent itself, it's still experimental. So that's why we have here as experimental. And then we have Pandas here as well. So we'll run that section right there. And then the next portion after that is actually importing the libraries themselves that we'll need to be able to run this code and also setting the API key for OpenAI in this case. So let's run that. And here what I'm doing, I'm just importing LangChain agents and agent type, which we'll need that. And then the LangChain experimental agent, we're just importing the create CSV agent. And then the LangChain OpenAI, chat OpenAI and OpenAI, we're just importing those pieces that we will need to interface with OpenAI in this case. And so when it comes to your API key, as you can see, I don't have my API key anywhere here, like at least the actual API key. And so if you come to the side here, you'll see this secrets key. And if you come here, you can actually set a variable here under the name. You just write in OpenAI API key. And then the value of that variable, you just pass in the API key actually from OpenAI. And so once you have that, in there saved, you enable this notebook access so the notebook can access your secrets. And after that, they have this boilerplate down here that you can use to be able to get that API key and use it within your code. So we'll adapt this. All we need to do is we'll need to just change the secret name here to our secret name here. And then as you can see on the side here, that's what we've done. And now we have that API key loaded in our DataFrame. So let's run that piece as well. And so that will be done. So the next stage now is loading the dataset. And once we load the dataset, we have the LLM, we load the dataset, we combine both of them to create the CSV, and then we continue to the chat. So for the dataset, I just want to talk to you a little bit about this. So here I chose some datasets from data.gov, which is the government website for datasets from the government. And the one that I chose was a consumer complaint database. And essentially what it does, it's a collection of complaints about consumer financial products. and services. And so these complaints are published after the company responds confirming a commercial relationship with the consumer or after 15 days, whichever comes first. And so complaints referred to other regulators such as complaints about depository institutions with less than $10 billion in assets are not published in the Consumer Complaint Database. The database generally updates daily. So we don't really care much about that. We care just about the data in this case. And this is a huge dataset and I think it has about 5.6 million rows. So it's a big dataset. It took a while for it to run. And let's just run this function. Now I can walk you through as well this function itself, what it does. So here you're importing pandas and I'm importing functools. And this functools, the reason why I'm using these functools here as a decorator is it's a cache. So this is caching. So I'm caching the data because it's a huge dataset. Every time I rerun this, I don't want to like Especially if it's the second time I'm running it. I don't want to reload the entire and take a long time for it to load. So in this case, this is the dataset that we are loading. I also added the data type for this dataset because it was throwing an error. So I just added that already pre-appended. And then I'm passing that to the read TSV and passing the URL, which the URL is right here. It's a zipped file and we'll pass that in there and also the data type and then the compression as zip. Compression, and then it should return a DataFrame out of that as a CSV. And this is what it did here. So let's run it again and see if it already cached. It didn't because I restarted the runtime environment. So it's going to run through that and load that dataset and return the head of the dataset here. So while that is running, we'll just continue walking through the code. And once that loads, the next phase is to save that dataset as a CSV within our dataset here. So if you come here to sample data, you should be able to load that. And in this case, we already have that loaded. So consumer complaints, and this was from the previous run. So we're not going to run this piece because we already have it in there. And the next part after that would be to look at the other dataset that I have here, which is FDIC failed bank list dataset, which is a very interesting dataset in itself. And so this is often appointed as receiver for failed banks. So FDIC in itself. And so This list includes banks which have failed since October 1st, 2000. And this link will take you to that dataset if you want to interact with it later. And the same thing as above as well, you can interact with that dataset. And so for this dataset itself as well, I've also used the caching here for LRU cache. Also here, I'm loading the dataset, loading the URL there, and we're getting the dataset and we're just showing the top 5 datasets here. So we have the bank name, the city where that bank was based, the state, The certificate, I'm not sure what that is for. It's probably very important, but in this case, I didn't really go into details for that. And then the acquiring institution, because most of these banks, if they failed, they get acquired by other banks. They kind of like bail them out. So Signature Bank, as you would expect that this was like during the COVID times, it fell during the crypto crash. And most of this actually really, if you look at them, they were like 2023 and then this one in 2024, they're Republic First Bank. And you see the bank that acquired it. So some of these were saved during that time. So that's an interesting dataset by itself. And so once that's loaded, that's how it looks like. And then also for this one, we just save it to CSV and it's saved here as well on the side. And so once that dataset is saved, the next stage now is to query the CSV file. But before we query, we have to create our agent, right? Like I mentioned earlier. So to create our agent, we would need to Instantiate agent, we create use the create CSV agent that we imported. And then here we're just passing in the temperature as zero because I don't want it to. I want to kind of maintain some reliability, kind of same output each time a little bit on that. I don't want it to be too creative there, the model to be too creative. And then the model itself is GPT four all. And then the API key that we loaded earlier, we're just passing it in here as well. And then the path here is to the two files that we have in this case. So we have the complaint. CSV, and then the filled bank CSV dataset. And then verbose true, of course, that's kind of like letting it say what it's doing, kind of walking us through the process. And then the agent type here, we're using OpenAI Functions tool. So functions call there. And this will be for the REPL. Also here, another important thing, allow dangerous code true. So we're allowing it to run dangerous code. And this is essentially when you have the LLM generating code, code that then gets run in a REPL, there's a chance it will generate some code that's malicious, right? And that's not great. Like delete some stuff, right? For example. So in this case, we're just telling it, yes, you can run that code. It's fine. Because what I noticed as well when I was running this, if when I didn't have this on, this allow dangerous code, it was failing and it was throwing some errors about some JSON issues there. So when I allowed it, then it ran. So I'm guessing it's something to do with the type of code it was generating and some of the guardrails that were already in there if you put this as false. And so let's go up here and see. Yes, our datasets have finished loading. And so this is the dataset for the complaint dataset. So you can see there's date received, product type, and all this information here as well. Most of it is really credit card complaints in that case. And so once that's loaded, we were here creating the dataset. So let's close up some of this stuff as well so we can go back and follow along what we were doing. We have already uploaded our datasets, querying the dataset. So allow dangerous. And code run. We talked about that. So I'm not going to run this because this took a while as well to run. And let's assume it's already running. If it's not good, then we'll know it once we start running these queries. So once you've created the CSV agent itself, you've instantiated it. It needs the path to the files and it needs the LLM for it to form the agent itself, right? And that's what we did. And it was passing the tools that it needs to be able to become an agent to use if it wants to use. And so the The main function tool that we'll use is the REPL. And we'll talk about the REPL here at the end here to tell you what it does. The next step would be to write the query. So first of all, let's try it out. So the first query that we want to run here is how many rows are there in each dataset? So for the 2 datasets, at least you shouldn't give us like what the number of the datasets are within each another. So basically telling us how many rows are in each of the 2 datasets that we just passed to it. So let's see what it tells us here. So It gave us the answer. So the first dataset, DataFrame 1, which is this complaints dataset, it's saying is 5.618602 rows. And then the second one is 569 rows. So the failed datasets data. So that's accurate, but I mean, it could be accurate, right? But we want to verify, right, that it's actually accurate. So the next step we want to do is let's use Pandas because we know Pandas, when you're using Python, you can write queries using Pandas to be able to generate or interact with your dataset. So in this case, we're just going to confirm the answer using Pandas. And we'll do here, what we'll do is just consumer data, complaint data, and this is the DataFrame. And then we're just asking for the shape of the dataset. And so this should return the number of rows. And then we do the same thing as well with failed banks. And our responses here should match this and this. If not, then we have an issue with the agent. So let's run that. And there you go. So we have consumer dataset, 5,618,602, and then 569,569. So that's correct. So it did that correctly. Now let's think of maybe another question that we want to ask it. So let's ask it to create some graphs from the datasets that are insightful. We're just kind of leaving all the decisions to it for the agent to decide. So generate a graph with insights from the dataset. Let's see how it does with that. So this is a very generic question. So one thing that I've also noticed when I was playing around with it, at one point it could answer a specific question, especially if it's dealing with 2 data frames. And then sometimes it could fail for just the exact same prompt, which I don't really get what, why that is. It does that. My assumption is probably the response from the LLM or the code generated by the LLM is what's causing the problem, but I could be wrong. And also that kind of shows that it's not for production really. And so it did generate in this case some datasets and you can see here the Python REPL AST and it passed in the query. And this is the dataset that it, I guess not the dataset, but more of like the code that they use to generate this specific chart. So we have the number of complaints by company. And so TransUnion Intermediate Holdings, Inc., and then Experian. So these are the 2 credit card companies and we're just literally showing the complaints. So it seems like TransUnion is getting a lot of complaints from consumers, which is very interesting. And then Experian is also getting some complaints, but not as much. So quite interesting for that one. And then for the banks closure by state. So we have Pennsylvania, Iowa, Kansas, California, New York. They all seem to be the same. They all have the same bank closures, maybe one each. Yeah, those are the number of bank closures. So that's each. And then for this number of complaints, at least 3 here for TransUnion and Experian. So yeah, good insights. Good insights. I think that's some good insights. So here it also explains, here are the generated graphs with insights from the datasets. This bar shows that Shows the number of consumer complaints received by each company. It highlights which companies have the most complaints. And then number of banks. This bar chart displays the number of bank closures in each state. It provides insights into which states have experienced the most bank closures. All right. So quite interesting, right? So this is pretty good. You can, you can tell like it does, it does okay. But one thing that I want to highlight is I don't think this is ready for production use cases, in my opinion. This is, and for so many reasons. First of all, It can execute any arbitrary code that can then pass to the REPL and do some damage. So if you're passing in any data to it, it's probably just data that you don't really care much for it, or especially for privacy concerns as well. But for fun and play, yeah, sure, go ahead and play. And I think this is the reason why I'm creating this video specifically. It's not really to tell you that this is for production. It's more for you to be aware of how to use our agent, for example, for this case. And this is one of many that I will create. So there's others like Havana AI that kind of interacts with your database, data warehouse to be able to generate insights from that. And that also complements with a vector database to be able to generate insights from documentation, DDL schemas, and also complement and then chat with your database and be able to retrieve that. So that's a whole different topic and I'll cover that in a separate video. That might be a little bit more closer to production use case that you want to use. But this is definitely not production use case. And Down here, let's go over at least one of the topics that is very interesting, right? You've mentioned, you've heard me probably mention REPL a lot. So what is REPL to begin with, right? It's a tool, it's an interactive programming environment that kind of takes single user input, which is the reading part of the REPL, which is the R. And so it reads in and then it executes that code that it's generated and then returns the output to the user. And then it waits for the next input to be as part of the loop. So that's what it means by read, eval, and then print loop. So in this case, let's look at an example of how that looks like. So let's open this up. And I created a simple class here, function that kind of does what the REPL is doing behind the scenes with as a tool for the create CSV agent from LangChain. So in this case, I have a class Python AST REPL. tool. And the Python AST module is the one that kind of takes in the code and decomposes that to be able to use that. And you'll see how it does down here as well. So I'm initializing here a DataFrame, which we then pass it to an execution environment. We just need to pass that DataFrame to the execution environment so that way it understands when it's getting the code and decomposes and writes that code to execute against the DataFrame, it knows what DataFrame it's executing against. And then the execute function here, We just use the try exception blocks here. And the first part here, we are parsing the code into an AST. And so the code here— and AST is a very huge topic. I don't want to even get into it. That's a whole topic by itself. But every programming language has that abstraction to it as well. So in this case, AST, we are parsing that. We've got the code that gets parsed into it and then we're executing it. And then compile AST into object, code object. So we're compiling that, we're getting the tree. And then the file name and then execution as well there. And so once we execute here into the environment that we already created up here for the DataFrame, and after that we capture the results under this results variable and we just return the result. And if there's an issue, it returns the exception there. And so to use it in an example, let me just run that. So in this case, to just see how that works, in this case, how it can run that and return the results. Here we're using a Pandas DataFrame and we're using the California house Housing test data. And that's a dataset that comes as part of the sample dataset. So this is the dataset itself. We'll just open it here so you can see how it looks like. So this is the dataset. It looks something like this. So quite a lot of values in there. I think 3,000 entries. So yeah, so that's 3,000 entries. So we're going to ask a question out of it. We're just going to pass in the code that we're going to pass into it as the results as well. And the DataFrame, we just want the shape of the DataFrame, which means we just want how many rows are in this dataset. Essentially, that's what we're asking with this code. And so it should print 3,000 because we know there's 3,000 rows. So let's see if it does. Yes, and it did. 3,000 rows. So it returned 3,000 rows. So if you think of it this way, you have the LLM, which you ask questions, the LLM looks at your query and decides, yeah, you want to get how many rows are in your DataFrame. So it already knows what the Pandas code looks like to get the number of rows within your DataFrame. So it then passes that over to the agent of which it's still part of it, and then passes that to the REPL now, which this is the REPL itself. And then the REPL decomposes that and executes it and returns the output. And so the output then gets passed into, well, let me back it up a little bit. So the LLM generates the code and then that code gets passed over to the REPL and the REPL runs that code. And that's exactly what it does here. So this runs the code that the LLM generated and then it generates a response. And then that response gets in turn gets passed over to the LLM to kind of craft that final response that looks like this in this case. And so this is kind of how it works. And so just wanted to run through this real quick to give a general overview of how this works. And if you wanna dig more into it, there's more examples or more documentation here. There's also using the zero-shot React description. If you don't wanna use OpenAI, Function calling, you can use the zero-shot, which you're just providing it some examples of what it looks like, and then the LLM is going to infer from those and generate a code that then is going to get passed to the REPL. And so there's definitely some experimentation there with that. I haven't personally tried it with Ollama. I, as you know, I like using Ollama a lot in these processes to, you know, free as much as you can. And so that's just a quick snippet of how that works. In the future videos that I'm planning to do, I'm planning on using Vana AI and using that to query datasets and chat with your dataset or your database in this case. And for database, it's much bigger, right? It can take so many different types of files. So you have things like Parquet files, you have Excel files, you have CSV files, you can name it. Just all the structured data can go into a database, which then you can chat with those documents that way. So there's definitely a lot of ways to deal with that, but I'll explore those in the next video as well. The other aspect of it as well that I wanted to talk about is, maybe you might be wondering right now, why would— maybe I could have just loaded this dataset to a vector database and then use RAG and just chat with it that way. Well, there's several reasons why I didn't do that to begin with. So vector databases are not really designed for structured data. And there's several reasons for that. And one of them being the data representation. So vector databases are more optimized for dimensional vector representations or of unstructured or semi-structured data. And they do excel at storing and querying embeddings, which are kind of like dense numerical representations of complex data like text, images, audio. And so structured data, on the other hand, are, you know, they're typically composed of well-defined fields with specific data types and relationships. And so vector databases won't perform so well in that regard, in my opinion. And then there's also the query mechanisms, you know, for vector databases, it uses things like similarity on, you know, on search operations, things like k-nearest neighbors, ANN searches. And so these operations, they're less relevant for like structured data, which would benefit from things like queries, matching queries, or, you know, there's joins as well if you're dealing with like several data frames or several tables. And then you have range queries for different ranges. So that, those are things that vector databases wouldn't be able to do in this case. And then as well, there's schema flexibility, analytical abilities, which are really more for databases, not more of like vector databases in this case. And one thing also that I wanted to make sure that I raised to you is that there's a lot of, definitely a lot of problems with this. You don't want to run this. And I think I touched a little bit on it. You don't want to do this in production because the LLM could run generate code that's malicious and it might not work so well for you. So I wouldn't push this for production. Use this for toy projects, experiment with it, extend it, and maybe you might be able to build it to a point where it gets to be production ready. But, you know, for now I wouldn't use it that way. It's good for experimentation and learning purposes. And that's why this video is for really for you to learn and look at some of the different ways of doing this. And so with that, as always, this code will be in my GitHub repo. I'll add it in there, play around with it. Let me know what you think about it. And I'll see you in the next video talking about more RAG and more LLM and AI. Bye-bye.
More from Tony

Summarize Videos and Research Companies with CrewAI + Gemini Models [Quickstart Guide]

How to Make Your AI Agents More Reliable with CrewAI Task Guardrails (Step-by-Step Tutorial)








