AlirezaHSZ commited on
Commit
c00c636
·
verified ·
1 Parent(s): 3570600

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -98
app.py CHANGED
@@ -3,22 +3,20 @@ import fitz # PyMuPDF
3
  import streamlit as st
4
  import google.generativeai as genai
5
  from dotenv import load_dotenv
6
- from google.api_core.exceptions import GoogleAPIError, InvalidArgument
7
 
8
  from langchain.text_splitter import RecursiveCharacterTextSplitter
9
  from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
10
  from langchain_community.vectorstores import FAISS
11
- from langchain.chains.question_answering import load_qa_chain
12
  from langchain.prompts import PromptTemplate
 
 
13
 
14
-
15
- # Load environment variables
16
  load_dotenv()
17
  api_key = os.getenv("GOOGLE_API_KEY")
18
  genai.configure(api_key=api_key)
19
 
20
-
21
- # ✅ Function to read all PDF files (Farsi + English)
22
  def get_pdf_text(pdf_docs):
23
  text = ""
24
  for pdf in pdf_docs:
@@ -29,15 +27,12 @@ def get_pdf_text(pdf_docs):
29
  text += page_text + "\n"
30
  return text
31
 
32
-
33
- # ✅ Function to split text into chunks
34
  def get_text_chunks(text):
35
  splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
36
- chunks = splitter.split_text(text)
37
- return chunks
38
 
39
-
40
- # ✅ Function to get embeddings for each chunk and save to vector store
41
  def get_vector_store(chunks):
42
  try:
43
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
@@ -46,94 +41,89 @@ def get_vector_store(chunks):
46
  except Exception as e:
47
  raise RuntimeError(f"Error creating vector store: {e}")
48
 
49
-
50
- # ✅ Function to get conversational chain (map_reduce برای متن‌های بلند)
51
  def get_conversational_chain():
52
- map_prompt = """
53
- You are a helpful assistant. Summarize the following context to capture the key points
54
- that are relevant for answering the final question.
55
- Do NOT add extra info, just summarize faithfully.
56
-
57
- Context:
58
- {context}
59
-
60
- Summary:
61
- """
62
-
63
- combine_prompt = """
64
- You are a helpful assistant. Answer the question as detailed as possible
65
- using ONLY the provided summaries.
66
- If the answer is not in the summaries, say: "answer is not available in the context".
67
- Do not make up answers.
 
 
 
 
 
 
68
 
69
- Summaries:
70
- {summaries}
71
 
72
- Question:
73
- {question}
 
74
 
75
- Answer:
76
- """
 
 
 
77
 
78
- try:
79
- model = ChatGoogleGenerativeAI(model="gemini-2.5-pro", client=genai, temperature=0.3)
80
-
81
- map_prompt_template = PromptTemplate(template=map_prompt, input_variables=["context"])
82
- combine_prompt_template = PromptTemplate(template=combine_prompt, input_variables=["summaries", "question"])
83
-
84
- chain = load_qa_chain(
85
- llm=model,
86
- chain_type="map_reduce",
87
- map_prompt=map_prompt_template,
88
- combine_prompt=combine_prompt_template
89
- )
90
- return chain
91
- except Exception as e:
92
- raise RuntimeError(f"Error creating conversational chain: {e}")
93
 
 
 
 
 
 
 
 
 
94
 
95
- # Function to clear chat history
96
  def clear_chat_history():
97
  st.session_state.messages = [{"role": "assistant", "content": "در خدمتیم"}]
98
 
99
-
100
- # ✅ Function to handle user input
101
  def user_input(user_question):
102
  try:
103
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
104
  new_db = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
105
 
106
- # similarity_search with more results (better recall for long docs)
107
  docs = new_db.similarity_search(user_question, k=12)
108
 
 
109
  chain = get_conversational_chain()
110
- response = chain({"input_documents": docs, "question": user_question}, return_only_outputs=True)
 
111
  return response
112
  except Exception as e:
113
  raise RuntimeError(f"Error while answering: {e}")
114
 
115
-
116
- # ✅ Main function to run the Streamlit app
117
  def main():
118
- st.set_page_config(
119
- page_title="Chatbot",
120
- layout="wide",
121
- initial_sidebar_state="expanded"
122
- )
123
 
124
- # Dark theme styling
125
  st.markdown(
126
  """
127
  <style>
128
- body {
129
- background-color: #000000;
130
- color: #ffffff;
131
- }
132
- .main {
133
- background-color: #333333;
134
- padding: 20px;
135
- border-radius: 10px;
136
- }
137
  </style>
138
  """,
139
  unsafe_allow_html=True
@@ -143,9 +133,8 @@ def main():
143
  st.session_state.uploaded = False
144
 
145
  if not st.session_state.uploaded:
146
- # Upload Page
147
  st.title("Your personal assistant ...")
148
- pdf_docs = st.file_uploader("فایل پی دی اف مورد نظر را آپلود کنید ", accept_multiple_files=True)
149
  if st.button("تایید"):
150
  if pdf_docs:
151
  try:
@@ -158,9 +147,8 @@ def main():
158
  except RuntimeError as e:
159
  st.error(str(e))
160
  else:
161
- st.error("لطفا حداقل یک فایل را آپلود کنید")
162
  else:
163
- # Chat Page
164
  st.title("Assistant ready ...")
165
  st.write("میتونین سوالتونو بپرسین 👇")
166
 
@@ -169,53 +157,41 @@ def main():
169
  clear_chat_history()
170
  st.rerun()
171
 
172
- st.button('حذف مکالمه', on_click=clear_chat_history)
173
 
174
  if "messages" not in st.session_state:
175
  st.session_state.messages = [{"role": "assistant", "content": "در خدمتیم"}]
176
 
177
- # Show history
178
  for message in st.session_state.messages:
179
  with st.chat_message(message["role"]):
180
  st.markdown(
181
- f"""
182
- <div style="direction: rtl; text-align: right; font-size: 16px;">
183
- {message["content"]}
184
- </div>
185
- """,
186
  unsafe_allow_html=True
187
  )
188
 
 
189
  if prompt := st.chat_input():
190
  st.session_state.messages.append({"role": "user", "content": prompt})
191
  with st.chat_message("user"):
192
  st.markdown(
193
- f"""
194
- <div style="direction: rtl; text-align: right; font-size: 16px;">
195
- {prompt}
196
- </div>
197
- """,
198
  unsafe_allow_html=True
199
  )
200
 
201
  if st.session_state.messages[-1]["role"] != "assistant":
202
  try:
203
  with st.chat_message("assistant"):
204
- response = user_input(prompt)
205
- if response:
206
- full_response = response['output_text']
207
  st.markdown(
208
- f"""
209
- <div style="direction: rtl; text-align: right; font-size: 16px;">
210
- {full_response}
211
- </div>
212
- """,
213
  unsafe_allow_html=True
214
  )
215
  st.session_state.messages.append({"role": "assistant", "content": full_response})
216
  except RuntimeError as e:
217
  st.error(str(e))
218
 
219
-
220
  if __name__ == "__main__":
221
  main()
 
3
  import streamlit as st
4
  import google.generativeai as genai
5
  from dotenv import load_dotenv
 
6
 
7
  from langchain.text_splitter import RecursiveCharacterTextSplitter
8
  from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
9
  from langchain_community.vectorstores import FAISS
 
10
  from langchain.prompts import PromptTemplate
11
+ from langchain.chains import LLMChain
12
+ from langchain.chains import MapReduceDocumentsChain, ReduceDocumentsChain, StuffDocumentsChain
13
 
14
+ # ========== Config ==========
 
15
  load_dotenv()
16
  api_key = os.getenv("GOOGLE_API_KEY")
17
  genai.configure(api_key=api_key)
18
 
19
+ # ========== PDF to text (fa+en) ==========
 
20
  def get_pdf_text(pdf_docs):
21
  text = ""
22
  for pdf in pdf_docs:
 
27
  text += page_text + "\n"
28
  return text
29
 
30
+ # ========== Chunking ==========
 
31
  def get_text_chunks(text):
32
  splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
33
+ return splitter.split_text(text)
 
34
 
35
+ # ========== Build & save vector store ==========
 
36
  def get_vector_store(chunks):
37
  try:
38
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
 
41
  except Exception as e:
42
  raise RuntimeError(f"Error creating vector store: {e}")
43
 
44
+ # ========== Map-Reduce QA chain (بدون load_qa_chain) ==========
 
45
  def get_conversational_chain():
46
+ # Map: برای هر تکه، خلاصه/نکات مرتبط با سؤال را بیرون بکش
47
+ map_prompt_tmpl = PromptTemplate(
48
+ template=(
49
+ "You are a helpful assistant. Extract a concise summary strictly from the context "
50
+ "that would help answer the final question. Do NOT add outside info.\n\n"
51
+ "Context:\n{context}\n\n"
52
+ "Question:\n{question}\n\n"
53
+ "Summary:"
54
+ ),
55
+ input_variables=["context", "question"],
56
+ )
57
+ # Reduce/Combine: پاسخ نهایی با اتکا به خلاصه‌های مرحله Map
58
+ combine_prompt_tmpl = PromptTemplate(
59
+ template=(
60
+ "You are a helpful assistant. Answer the question using ONLY the provided summaries. "
61
+ "If the answer is not in the summaries, say: \"answer is not available in the context\".\n\n"
62
+ "Summaries:\n{summaries}\n\n"
63
+ "Question:\n{question}\n\n"
64
+ "Answer:"
65
+ ),
66
+ input_variables=["summaries", "question"],
67
+ )
68
 
69
+ model = ChatGoogleGenerativeAI(model="gemini-2.5-pro", client=genai, temperature=0.3)
 
70
 
71
+ # LLM chains برای دو مرحله
72
+ map_llm_chain = LLMChain(llm=model, prompt=map_prompt_tmpl) # expects: context, question
73
+ combine_llm_chain = LLMChain(llm=model, prompt=combine_prompt_tmpl) # expects: summaries, question
74
 
75
+ # StuffDocumentsChain برای ترکیب خلاصه‌ها
76
+ combine_documents_chain = StuffDocumentsChain(
77
+ llm_chain=combine_llm_chain,
78
+ document_variable_name="summaries" # باید با ورودی پرامپت combine یکی باشد
79
+ )
80
 
81
+ # Reduce chain
82
+ reduce_chain = ReduceDocumentsChain(
83
+ combine_documents_chain=combine_documents_chain,
84
+ collapse_documents_chain=None, # می‌تونی در صورت خیلی زیاد بودن خلاصه‌ها یک collapse هم اضافه کنی
85
+ token_max=None
86
+ )
 
 
 
 
 
 
 
 
 
87
 
88
+ # Map-Reduce chain اصلی
89
+ chain = MapReduceDocumentsChain(
90
+ llm_chain=map_llm_chain, # expects: context, question
91
+ reduce_documents_chain=reduce_chain,
92
+ document_variable_name="context", # باید با ورودی پرامپت map یکی باشد
93
+ return_intermediate_steps=False,
94
+ )
95
+ return chain
96
 
97
+ # ========== Chat history utils ==========
98
  def clear_chat_history():
99
  st.session_state.messages = [{"role": "assistant", "content": "در خدمتیم"}]
100
 
101
+ # ========== Ask ==========
 
102
  def user_input(user_question):
103
  try:
104
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
105
  new_db = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
106
 
107
+ # برای متن‌های بلند recall را افزایش بده
108
  docs = new_db.similarity_search(user_question, k=12)
109
 
110
+ # اجرای زنجیره
111
  chain = get_conversational_chain()
112
+ response = chain.invoke({"input_documents": docs, "question": user_question})
113
+ # MapReduceDocumentsChain خروجی را در output_text می‌دهد
114
  return response
115
  except Exception as e:
116
  raise RuntimeError(f"Error while answering: {e}")
117
 
118
+ # ========== UI ==========
 
119
  def main():
120
+ st.set_page_config(page_title="Chatbot", layout="wide", initial_sidebar_state="expanded")
 
 
 
 
121
 
 
122
  st.markdown(
123
  """
124
  <style>
125
+ body { background-color: #000000; color: #ffffff; }
126
+ .main { background-color: #333333; padding: 20px; border-radius: 10px; }
 
 
 
 
 
 
 
127
  </style>
128
  """,
129
  unsafe_allow_html=True
 
133
  st.session_state.uploaded = False
134
 
135
  if not st.session_state.uploaded:
 
136
  st.title("Your personal assistant ...")
137
+ pdf_docs = st.file_uploader("فایل پیدیاف(ها) را آپلود کنید", type="pdf", accept_multiple_files=True)
138
  if st.button("تایید"):
139
  if pdf_docs:
140
  try:
 
147
  except RuntimeError as e:
148
  st.error(str(e))
149
  else:
150
+ st.error("لطفاً حداقل یک فایل را آپلود کنید")
151
  else:
 
152
  st.title("Assistant ready ...")
153
  st.write("میتونین سوالتونو بپرسین 👇")
154
 
 
157
  clear_chat_history()
158
  st.rerun()
159
 
160
+ st.button("حذف مکالمه", on_click=clear_chat_history)
161
 
162
  if "messages" not in st.session_state:
163
  st.session_state.messages = [{"role": "assistant", "content": "در خدمتیم"}]
164
 
165
+ # نمایش تاریخچه
166
  for message in st.session_state.messages:
167
  with st.chat_message(message["role"]):
168
  st.markdown(
169
+ f"<div style='direction: rtl; text-align: right; font-size: 16px;'>{message['content']}</div>",
 
 
 
 
170
  unsafe_allow_html=True
171
  )
172
 
173
+ # ورودی چت
174
  if prompt := st.chat_input():
175
  st.session_state.messages.append({"role": "user", "content": prompt})
176
  with st.chat_message("user"):
177
  st.markdown(
178
+ f"<div style='direction: rtl; text-align: right; font-size: 16px;'>{prompt}</div>",
 
 
 
 
179
  unsafe_allow_html=True
180
  )
181
 
182
  if st.session_state.messages[-1]["role"] != "assistant":
183
  try:
184
  with st.chat_message("assistant"):
185
+ result = user_input(prompt)
186
+ if result:
187
+ full_response = result.get("output_text", "")
188
  st.markdown(
189
+ f"<div style='direction: rtl; text-align: right; font-size: 16px;'>{full_response}</div>",
 
 
 
 
190
  unsafe_allow_html=True
191
  )
192
  st.session_state.messages.append({"role": "assistant", "content": full_response})
193
  except RuntimeError as e:
194
  st.error(str(e))
195
 
 
196
  if __name__ == "__main__":
197
  main()