muhammadsalmanalfaridzi commited on
Commit
8f0f135
·
verified ·
1 Parent(s): 3f5e803

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -25
app.py CHANGED
@@ -15,11 +15,17 @@ from pathlib import Path
15
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
16
 
17
  # LLM dan indexing
18
- from llama_index.core import Settings, VectorStoreIndex, SimpleDirectoryReader, PromptTemplate
 
 
 
 
19
  from llama_index.llms.cerebras import Cerebras
20
  from llama_index.embeddings.nomic import NomicEmbedding
21
- from llama_index.core.node_parser import MarkdownNodeParser
22
  from llama_index.readers.docling import DoclingReader
 
 
 
23
 
24
  # Speech-to-text dan text-to-speech dengan Groq
25
  from groq import Groq
@@ -54,7 +60,13 @@ groq_client = Groq(api_key=GROQ_API_KEY)
54
  def load_cerebras_llm():
55
  logging.info("Memuat Cerebras LLM")
56
  try:
57
- llm = Cerebras(model="llama-4-scout-17b-16e-instruct", api_key=CEREBRAS_API_KEY)
 
 
 
 
 
 
58
  logging.debug("Cerebras LLM berhasil dimuat")
59
  return llm
60
  except Exception as e:
@@ -67,7 +79,8 @@ def create_embedding():
67
  embed_model = NomicEmbedding(
68
  model_name="nomic-embed-text-v1.5",
69
  vision_model_name="nomic-embed-vision-v1.5",
70
- api_key=NOMIC_API_KEY
 
71
  )
72
  Settings.embed_model = embed_model
73
  logging.debug("Embedding model berhasil di-set")
@@ -97,27 +110,107 @@ def load_documents(file_list):
97
  for doc in docs:
98
  # Menyimpan metadata sumber dokumen
99
  doc.metadata["source"] = file_name
 
100
  documents.append(doc)
101
  if not documents:
102
  logging.error("Tidak ditemukan dokumen yang valid.")
103
  return "Tidak ditemukan dokumen yang valid.", None
104
 
105
  llm = load_cerebras_llm()
106
- create_embedding()
107
- node_parser = MarkdownNodeParser()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  # Custom prompt yang memaksa jawaban hanya berdasarkan dokumen
109
- custom_prompt = """
110
- You are a helpful assistant that can only answer questions based solely on the provided document context.
111
- If the answer is not contained within the document context, respond with "I don't have enough information about that aspect of the document."
112
- Context:
 
 
 
 
 
 
 
113
  {context_str}
114
- Query: {query_str}
115
- Answer:"""
116
- qa_prompt_tmpl = PromptTemplate(custom_prompt)
117
- index = VectorStoreIndex.from_documents(documents, transformations=[node_parser], show_progress=True)
118
- Settings.llm = llm
119
- query_engine = index.as_query_engine(streaming=True)
120
- query_engine.update_prompts({"response_synthesizer:text_qa_template": qa_prompt_tmpl})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  file_key = f"doc-{uuid.uuid4()}"
122
  global_file_cache[file_key] = query_engine
123
  logging.info(f"Berhasil memuat {len(documents)} dokumen: {', '.join(doc_names)} dengan file_key: {file_key}")
@@ -139,15 +232,34 @@ async def document_chat(file_key: str, prompt: str, audio_file=None, translate_a
139
  transcription = transcribe_or_translate_audio(audio_file, translate=translate_audio)
140
  logging.debug(f"Hasil transkripsi: {transcription}")
141
  prompt = f"{prompt} {transcription}".strip()
 
 
 
 
 
 
142
  response = await asyncio.to_thread(query_engine.query, prompt)
143
  answer = str(response)
144
- # Tambahkan informasi sumber dokumen
145
- if hasattr(response, "get_documents"):
146
- docs = response.get_documents()
147
- if docs:
148
- sources = "\n\n".join([f"Source: {doc.metadata.get('source', 'No source')}" for doc in docs])
149
- answer = answer + "\n\n" + sources
150
- return history + [(prompt, answer)]
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  except Exception as e:
152
  logging.error(f"Error processing document_chat: {e}")
153
  return history + [(prompt, f"Error processing query: {str(e)}")]
@@ -253,7 +365,12 @@ def doc_chat_with_tts(prompt, history, file_key, audio_file, translate, voice, e
253
  audio_path = None
254
  else:
255
  logging.info("Memulai konversi jawaban akhir ke audio dengan TTS")
256
- audio_path = convert_text_to_speech(last_assistant, voice)
 
 
 
 
 
257
  logging.info(f"Audio output dihasilkan: {audio_path}")
258
  else:
259
  audio_path = None
 
15
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
16
 
17
  # LLM dan indexing
18
+ from llama_index.core import Settings, VectorStoreIndex, SimpleDirectoryReader, PromptTemplate, ServiceContext
19
+ from llama_index.core.retrievers import VectorIndexRetriever
20
+ from llama_index.core.query_engine import RetrieverQueryEngine
21
+ from llama_index.core.postprocessor import SimilarityPostprocessor, KeywordNodePostprocessor
22
+ from llama_index.core.node_parser import MarkdownNodeParser, SentenceSplitter
23
  from llama_index.llms.cerebras import Cerebras
24
  from llama_index.embeddings.nomic import NomicEmbedding
 
25
  from llama_index.readers.docling import DoclingReader
26
+ from llama_index.core.response_synthesizers import CompactAndRefine
27
+ from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter
28
+ from llama_index.vector_stores.faiss import FaissVectorStore
29
 
30
  # Speech-to-text dan text-to-speech dengan Groq
31
  from groq import Groq
 
60
  def load_cerebras_llm():
61
  logging.info("Memuat Cerebras LLM")
62
  try:
63
+ llm = Cerebras(
64
+ model="llama-4-scout-17b-16e-instruct",
65
+ api_key=CEREBRAS_API_KEY,
66
+ temperature=0.1, # Temperatur rendah untuk mengurangi kreativitas
67
+ max_tokens=1024, # Batasi panjang output
68
+ top_p=0.9 # Mengurangi variasi respons
69
+ )
70
  logging.debug("Cerebras LLM berhasil dimuat")
71
  return llm
72
  except Exception as e:
 
79
  embed_model = NomicEmbedding(
80
  model_name="nomic-embed-text-v1.5",
81
  vision_model_name="nomic-embed-vision-v1.5",
82
+ api_key=NOMIC_API_KEY,
83
+ embed_batch_size=10 # Batching untuk performa
84
  )
85
  Settings.embed_model = embed_model
86
  logging.debug("Embedding model berhasil di-set")
 
110
  for doc in docs:
111
  # Menyimpan metadata sumber dokumen
112
  doc.metadata["source"] = file_name
113
+ doc.metadata["file_name"] = file_name
114
  documents.append(doc)
115
  if not documents:
116
  logging.error("Tidak ditemukan dokumen yang valid.")
117
  return "Tidak ditemukan dokumen yang valid.", None
118
 
119
  llm = load_cerebras_llm()
120
+ embed_model = create_embedding()
121
+
122
+ # Gunakan SentenceSplitter untuk chunking yang lebih baik
123
+ node_parser = SentenceSplitter(
124
+ chunk_size=512, # Ukuran chunk
125
+ chunk_overlap=50, # Overlap antar chunk untuk menjaga konteks
126
+ separator=" ", # Pemisah
127
+ paragraph_separator="\n\n",
128
+ secondary_chunking_regex="[^,.;。]+[,.;。]?",
129
+ )
130
+
131
+ # Set service context untuk pengaturan global
132
+ service_context = ServiceContext.from_defaults(
133
+ llm=llm,
134
+ embed_model=embed_model,
135
+ node_parser=node_parser
136
+ )
137
+ Settings.llm = llm
138
+ Settings.embed_model = embed_model
139
+
140
  # Custom prompt yang memaksa jawaban hanya berdasarkan dokumen
141
+ qa_template = """
142
+ Kamu adalah asisten yang sangat hati-hati yang hanya menjawab berdasarkan informasi yang ada dalam dokumen.
143
+ Jika pertanyaan tidak dapat dijawab hanya berdasarkan konteks, katakan "Maaf, saya tidak menemukan informasi tersebut dalam dokumen yang diberikan."
144
+
145
+ Jika pertanyaannya tidak relevan dengan dokumen, katakan "Pertanyaan ini tidak relevan dengan dokumen yang sedang dianalisis."
146
+
147
+ Jangan pernah mengada-ada atau membuat informasi. Jika kamu tidak yakin, katakan bahwa kamu tidak bisa menjawab dengan pasti berdasarkan dokumen.
148
+
149
+ Saat menjawab, selalu berikan kembali sumber informasimu dengan format yang jelas.
150
+
151
+ Konteks Dokumen:
152
  {context_str}
153
+
154
+ Pertanyaan: {query_str}
155
+
156
+ Jawabanmu (hanya berdasarkan konteks dokumen):
157
+ """
158
+ qa_prompt_tmpl = PromptTemplate(qa_template)
159
+
160
+ # Inisialisasi FAISS Vector Store
161
+ vector_store = FaissVectorStore(dim=embed_model.embed_dim)
162
+
163
+ # Parse dokumen menjadi node
164
+ nodes = node_parser.get_nodes_from_documents(documents)
165
+
166
+ # Embed nodes dan simpan ke FAISS
167
+ for i, node in enumerate(nodes):
168
+ if i % 10 == 0:
169
+ logging.debug(f"Embedding node {i+1}/{len(nodes)}")
170
+ node_embedding = embed_model.get_text_embedding(
171
+ node.get_content(metadata_mode="all")
172
+ )
173
+ node.embedding = node_embedding
174
+ vector_store.add(node_embedding, node.node_id, node)
175
+
176
+ logging.info(f"Berhasil embedding {len(nodes)} nodes ke FAISS vector store")
177
+
178
+ # Buat index dengan FAISS vector store
179
+ index = VectorStoreIndex.from_vector_store(
180
+ vector_store=vector_store,
181
+ service_context=service_context,
182
+ show_progress=True
183
+ )
184
+
185
+ # Buat retriever dengan parameter yang dioptimalkan
186
+ retriever = VectorIndexRetriever(
187
+ index=index,
188
+ similarity_top_k=5, # Ambil 5 dokumen teratas
189
+ vector_store_query_mode="hybrid", # Gunakan hybrid search (keyword + semantic)
190
+ alpha=0.5 # Bobot untuk hybrid search
191
+ )
192
+
193
+ # Buat postprocessor untuk penyaringan hasil retrieval
194
+ postprocessors = [
195
+ SimilarityPostprocessor(similarity_cutoff=0.7), # Hapus hasil dengan skor rendah
196
+ KeywordNodePostprocessor(required_keywords=[]), # Filter by keyword (opsional)
197
+ ]
198
+
199
+ # Buat response synthesizer yang lebih robust
200
+ response_synthesizer = CompactAndRefine(
201
+ service_context=service_context,
202
+ text_qa_template=qa_prompt_tmpl,
203
+ refine_template=qa_prompt_tmpl,
204
+ verbose=True
205
+ )
206
+
207
+ # Buat query engine dengan komponen yang dioptimalkan
208
+ query_engine = RetrieverQueryEngine(
209
+ retriever=retriever,
210
+ response_synthesizer=response_synthesizer,
211
+ node_postprocessors=postprocessors
212
+ )
213
+
214
  file_key = f"doc-{uuid.uuid4()}"
215
  global_file_cache[file_key] = query_engine
216
  logging.info(f"Berhasil memuat {len(documents)} dokumen: {', '.join(doc_names)} dengan file_key: {file_key}")
 
232
  transcription = transcribe_or_translate_audio(audio_file, translate=translate_audio)
233
  logging.debug(f"Hasil transkripsi: {transcription}")
234
  prompt = f"{prompt} {transcription}".strip()
235
+
236
+ # Pastikan prompt valid dan tidak kosong
237
+ if not prompt or prompt.strip() == "":
238
+ return history + [("", "Pertanyaan tidak boleh kosong. Silakan ajukan pertanyaan.")]
239
+
240
+ # Proses query
241
  response = await asyncio.to_thread(query_engine.query, prompt)
242
  answer = str(response)
243
+
244
+ # Tambahkan informasi sumber dokumen dengan format yang lebih jelas
245
+ sources_text = ""
246
+ if hasattr(response, "source_nodes") and response.source_nodes:
247
+ sources = []
248
+ for i, node in enumerate(response.source_nodes, 1):
249
+ source = node.metadata.get('source', 'Tidak ada sumber')
250
+ score = node.score if hasattr(node, 'score') else 'N/A'
251
+ content_preview = node.get_content()[:100] + "..." if len(node.get_content()) > 100 else node.get_content()
252
+ sources.append(f"[{i}] Sumber: {source} (Relevansi: {score:.2f})\nPreview: {content_preview}")
253
+ sources_text = "\n\n" + "Sumber Informasi:\n" + "\n".join(sources)
254
+
255
+ # Jika tidak ada sumber yang relevan dan jawaban terlalu generik, kembalikan informasi tidak ditemukan
256
+ if (not hasattr(response, "source_nodes") or not response.source_nodes) and \
257
+ not "tidak menemukan informasi" in answer.lower():
258
+ answer = "Maaf, saya tidak menemukan informasi yang relevan dalam dokumen yang diberikan."
259
+
260
+ final_answer = answer + sources_text
261
+
262
+ return history + [(prompt, final_answer)]
263
  except Exception as e:
264
  logging.error(f"Error processing document_chat: {e}")
265
  return history + [(prompt, f"Error processing query: {str(e)}")]
 
365
  audio_path = None
366
  else:
367
  logging.info("Memulai konversi jawaban akhir ke audio dengan TTS")
368
+ # Hapus bagian sumber untuk TTS
369
+ if "Sumber Informasi:" in last_assistant:
370
+ tts_text = last_assistant.split("Sumber Informasi:")[0].strip()
371
+ else:
372
+ tts_text = last_assistant
373
+ audio_path = convert_text_to_speech(tts_text, voice)
374
  logging.info(f"Audio output dihasilkan: {audio_path}")
375
  else:
376
  audio_path = None