Spaces:
Running
Running
| from langchain_groq import ChatGroq | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_core.output_parsers import StrOutputParser | |
| from dotenv import load_dotenv | |
| import os | |
| load_dotenv() | |
| def get_llm(): | |
| """Groq LLM (Llama 3.1) returns a concise answer based on the provided context and question.""" | |
| return ChatGroq( | |
| api_key=os.getenv("GROQ_API_KEY"), | |
| model="llama-3.1-8b-instant", | |
| temperature=0, # never want randomness in RAG answers, want dirct answers based on retrieved context | |
| ) | |
| def build_prompt(): | |
| """Create a prompt template that instructs the LLM to answer the question based on the retrieved context.""" | |
| template = """You are a helpful assistant. Answer the question | |
| based ONLY on the context below. If the answer is not in the | |
| context, say "I don't have enough information to answer that." | |
| Context: | |
| {context} | |
| Question: {question} | |
| Answer:""" | |
| return ChatPromptTemplate.from_template(template) | |
| def format_docs(docs): | |
| """the context is the retrieved documents (chunks). | |
| We format them as a single string to feed into the prompt.""" | |
| return "\n\n".join(doc.page_content for doc in docs) | |
| def answer_question(vectorstore, question, k=3): | |
| """ | |
| Full RAG pipeline: | |
| Retrieve → Augment (prompt) → Generate | |
| """ | |
| # 1. Retrieve: Find relevant chunks related to the question | |
| retrieved_docs = vectorstore.similarity_search( | |
| question, | |
| k=k | |
| ) | |
| context = format_docs(retrieved_docs) | |
| # 2. Augment: content push to prompt | |
| prompt = build_prompt() | |
| # 3. Generate: LLM gets the prompt and generates an answer | |
| llm = get_llm() | |
| chain = prompt | llm | StrOutputParser() | |
| answer = chain.invoke({"context": context, "question": question}) | |
| return answer, retrieved_docs | |
| def rewrite_query(question, chat_history): | |
| """ | |
| User এর প্রশ্নকে retrieval-friendly করে rewrite করে। | |
| - Follow-up প্রশ্নে আগের context যোগ করে | |
| - Vague শব্দ expand করে | |
| """ | |
| # History না থাকলে আর প্রশ্ন বড় হলে — rewrite এর দরকার কম | |
| history_text = "" | |
| for turn in chat_history[-3:]: # শুধু শেষ ৩ turn (token বাঁচাতে) | |
| history_text += f"User: {turn['question']}\n" | |
| history_text += f"Assistant: {turn['answer'][:150]}\n" | |
| rewrite_prompt = """Given the conversation history and a follow-up | |
| question, rewrite the question into a standalone search query that will | |
| retrieve relevant information from a document. | |
| Rules: | |
| - Make it self-contained (resolve "he", "it", "that" using history) | |
| - Expand vague terms (e.g., "specialization" → "skills expertise field") | |
| - Keep it concise (one line) | |
| - Output ONLY the rewritten query, nothing else | |
| Conversation History: | |
| {history} | |
| Follow-up Question: {question} | |
| Standalone Search Query:""" | |
| prompt = ChatPromptTemplate.from_template(rewrite_prompt) | |
| llm = get_llm() | |
| chain = prompt | llm | StrOutputParser() | |
| rewritten = chain.invoke({ | |
| "history": history_text if history_text else "(none)", | |
| "question": question, | |
| }) | |
| return rewritten.strip() | |
| def answer_with_memory(vectorstore, question, chat_history, k=6): | |
| """ | |
| Query rewriting সহ RAG। | |
| """ | |
| # ১. Query Rewriting — retrieve করার আগে প্রশ্ন উন্নত করি | |
| search_query = rewrite_query(question, chat_history) | |
| # ২. Retrieve (rewritten query দিয়ে) | |
| retrieved_docs = vectorstore.similarity_search(search_query, k=k) | |
| context = format_docs(retrieved_docs) | |
| # ৩. History কে text এ রূপান্তর | |
| history_text = "" | |
| for turn in chat_history: | |
| history_text += f"User: {turn['question']}\n" | |
| history_text += f"Assistant: {turn['answer']}\n" | |
| # ৪. Answer generation (মূল প্রশ্ন দিয়ে, rewritten না) | |
| template = """You are a friendly assistant for a document Q&A system. | |
| The "Context" below is content from a document the user uploaded. | |
| Guidelines: | |
| - If the user greets you (hi, hello) or asks who you are, respond warmly | |
| and briefly explain you answer questions about their uploaded document. | |
| - When the user refers to "the file", "the document", "this", or asks to | |
| "summarize", they mean the uploaded document in the Context. | |
| - For document questions, answer based ONLY on the Context. | |
| - For document questions, answer based on the Context. Look carefully | |
| through ALL provided context sections before concluding information | |
| isn't there. Only say "I couldn't find that in the document" if the | |
| information is genuinely absent. | |
| - Answer in the SAME language as the document content in the Context. | |
| If the Context is in English, answer in English. If the document | |
| question is in a different language, you may still answer in the | |
| document's language. | |
| Conversation History: | |
| {history} | |
| Context: | |
| {context} | |
| Question: {question} | |
| Answer:""" | |
| prompt = ChatPromptTemplate.from_template(template) | |
| llm = get_llm() | |
| chain = prompt | llm | StrOutputParser() | |
| answer = chain.invoke({ | |
| "history": history_text, | |
| "context": context, | |
| "question": question, # মূল প্রশ্ন — natural উত্তরের জন্য | |
| }) | |
| return answer, retrieved_docs |