| |
| import os |
| import streamlit as st |
| import numpy as np |
| import faiss |
| from sentence_transformers import SentenceTransformer |
| from typing import List, Tuple |
| from pathlib import Path |
| from langchain.schema import SystemMessage, HumanMessage |
| from dotenv import load_dotenv |
| load_dotenv() |
|
|
| import google.generativeai as genai |
|
|
| |
| |
| |
| st.set_page_config(page_title="SystemVerilog Chatbot (Modern LangChain)", layout="wide") |
|
|
| |
| GENAI_API_KEY = os.getenv("googleapikey") |
| HF_TOKEN = os.getenv("hf") |
|
|
| if not GENAI_API_KEY: |
| st.error("Please set environment variable googleapikey") |
| st.stop() |
|
|
| genai.configure(api_key=GENAI_API_KEY) |
|
|
| |
| |
| |
| def load_text(path: str) -> str: |
| p = Path(path) |
| if not p.exists(): |
| st.error(f"File not found: {path}") |
| st.stop() |
| return p.read_text(encoding="utf-8") |
|
|
| def simple_recursive_split(text: str, chunk_size: int = 1000, overlap: int = 200) -> List[str]: |
| """A simple recursive character text splitter. Keeps splits at whitespace when possible.""" |
| chunks = [] |
| start = 0 |
| text_len = len(text) |
| while start < text_len: |
| end = min(start + chunk_size, text_len) |
| |
| if end < text_len: |
| last_space = text.rfind(" ", start, end) |
| if last_space > start: |
| end = last_space |
| chunk = text[start:end].strip() |
| if chunk: |
| chunks.append(chunk) |
| start = end - overlap |
| if start < 0: |
| start = 0 |
| if start >= text_len: |
| break |
| return chunks |
|
|
| |
| |
| |
| @st.experimental_singleton |
| def load_embedding_model(model_name: str = "sentence-transformers/all-MiniLM-L6-v2"): |
| return SentenceTransformer(model_name) |
|
|
| @st.experimental_singleton |
| def build_faiss_index(texts: List[str], embed_model: SentenceTransformer) -> Tuple[faiss.IndexFlatIP, np.ndarray]: |
| |
| embeddings = embed_model.encode(texts, show_progress_bar=True, convert_to_numpy=True) |
| |
| norms = np.linalg.norm(embeddings, axis=1, keepdims=True) |
| norms[norms == 0] = 1.0 |
| embeddings = embeddings / norms |
| d = embeddings.shape[1] |
| index = faiss.IndexFlatIP(d) |
| index.add(embeddings.astype("float32")) |
| return index, embeddings |
|
|
| def retrieve_top_k(query: str, k: int, index: faiss.IndexFlatIP, embed_model: SentenceTransformer, texts: List[str]) -> List[Tuple[int, float, str]]: |
| q_emb = embed_model.encode([query], convert_to_numpy=True) |
| q_emb = q_emb / np.linalg.norm(q_emb, axis=1, keepdims=True) |
| D, I = index.search(q_emb.astype("float32"), k) |
| results = [] |
| for idx, score in zip(I[0], D[0]): |
| results.append((int(idx), float(score), texts[idx])) |
| return results |
|
|
| |
| |
| |
| def call_gemini(prompt: str, model: str = "gemini-2.0-flash", temperature: float = 0.2, max_output_tokens: int = 600) -> str: |
| """ |
| Use the google.generativeai SDK to generate text. |
| NOTE: SDK function names vary between versions. If this exact call fails for your installed SDK, |
| replace the body with the appropriate `genai` call. Example alternatives appear in comments below. |
| """ |
| |
| |
| resp = genai.generate( |
| model=model, |
| temperature=temperature, |
| max_output_tokens=max_output_tokens, |
| |
| |
| input=prompt |
| ) |
| |
| |
| try: |
| |
| if hasattr(resp, "candidates"): |
| return resp.candidates[0].output[0].content[0].text |
| |
| if hasattr(resp, "output_text"): |
| return resp.output_text |
| |
| return str(resp) |
| except Exception: |
| return str(resp) |
|
|
| |
| |
| |
| DOC_PATH = "pdf_extracted_text.txt" |
| raw_text = load_text(DOC_PATH) |
| chunks = simple_recursive_split(raw_text, chunk_size=1000, overlap=500) |
|
|
| embed_model = load_embedding_model() |
| index, embeddings = build_faiss_index(chunks, embed_model) |
|
|
| |
| |
| |
| st.title("🤖 SystemVerilog Documentation Chatbot (Modern LangChain-style)") |
|
|
| if "chat_history" not in st.session_state: |
| st.session_state.chat_history = [] |
|
|
| user_query = st.chat_input("Enter your SystemVerilog question...") |
|
|
| if user_query: |
| |
| k = 6 |
| retrieved = retrieve_top_k(user_query, k, index, embed_model, chunks) |
| context_text = "\n\n".join([f"Chunk {idx} (score {score:.3f}):\n{txt}" for idx, score, txt in retrieved]) |
|
|
| |
| system_msg = SystemMessage(content=( |
| "You are an expert SystemVerilog Verification Engineer and technical educator. " |
| "Answer the user's question using ONLY the provided context. If the context does not contain the answer, say you don't know. " |
| "Be concise, include examples when useful, and annotate code blocks as SystemVerilog." |
| )) |
| human_msg = HumanMessage(content=f"User question: {user_query}\n\nUse the following context:\n{context_text}") |
|
|
| |
| prompt = f"""SYSTEM: |
| {system_msg.content} |
| |
| CONTEXT: |
| {context_text} |
| |
| USER QUESTION: |
| {user_query} |
| |
| INSTRUCTIONS: |
| - Use only the CONTEXT above for factual content. |
| - Provide SystemVerilog code blocks where appropriate, label them as `systemverilog`. |
| - If the context lacks the answer, explicitly say: "I don't have enough information in the document to answer that." |
| """ |
|
|
| |
| gen_text = call_gemini(prompt, model="gemini-2.0-flash", temperature=0.3, max_output_tokens=700) |
|
|
| |
| st.session_state.chat_history.append({"role": "user", "content": user_query}) |
| st.session_state.chat_history.append({"role": "assistant", "content": gen_text}) |
|
|
| |
| for msg in st.session_state.chat_history: |
| if msg["role"] == "user": |
| with st.chat_message("user"): |
| st.markdown(msg["content"]) |
| else: |
| with st.chat_message("assistant"): |
| st.markdown(msg["content"]) |
|
|