Nikhithapotnuru commited on
Commit
541d841
·
verified ·
1 Parent(s): ad0f574

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +220 -0
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ from pathlib import Path
4
+ from dotenv import load_dotenv
5
+
6
+ import streamlit as st
7
+ from langchain_community.document_loaders import PyPDFLoader
8
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
9
+ from langchain_community.vectorstores import FAISS
10
+
11
+ # Google (Gemini) bindings
12
+ import google.generativeai as genai
13
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
14
+
15
+ # Load local .env if present (Spaces uses Secrets; .env optional)
16
+ load_dotenv()
17
+
18
+ # -------------------------
19
+ # Config / paths
20
+ # -------------------------
21
+ # On Hugging Face Spaces, use writable path /workspace or repo root.
22
+ WORKDIR = Path("/workspace") if Path("/workspace").exists() else Path(".")
23
+ DATA_FILE = WORKDIR / "350_QA_dataset.pdf" # copy dataset into the repo, or mount/upload
24
+ VECTOR_DIR = WORKDIR / "vectorstore"
25
+
26
+ # Read the Google API key from environment (Spaces secrets show up as env vars)
27
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API") # You will set this in Space Secrets
28
+
29
+ # -------------------------
30
+ # Utility: pick available models dynamically
31
+ # -------------------------
32
+ def pick_models():
33
+ """
34
+ Query genai.list_models() and pick:
35
+ - an embedding model (supports 'embedText')
36
+ - a generation/chat model (supports 'generateContent' or 'generateText')
37
+ Returns (embedding_model_name, chat_model_name)
38
+ """
39
+ genai.configure(api_key=GOOGLE_API_KEY)
40
+ models = genai.list_models()
41
+
42
+ embed_model = None
43
+ chat_model = None
44
+ for m in models:
45
+ caps = getattr(m, "supported_generation_methods", None) or getattr(m, "capabilities", None) or []
46
+ # normalize to list of strings
47
+ caps = list(caps)
48
+ if "embedText" in caps and embed_model is None:
49
+ embed_model = m.name
50
+ if ("generateContent" in caps or "generateText" in caps) and chat_model is None:
51
+ chat_model = m.name
52
+ if embed_model and chat_model:
53
+ break
54
+
55
+ # Fallback defaults (if unavailable in your account, the list_models() result will guide you)
56
+ if embed_model is None:
57
+ embed_model = "models/text-embedding-004"
58
+ if chat_model is None:
59
+ # do not hardcode gemini; fallback to a safe chat model name if available in your account
60
+ chat_model = "models/chat-bison-001" # may still 404 if not available; the UI will show available list
61
+ return embed_model, chat_model
62
+
63
+
64
+ # -------------------------
65
+ # Build vector store
66
+ # -------------------------
67
+ def build_vectorstore(embedding_model):
68
+ if not DATA_FILE.exists():
69
+ raise FileNotFoundError(f"{DATA_FILE} not found in repo. Upload the PDF to the repo root or /workspace.")
70
+
71
+ st.info("Loading PDF and creating chunks (this runs once)...")
72
+ loader = PyPDFLoader(str(DATA_FILE))
73
+ docs = loader.load()
74
+
75
+ splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=150)
76
+ chunks = splitter.split_documents(docs)
77
+
78
+ st.info(f"Using embedding model: {embedding_model}")
79
+ embeddings = GoogleGenerativeAIEmbeddings(model=embedding_model, google_api_key=GOOGLE_API_KEY)
80
+
81
+ vectorstore = FAISS.from_documents(chunks, embeddings)
82
+ VECTOR_DIR.mkdir(parents=True, exist_ok=True)
83
+ vectorstore.save_local(str(VECTOR_DIR))
84
+ st.success("Vector store built and saved.")
85
+
86
+
87
+ # -------------------------
88
+ # Load vector store
89
+ # -------------------------
90
+ def load_vectorstore(embedding_model):
91
+ if not VECTOR_DIR.exists():
92
+ st.warning("Vectorstore not found. Build it first (button above).")
93
+ return None
94
+ embeddings = GoogleGenerativeAIEmbeddings(model=embedding_model, google_api_key=GOOGLE_API_KEY)
95
+ return FAISS.load_local(str(VECTOR_DIR), embeddings, allow_dangerous_deserialization=True)
96
+
97
+
98
+ # -------------------------
99
+ # Generate answer (RAG)
100
+ # -------------------------
101
+ SYSTEM_PROMPT = """
102
+ You are an EV Service Expert Assistant for a customer support team of an electric vehicle manufacturer.
103
+ Your primary knowledge source is an internal 350-entry complaint and resolution knowledge base extracted from "350_QA_dataset.pdf".
104
+
105
+ You will receive retrieved chunks and the user's query. Use ONLY the retrieved context.
106
+
107
+ Respond using this structure:
108
+ 1. Issue summary
109
+ 2. Likely cause / explanation
110
+ 3. Recommended solution / actions
111
+ 4. When to visit the service center
112
+
113
+ If no matching context exists, say:
114
+ "This specific issue is not covered in my internal EV complaint database. Based on general patterns, here are some safe next steps..."
115
+ """
116
+
117
+ def answer_query(chat_model_name, query, vectorstore, history=None, k=5):
118
+ # similarity search
119
+ docs = vectorstore.similarity_search(query, k=k) if vectorstore else []
120
+ context = "\n\n---\n\n".join(d.page_content for d in docs) if docs else "[No matching context found]"
121
+
122
+ # build prompt
123
+ history_text = ""
124
+ if history:
125
+ for m in history:
126
+ role = "User" if m["role"] == "user" else "Assistant"
127
+ history_text += f"{role}: {m['content']}\n"
128
+ if not history_text:
129
+ history_text = "[No previous messages]"
130
+
131
+ prompt = [
132
+ SYSTEM_PROMPT,
133
+ "\n\nConversation history:\n",
134
+ history_text,
135
+ "\n\nRetrieved context:\n",
136
+ context,
137
+ "\n\nCurrent user question:\n",
138
+ query,
139
+ ]
140
+
141
+ model = genai.GenerativeModel(chat_model_name)
142
+ resp = model.generate_content(prompt)
143
+ return resp.text, docs
144
+
145
+
146
+ # -------------------------
147
+ # Streamlit UI
148
+ # -------------------------
149
+ st.set_page_config(page_title="EV Service Assistant (Spaces)", layout="centered")
150
+ st.title("⚡ EV Service Expert (Hugging Face Space)")
151
+
152
+ if not GOOGLE_API_KEY:
153
+ st.error("Missing Google API key. Go to your Space settings → Secrets and add a secret named GOOGLE_API with your API key.")
154
+ st.stop()
155
+
156
+ with st.expander("Available models (fetched from Google)"):
157
+ try:
158
+ embed_model_name, chat_model_name = pick_models()
159
+ st.write("Embedding model selected:", embed_model_name)
160
+ st.write("Chat model selected:", chat_model_name)
161
+ except Exception as e:
162
+ st.error(f"Could not list models: {e}")
163
+ st.stop()
164
+
165
+ col1, col2 = st.columns([1, 2])
166
+ with col1:
167
+ if not VECTOR_DIR.exists():
168
+ if st.button("Build vector store from PDF"):
169
+ try:
170
+ build_vectorstore(embed_model_name)
171
+ except Exception as e:
172
+ st.error(f"Failed to build vectorstore: {e}")
173
+ else:
174
+ st.success("Vectorstore already exists.")
175
+ if st.button("Rebuild vectorstore (force)"):
176
+ try:
177
+ if VECTOR_DIR.exists():
178
+ import shutil
179
+ shutil.rmtree(VECTOR_DIR)
180
+ build_vectorstore(embed_model_name)
181
+ except Exception as e:
182
+ st.error(f"Rebuild failed: {e}")
183
+
184
+ with col2:
185
+ st.markdown("**System prompt (fixed):**")
186
+ st.code(SYSTEM_PROMPT, language="text")
187
+
188
+ # chat UI
189
+ if "messages" not in st.session_state:
190
+ st.session_state.messages = []
191
+
192
+ query = st.text_input("Describe the EV issue you want help with:")
193
+ if st.button("Submit") and query.strip():
194
+ vectorstore = load_vectorstore(embed_model_name)
195
+ if vectorstore is None:
196
+ st.error("Vectorstore not available. Build it first.")
197
+ else:
198
+ with st.spinner("Searching knowledge base and generating answer..."):
199
+ try:
200
+ answer, docs = answer_query(chat_model_name, query, vectorstore, history=st.session_state.messages, k=5)
201
+ except Exception as e:
202
+ st.error(f"Generation failed: {e}")
203
+ answer, docs = "[Error generating answer]", []
204
+
205
+ st.session_state.messages.append({"role": "user", "content": query})
206
+ st.session_state.messages.append({"role": "assistant", "content": answer, "sources": docs})
207
+
208
+ # render messages
209
+ for msg in st.session_state.messages:
210
+ if msg["role"] == "user":
211
+ st.markdown(f"**User:** {msg['content']}")
212
+ else:
213
+ st.markdown(f"**Assistant:** {msg['content']}")
214
+ if msg.get("sources"):
215
+ with st.expander("Retrieved context"):
216
+ for i, d in enumerate(msg["sources"], 1):
217
+ page = d.metadata.get("page", "n/a")
218
+ st.markdown(f"**Chunk {i} — page {page}**")
219
+ st.write(d.page_content)
220
+ st.markdown("---")