Hammad712 commited on
Commit
a2f88d9
Β·
1 Parent(s): 33aeeb4

Update config to load .env, add HF token support, and clean up settings

Browse files
app/config.py CHANGED
@@ -1,38 +1,38 @@
1
- import os
2
- from dotenv import load_dotenv
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
4
 
5
- # Load environment variables from .env
6
- load_dotenv()
7
-
8
  class Settings(BaseSettings):
9
  """Application settings loaded from environment variables."""
10
 
11
  # ───────────────────────────────────────────────────────────────────────────
12
  # Google API Keys
13
  # ───────────────────────────────────────────────────────────────────────────
14
- pagespeed_api_key: str = os.getenv("PAGESPEED_API_KEY", "")
15
- gemini_api_key: str = os.getenv("GEMINI_API_KEY", "")
16
-
17
  # ───────────────────────────────────────────────────────────────────────────
18
  # Chat & RAG Configuration
19
  # ───────────────────────────────────────────────────────────────────────────
20
- groq_api_key: str = os.getenv("GROQ_API_KEY", "")
21
- vectorstore_base_path: str = os.getenv("VECTORSTORE_BASE_PATH", "./vectorstores")
 
 
 
 
 
22
 
23
  # ───────────────────────────────────────────────────────────────────────────
24
  # MongoDB Configuration (Local)
25
  # ───────────────────────────────────────────────────────────────────────────
26
- mongo_uri: str = os.getenv("MONGO_URI", "mongodb://localhost:27017")
27
- mongo_chat_db: str = os.getenv("MONGO_CHAT_DB", "Education_chatbot")
28
- mongo_chat_collection: str = os.getenv("MONGO_CHAT_COLLECTION", "chat_histories")
29
 
30
  # ───────────────────────────────────────────────────────────────────────────
31
  # FastAPI Server Configuration
32
  # ───────────────────────────────────────────────────────────────────────────
33
- host: str = os.getenv("HOST", "0.0.0.0")
34
- port: int = int(os.getenv("PORT", "8000"))
35
- debug: bool = os.getenv("DEBUG", "False").lower() == "true"
36
 
37
  # ───────────────────────────────────────────────────────────────────────────
38
  # App Metadata (unchanged)
@@ -44,10 +44,14 @@ class Settings(BaseSettings):
44
  "using Google's APIs and Gemini AI"
45
  )
46
 
 
 
 
47
  model_config = SettingsConfigDict(
48
  env_file=".env",
49
- env_file_encoding="utf-8"
 
50
  )
51
 
52
- # Instantiate settings
53
  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 = "Education_chatbot"
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)
 
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/chat_history.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import List, Dict, Any
3
+ from pymongo import ReturnDocument
4
+
5
+ from app.config import settings
6
+ from .db import mongo_client, chat_collection_name
7
+ from .embeddings import get_llm
8
+ from langchain.prompts import ChatPromptTemplate
9
+ from .logging_config import logger
10
+
11
+ # Get the actual collection object
12
+ db = mongo_client[settings.mongo_chat_db]
13
+ coll = db[chat_collection_name]
14
+
15
+ # LLM & summarization prompt
16
+ llm = get_llm()
17
+ summarization_prompt = ChatPromptTemplate.from_messages([
18
+ ("system", "Summarize the following conversation into a concise summary:"),
19
+ ("human", "{chat_history}")
20
+ ])
21
+
22
+ class ChatHistoryManager:
23
+ @staticmethod
24
+ def create_session(chat_id: str) -> None:
25
+ """Ensure a document exists for this chat_id with empty messages."""
26
+ coll.update_one(
27
+ {"session_id": chat_id},
28
+ {"$setOnInsert": {"session_id": chat_id, "messages": []}},
29
+ upsert=True
30
+ )
31
+ logger.info("Initialized chat session %s", chat_id)
32
+
33
+ @staticmethod
34
+ def get_messages(chat_id: str) -> List[Dict[str, Any]]:
35
+ """Return the messages array for this session (or empty if none)."""
36
+ doc = coll.find_one({"session_id": chat_id}, {"_id": 0, "messages": 1})
37
+ return doc.get("messages", []) if doc else []
38
+
39
+ @staticmethod
40
+ def add_message(chat_id: str, role: str, content: str) -> None:
41
+ """Append a new {role,content,timestamp} entry to the messages array."""
42
+ entry = {
43
+ "type": role,
44
+ "content": content,
45
+ "timestamp": time.time()
46
+ }
47
+ coll.update_one(
48
+ {"session_id": chat_id},
49
+ {"$push": {"messages": entry}}
50
+ )
51
+ logger.debug("Appended %s message to %s", role, chat_id)
52
+
53
+ @staticmethod
54
+ def summarize_if_needed(chat_id: str, threshold: int = 10) -> bool:
55
+ """
56
+ If message count > threshold, summarize and replace all messages
57
+ with a single "ai" summary entry.
58
+ """
59
+ messages = ChatHistoryManager.get_messages(chat_id)
60
+ if len(messages) <= threshold:
61
+ return False
62
+
63
+ # Flatten for summarization
64
+ chat_text = "\n".join(f"{m['type'].upper()}: {m['content']}" for m in messages)
65
+
66
+ # Run summarization
67
+ summary_chain = summarization_prompt | llm
68
+ result = summary_chain.invoke({"chat_history": chat_text})
69
+ summary = getattr(result, "content", result)
70
+
71
+ # Replace entire messages array with the summary
72
+ coll.find_one_and_update(
73
+ {"session_id": chat_id},
74
+ {"$set": {"messages": [
75
+ {"type": "ai", "content": summary, "timestamp": time.time()}
76
+ ]}},
77
+ return_document=ReturnDocument.AFTER
78
+ )
79
+ logger.info("Summarized chat %s down to one message", chat_id)
80
+ return True
app/rag/embeddings.py CHANGED
@@ -2,6 +2,10 @@ import os
2
  from langchain_community.embeddings import HuggingFaceBgeEmbeddings
3
  from langchain.text_splitter import RecursiveCharacterTextSplitter
4
  from langchain.prompts import ChatPromptTemplate
 
 
 
 
5
 
6
  def get_llm():
7
  """
 
2
  from langchain_community.embeddings import HuggingFaceBgeEmbeddings
3
  from langchain.text_splitter import RecursiveCharacterTextSplitter
4
  from langchain.prompts import ChatPromptTemplate
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv() # now os.getenv(...) will pick up values from your .env file
8
+
9
 
10
  def get_llm():
11
  """
app/rag/routes.py CHANGED
@@ -22,6 +22,9 @@ from .utils import (
22
  )
23
  from .logging_config import logger
24
 
 
 
 
25
  router = APIRouter(prefix="/rag", tags=["rag"])
26
 
27
  @router.post("/ingest/{user_id}", response_model=IngestResponse)
@@ -92,32 +95,32 @@ async def create_chat_session(user_id: str):
92
  logger.error("Error creating chat for user_id=%s: %s", user_id, e, exc_info=True)
93
  raise HTTPException(status_code=500, detail=f"Failed to create chat session: {e}")
94
 
 
95
  @router.post("/chat/{user_id}/{chat_id}", response_model=ChatResponse)
96
  async def chat_with_user(user_id: str, chat_id: str, body: ChatRequest):
97
- """
98
- Send a user question to the RAG chain and return the LLM answer.
99
- - Loads the FAISS index for user_id (404 if not found).
100
- - Retrieves (or initializes) the MongoDBChatMessageHistory for chat_id.
101
- - Runs the ConversationalRetrievalChain to get an answer.
102
- - Returns the answer, plus re‐stores chat history in Mongo automatically.
103
- """
104
- question = body.question
105
- logger.info("Received chat request: user_id=%s, chat_id=%s, question='%s'", user_id, chat_id, question)
106
 
107
  try:
108
- # 1. Build the RAG chain (or 404 if no vectorstore)
109
- chain = build_rag_chain(user_id, chat_id)
 
 
 
110
 
111
- # 2. Call the chain
112
- result = chain.invoke({"question": question})
113
- # Some chains use "answer", some use "output_text"
114
- answer = result.get("answer") or result.get("output_text") or None
115
 
116
- if answer is None:
117
- logger.error("Chain returned no 'answer' or 'output_text': %s", result)
118
- raise Exception("Failed to retrieve answer from chain.")
 
 
 
 
119
 
120
- logger.info("Chain answered for chat_id=%s: %s", chat_id, answer)
 
121
 
122
  return ChatResponse(
123
  success=True,
@@ -126,11 +129,11 @@ async def chat_with_user(user_id: str, chat_id: str, body: ChatRequest):
126
  chat_id=chat_id,
127
  user_id=user_id
128
  )
 
129
  except HTTPException:
130
- # Re‐raise known HTTPExceptions (e.g. 404 from build_rag_chain)
131
  raise
132
  except Exception as e:
133
- logger.error("Error in chat endpoint for user_id=%s, chat_id=%s: %s", user_id, chat_id, e, exc_info=True)
134
  return ChatResponse(
135
  success=False,
136
  answer=None,
 
22
  )
23
  from .logging_config import logger
24
 
25
+ from .chat_history import ChatHistoryManager
26
+ from .logging_config import logger
27
+
28
  router = APIRouter(prefix="/rag", tags=["rag"])
29
 
30
  @router.post("/ingest/{user_id}", response_model=IngestResponse)
 
95
  logger.error("Error creating chat for user_id=%s: %s", user_id, e, exc_info=True)
96
  raise HTTPException(status_code=500, detail=f"Failed to create chat session: {e}")
97
 
98
+
99
  @router.post("/chat/{user_id}/{chat_id}", response_model=ChatResponse)
100
  async def chat_with_user(user_id: str, chat_id: str, body: ChatRequest):
101
+ question = body.question.strip()
102
+ logger.info("Chat request user=%s chat=%s question=%s", user_id, chat_id, question)
 
 
 
 
 
 
 
103
 
104
  try:
105
+ # 1) Ensure session exists
106
+ ChatHistoryManager.create_session(chat_id)
107
+
108
+ # 2) Summarize long histories
109
+ ChatHistoryManager.summarize_if_needed(chat_id, threshold=10)
110
 
111
+ # 3) Record the user message
112
+ ChatHistoryManager.add_message(chat_id, role="human", content=question)
 
 
113
 
114
+ # 4) Build and invoke the RAG chain
115
+ chain = build_rag_chain(user_id, chat_id)
116
+ history = ChatHistoryManager.get_messages(chat_id)
117
+ result = chain.invoke({"question": question, "chat_history": history})
118
+ answer = result.get("answer") or result.get("output_text")
119
+ if not answer:
120
+ raise Exception("No answer returned from chain")
121
 
122
+ # 5) Record the AI response
123
+ ChatHistoryManager.add_message(chat_id, role="ai", content=answer)
124
 
125
  return ChatResponse(
126
  success=True,
 
129
  chat_id=chat_id,
130
  user_id=user_id
131
  )
132
+
133
  except HTTPException:
 
134
  raise
135
  except Exception as e:
136
+ logger.error("Error chatting user=%s chat=%s: %s", user_id, chat_id, e, exc_info=True)
137
  return ChatResponse(
138
  success=False,
139
  answer=None,
requirements.txt CHANGED
@@ -10,3 +10,6 @@ langchain_community
10
  faiss-cpu
11
  pymongo
12
  langchain-mongodb
 
 
 
 
10
  faiss-cpu
11
  pymongo
12
  langchain-mongodb
13
+ huggingface_hub
14
+ python_dotenv
15
+ sentence_transformers