Spaces:
Sleeping
Sleeping
File size: 7,833 Bytes
12321f4 0b80b63 12321f4 c5a3183 12321f4 c5a3183 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 c5a3183 0b80b63 c5a3183 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 0b80b63 12321f4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
import os
from flask import Flask, request, jsonify, render_template
import google.generativeai as genai
# LangChain Community has the updated vector stores
from langchain_community.vectorstores import FAISS
from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from dotenv import load_dotenv
import logging
import re
from custom_prompt import get_custom_prompt
import time
# Configure logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
app = Flask(__name__)
# Store app start time for uptime calculation
app_start_time = time.time()
# Initialize the environment - Check multiple possible env var names
GOOGLE_API_KEY = (os.getenv("GOOGLE_API_KEY") or
os.getenv("GEMINI_API_KEY") or
os.getenv("GOOGLE_GEMINI_API_KEY"))
if not GOOGLE_API_KEY or GOOGLE_API_KEY == "your_api_key_here":
logger.error("No valid GOOGLE_API_KEY found in environment variables")
print("⚠️ Please set your Gemini API key in the environment variables")
print("Supported env var names: GOOGLE_API_KEY, GEMINI_API_KEY, GOOGLE_GEMINI_API_KEY")
else:
genai.configure(api_key=GOOGLE_API_KEY)
logger.info("API key configured successfully")
# Global variables for the chain and memory
qa_chain = None
memory = None
def initialize_chatbot():
global qa_chain, memory
logger.info("Initializing chatbot...")
# Initialize embeddings
try:
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
logger.info("Embeddings initialized")
except Exception as e:
logger.error(f"Error initializing embeddings: {str(e)}")
return False
# Load the vector store
try:
vector_store = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
logger.info("Vector store loaded successfully!")
except Exception as e:
logger.error(f"Error loading vector store: {str(e)}")
print(f"⚠️ Error loading vector store: {str(e)}")
print("Make sure your 'faiss_index' folder is in the same directory as this script.")
return False
# Create memory
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
logger.info("Conversation memory initialized")
# Initialize the language model
try:
llm = ChatGoogleGenerativeAI(
model="gemini-2.0-flash", # Updated to a newer recommended model
temperature=0.2,
top_p=0.85,
google_api_key=GOOGLE_API_KEY
)
logger.info("Language model initialized")
except Exception as e:
logger.error(f"Error initializing language model: {str(e)}")
return False
# Create the conversation chain with the custom prompt
try:
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory,
verbose=True,
return_source_documents=False,
combine_docs_chain_kwargs={"prompt": get_custom_prompt()},
)
logger.info("QA chain created successfully")
except Exception as e:
logger.error(f"Error creating QA chain: {str(e)}")
return False
return True
# Function to format links as HTML anchor tags
def format_links_as_html(text):
# Detect markdown style links [text](url)
markdown_pattern = r'\[(.*?)\]\((https?://[^\s\)]+)\)'
text = re.sub(markdown_pattern, r'<a href="\2" target="_blank">\1</a>', text)
# Handle URLs in square brackets [url]
bracket_pattern = r'\[(https?://[^\s\]]+)\]'
text = re.sub(bracket_pattern, r'<a href="\1" target="_blank">\1</a>', text)
# Regular URL pattern - THIS IS THE FIX
# The previous pattern r'(https?://[^\s\])+)' was invalid.
url_pattern = r'(?<!href=")(https?://[^\s<]+)'
# Replace URLs with HTML anchor tags
text = re.sub(url_pattern, r'<a href="\1" target="_blank">\1</a>', text)
return text
# Function to properly escape asterisks for markdown rendering
def escape_markdown(text):
return re.sub(r'(?<!\*)\*(?!\*)', r'\\*', text)
# Function to format markdown and handle asterisks with proper line breaks
def format_markdown_with_breaks(text):
text = text.replace('\\*', '*')
text = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', text)
lines = text.split('\n')
formatted_lines = []
for i, line in enumerate(lines):
stripped_line = line.strip()
if stripped_line.startswith('* '):
content = stripped_line[2:].strip()
# Use a bullet point character for lists
formatted_lines.append(f"<br>• {content}")
elif stripped_line.startswith('*'):
content = stripped_line[1:].strip()
formatted_lines.append(f"<br>• {content}")
else:
formatted_lines.append(line)
# Join the lines, but remove the initial <br> if it exists
result = '\n'.join(formatted_lines)
if result.startswith('<br>'):
result = result[4:]
return result
@app.route('/')
def home():
return render_template('index.html')
@app.route('/health')
def health():
"""Standard health check endpoint for uptime monitoring"""
try:
current_time = time.time()
uptime_seconds = current_time - app_start_time
health_status = {
"status": "healthy",
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
"uptime_seconds": round(uptime_seconds, 2),
"chatbot_initialized": qa_chain is not None
}
return jsonify(health_status), 200
except Exception as e:
logger.error(f"Health check failed: {str(e)}")
return jsonify({
"status": "unhealthy", "error": str(e),
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime())
}), 500
@app.route('/ping')
def ping():
"""Simple ping endpoint for basic uptime checks"""
return "pong", 200
@app.route('/api/chat', methods=['POST'])
def chat():
global qa_chain
if qa_chain is None:
if not initialize_chatbot():
return jsonify({"error": "Failed to initialize chatbot. Check server logs."}), 500
data = request.json
user_message = data.get('message', '')
if not user_message:
return jsonify({"error": "No message provided"}), 400
try:
logger.info(f"Processing user query: {user_message}")
# Use .invoke() instead of the deprecated __call__ method
result = qa_chain.invoke({"question": user_message})
answer = result.get("answer", "I'm sorry, I couldn't generate a response.")
# Format the answer
answer = escape_markdown(answer)
answer = format_links_as_html(answer)
answer = format_markdown_with_breaks(answer)
logger.info("Query processed successfully")
return jsonify({"answer": answer})
except Exception as e:
# Log the full traceback for better debugging
logger.exception(f"Error processing request: {str(e)}")
return jsonify({"error": f"An internal error occurred: {str(e)}"}), 500
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False) |