Spaces:
Runtime error
Runtime error
harivarshannn commited on
Commit ·
1fb1192
1
Parent(s): 2be24d4
Add LangChain RAG pipeline for custom PDF advisory with LFS tracking
Browse files- .gitattributes +1 -0
- 1_agro.pdf +3 -0
- 2-agro.pdf +3 -0
- 3-agrogpt.pdf +3 -0
- 4-agrogpt.pdf +3 -0
- app.py +14 -3
- rag_engine.py +103 -0
- requirements.txt +30 -0
.gitattributes
CHANGED
|
@@ -1,2 +1,3 @@
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.pdf filter=lfs diff=lfs merge=lfs -text
|
1_agro.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe196417aa478f1807235c513e6e9c4f23a3c52ecc7fcc4dc42703b7de53b2a0
|
| 3 |
+
size 8617145
|
2-agro.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e674cd3b6a214696d24d6876892cee6ffab3ae7e9499017f573c25690e1b901e
|
| 3 |
+
size 17831258
|
3-agrogpt.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:adbb66a8efc7bc24cb6b4afc99782519cf41d8b1ca0414c486db6627d7bc5c7f
|
| 3 |
+
size 4316300
|
4-agrogpt.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2e5c45b5cefa9eb34f7abba0e72e91ea20feba58e67bfbd0f0706f980d0df48d
|
| 3 |
+
size 6330107
|
app.py
CHANGED
|
@@ -18,6 +18,7 @@ import requests
|
|
| 18 |
# Import the new model functionality
|
| 19 |
from model import check_ollama_connection, generate_with_ollama, analyze_image_for_disease
|
| 20 |
from weather import get_current_weather, TN_DISTRICTS
|
|
|
|
| 21 |
|
| 22 |
app = Flask(__name__)
|
| 23 |
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable static file caching
|
|
@@ -114,6 +115,10 @@ def check_backend_status():
|
|
| 114 |
status_message = "Error: Could not connect to Ollama. Ensure GROQ_API_KEY is set in .env."
|
| 115 |
print(status_message, flush=True)
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
@app.route('/')
|
| 118 |
def index():
|
| 119 |
"""Serve the main page"""
|
|
@@ -219,15 +224,21 @@ def ask_question():
|
|
| 219 |
try:
|
| 220 |
# Construct prompt
|
| 221 |
weather_section = f"\n\n[Weather Context]: {weather_context.strip()}" if weather_context.strip() else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
full_prompt = (
|
| 223 |
-
"You are AgroGPT, an expert agriculture assistant. "
|
| 224 |
"Answer the following question clearly and concisely in plain text. "
|
| 225 |
"Structure your response EXACTLY as follows:\n"
|
| 226 |
-
"1. Provide a helpful answer in English.\n"
|
| 227 |
"2. Then write the header 'Malayalam Summary:' followed by the FULL answer translated into native Malayalam script (മലയാളം). Do NOT use English/Latin letters for Malayalam.\n"
|
| 228 |
"3. Then write the header 'Tamil Summary:' followed by the FULL answer translated into native Tamil script (தமிழ்). Do NOT use English/Latin letters for Tamil.\n"
|
| 229 |
"Use double line breaks between each section. Do NOT use markdown, asterisks, or bullet points.\n\n"
|
| 230 |
-
f"Question: {question}{weather_section}\n\nAnswer:"
|
| 231 |
)
|
| 232 |
|
| 233 |
print(f"Asking Ollama: {question}", flush=True)
|
|
|
|
| 18 |
# Import the new model functionality
|
| 19 |
from model import check_ollama_connection, generate_with_ollama, analyze_image_for_disease
|
| 20 |
from weather import get_current_weather, TN_DISTRICTS
|
| 21 |
+
from rag_engine import initialize_knowledge_base, query_rag
|
| 22 |
|
| 23 |
app = Flask(__name__)
|
| 24 |
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable static file caching
|
|
|
|
| 115 |
status_message = "Error: Could not connect to Ollama. Ensure GROQ_API_KEY is set in .env."
|
| 116 |
print(status_message, flush=True)
|
| 117 |
|
| 118 |
+
# Initialize the vector DB in the background
|
| 119 |
+
print("Triggering background RAG knowledge base initialization...", flush=True)
|
| 120 |
+
threading.Thread(target=initialize_knowledge_base).start()
|
| 121 |
+
|
| 122 |
@app.route('/')
|
| 123 |
def index():
|
| 124 |
"""Serve the main page"""
|
|
|
|
| 224 |
try:
|
| 225 |
# Construct prompt
|
| 226 |
weather_section = f"\n\n[Weather Context]: {weather_context.strip()}" if weather_context.strip() else ""
|
| 227 |
+
|
| 228 |
+
# Fetch RAG context
|
| 229 |
+
print("Fetching expert PDF knowledge context...", flush=True)
|
| 230 |
+
rag_context = query_rag(question)
|
| 231 |
+
rag_section = f"\n\n[Expert PDF Advisory Knowledge Base Context]:\n{rag_context}\n\nUse this information above to enhance your answer if relevant." if rag_context else ""
|
| 232 |
+
|
| 233 |
full_prompt = (
|
| 234 |
+
"You are AgroGPT, an expert agriculture assistant with access to detailed agricultural advisory documents. "
|
| 235 |
"Answer the following question clearly and concisely in plain text. "
|
| 236 |
"Structure your response EXACTLY as follows:\n"
|
| 237 |
+
"1. Provide a helpful, robust answer in English. Ensure you include relevant information from the knowledge base if available.\n"
|
| 238 |
"2. Then write the header 'Malayalam Summary:' followed by the FULL answer translated into native Malayalam script (മലയാളം). Do NOT use English/Latin letters for Malayalam.\n"
|
| 239 |
"3. Then write the header 'Tamil Summary:' followed by the FULL answer translated into native Tamil script (தமிழ்). Do NOT use English/Latin letters for Tamil.\n"
|
| 240 |
"Use double line breaks between each section. Do NOT use markdown, asterisks, or bullet points.\n\n"
|
| 241 |
+
f"Question: {question}{weather_section}{rag_section}\n\nAnswer:"
|
| 242 |
)
|
| 243 |
|
| 244 |
print(f"Asking Ollama: {question}", flush=True)
|
rag_engine.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from langchain_community.document_loaders import PyPDFLoader
|
| 4 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 5 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 6 |
+
from langchain_community.vectorstores import FAISS
|
| 7 |
+
|
| 8 |
+
# Path to local PDFs
|
| 9 |
+
PDF_FILES = [
|
| 10 |
+
"1_agro.pdf",
|
| 11 |
+
"2-agro.pdf",
|
| 12 |
+
"3-agrogpt.pdf",
|
| 13 |
+
"4-agrogpt.pdf"
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
# Writeable path on Hugging Face for the vector store
|
| 17 |
+
VECTORSTORE_DIR = "/tmp/vectorstore"
|
| 18 |
+
|
| 19 |
+
# Global variable to hold the FAISS index in memory
|
| 20 |
+
_vector_store = None
|
| 21 |
+
|
| 22 |
+
def get_embeddings_model():
|
| 23 |
+
"""Return the HuggingFace embeddings model. Uses a lightweight fast model."""
|
| 24 |
+
# Using all-MiniLM-L6-v2 as it's very fast and effective for semantic search
|
| 25 |
+
return HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 26 |
+
|
| 27 |
+
def initialize_knowledge_base():
|
| 28 |
+
"""
|
| 29 |
+
Checks if the vector database exists in /tmp/vectorstore.
|
| 30 |
+
If not, it reads the PDFs, chunks them, generates embeddings, and saves the DB.
|
| 31 |
+
"""
|
| 32 |
+
global _vector_store
|
| 33 |
+
|
| 34 |
+
if os.path.exists(VECTORSTORE_DIR) and os.path.exists(os.path.join(VECTORSTORE_DIR, "index.faiss")):
|
| 35 |
+
print(f"Loading existing vector database from {VECTORSTORE_DIR}...")
|
| 36 |
+
_vector_store = FAISS.load_local(VECTORSTORE_DIR, get_embeddings_model(), allow_dangerous_deserialization=True)
|
| 37 |
+
return _vector_store
|
| 38 |
+
|
| 39 |
+
print("Vector database not found. Initializing knowledge base (this may take a minute)...", flush=True)
|
| 40 |
+
os.makedirs(VECTORSTORE_DIR, exist_ok=True)
|
| 41 |
+
|
| 42 |
+
documents = []
|
| 43 |
+
for pdf_file in PDF_FILES:
|
| 44 |
+
try:
|
| 45 |
+
if os.path.exists(pdf_file):
|
| 46 |
+
print(f"Parsing {pdf_file}...", flush=True)
|
| 47 |
+
loader = PyPDFLoader(pdf_file)
|
| 48 |
+
documents.extend(loader.load())
|
| 49 |
+
else:
|
| 50 |
+
print(f"Warning: {pdf_file} not found in the root directory.", flush=True)
|
| 51 |
+
except Exception as e:
|
| 52 |
+
print(f"Error parsing {pdf_file}: {e}", flush=True)
|
| 53 |
+
|
| 54 |
+
if not documents:
|
| 55 |
+
print("No documents were loaded. Vector database initialization skipped.", flush=True)
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
print(f"Total pages loaded: {len(documents)}. Splitting text...", flush=True)
|
| 59 |
+
|
| 60 |
+
# Split the documents into manageable chunks
|
| 61 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
| 62 |
+
chunk_size=1000,
|
| 63 |
+
chunk_overlap=200,
|
| 64 |
+
length_function=len
|
| 65 |
+
)
|
| 66 |
+
chunks = text_splitter.split_documents(documents)
|
| 67 |
+
|
| 68 |
+
print(f"Created {len(chunks)} text chunks. Generating embeddings...", flush=True)
|
| 69 |
+
|
| 70 |
+
# Generate embeddings and build FAISS index
|
| 71 |
+
embeddings = get_embeddings_model()
|
| 72 |
+
_vector_store = FAISS.from_documents(chunks, embeddings)
|
| 73 |
+
|
| 74 |
+
# Save for subsequent requests
|
| 75 |
+
_vector_store.save_local(VECTORSTORE_DIR)
|
| 76 |
+
print(f"Vector database built and saved to {VECTORSTORE_DIR} successfully.", flush=True)
|
| 77 |
+
|
| 78 |
+
return _vector_store
|
| 79 |
+
|
| 80 |
+
def query_rag(question: str, k: int = 3) -> str:
|
| 81 |
+
"""
|
| 82 |
+
Searches the FAISS vector database for chunks related to the question.
|
| 83 |
+
Returns a formatted string containing the retrieved context.
|
| 84 |
+
"""
|
| 85 |
+
global _vector_store
|
| 86 |
+
|
| 87 |
+
if not _vector_store:
|
| 88 |
+
_vector_store = initialize_knowledge_base()
|
| 89 |
+
|
| 90 |
+
if not _vector_store:
|
| 91 |
+
# If it's still None (e.g. PDFs missing or error), return empty context
|
| 92 |
+
return ""
|
| 93 |
+
|
| 94 |
+
try:
|
| 95 |
+
# Perform similarity search
|
| 96 |
+
docs = _vector_store.similarity_search(question, k=k)
|
| 97 |
+
|
| 98 |
+
# Format the retrieved documents into a single context string
|
| 99 |
+
context = "\n\n".join([f"[Source: {doc.metadata.get('source', 'Unknown')}]\n{doc.page_content}" for doc in docs])
|
| 100 |
+
return context
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"Error querying RAG: {e}", flush=True)
|
| 103 |
+
return ""
|
requirements.txt
CHANGED
|
@@ -16,6 +16,27 @@ torchvision>=0.15.0
|
|
| 16 |
Pillow>=9.0.0
|
| 17 |
numpy>=1.21.0
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# Data processing
|
| 20 |
pandas>=1.3.0
|
| 21 |
scikit-learn>=1.0.0
|
|
@@ -36,3 +57,12 @@ Flask-Login>=0.6.3
|
|
| 36 |
Flask-SQLAlchemy>=3.1.1
|
| 37 |
Flask-Limiter>=3.7.0
|
| 38 |
psycopg2-binary>=2.9.9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
Pillow>=9.0.0
|
| 17 |
numpy>=1.21.0
|
| 18 |
|
| 19 |
+
# Data processing
|
| 20 |
+
pandas>=1.3.0
|
| 21 |
+
scikit-learn>=1.0.0
|
| 22 |
+
# AgroGPT Requirements
|
| 23 |
+
# Core web framework
|
| 24 |
+
Flask==3.1.2
|
| 25 |
+
Werkzeug==3.1.3
|
| 26 |
+
|
| 27 |
+
# LLM Interface
|
| 28 |
+
# ollama>=0.1.0
|
| 29 |
+
groq>=0.9.0
|
| 30 |
+
|
| 31 |
+
# Machine Learning and AI (for Vision Model)
|
| 32 |
+
# Restored for Azure deployment
|
| 33 |
+
torch>=2.0.0
|
| 34 |
+
torchvision>=0.15.0
|
| 35 |
+
|
| 36 |
+
# Image processing
|
| 37 |
+
Pillow>=9.0.0
|
| 38 |
+
numpy>=1.21.0
|
| 39 |
+
|
| 40 |
# Data processing
|
| 41 |
pandas>=1.3.0
|
| 42 |
scikit-learn>=1.0.0
|
|
|
|
| 57 |
Flask-SQLAlchemy>=3.1.1
|
| 58 |
Flask-Limiter>=3.7.0
|
| 59 |
psycopg2-binary>=2.9.9
|
| 60 |
+
|
| 61 |
+
# RAG and LangChain requirements
|
| 62 |
+
langchain>=0.1.0
|
| 63 |
+
langchain-community>=0.0.10
|
| 64 |
+
langchain-groq>=0.0.1
|
| 65 |
+
langchain-huggingface>=0.0.1
|
| 66 |
+
pypdf>=4.0.0
|
| 67 |
+
faiss-cpu>=1.7.0
|
| 68 |
+
sentence-transformers>=2.2.0
|