This project is an example Retrieval-Augmented Generation (RAG) application built with Haystack. It demonstrates how to create a functional search and generative question-answering system with a user-friendly interface.
The backend is built with FastAPI and Haystack 2. It provides a RAG pipeline using OpenAIGenerator.
The frontend is a React application leveraging Bootstrap. It offers an intuitive interface for users to interact with the RAG system, upload documents, and perform searches.
To quickly test this application, do the following:
-
Clone the repository:
git clone [email protected]:deepset-ai/haystack-rag-app.git
-
Switch to the project directory:
cd haystack-rag-app
-
Create a
.env
configuration file by copying the example:cp .env.example .env
Edit the .env file accordingly. Change the
OPENSEARCH_PASSWORD
. AddOPENAI_API_KEY
. OpenSearch host URL doesn't have to be changed if running this example with Docker Compose. -
Start the application using Docker Compose:
docker-compose up
(Or
docker-compose up -d
to run in detached mode.) -
Once all containers are up and running, use a browser to access the UI.
The above steps will set up and run all necessary components, including the backend services, frontend, OpenSearch, and the nginx proxy. After a minute or two you can start uploading documents and querying the system as described in the "How to Use" section below.
Note: It may take some time for all containers to start. Check container logs if you're having trouble. For instance, the backend containers should report the following when it's ready:
nginx-1 | 2024/10/24 13:30:18 [notice] 1#1: nginx/1.27.2
nginx-1 | 2024/10/24 13:30:18 [notice] 1#1: start worker processes
indexing_service-1 | INFO: 127.0.0.1:37836 - "GET /health HTTP/1.1" 200 OK
query_service-1 | INFO: 127.0.0.1:58698 - "GET /health HTTP/1.1" 200 OK
- Ensure that all containers are running and the backend is ready.
- Open your web browser and navigate to http://localhost:8080.
- Use the interface to upload documents that you want to search through. Uploaded files are stored in the
files/uploads
directory inside the backend containers (Docker Compose will mount a volume for this). Currently, the frontend code limits a one-time upload to 110MB. - Uploading large files may take a while since the files are indexed synchronously.
- Once documents are uploaded, you can ask questions and search the documents.
- The query backend service will use the RAG pipeline to process your query and return relevant results.
Note: This RAG application currently supports only PDF, TXT, and Markdown file formats.
When all containers (indexing service, query service, frontend, opensearch and nginx proxy) are running, the application architecture works as follows:
-
nginx proxy container: This container acts as a reverse proxy and is the entry point for all incoming requests. It listens on port
8080
and routes traffic based on the request URI:- Requests to
/
: These are routed to the frontend container, which serves the React UI application build. - Requests to
/api
: These are proxied to the backend services, which handle API requests.
- Requests to
-
frontend container: This container hosts the React UI application. It doesn't normally receive external requests but is accessed through the nginx proxy.
-
backend services containers: These containers run the indexing and query services and process API requests.
-
opensearch container: This container runs OpenSearch. It is accessed by the backend services. It is set up automatically and the index is created automatically by the indexing service.
This setup allows for a clean separation of concerns:
- The nginx proxy handles request routing.
- The frontend container focuses on serving static assets and the React UI build.
- The backend containers manage API requests, indexing and the RAG pipeline.
This architecture provides several benefits:
- Improved security by not exposing the backend directly.
- Simplified frontend/backend communication through a single domain.
- Easy scalability and potential for load balancing in the future.
You can access the application by navigating to http://localhost:8080 in your web browser. The nginx proxy will handle routing your requests based on the URL path.
If the containers are running on a remote server instead of your local machine, you can access the application via ssh port forwarding. Here's how:
-
Run
ssh
to the remote server where the containers are running, using the-L
option to set up port tunneling. For example:ssh ec2-user@haystack-lab -L 8080:localhost:8080
This command will open a local port
8080
that will be tunneled to the remote server and forwarded to the nginx proxy container running there. -
After establishing the ssh connection with port forwarding, you can access the application by pointing your browser to http://localhost:8080.
This method allows you to securely access the application running on the remote server as if it were running on your local machine.
In case you want to build and test the UI locally while the containers are running on a remote server, you can do the following:
-
Clone the repository:
git clone [email protected]:deepset-ai/haystack-rag-app.git
-
Switch to the frontend project directory:
cd haystack-rag-app/frontend
-
Install dependencies:
npm install
-
Run the development server:
export REACT_APP_HAYSTACK_API_URL=http://localhost:8080/api && \ npm start
This will start the frontend application on http://localhost:3000. With the active ssh port forwarding as described above, the UI will be able to connect to the backend running on the remote server.
This application can also be deployed to Kubernetes using the Helm charts provided in the charts/
directory. For detailed deployment instructions and configuration options, see charts/README.md.
Quick start with Helm:
# Edit required values in charts/values.yaml
cd charts && \
helm install hra . -f values.yaml
- This example application is based on Haystack 2.
- OpenSearch host URL in .env.example is configured by default to work with Docker Compose, there's no need to change it.
- OpenSearch password has to match a fairly strict criteria (capital letters, numbers, etc.). OpenSearch will complain along the lines of "a minimum 8 character password and must contain at least one uppercase letter, one lowercase letter, one digit, and one special character" if the password does not meet the requirements.
- Do not create the OpenSearch index manually.
- This project uses SentenceTransformersDocumentEmbedder and SentenceTransformersTextEmbedder to embed documents and the query. Change
USE_OPENAI_EMBEDDER
in the.env
file totrue
to use OpenAIDocumentEmbedder and OpenAITextEmbedder instead. Unless starting containers from scratch, delete the OpenSearch index before switching the embedders (vector dimensions will be different). - If your frontend is hosted on a different domain than the API, you need to add the frontend domain to the
allow_origins
list in backend/src/common/api_utils.py.
The following unauthenticated API routes are available via the nginx proxy:
POST /api/search
: Accepts a search query and returns results from the RAG pipeline.GET /api/files
: Returns a list of all uploaded files.POST /api/files
: Allows uploading of files to be indexed by the RAG pipeline.GET /api/health
: Returns a simple "OK" response to check if the nginx proxy is running.
While inside the ./haystack-rag-app directory:
(source .env && curl -X GET https://localhost:9200 -u "admin:$OPENSEARCH_PASSWORD" --insecure)
While inside the ./haystack-rag-app directory:
(source .env && curl -X GET https://localhost:9200/default/_search?pretty -u "admin:$OPENSEARCH_PASSWORD" --insecure)
While inside the ./haystack-rag-app directory:
(source .env && curl -X DELETE https://localhost:9200/default -u "admin:$OPENSEARCH_PASSWORD" --insecure)
curl -X POST http://localhost:8080/api/search -H "Content-Type: application/json" -d '{"query": "What is the capital of France?"}'
If you need to completely reset your Docker containers and images environment, you can do the following:
docker rm -f $(docker ps -aq) && \
docker rmi -f $(docker images -aq) && \
docker system prune -a --volumes && \
docker volume rm $(docker volume ls -q)
Careful! This will remove all containers, images, and volumes.