File size: 6,343 Bytes
9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 9fa59c9 5fb4fa6 |
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 |
import uuid
from datetime import datetime
from urllib.parse import quote_plus
from pymongo import MongoClient
from langchain.prompts import ChatPromptTemplate
from langchain_mongodb.chat_message_histories import MongoDBChatMessageHistory
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from llm_provider import llm
from vectorstore_manager import get_user_retriever
# === Prompt Template ===
quiz_solving_prompt = '''
You are an assistant specialized in solving quizzes. Your goal is to provide accurate, concise, and contextually relevant answers.
Use the following retrieved context to answer the user's question.
If the context lacks sufficient information, respond with "I don't know." Do not make up answers or provide unverified information.
Guidelines:
1. Extract key information from the context to form a coherent response.
2. Maintain a clear and professional tone.
3. If the question requires clarification, specify it politely.
Retrieved context:
{context}
User's question:
{question}
Your response:
'''
user_prompt = ChatPromptTemplate.from_messages([
("system", quiz_solving_prompt),
("human", "{question}")
])
# === MongoDB Configuration ===
PASSWORD = quote_plus("momimaad@123")
MONGO_URI = f"mongodb+srv://hammad:{PASSWORD}@cluster0.2a9yu.mongodb.net/"
DB_NAME = "Education_chatbot"
HISTORY_COLLECTION = "chat_histories" # used by MongoDBChatMessageHistory
SESSIONS_COLLECTION = "chat_sessions" # to track chat metadata
CHAINS_COLLECTION = "user_chains" # to track per-user vectorstore paths
# Initialize MongoDB client and collections
client = MongoClient(MONGO_URI)
db = client[DB_NAME]
sessions_collection = db[SESSIONS_COLLECTION]
chains_collection = db[CHAINS_COLLECTION]
# === Core Functions ===
def create_new_chat(user_id: str) -> str:
"""
Create a new chat session for the given user, persist metadata in MongoDB,
and ensure a vectorstore path is registered for that user.
Returns the new chat_id.
"""
chat_id = f"{user_id}-{uuid.uuid4()}"
created_at = datetime.utcnow()
# Persist chat session metadata
sessions_collection.insert_one({
"chat_id": chat_id,
"user_id": user_id,
"created_at": created_at
})
# Initialize chat history storage in its own collection via LangChain helper
MongoDBChatMessageHistory(
session_id=chat_id,
connection_string=MONGO_URI,
database_name=DB_NAME,
collection_name=HISTORY_COLLECTION,
)
# If the user has no chain/vectorstore registered yet, register it
if chains_collection.count_documents({"user_id": user_id}, limit=1) == 0:
# This also creates the vectorstore on disk via vectorstore_manager.ingest_report
# You should call ingest_report first elsewhere before chat
chains_collection.insert_one({
"user_id": user_id,
"vectorstore_path": f"user_vectorstores/{user_id}_faiss"
})
return chat_id
def get_chain_for_user(user_id: str, chat_id: str) -> ConversationalRetrievalChain:
"""
Reconstructs (or creates) the user's ConversationalRetrievalChain
using their vectorstore and the chat-specific memory object.
"""
# Step 1: Load raw MongoDB-backed chat history
mongo_history = MongoDBChatMessageHistory(
session_id=chat_id,
connection_string=MONGO_URI,
database_name=DB_NAME,
collection_name=HISTORY_COLLECTION,
)
# Step 2: Wrap it in a ConversationBufferMemory so that LangChain accepts it
memory = ConversationBufferMemory(
memory_key="chat_history",
chat_memory=mongo_history,
return_messages=True
)
# Step 3: Look up vectorstore path for this user
chain_doc = chains_collection.find_one({"user_id": user_id})
if not chain_doc:
raise ValueError(f"No vectorstore registered for user {user_id}")
# Step 4: Initialize retriever from vectorstore
retriever = get_user_retriever(user_id)
# Step 5: Create and return the chain with a valid Memory instance
return ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
return_source_documents=True,
chain_type="stuff",
combine_docs_chain_kwargs={"prompt": user_prompt},
memory=memory,
verbose=False,
)
def summarize_messages(chat_history: MongoDBChatMessageHistory) -> bool:
"""
If the chat history grows too long, summarize it to keep the memory concise.
Returns True if a summary was performed.
"""
messages = chat_history.messages
if not messages:
return False
summarization_prompt = ChatPromptTemplate.from_messages([
("system", "Summarize the following conversation into a concise message:"),
("human", "{chat_history}")
])
summarization_chain = summarization_prompt | llm
summary = summarization_chain.invoke({"chat_history": messages})
chat_history.clear()
chat_history.add_ai_message(summary.content)
return True
def stream_chat_response(user_id: str, chat_id: str, query: str):
"""
Given a user_id, chat_id, and a query string, streams back the AI response
while persisting both user and AI messages to MongoDB.
"""
# Ensure the chain and memory are set up
chain = get_chain_for_user(user_id, chat_id)
# Since we used ConversationBufferMemory, the underlying MongoDBChatMessageHistory is accessible at:
chat_memory_wrapper = chain.memory # type: ConversationBufferMemory
mongo_history = chat_memory_wrapper.chat_memory # type: MongoDBChatMessageHistory
# Optionally summarize if too many messages
summarize_messages(mongo_history)
# Add the user message to history
mongo_history.add_user_message(query)
# Stream the response
response_accum = ""
for chunk in chain.stream({"question": query, "chat_history": mongo_history.messages}):
if "answer" in chunk:
print(chunk["answer"], end="", flush=True)
response_accum += chunk["answer"]
else:
# Unexpected chunk format
print(f"[Unexpected chunk]: {chunk}")
# Persist the AI's final message
if response_accum:
mongo_history.add_ai_message(response_accum)
|