LangChain RAG with React, FastAPI, Cosmos DB Vectors: Part 3

In Part 3, we focus on crafting a dynamic React web interface, the final step in our series. This installment marks the conclusion of our three-part journey, where we’ve achieved significant milestones, from loading vectors into Azure Cosmos DB for MongoDB vCore to developing our React UI.

In this multi-part series, we embarked on a journey to harness the power of Azure Cosmos DB for MongoDB vCore. We set up our database and loaded our vector database in Part 1. The next phase, documented in Part 2, involved leveraging LangChain and Python FastAPI to craft a RAG (Retrieval-Augmented Generation) interface, empowering us to query and interact with our data in an intuitive manner. Now, as we delve into the culmination of our series in Part 3, we shift our focus to the creation of a sleek and dynamic React web user interface. This final installment brings together the critical components of our project, providing a user-friendly gateway to our AI.

Series Diagram - LangChain RAG with React, FastAPI, Cosmos DB for MongoDB vCore Vector Store
Series Diagram – LangChain RAG with React, FastAPI, Cosmos DB for MongoDB vCore Vector Store

In this article

Prerequisites

Install Node.js

To run the React web application, Node.js is required. This project has been verified with the latest Long Term Support (LTS) version 20.11.1 at the time of this publication. Download and install Node.js to follow along with the code in this article. Head over to the Node.js download page and select ‘Previous Releases’.

Node.js Download page with 'Previous Releases' highlighted
Node.js Download Page

Select ‘Releases’ for v20.11.1.

Node.js ‘Previous Releases’ Download Page

For the installation on Microsoft Windows, you have the option to download the MSI for a simplified setup.

Node.js v20.11.1 Download Page

During installation, make sure to install the Node Package Manager (NPM) as it is essential for this process. Also, ensure that Node.js is accessible from the operating system PATH, ‘Add to PATH’.

Node.js Setup Windows MSI
Node.js setup for Windows MSI

Validate the installation by executing the following command in a terminal or command prompt:

node --version 
v20.11.1

Download the Project

You can find the code for the project on my GitHub.

Github Logo

Clone the project from GitHub 

Launch FastAPI from Part 2

We require the FastAPI service to be operational for the web interface to establish a connection. As detailed in Part 2, execute the provided commands within the demo_api directory to launch the server.

venv\Scripts\activate
python main.py
Python FasterAPI Swagger default page
Python FasterAPI Swagger Docs (localhost)

LangChain RAG with React Web User Interface

With the code downloaded and Node.js installed, it is necessary to proceed by installing the dependencies before testing out the React interface.

Run the following command from the demo_web directory to perform a clean install of project dependencies, this may take some time.

npm ci
added 1599 packages, and audited 1600 packages in 7m

Your results from executing this command may differ from mine, depending on your environment and the timing of the code execution (i.e. the number of packages and vulnerability issues).

Next, it is necessary to create a file named ‘.env’ within the demo_web directory in order to store the environment variables.

REACT_APP_API_HOST=http://127.0.0.1:8000
Environment VariableDescription
REACT_APP_API_HOSTUrl to our FastAPI server. Default to our local machine: http://127.0.0.1:8000
.env file variables

Now, we have the ability to execute the following command from the demo_web directory to initiate the React web user interface.

npm start

Here is a brief demonstration video showcasing the React web user interface.

Research Helper – React web user interface

Walkthrough of the React Project

The React project does not entail as much complexity as the initial two segments in this series. The first article dealt with the setup of Azure CosmosDB for MongoDB vector search, while the second article, being the heart of the project, focused on LangChain and Python FastAPI.

With regards to this project, the ‘Main.js’ file serves as the central module or user landing page, while the specialized ‘Search.js’ file is responsible for managing the Q&A functionality. In order to enhance file readability and ease of maintenance, the search functionality has been segmented into separate sections.

React components diagram for LangChain RAG with React, Python FastAPI, Cosmos DB Vectors: Part 3
React Component Diagram

Main

Main serves as the crucial central component within the structure of the React application, being the designated point of entry for routing by the application.

import {React, Component } from 'react'
import Search from './Search/Search'
import './Main.css'

class App extends Component {
constructor() {
super()
this.state = { question: '', searchResults: [] }
}

render() {
return (
<div className="Main">
<div className="Main-Header">
<img src="/logoresearch.png" alt="LangChain RAG with React Demo Logo" height={'95%'} />
</div>
<div className="Main-Body">
<div className="Main-Content">
<Search />
</div>
</div>
<div className="Main-Footer">
<b>Disclaimer: Sample Application</b>
<br />
Please note that this sample application is provided for demonstration
purposes only and should not be used in production environments
without proper validation and testing.
</div>
</div>
)
}
}

export default App

The Main component centrally manages the application, initializing state variables for the API query and search results. It generates JSX code in the render function to define the main page layout, including elements like the rocket logo, a section containing the search component (details to follow), and a footer with a sample disclaimer about the application’s nature.

The Search component serves as the primary interface for interacting with the FastAPI RAG Q&A endpoints and associated React functions.

import React, { useState } from 'react'
import { Button, Box, Stack, TextField } from '@mui/material'
import CircularProgress from '@mui/material/CircularProgress'

import SendIcon from '@mui/icons-material/Send'
import SearchResults from './SearchResults'
import SearchAnswer from './SearchAnswer'

export default function Search() {
const [question, setQuestion] = useState('What is supersonic combustion')
const [results, setResults] = useState('')
const [isLoading, setIsLoading] = useState(false)

const handleSerchByQuestion = () => {
setIsLoading(true)
setResults('')
fetch(process.env.REACT_APP_API_HOST + '/search/qa/' + question)
.then((response) => response.json())
.then((res) => {
setResults(res)
setIsLoading(false)
})
}

return (
<Stack direction="column" spacing={2}>
<Stack direction="row" spacing={0}>
<TextField
sx={{ width: '80%' }}
variant="outlined"
label="question"
helperText="Ask a question of your data"
defaultValue="What is supersonic combustion"
value={question}
onChange={(event) => setQuestion(event.target.value)}
></TextField>
<Button
variant="contained"
endIcon={<SendIcon />}
onClick={handleSerchByQuestion}
sx={{ mb: 3, ml: 3, mt: 1 }}
>
Submit
</Button>
</Stack>
{isLoading === true && (
<Box
sx={{
width: '100%',
alignContent: 'center',
alignItems: 'center',
textAlign: 'center',
}}
>
<CircularProgress size={80} />
</Box>
)}

{results !== '' && (
<Stack direction="column" spacing={2}>
<SearchAnswer resultText={results['text']} />
</Stack>
)}

{results !== '' && (
<SearchResults searchResults={results['ResourceCollection']} />
)}
</Stack>
)
}

The component Search allows users to input a question, submit it, and then sends the results to the next search components. It utilizes state hooks (useState) to manage the question, search results, and loading status. When the user clicks the submit button, a function called handleSearchByQuestion is triggered. This function sets the loading state to true, clears any previous results, makes an API call to search for the question, and updates the state with the received results. SearchAnswer is responsible for handling the text answer from the API, while SearchResults handles the retrieved documents (ResourceCollection).

Search Answer

The SearchAnswer component is compact, as it presently solely presents the response (answer test) provided by the LangChain RAG FastAPI endpoint.

import React from 'react'

import { Box, Paper, Stack, Typography } from '@mui/material'

export default function SearchAnswer(resultText) {
return (
<Paper sx={{ p: 2 }}>
<Stack direction="column" spacing={2} useFlexGap flexWrap="wrap">
<Typography
variant="subtitle1"
sx={{ color: 'grey', fontSize: '12pt' }}
>
Answer:
</Typography>
<Box
sx={{
border: 1,
borderColor: 'lightgray',
borderRadius: 3,
p: 1,
fontSize: 14,
}}
>
{resultText.resultText}
</Box>
</Stack>
</Paper>
)
}

The SearchAnswer component presents the search result within a Paper element, providing padding to distinguish it from the background. The displayed result text is obtained from the resultText property, which is populated with the response from the API.

Search Results

The Search Results component will display the retrieved documents from the Azure Cosmos DB for Mongo DB vector search, arranged in order of relevance. Furthermore, it will allow users to access the text content of the document and view the original document stored on an Azure Storage Account from Part 1.

import React, { useState } from 'react'

import { IconButton, Box, Paper, Typography } from '@mui/material'

import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from '@mui/material'

import {
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from '@mui/material'

import NotesTwoToneIcon from '@mui/icons-material/NotesTwoTone'
import CancelIcon from '@mui/icons-material/Cancel'

import ImageTwoToneIcon from '@mui/icons-material/ImageTwoTone'

import './SearchResults.css'

export default function SearchResults(searchResults) {
const [content, setContent] = useState('')
const [imageIdenfidier, setImageIdentifier] = useState('')
const [imageData, setImageData] = useState('')

const retrieve_image_data = (image_id) => {
if (image_id) {
fetch(process.env.REACT_APP_API_HOST + '/content/image/' + image_id)
.then((response) => response.text())
.then((body) => {
return setImageData(body)
})
}
}

return (
<Box>
<Dialog open={imageIdenfidier !== ''} sx={{ m: '0px', p: '0px' }}>
<DialogActions sx={{ m: '0px', p: '0px' }}>
<IconButton>
<CancelIcon
onClick={() => {
setImageIdentifier('')
}}
/>
</IconButton>
</DialogActions>
<DialogContent sx={{ m: '0px', p: '0px' }}>
<DialogContentText sx={{ m: '0px', p: '0px' }}>
{retrieve_image_data(imageIdenfidier)}
<img src={imageData} alt="" width={'600px'} />
</DialogContentText>
</DialogContent>
</Dialog>
<Dialog open={content !== ''} sx={{ m: '0px', p: '0px' }} scroll="paper">
<DialogTitle>Resource Content</DialogTitle>
<IconButton
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CancelIcon
onClick={() => {
setContent('')
}}
/>
</IconButton>
<DialogContent dividers>
<DialogContentText>{content}</DialogContentText>
</DialogContent>
</Dialog>
<Typography variant="subtitle1" sx={{ color: 'grey', fontSize: '10pt' }}>
Document used to answer question:
</Typography>
<div className="Search-Results">
<TableContainer component={Paper}>
<Table sx={{ minWidth: '100%' }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Source</TableCell>
<TableCell align="center">Content</TableCell>
<TableCell align="center">View Page</TableCell>
</TableRow>
</TableHead>
<TableBody>
{searchResults.searchResults.map((row, index) => (
<TableRow
key={row.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.title}
</TableCell>
<TableCell component="th" scope="row">
{row.source}
</TableCell>
<TableCell align="center" component="th" scope="row">
<IconButton
onClick={() => {
setContent(row.content)
}}
>
<NotesTwoToneIcon />
</IconButton>
</TableCell>
<TableCell align="center" component="th" scope="row">
<IconButton
onClick={() => {
setImageIdentifier(row.resource_id + '/' + row.page_id)
}}
>
<ImageTwoToneIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</Box>
)
}

SearchResults is designed to facilitate the viewing and interaction with retrieved documents. Retrieved documents are presented in a ‘scrollable’ table format. For enhanced user experience, dialog boxes are employed to showcase content and images upon clicking the respective icons associated with each returned document. Furthermore, the function retrieve_image_data is implemented to procure image data from our Azure Storage Container for the selected document.

The series on LangChain RAG with React, FastAPI, and Cosmos DB has been a remarkable journey. This marks the conclusion of the three-part series, where we have accomplished significant milestones. These include loading vectors into Azure Cosmos DB for MongoDB vCore, uploading blobs into an Azure Storage Account, setting up our LangChain RAG interface with FastAPI, Python, and OpenAI, and ultimately developing our React web user interface. As a next step, you can enhance your LLM performance with Azure Cosmos DB semantic cache. Check out the article on how to do this: Improve LLM Performance Using Semantic Cache with Cosmos DB

Leave a Reply

Discover more from Stochastic Coder

Subscribe now to keep reading and get access to the full archive.

Continue reading