Update Chat History Logic
Browse files- app/page_speed/config.py +31 -13
- app/rag/routes.py +29 -9
- app/rag/utils.py +19 -18
app/page_speed/config.py
CHANGED
|
@@ -1,39 +1,57 @@
|
|
| 1 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 2 |
-
from urllib.parse import quote_plus
|
| 3 |
|
| 4 |
class Settings(BaseSettings):
|
|
|
|
|
|
|
|
|
|
| 5 |
# Google API Keys
|
|
|
|
| 6 |
pagespeed_api_key: str
|
| 7 |
gemini_api_key: str
|
| 8 |
|
|
|
|
| 9 |
# Chat & RAG Configuration
|
|
|
|
| 10 |
groq_api_key: str
|
| 11 |
vectorstore_base_path: str = "./vectorstores"
|
| 12 |
|
| 13 |
-
#
|
|
|
|
|
|
|
| 14 |
huggingfacehub_api_token: str
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
mongo_chat_db: str = "MAAS"
|
| 19 |
mongo_chat_collection: str = "chat_histories"
|
| 20 |
|
| 21 |
-
#
|
|
|
|
|
|
|
| 22 |
host: str = "0.0.0.0"
|
| 23 |
port: int = 8000
|
| 24 |
debug: bool = False
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
|
|
|
|
|
|
|
|
|
| 32 |
model_config = SettingsConfigDict(
|
| 33 |
env_file=".env",
|
| 34 |
env_file_encoding="utf-8",
|
| 35 |
-
extra="ignore"
|
| 36 |
)
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
settings = Settings()
|
|
|
|
| 1 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
| 2 |
|
| 3 |
class Settings(BaseSettings):
|
| 4 |
+
"""Application settings loaded from environment variables."""
|
| 5 |
+
|
| 6 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 7 |
# Google API Keys
|
| 8 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
pagespeed_api_key: str
|
| 10 |
gemini_api_key: str
|
| 11 |
|
| 12 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 13 |
# Chat & RAG Configuration
|
| 14 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 15 |
groq_api_key: str
|
| 16 |
vectorstore_base_path: str = "./vectorstores"
|
| 17 |
|
| 18 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 19 |
+
# Hugging Face Hub
|
| 20 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
huggingfacehub_api_token: str
|
| 22 |
|
| 23 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
# MongoDB Configuration (Local)
|
| 25 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 26 |
+
mongo_uri: str = "mongodb://localhost:27017"
|
| 27 |
mongo_chat_db: str = "MAAS"
|
| 28 |
mongo_chat_collection: str = "chat_histories"
|
| 29 |
|
| 30 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 31 |
+
# FastAPI Server Configuration
|
| 32 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 33 |
host: str = "0.0.0.0"
|
| 34 |
port: int = 8000
|
| 35 |
debug: bool = False
|
| 36 |
|
| 37 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
+
# App Metadata (unchanged)
|
| 39 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 40 |
+
app_name: str = "PageSpeed Insights Report Generator"
|
| 41 |
+
app_version: str = "1.0.0"
|
| 42 |
+
app_description: str = (
|
| 43 |
+
"Professional API for generating PageSpeed Insights reports "
|
| 44 |
+
"using Google's APIs and Gemini AI"
|
| 45 |
+
)
|
| 46 |
|
| 47 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 48 |
+
# Tell Pydantic to load from .env and ignore extras
|
| 49 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 50 |
model_config = SettingsConfigDict(
|
| 51 |
env_file=".env",
|
| 52 |
env_file_encoding="utf-8",
|
| 53 |
+
extra="ignore",
|
| 54 |
)
|
| 55 |
|
| 56 |
+
# Single shared Settings instance
|
| 57 |
+
settings = Settings()
|
app/rag/routes.py
CHANGED
|
@@ -4,6 +4,7 @@ from fastapi import APIRouter, HTTPException
|
|
| 4 |
|
| 5 |
from .schemas import SetupRequest, ChatRequest, SetupResponse, ChatResponse
|
| 6 |
from .utils import (
|
|
|
|
| 7 |
text_splitter,
|
| 8 |
embeddings,
|
| 9 |
save_vectorstore_to_disk,
|
|
@@ -31,6 +32,23 @@ async def setup_rag_session(onboarding_id: str, body: SetupRequest):
|
|
| 31 |
onboarding_id, vectorstore_path
|
| 32 |
)
|
| 33 |
vs_path = vectorstore_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
else:
|
| 35 |
if not body.documents:
|
| 36 |
logger.error(
|
|
@@ -40,6 +58,15 @@ async def setup_rag_session(onboarding_id: str, body: SetupRequest):
|
|
| 40 |
status_code=400,
|
| 41 |
detail="Vectorstore does not exist; please provide documents to ingest."
|
| 42 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# Ingest new vectorstore
|
| 44 |
all_text = "\n\n".join(body.documents)
|
| 45 |
text_chunks = text_splitter.split_text(all_text)
|
|
@@ -48,18 +75,11 @@ async def setup_rag_session(onboarding_id: str, body: SetupRequest):
|
|
| 48 |
vs = _FAISS.from_texts(texts=text_chunks, embedding=embeddings)
|
| 49 |
vs_path = save_vectorstore_to_disk(vs, onboarding_id)
|
| 50 |
logger.info("Saved FAISS index to %s", vs_path)
|
| 51 |
-
upsert_vectorstore_metadata(onboarding_id, vs_path)
|
| 52 |
logger.info(
|
| 53 |
"Upserted vectorstore metadata for onboarding_id=%s", onboarding_id
|
| 54 |
)
|
| 55 |
-
|
| 56 |
-
# Create new chat session
|
| 57 |
-
chat_id = str(uuid.uuid4())
|
| 58 |
-
ChatHistoryManager.create_session(chat_id)
|
| 59 |
-
logger.info(
|
| 60 |
-
"Created new chat session %s for onboarding_id=%s",
|
| 61 |
-
chat_id, onboarding_id
|
| 62 |
-
)
|
| 63 |
|
| 64 |
return SetupResponse(
|
| 65 |
success=True,
|
|
|
|
| 4 |
|
| 5 |
from .schemas import SetupRequest, ChatRequest, SetupResponse, ChatResponse
|
| 6 |
from .utils import (
|
| 7 |
+
get_vectorstore_metadata,
|
| 8 |
text_splitter,
|
| 9 |
embeddings,
|
| 10 |
save_vectorstore_to_disk,
|
|
|
|
| 32 |
onboarding_id, vectorstore_path
|
| 33 |
)
|
| 34 |
vs_path = vectorstore_path
|
| 35 |
+
|
| 36 |
+
chat_session = get_vectorstore_metadata(onboarding_id)
|
| 37 |
+
|
| 38 |
+
if chat_session:
|
| 39 |
+
chat_id = str(chat_session["chat_id"])
|
| 40 |
+
logger.info("Using existing chat session id=%s for onboarding_id=%s", chat_id, onboarding_id)
|
| 41 |
+
else:
|
| 42 |
+
logger.warning("No chat session found for onboarding_id=%s", onboarding_id)
|
| 43 |
+
|
| 44 |
+
return SetupResponse(
|
| 45 |
+
success=True,
|
| 46 |
+
message="RAG setup completed with existing vectorstore.",
|
| 47 |
+
onboarding_id=onboarding_id,
|
| 48 |
+
chat_id=chat_id,
|
| 49 |
+
vectorstore_path=vs_path
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
else:
|
| 53 |
if not body.documents:
|
| 54 |
logger.error(
|
|
|
|
| 58 |
status_code=400,
|
| 59 |
detail="Vectorstore does not exist; please provide documents to ingest."
|
| 60 |
)
|
| 61 |
+
|
| 62 |
+
# Create new chat session
|
| 63 |
+
chat_id = str(uuid.uuid4())
|
| 64 |
+
ChatHistoryManager.create_session(chat_id)
|
| 65 |
+
logger.info(
|
| 66 |
+
"Created new chat session %s for onboarding_id=%s",
|
| 67 |
+
chat_id, onboarding_id
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
# Ingest new vectorstore
|
| 71 |
all_text = "\n\n".join(body.documents)
|
| 72 |
text_chunks = text_splitter.split_text(all_text)
|
|
|
|
| 75 |
vs = _FAISS.from_texts(texts=text_chunks, embedding=embeddings)
|
| 76 |
vs_path = save_vectorstore_to_disk(vs, onboarding_id)
|
| 77 |
logger.info("Saved FAISS index to %s", vs_path)
|
| 78 |
+
upsert_vectorstore_metadata(onboarding_id, vs_path, chat_id)
|
| 79 |
logger.info(
|
| 80 |
"Upserted vectorstore metadata for onboarding_id=%s", onboarding_id
|
| 81 |
)
|
| 82 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
return SetupResponse(
|
| 85 |
success=True,
|
app/rag/utils.py
CHANGED
|
@@ -16,25 +16,25 @@ from app.rag.prompt_library import page_speed_prompt, default_user_prompt,seo_pr
|
|
| 16 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
# 1. Helper: Path to Store (or Load) a User's FAISS Vectorstore on Disk
|
| 18 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 19 |
-
def get_vectorstore_path(
|
| 20 |
"""
|
| 21 |
Ensure a local directory exists for this user's vectorstore.
|
| 22 |
-
Returns a path like './vectorstores/{
|
| 23 |
"""
|
| 24 |
base_dir = settings.vectorstore_base_path
|
| 25 |
-
user_dir = os.path.join(base_dir,
|
| 26 |
# os.makedirs(user_dir, exist_ok=True)
|
| 27 |
return user_dir
|
| 28 |
|
| 29 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 30 |
# 2. Build or Load an Existing FAISS Index for a User
|
| 31 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
-
def build_or_load_vectorstore(
|
| 33 |
"""
|
| 34 |
Attempt to load an existing FAISS index for this user.
|
| 35 |
If not found on disk, raise a FileNotFoundError.
|
| 36 |
"""
|
| 37 |
-
user_dir = get_vectorstore_path(
|
| 38 |
faiss_index_path = os.path.join(user_dir, "faiss_index")
|
| 39 |
|
| 40 |
if not os.path.isdir(faiss_index_path):
|
|
@@ -50,12 +50,12 @@ def build_or_load_vectorstore(user_id: str) -> FAISS:
|
|
| 50 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
# 3. Save a FAISS Vectorstore to Disk for a User
|
| 52 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
-
def save_vectorstore_to_disk(vectorstore: FAISS,
|
| 54 |
"""
|
| 55 |
Save the FAISS vectorstore under './vectorstores/{user_id}/faiss_index'.
|
| 56 |
Returns the path to that saved folder.
|
| 57 |
"""
|
| 58 |
-
user_dir = get_vectorstore_path(
|
| 59 |
faiss_index_path = os.path.join(user_dir, "faiss_index")
|
| 60 |
os.makedirs(faiss_index_path, exist_ok=True)
|
| 61 |
vectorstore.save_local(folder_path=faiss_index_path)
|
|
@@ -64,21 +64,22 @@ def save_vectorstore_to_disk(vectorstore: FAISS, user_id: str) -> str:
|
|
| 64 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 65 |
# 4. Upsert or Fetch Vectorstore Metadata in MongoDB
|
| 66 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 67 |
-
def upsert_vectorstore_metadata(
|
| 68 |
"""
|
| 69 |
Insert or update a document mapping user_id β vectorstore_path in MongoDB.
|
| 70 |
"""
|
| 71 |
vectorstore_meta_coll.update_one(
|
| 72 |
-
{"
|
| 73 |
-
{"$set": {"vectorstore_path": vectorstore_path}
|
|
|
|
| 74 |
upsert=True
|
| 75 |
)
|
| 76 |
|
| 77 |
-
def get_vectorstore_metadata(
|
| 78 |
"""
|
| 79 |
-
Retrieve the metadata doc (if any) for this
|
| 80 |
"""
|
| 81 |
-
return vectorstore_meta_coll.find_one({"
|
| 82 |
|
| 83 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββββ
|
| 84 |
# 5. Initialize (or Return) a MongoDBChatMessageHistory for chat_id
|
|
@@ -95,20 +96,20 @@ def initialize_chat_history(chat_id: str) -> MongoDBChatMessageHistory:
|
|
| 95 |
)
|
| 96 |
|
| 97 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 98 |
-
# 6. Build a ConversationalRetrievalChain (RAG Chain) for
|
| 99 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 100 |
-
def build_rag_chain(
|
| 101 |
"""
|
| 102 |
-
- Loads the FAISS index for
|
| 103 |
- Creates a retriever (k=3).
|
| 104 |
- Wraps MongoDBChatMessageHistory in a ConversationBufferMemory.
|
| 105 |
- Attaches the ChatGroq LLM + user_prompt.
|
| 106 |
"""
|
| 107 |
# 1. Load FAISS index (or 404 if not found)
|
| 108 |
try:
|
| 109 |
-
faiss_vs = build_or_load_vectorstore(
|
| 110 |
except FileNotFoundError:
|
| 111 |
-
raise HTTPException(status_code=404, detail="Vectorstore not found for this
|
| 112 |
|
| 113 |
retriever = faiss_vs.as_retriever(search_kwargs={"k": 5})
|
| 114 |
|
|
|
|
| 16 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
# 1. Helper: Path to Store (or Load) a User's FAISS Vectorstore on Disk
|
| 18 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 19 |
+
def get_vectorstore_path(onboarding_id: str) -> str:
|
| 20 |
"""
|
| 21 |
Ensure a local directory exists for this user's vectorstore.
|
| 22 |
+
Returns a path like './vectorstores/{onboarding_id}'.
|
| 23 |
"""
|
| 24 |
base_dir = settings.vectorstore_base_path
|
| 25 |
+
user_dir = os.path.join(base_dir, onboarding_id)
|
| 26 |
# os.makedirs(user_dir, exist_ok=True)
|
| 27 |
return user_dir
|
| 28 |
|
| 29 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 30 |
# 2. Build or Load an Existing FAISS Index for a User
|
| 31 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
+
def build_or_load_vectorstore(onboarding_id: str) -> FAISS:
|
| 33 |
"""
|
| 34 |
Attempt to load an existing FAISS index for this user.
|
| 35 |
If not found on disk, raise a FileNotFoundError.
|
| 36 |
"""
|
| 37 |
+
user_dir = get_vectorstore_path(onboarding_id)
|
| 38 |
faiss_index_path = os.path.join(user_dir, "faiss_index")
|
| 39 |
|
| 40 |
if not os.path.isdir(faiss_index_path):
|
|
|
|
| 50 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
# 3. Save a FAISS Vectorstore to Disk for a User
|
| 52 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
def save_vectorstore_to_disk(vectorstore: FAISS, vectorstore_name: str) -> str:
|
| 54 |
"""
|
| 55 |
Save the FAISS vectorstore under './vectorstores/{user_id}/faiss_index'.
|
| 56 |
Returns the path to that saved folder.
|
| 57 |
"""
|
| 58 |
+
user_dir = get_vectorstore_path(vectorstore_name)
|
| 59 |
faiss_index_path = os.path.join(user_dir, "faiss_index")
|
| 60 |
os.makedirs(faiss_index_path, exist_ok=True)
|
| 61 |
vectorstore.save_local(folder_path=faiss_index_path)
|
|
|
|
| 64 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 65 |
# 4. Upsert or Fetch Vectorstore Metadata in MongoDB
|
| 66 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 67 |
+
def upsert_vectorstore_metadata(onboarding_id: str, vectorstore_path: str, chat_id: str) -> None:
|
| 68 |
"""
|
| 69 |
Insert or update a document mapping user_id β vectorstore_path in MongoDB.
|
| 70 |
"""
|
| 71 |
vectorstore_meta_coll.update_one(
|
| 72 |
+
{"onboarding_id": onboarding_id},
|
| 73 |
+
{"$set": {"vectorstore_path": vectorstore_path},
|
| 74 |
+
"$setOnInsert": {"chat_id": chat_id}},
|
| 75 |
upsert=True
|
| 76 |
)
|
| 77 |
|
| 78 |
+
def get_vectorstore_metadata(onboarding_id: str) -> Optional[Dict[str, Any]]:
|
| 79 |
"""
|
| 80 |
+
Retrieve the metadata doc (if any) for this onboarding_id.
|
| 81 |
"""
|
| 82 |
+
return vectorstore_meta_coll.find_one({"onboarding_id": onboarding_id})
|
| 83 |
|
| 84 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββββ
|
| 85 |
# 5. Initialize (or Return) a MongoDBChatMessageHistory for chat_id
|
|
|
|
| 96 |
)
|
| 97 |
|
| 98 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 99 |
+
# 6. Build a ConversationalRetrievalChain (RAG Chain) for onboarding_id + chat_id
|
| 100 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 101 |
+
def build_rag_chain(onboarding_id: str, chat_id: str, prompt_type: str) -> ConversationalRetrievalChain:
|
| 102 |
"""
|
| 103 |
+
- Loads the FAISS index for onboarding_id.
|
| 104 |
- Creates a retriever (k=3).
|
| 105 |
- Wraps MongoDBChatMessageHistory in a ConversationBufferMemory.
|
| 106 |
- Attaches the ChatGroq LLM + user_prompt.
|
| 107 |
"""
|
| 108 |
# 1. Load FAISS index (or 404 if not found)
|
| 109 |
try:
|
| 110 |
+
faiss_vs = build_or_load_vectorstore(onboarding_id)
|
| 111 |
except FileNotFoundError:
|
| 112 |
+
raise HTTPException(status_code=404, detail="Vectorstore not found for this onboarding_id. Call /rag/ingest first.")
|
| 113 |
|
| 114 |
retriever = faiss_vs.as_retriever(search_kwargs={"k": 5})
|
| 115 |
|