import gradio as gr
import google.generativeai as genai
import os
import textwrap
import re
from dotenv import load_dotenv
# RAG Specific Imports
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
# --- Configuration ---
load_dotenv()
try:
api_key = os.getenv("GOOGLE_API_KEY")
if not api_key:
raise ValueError("GOOGLE_API_KEY not found in secrets.")
genai.configure(api_key=api_key)
except (ValueError, AttributeError) as e:
print(f"⚠️ Configuration Error: {e}")
exit()
# --- RAG Setup Function ---
# This function will now be called directly, only once.
def setup_rag_pipeline():
global retriever
print("Setting up RAG pipeline...")
try:
input_file = 'filtered_gardening_kb.txt'
with open(input_file, 'r', encoding='utf-8') as f:
source_text = f.read()
print("Chunking the document...")
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=200)
documents = text_splitter.create_documents([source_text])
print(f"Creating embeddings for {len(documents)} chunks...")
# Note: Embedding has a different, much higher free quota than generation.
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
vector_store = FAISS.from_documents(documents, embeddings)
retriever = vector_store.as_retriever(search_kwargs={"k": 7})
print("✅ RAG pipeline setup complete!")
except FileNotFoundError:
print(f"Error: '{input_file}' not found. Please ensure your processed data file exists.")
retriever = None
except Exception as e:
print(f"An error occurred during RAG setup: {e}")
retriever = None
# --- Gemini Model and Prompt ---
MODEL_NAME = 'gemini-1.5-flash-latest'
SYSTEM_PROMPT = textwrap.dedent("""
You are a knowledgeable and friendly gardening expert from a community chat group. Your entire knowledge base comes from the CONTEXT CHUNKS provided below, which are excerpts from your group's conversations.
Your goal is to answer the user's questions in a helpful, conversational, and natural way, as if you are sharing your collective experience.
**Your Guidelines:**
1. Base all answers on the source: Your responses must be directly inspired by and grounded in the information within the CONTEXT CHUNKS.
2. Synthesize and Connect: Feel free to connect related ideas from different parts of the context to provide a more complete answer.
3. Adopt a helpful persona: Sound like an experienced member of a gardening group sharing advice.
4. Handle missing information gracefully: If the user's question is completely outside the scope of the provided text, state that the chats don't cover that topic. If it's related but not explicit, you can say, "While the chats don't mention that directly, the general advice on [related topic] suggests that..."
5. Do NOT add citations: Just provide a clean, direct answer.
""").strip()
def generate_gemini_response(message, chat_history):
if not retriever:
return "The RAG pipeline is not initialized. Please check application logs."
relevant_docs = retriever.invoke(message)
context_chunks = "\n---\n".join([doc.page_content for doc in relevant_docs])
full_prompt = f"""{SYSTEM_PROMPT}
Source Optimized with RAG
""") chatbot = gr.Chatbot( elem_id="chatbot", avatar_images=(None, "https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d473531a3ea34baa.gif"), show_label=False, type="messages" ) with gr.Row(elem_id="input-area"): txt_msg = gr.Textbox( placeholder="Ask a question about your source document...", scale=7, show_label=False, container=False, elem_id="input-box" ) btn_submit = gr.Button("Send", variant="primary", scale=1, elem_id="send-button") def handle_message(message, chat_history): chat_history.append({"role": "user", "content": message}) bot_message_content = generate_gemini_response(message, chat_history) chat_history.append({"role": "assistant", "content": bot_message_content}) return chat_history def process_and_clear(message, chat_history): new_history = handle_message(message, chat_history) return new_history, "" btn_submit.click(process_and_clear, [txt_msg, chatbot], [chatbot, txt_msg]) txt_msg.submit(process_and_clear, [txt_msg, chatbot], [chatbot, txt_msg]) # The demo.load() is removed, as setup is now handled before the Blocks are defined. if __name__ == "__main__": # For deploying on Hugging Face, it's better to set debug=False demo.queue().launch(debug=False)