Skip to content

Commit

Permalink
New Freebie: Local-RAG
Browse files Browse the repository at this point in the history
  • Loading branch information
ctavolazzi committed Jun 22, 2024
1 parent 639dff7 commit e062879
Show file tree
Hide file tree
Showing 10 changed files with 611 additions and 0 deletions.
72 changes: 72 additions & 0 deletions freebies/local-rag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# RAG Chatbot Application

This is a Retrieval-Augmented Generation (RAG) chatbot application using Langchain, Ollama, and Gradio.

## Features

- Uses Ollama for local language model inference
- Implements RAG for more accurate and contextual responses
- Uses Chroma as a vector store for efficient similarity search
- Provides a user-friendly chat interface with Gradio

## Setup

1. Clone this repository:
```
git clone https://github.com/yourusername/rag-chatbot.git
cd rag-chatbot
```

2. Run the setup script:
```
python setup.py
```
This will download necessary files, install dependencies, and set up Ollama.

3. Activate the virtual environment:
- On Windows: `.\venv\Scripts\activate`
- On macOS/Linux: `source venv/bin/activate`

4. Run the application:
```
python app.py
```

5. Open your web browser and go to `http://localhost:7860` to interact with the chatbot.

## Configuration

You can configure the application by editing the `.env` file. Available options include:

- `OLLAMA_MODEL`: The Ollama model to use (default: "llama2:chat")
- `OLLAMA_URL`: The URL of the Ollama server (default: "http://localhost:11434")
- `INITIAL_DB`: Whether to initialize a new vector database (default: False)
- `DEBUG`: Enable debug logging (default: False)

## Adding New Documents

To add a new document to the existing knowledge base:

1. Place the new document in the `data/` directory.
2. Run the following Python code:

```python
from data_loader import add_new_document
from vector_store import get_vector_store, add_documents_to_store

new_chunks = add_new_document('path/to/your/new/document.txt')
vector_store = get_vector_store()
add_documents_to_store(vector_store, new_chunks)
```

## Troubleshooting

If you encounter any issues, please check the application logs or file an issue on the GitHub repository.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License.
52 changes: 52 additions & 0 deletions freebies/local-rag/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Main application script for the RAG Chatbot.
"""

import gradio as gr
import logging
from chatbot import chat_ollama
from data_loader import get_document_chunks
from vector_store import get_vector_store, VectorStoreError
from config import CONFIG

# Set up logging
logging.basicConfig(
level=logging.DEBUG if CONFIG['DEBUG'] else logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def main():
"""Initialize and run the RAG Chatbot application."""
try:
if CONFIG['INITIAL_DB']:
logger.info("Initializing vector database...")
document_chunks = get_document_chunks()
get_vector_store(document_chunks)
else:
logger.info("Loading existing vector database...")
get_vector_store()

gradio_interface = gr.ChatInterface(
chat_ollama,
chatbot=gr.Chatbot(),
textbox=gr.Textbox(placeholder="Example: Who is Alice?", container=False, scale=7),
title="The Ollama RAG Chatbot",
description=f"Ask the {CONFIG['OLLAMA_MODEL']} chatbot a question!",
theme='gradio/base',
retry_btn=None,
undo_btn="Delete Previous",
clear_btn="Clear",
)

logger.info("Starting Gradio interface...")
gradio_interface.launch()
except VectorStoreError as e:
logger.error(f"Vector store error: {str(e)}")
print(f"An error occurred with the vector store: {str(e)}")
except Exception as e:
logger.error(f"An unexpected error occurred: {str(e)}")
print(f"An unexpected error occurred: {str(e)}")

if __name__ == "__main__":
main()
78 changes: 78 additions & 0 deletions freebies/local-rag/chatbot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Core chatbot functionality for the RAG Chatbot application.
"""

import logging
from llm import get_ollama, OllamaError
from vector_store import get_vector_store, VectorStoreError

logger = logging.getLogger(__name__)

MAX_HISTORY_LENGTH = 5 # Adjust as needed

def validate_input(message):
"""
Validate the user input.
Args:
message (str): The user's input message.
Returns:
bool: True if input is valid, False otherwise.
"""
if not message or not isinstance(message, str):
return False
if len(message.strip()) == 0:
return False
# Add more validation rules as needed
return True

def chat_ollama(message, history):
"""
Generate a response using the Ollama model and RAG.
Args:
message (str): The user's input message.
history (list): The chat history.
Returns:
str: The generated response.
"""
if not validate_input(message):
return "I'm sorry, but I couldn't understand your input. Could you please rephrase your question?"

try:
ollama = get_ollama()
chroma_db = get_vector_store()

result_chunks = chroma_db.similarity_search(message)

chroma_knowledge = ""
sources = ""
for idx, chunk in enumerate(result_chunks, 1):
chroma_knowledge += f"[{idx}]\n{chunk.page_content}\n"
sources += f"[{idx}]\n{chunk.metadata['source']}\n"

# Limit the history to the last MAX_HISTORY_LENGTH exchanges
limited_history = history[-MAX_HISTORY_LENGTH:]
history_str = "\n".join([f"Human: {h[0]}\nAI: {h[1]}" for h in limited_history])

prompt = f"""Answer the following question using the provided knowledge and the chat history:
###KNOWLEDGE: {chroma_knowledge}
###CHAT-HISTORY: {history_str}
###QUESTION: {message}"""

result = ollama(prompt)
return f"{result}\n\n\nReferences:\n{sources}"
except VectorStoreError as e:
logger.error(f"Vector store error in chat_ollama: {str(e)}")
return "I'm sorry, but I'm having trouble accessing my knowledge base right now."
except OllamaError as e:
logger.error(f"Ollama error in chat_ollama: {str(e)}")
return "I'm sorry, but I'm having trouble generating a response right now."
except Exception as e:
logger.error(f"Unexpected error in chat_ollama: {str(e)}")
return "I apologize, but I'm experiencing an unexpected issue. Please try again later."
36 changes: 36 additions & 0 deletions freebies/local-rag/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Configuration settings for the RAG Chatbot application.
"""

import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

CONFIG = {
# Paths
'BASE_DIR': os.path.dirname(os.path.abspath(__file__)),
'DATA_PATH': os.getenv('DATA_PATH', os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")),
'CHROMA_PATH': os.getenv('CHROMA_PATH', os.path.join(os.path.dirname(os.path.abspath(__file__)), "chroma")),

# Ollama settings
'OLLAMA_MODEL': os.getenv('OLLAMA_MODEL', "llama2:chat"),
'OLLAMA_URL': os.getenv('OLLAMA_URL', "http://localhost:11434"),

# Text splitting settings
'HEADERS_TO_SPLIT_ON': [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
("####", "Header 4"),
],
'CHUNK_SIZE': int(os.getenv('CHUNK_SIZE', 500)),
'CHUNK_OVERLAP': int(os.getenv('CHUNK_OVERLAP', 100)),

# Vector DB settings
'INITIAL_DB': os.getenv('INITIAL_DB', 'False').lower() == 'true',

# Application settings
'DEBUG': os.getenv('DEBUG', 'False').lower() == 'true',
}
66 changes: 66 additions & 0 deletions freebies/local-rag/data_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
Module for loading and preprocessing documents for the RAG Chatbot.
"""

import os
import logging
from langchain.document_loaders import TextLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
from config import CONFIG

logger = logging.getLogger(__name__)

def load_documents():
"""Load documents from the data directory."""
documents = []
for file in os.listdir(CONFIG['DATA_PATH']):
try:
loader = TextLoader(os.path.join(CONFIG['DATA_PATH'], file))
documents.append(loader.load()[0])
logger.info(f"Loaded document: {file}")
except Exception as e:
logger.error(f"Error loading document {file}: {str(e)}")
return documents

def split_documents(documents):
"""Split documents into chunks."""
try:
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=CONFIG['HEADERS_TO_SPLIT_ON'], strip_headers=False)
chunks_array = []

for doc in documents:
md_chunks = md_splitter.split_text(doc.page_content)
for chunk in md_chunks:
chunk.metadata.update(doc.metadata)

txt_splitter = RecursiveCharacterTextSplitter(
chunk_size=CONFIG['CHUNK_SIZE'],
chunk_overlap=CONFIG['CHUNK_OVERLAP'],
length_function=len,
add_start_index=True
)
txt_chunks = txt_splitter.split_documents(md_chunks)
chunks_array.extend(txt_chunks)

logger.info(f"Split documents into {len(chunks_array)} chunks")
return chunks_array
except Exception as e:
logger.error(f"Error splitting documents: {str(e)}")
raise

def get_document_chunks():
"""Load and split documents into chunks."""
documents = load_documents()
return split_documents(documents)

def add_new_document(file_path):
"""Add a new document to the existing database."""
try:
loader = TextLoader(file_path)
new_doc = loader.load()[0]
chunks = split_documents([new_doc])
logger.info(f"Added new document: {file_path}")
return chunks
except Exception as e:
logger.error(f"Error adding new document {file_path}: {str(e)}")
raise
24 changes: 24 additions & 0 deletions freebies/local-rag/llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Module for interacting with the Ollama language model.
"""

import logging
from langchain_community.llms import Ollama
from config import CONFIG

logger = logging.getLogger(__name__)

class OllamaError(Exception):
"""Custom exception for Ollama-related errors."""
pass

def get_ollama():
"""Initialize and return an Ollama instance."""
try:
ollama = Ollama(base_url=CONFIG['OLLAMA_URL'], model=CONFIG['OLLAMA_MODEL'])
# Test the connection
ollama("Test")
return ollama
except Exception as e:
logger.error(f"Error initializing Ollama: {str(e)}")
raise OllamaError(f"Failed to initialize Ollama: {str(e)}")
8 changes: 8 additions & 0 deletions freebies/local-rag/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
langchain==0.0.351
langchain-community==0.0.13
chromadb==0.4.22
pdfplumber==0.10.3
gradio==4.14.0
ollama==0.1.6
python-dotenv==1.0.0
requests==2.31.0
Loading

0 comments on commit e062879

Please sign in to comment.