File size: 2,511 Bytes
02fb7d4 98e5020 02fb7d4 98e5020 02fb7d4 | 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 | from pathlib import Path
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.documents import Document
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_google_genai import ChatGoogleGenerativeAI
from dotenv import load_dotenv
load_dotenv(override=True)
MODEL = "gemini-2.5-flash"
DB_NAME = str(Path(__file__).parent.parent / "vector_db")
embeddings = HuggingFaceEmbeddings(model_name="Qwen/Qwen3-Embedding-0.6B", model_kwargs={"trust_remote_code": True})
RETRIEVAL_K = 10
llm = ChatGoogleGenerativeAI(model=MODEL)
SYSTEM_PROMPT = """
You are an AI assistant for Anurag's portfolio.
Your job is to give clean, structured, and easy-to-read answers.
STRICT RULES:
- Never output raw markdown like ** or ##
- Never dump raw context
- Always summarize information
- Keep answers concise and structured
FORMATTING RULES:
If the user asks to list projects:
Return in this format:
Projects:
1. Project Name
- Description: short 1-line summary
- Tech: comma-separated tools
- Purpose: what problem it solves
2. Project Name
- Description: ...
- Tech: ...
- Purpose: ...
If the user asks about ONE project:
Return:
Project: <name>
Description: ...
Tech: ...
Key Features:
- ...
- ...
GENERAL RULES:
- Use bullet points instead of paragraphs
- Keep spacing clean
- No long text blocks
Context:
{context}
"""
vectorstore = Chroma(persist_directory=DB_NAME, embedding_function=embeddings)
retriever = vectorstore.as_retriever()
def fetch_context(question: str) -> list[Document]:
return retriever.invoke(question, k=RETRIEVAL_K)
def combined_question(question: str, history: list[dict] = []) -> str:
prior = "\n".join(m["content"] for m in history if m["role"] == "user")
return prior + "\n" + question
def answer_question(question: str, history: list[dict] = []) -> tuple[str, list[Document]]:
combined = combined_question(question, history)
docs = fetch_context(combined)
context = "\n\n".join(doc.page_content for doc in docs)
system_prompt = SYSTEM_PROMPT.format(context=context)
messages = [SystemMessage(content=system_prompt)]
for m in history:
if m["role"] == "user":
messages.append(HumanMessage(content=m["content"]))
else:
messages.append(AIMessage(content=m["content"]))
messages.append(HumanMessage(content=question))
response = llm.invoke(messages)
return response.content, docs |