localRAG/local_rag.py
Philipp Mock 3fd6b8dd0d - added three iwm articles as test data
- added simple webview and FastAPI server
2026-02-03 10:57:05 +01:00

196 lines
6.6 KiB
Python

"""
Local RAG setup with LangChain, Ollama, and FAISS
Minimal dependencies, simple code
"""
import os
from pathlib import Path
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_ollama import ChatOllama
class LocalRAG:
def __init__(self, vectorstore_path="./vectorstore", ollama_model="mistral:7b"):
"""Initialize local RAG system"""
self.vectorstore_path = vectorstore_path
self.ollama_model = ollama_model
# Embeddings
print("Loading embeddings model...")
self.embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
# Text splitter
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
# Ollama LLM
print(f"Connecting to Ollama (model: {ollama_model})...")
self.llm = ChatOllama(
model=ollama_model,
base_url="http://localhost:11434"
)
# Vector store (load if exists, otherwise None)
self.vectorstore = None
self._load_vectorstore()
def _load_vectorstore(self):
"""Load existing vector store if available"""
index_file = os.path.join(self.vectorstore_path, "index.faiss")
if os.path.exists(index_file):
try:
self.vectorstore = FAISS.load_local(
self.vectorstore_path,
self.embeddings,
allow_dangerous_deserialization=True
)
print(f"Loaded existing vector store from {self.vectorstore_path}")
except Exception as e:
print(f"Could not load vector store: {e}")
self.vectorstore = None
def add_documents(self, file_paths):
"""Add documents to the vector store"""
print(f"\nLoading {len(file_paths)} document(s)...")
all_docs = []
for file_path in file_paths:
path = Path(file_path)
if not path.exists():
print(f"Warning: {file_path} not found, skipping")
continue
# Load document
if path.suffix.lower() == '.pdf':
loader = PyPDFLoader(str(path))
elif path.suffix.lower() in ['.txt', '.md']:
loader = TextLoader(str(path))
else:
print(f"Warning: Unsupported file type {path.suffix}, skipping")
continue
docs = loader.load()
chunks = self.text_splitter.split_documents(docs)
all_docs.extend(chunks)
print(f" - {path.name}: {len(chunks)} chunks")
if not all_docs:
print("No documents loaded!")
return
# Create or update vector store
print(f"\nCreating embeddings for {len(all_docs)} chunks...")
if self.vectorstore is None:
self.vectorstore = FAISS.from_documents(all_docs, self.embeddings)
else:
new_store = FAISS.from_documents(all_docs, self.embeddings)
self.vectorstore.merge_from(new_store)
# Save
os.makedirs(self.vectorstore_path, exist_ok=True)
self.vectorstore.save_local(self.vectorstore_path)
print(f"Vector store saved to {self.vectorstore_path}")
def list_documents(self):
"""List all documents in the vector store"""
if self.vectorstore is None:
print("No documents in vector store.")
return []
# Get all documents from the vector store
# We'll retrieve a large number to get all documents
all_docs = self.vectorstore.similarity_search("", k=10000) # Large k to get all
# Extract unique document sources from metadata
documents = {}
for doc in all_docs:
source = doc.metadata.get('source', 'Unknown')
if source not in documents:
documents[source] = {
'source': source,
'chunks': 0,
'page': doc.metadata.get('page', None)
}
documents[source]['chunks'] += 1
# Convert to list and sort
doc_list = list(documents.values())
doc_list.sort(key=lambda x: x['source'])
print(f"\nDocuments in vector store ({len(doc_list)} unique documents):")
print("-" * 60)
for doc_info in doc_list:
print(f" - {doc_info['source']}")
print(f" Chunks: {doc_info['chunks']}")
if doc_info['page'] is not None:
print(f" Page: {doc_info['page']}")
return doc_list
def query(self, question, k=4):
"""Query the RAG system"""
if self.vectorstore is None:
return "Error: No documents loaded. Please add documents first."
print(f"\nSearching for relevant documents...")
docs = self.vectorstore.similarity_search(question, k=k)
print(f"Found {len(docs)} relevant documents")
# Combine context from documents
context = "\n\n".join([doc.page_content for doc in docs])
# Create prompt
prompt = f"""Use the following context to answer the question.
If you don't know the answer, say that you don't know instead of making up an answer.
Context:
{context}
Question: {question}
Answer:"""
print("Generating answer with Ollama...")
response = self.llm.invoke(prompt)
answer = response.content if hasattr(response, 'content') else str(response)
return answer
def main():
"""Example usage"""
print("=" * 60)
print("Local RAG with LangChain, Ollama, and FAISS")
print("=" * 60)
# Initialize
rag = LocalRAG(ollama_model="mistral:7b")
# Add documents (uncomment and add your file paths)
# rag.add_documents([
# "data/dok1.pdf",
# "data/dok2.pdf",
# "data/dok3.pdf"
# ])
# List documents
rag.list_documents()
# Query
question = "What do the documents say about modality for perceived message perception?"
answer = rag.query(question)
print(f"\nQuestion: {question}")
print(f"Answer: {answer}")
# print("\nSetup complete! Uncomment the code above to add documents and query.")
if __name__ == "__main__":
main()