DEV Community

Emmanuel Onwuegbusi
Emmanuel Onwuegbusi

Posted on

Build a chatbot to interact with your Pandas DataFrame using Reflex

This article will guide you in creating a chatbot that allows you to upload a CSV dataset. You can then ask questions about the data, and the system, powered by a language model, will provide answers based on the uploaded CSV data.

The following is a sample of the chatbot:
chatbot1

We will use Reflex to build this chatbot.

Outline

  • Get an OpenAI API Key
  • Create a new folder, open it with a code editor
  • Create a virtual environment and activate
  • Install requirements
  • reflex setup
  • my_dataframe_chatbot.py
  • state.py
  • style.py
  • .gitignore
  • run app
  • conclusion

Get an OpenAI API Key

First, get your own OpenAI API key:

  • Go to https://platform.openai.com/account/api-keys.
  • Click on the + Create new secret key button.
  • Enter an identifier name (optional) and click on the Create secret key button.
  • Copy the API key to be used in this tutorial

openai key creation process

Create a new folder, open it with a code editor

Create a new folder and name it my_dataframe_chatbot then open it with a code editor like VS Code.

Create a virtual environment and activate

Open the terminal. Use the following command to create a virtual environment .venv and activate it:

 python3 -m venv .venv 
Enter fullscreen mode Exit fullscreen mode
 source .venv/bin/activate 
Enter fullscreen mode Exit fullscreen mode

Install requirements

We will need to install reflex to build the app, pandas to read the CSV file, and also openai langchain langchain-experimental to initialize an agent to generate answers to a user's questions of an uploaded CSV file.
Run the following command in the terminal:

 pip install reflex==0.3.1 pandas==2.1.1 openai==0.28.1 langchain==0.0.326 langchain-experimental==0.0.36 
Enter fullscreen mode Exit fullscreen mode

reflex setup

Now, we need to create the project using reflex. Run the following command to initialize the template app in my_dataframe_chatbot directory.

 reflex init --template blank 
Enter fullscreen mode Exit fullscreen mode

The above command will create the following file structure in my_dataframe_chatbot directory:

dataframefilestructure

You can run the app using the following command in your terminal to see a welcome page when you go to http://localhost:3000/ in your browser

 reflex run 
Enter fullscreen mode Exit fullscreen mode

my_dataframe_chatbot.py

We need to build the structure and interface of the app and add components. Go to the my_dataframe_chatbot subdirectory and open the my_dataframe_chatbot.py file. This is where we will add components to build the structure and interface of the app. Add the following code to it:

 import reflex as rx from my_dataframe_chatbot import style from my_dataframe_chatbot.state import State def error_text() -> rx.Component: """return a text component to show error.""" return rx.text(State.error_texts, text_align="center", font_weight="bold", color="red",) def head_text() -> rx.Component: """The header: return a text, text, divider""" return rx.vstack( rx.text("Chat with your data", font_size="2em", text_align="center", font_weight="bold", color="white",), rx.text("(Note: input your openai api key, upload your csv file then click submit to start chat)", text_align="center", color="white",), rx.divider(border_color="white"), ) def openai_key_input() -> rx.Component: """return a password component""" return rx.password( value=State.openai_api_key, placeholder="Enter your openai key", on_change=State.set_openai_api_key, style=style.openai_input_style, ) color = "rgb(107,99,246)" def upload_csv(): """The upload component.""" return rx.vstack( rx.upload( rx.vstack( rx.button( "Select File", color=color, bg="white", border=f"1px solid {color}", ), rx.text( "Drag and drop files here or click to select files" ), ), multiple=False, accept = { "text/csv": [".csv"], # CSV format }, max_files=1, border=f"1px dotted {color}", padding="2em", ), rx.hstack(rx.foreach(rx.selected_files, rx.text)), rx.button( "Submit to start chat", on_click=lambda: State.handle_upload( rx.upload_files() ), ), padding="2em", ) def confirm_upload() -> rx.Component: """text component to show upload confirmation.""" return rx.text(State.upload_confirmation, text_align="center", font_weight="bold", color="green",) def qa(question: str, answer: str) -> rx.Component: """return the chat component.""" return rx.box( rx.box( rx.text(question, text_align="right", color="black"), style=style.question_style, ), rx.box( rx.text(answer, text_align="left", color="black"), style=style.answer_style, ), margin_y="1em", ) def chat() -> rx.Component: """iterate over chat_history.""" return rx.box( rx.foreach( State.chat_history, lambda messages: qa(messages[0], messages[1]), ) ) def loading_skeleton() -> rx.Component: """return the skeleton component.""" return rx.container( rx.skeleton_circle( size="30px", is_loaded=State.is_loaded_skeleton, speed=1.5, text_align="center", ), display="flex", justify_content="center", align_items="center", ) def action_bar() -> rx.Component: """return the chat input and ask button.""" return rx.hstack( rx.input( value=State.question, placeholder="Ask a question about your data", on_change=State.set_question, style=style.input_style, ), rx.button( "Ask", on_click=State.answer, style=style.button_style, ),margin_top="3rem", ) def index() -> rx.Component: return rx.container( error_text(), head_text(), openai_key_input(), upload_csv(), confirm_upload(), chat(), loading_skeleton(), action_bar(), ) app = rx.App() app.add_page(index) app.compile() 
Enter fullscreen mode Exit fullscreen mode

The above code will render the text heading, an input field to enter your openai api key, a component to upload your CSV file, the chat component, and a component to input your questions to get answers.

state.py

Create a new file state.py in the my_dataframe_chatbot subdirectory and add the following code:

 # import reflex import reflex as rx from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent from langchain.chat_models import ChatOpenAI from langchain.agents.agent_types import AgentType import pandas as pd import os class State(rx.State): # The current question being asked. question: str error_texts: str # Keep track of the chat history as a list of (question, answer) tuples. chat_history: list[tuple[str, str]] openai_api_key: str # The files to show. csv_file: list[str] upload_confirmation: str = "" file_path: str is_loaded_skeleton: bool = True async def handle_upload( self, files: list[rx.UploadFile] ): """Handle the upload of file(s). Args: files: The uploaded files. """ for file in files: upload_data = await file.read() outfile = rx.get_asset_path(file.filename) self.file_path = outfile # Save the file. with open(outfile, "wb") as file_object: file_object.write(upload_data) # Update the csv_file var. self.csv_file.append(file.filename) self.upload_confirmation = "csv file uploaded successfully, you can now interact with your data" def answer(self): # turn loading state of the skeleton component to False self.is_loaded_skeleton = False yield # check if openai_api_key is empty to return an error if self.openai_api_key == "": self.error_texts = "enter your openai api" return # check if csv_file is empty to return an error if not self.csv_file: self.error_texts = "ensure you upload a csv file and enter your openai api key" return if os.path.exists(self.file_path): df = pd.read_csv(self.file_path) else: self.error_texts = "ensure you upload a csv file" return # initializes an agent for working with a chatbot and integrates it with a Pandas DataFrame agent = create_pandas_dataframe_agent( ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613", openai_api_key=self.openai_api_key), df, verbose=True, agent_type=AgentType.OPENAI_FUNCTIONS, ) self.upload_confirmation = "" # Add to the answer as the chatbot responds. answer = "" self.chat_history.append((self.question, answer)) yield # run the agent against a question output = agent.run(self.question) self.is_loaded_skeleton = True # Clear the question input. self.question = "" # Yield here to clear the frontend input before continuing. yield # update answer from output for item in output: answer += item self.chat_history[-1] = ( self.chat_history[-1][0], answer, ) yield 
Enter fullscreen mode Exit fullscreen mode

The above code handles the upload of files, it takes in questions and generates answers.

The handle_upload function manages the asynchronous upload of file(s) provided as a list of rx.UploadFile objects. It reads the uploaded data, specifies an output file path outfile, and saves the uploaded file. Additionally, it updates self.csv_file with the uploaded file's name and provides a confirmation message to self.upload_confirmation to indicate the successful upload of a CSV file.

The answer function interacts with OpenAI's GPT-3.5 Turbo model. It first sets loading state indicators and performs error checks, ensuring that the OpenAI API key is provided and a CSV file is uploaded. If the CSV file exists, it reads the data into a Pandas DataFrame df. The function initializes a chatbot agent and runs it, updating the conversation history as responses are received.

style.py

Create a new file style.py in the my_dataframe_chatbot subdirectory and add the following code. This will add styling to the page and components:

 shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" chat_margin = "20%" message_style = dict( padding="1em", border_radius="5px", margin_y="0.5em", box_shadow=shadow, ) # Set specific styles for questions and answers. question_style = message_style | dict( bg="#F5EFFE", margin_left=chat_margin ) answer_style = message_style | dict( bg="#DEEAFD", margin_right=chat_margin ) # Styles for the action bar. input_style = dict( border_width="1px", padding="1em", box_shadow=shadow ) button_style = dict(box_shadow=shadow) # style for openai input openai_input_style = { "color": "white", "margin-top": "3rem", "margin-bottom": "0.5rem", } 
Enter fullscreen mode Exit fullscreen mode

.gitignore

You can add the .venv directory to the .gitignore file to get the following:

 *.db *.py[cod] .web __pycache__/ .venv/ 
Enter fullscreen mode Exit fullscreen mode

Run app

Run the following in the terminal to start the app:

 reflex run 
Enter fullscreen mode Exit fullscreen mode

You should see an interface as follows when you go to http://localhost:3000/

chatbotimage2

First, you can enter your OpenAI API key. Then, upload a CSV file. Afterward, you can inquire with the chatbot about your dataset, and it will provide responses.

I tested the app with a CSV file that also contains an age column and I have the following chat. The chatbot produced correct responses to the question I asked:

chatbotresponses

Conclusion

You can get the code here: https://github.com/emmakodes/my_dataframe_chatbot

Top comments (3)

Collapse
 
felixdev9 profile image
Felix

Reflex is a nice application.

Collapse
 
emmakodes_ profile image
Emmanuel Onwuegbusi

Very True

Collapse
 
artydev profile image
artydev

Great, Thank you