Spaces:
Paused
Paused
Add greeting function to app.py
Browse files- .gitignore +1 -1
- RAG4_Voice_Fast +1 -0
- app.py +138 -180
- vito_stt.py +254 -0
.gitignore
CHANGED
@@ -23,7 +23,7 @@ var/
|
|
23 |
*.egg
|
24 |
|
25 |
# ν΄λ
|
26 |
-
documents/
|
27 |
faiss_index/
|
28 |
cached_data/
|
29 |
preprocessed_index/
|
|
|
23 |
*.egg
|
24 |
|
25 |
# ν΄λ
|
26 |
+
!documents/
|
27 |
faiss_index/
|
28 |
cached_data/
|
29 |
preprocessed_index/
|
RAG4_Voice_Fast
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Subproject commit 1f59ca46087f51b255eebf8f37d21083256683fe
|
app.py
CHANGED
@@ -14,13 +14,14 @@ from pathlib import Path
|
|
14 |
from langchain.schema import Document
|
15 |
|
16 |
from config import (
|
17 |
-
PDF_DIRECTORY, CACHE_DIRECTORY, CHUNK_SIZE, CHUNK_OVERLAP,
|
18 |
LLM_MODEL, LOG_LEVEL, LOG_FILE, print_config, validate_config
|
19 |
)
|
20 |
from optimized_document_processor import OptimizedDocumentProcessor
|
21 |
from vector_store import VectorStore
|
22 |
|
23 |
import sys
|
|
|
24 |
print("===== Script starting =====")
|
25 |
sys.stdout.flush() # μ¦μ μΆλ ₯ κ°μ
|
26 |
|
@@ -31,40 +32,42 @@ sys.stdout.flush()
|
|
31 |
print("Config loaded!")
|
32 |
sys.stdout.flush()
|
33 |
|
|
|
34 |
# λ‘κΉ
μ€μ κ°μ
|
35 |
def setup_logging():
|
36 |
"""μ ν리μΌμ΄μ
λ‘κΉ
μ€μ """
|
37 |
# λ‘κ·Έ λ 벨 μ€μ
|
38 |
log_level = getattr(logging, LOG_LEVEL.upper(), logging.INFO)
|
39 |
-
|
40 |
# λ‘κ·Έ ν¬λ§· μ€μ
|
41 |
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
42 |
formatter = logging.Formatter(log_format)
|
43 |
-
|
44 |
# λ£¨νΈ λ‘κ±° μ€μ
|
45 |
root_logger = logging.getLogger()
|
46 |
root_logger.setLevel(log_level)
|
47 |
-
|
48 |
# νΈλ€λ¬ μ΄κΈ°ν
|
49 |
# μ½μ νΈλ€λ¬
|
50 |
console_handler = logging.StreamHandler()
|
51 |
console_handler.setFormatter(formatter)
|
52 |
root_logger.addHandler(console_handler)
|
53 |
-
|
54 |
# νμΌ νΈλ€λ¬ (νμ μ)
|
55 |
try:
|
56 |
file_handler = RotatingFileHandler(
|
57 |
-
LOG_FILE,
|
58 |
-
maxBytes=10*1024*1024, # 10 MB
|
59 |
backupCount=5
|
60 |
)
|
61 |
file_handler.setFormatter(formatter)
|
62 |
root_logger.addHandler(file_handler)
|
63 |
except Exception as e:
|
64 |
console_handler.warning(f"λ‘κ·Έ νμΌ μ€μ μ€ν¨: {e}, μ½μ λ‘κΉ
λ§ μ¬μ©ν©λλ€.")
|
65 |
-
|
66 |
return logging.getLogger("AutoRAG")
|
67 |
|
|
|
68 |
# λ‘κ±° μ€μ
|
69 |
logger = setup_logging()
|
70 |
|
@@ -99,12 +102,10 @@ if config_status["status"] != "valid":
|
|
99 |
for warning in config_status["warnings"]:
|
100 |
logger.warning(f"μ€μ κ²½κ³ : {warning}")
|
101 |
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
# μμ ν μν¬νΈ
|
106 |
try:
|
107 |
from rag_chain import RAGChain
|
|
|
108 |
RAG_CHAIN_AVAILABLE = True
|
109 |
print("RAG μ²΄μΈ λͺ¨λ λ‘λ μ±κ³΅!")
|
110 |
except ImportError as e:
|
@@ -117,6 +118,7 @@ except Exception as e:
|
|
117 |
# ν΄λ°± RAG κ΄λ ¨ λͺ¨λλ 미리 νμΈ
|
118 |
try:
|
119 |
from fallback_rag_chain import FallbackRAGChain
|
|
|
120 |
FALLBACK_AVAILABLE = True
|
121 |
print("ν΄λ°± RAG μ²΄μΈ λͺ¨λ λ‘λ μ±κ³΅!")
|
122 |
except ImportError as e:
|
@@ -125,6 +127,7 @@ except ImportError as e:
|
|
125 |
|
126 |
try:
|
127 |
from offline_fallback_rag import OfflineFallbackRAG
|
|
|
128 |
OFFLINE_FALLBACK_AVAILABLE = True
|
129 |
print("μ€νλΌμΈ ν΄λ°± RAG λͺ¨λ λ‘λ μ±κ³΅!")
|
130 |
except ImportError as e:
|
@@ -163,7 +166,7 @@ class AutoRAGChatApp:
|
|
163 |
"""
|
164 |
try:
|
165 |
logger.info("AutoRAGChatApp μ΄κΈ°ν μμ")
|
166 |
-
|
167 |
# λ°μ΄ν° λλ ν 리 μ μ (μ€μ μμ κ°μ Έμ΄)
|
168 |
# μ λ κ²½λ‘λ‘ λ³ννμ¬ μ¬μ©
|
169 |
self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
|
@@ -173,10 +176,10 @@ class AutoRAGChatApp:
|
|
173 |
self.vector_index_dir = os.path.join(self.cache_directory, "vector_index")
|
174 |
|
175 |
logger.info(f"μ€μ λ PDF λλ ν 리 (μ λ κ²½λ‘): {self.pdf_directory}")
|
176 |
-
|
177 |
# λλ ν 리 κ²μ¦
|
178 |
self._verify_pdf_directory()
|
179 |
-
|
180 |
# λλ ν 리 μμ±
|
181 |
self._ensure_directories_exist()
|
182 |
|
@@ -211,9 +214,9 @@ class AutoRAGChatApp:
|
|
211 |
# μμ μ μλμΌλ‘ λ¬Έμ λ‘λ λ° μ²λ¦¬
|
212 |
logger.info("λ¬Έμ μλ λ‘λ λ° μ²λ¦¬ μμ...")
|
213 |
self.auto_process_documents()
|
214 |
-
|
215 |
logger.info("AutoRAGChatApp μ΄κΈ°ν μλ£")
|
216 |
-
|
217 |
except Exception as e:
|
218 |
logger.critical(f"μ ν리μΌμ΄μ
μ΄κΈ°ν μ€ μ¬κ°ν μ€λ₯: {e}", exc_info=True)
|
219 |
# κΈ°λ³Έ μν μ€μ μΌλ‘ μ΅μνμ κΈ°λ₯ μ μ§
|
@@ -233,7 +236,7 @@ class AutoRAGChatApp:
|
|
233 |
self.chunks_dir,
|
234 |
self.vector_index_dir
|
235 |
]
|
236 |
-
|
237 |
for directory in directories:
|
238 |
try:
|
239 |
os.makedirs(directory, exist_ok=True)
|
@@ -254,7 +257,7 @@ class AutoRAGChatApp:
|
|
254 |
if not os.path.exists(file_path):
|
255 |
logger.error(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
256 |
raise FileNotFoundError(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
257 |
-
|
258 |
try:
|
259 |
logger.info(f"doclingμΌλ‘ μ²λ¦¬ μλ: {file_path}")
|
260 |
|
@@ -287,14 +290,14 @@ class AutoRAGChatApp:
|
|
287 |
except TimeoutError as te:
|
288 |
logger.warning(f"docling μ²λ¦¬ μκ° μ΄κ³Ό: {te}")
|
289 |
logger.info("PyPDFLoaderλ‘ λ체ν©λλ€.")
|
290 |
-
|
291 |
# PyPDFLoaderλ‘ λ체
|
292 |
try:
|
293 |
return self.document_processor.process_pdf(file_path, use_docling=False)
|
294 |
except Exception as inner_e:
|
295 |
logger.error(f"PyPDFLoader μ²λ¦¬ μ€λ₯: {inner_e}", exc_info=True)
|
296 |
raise DocumentProcessingError(f"PDF λ‘λ© μ€ν¨ (PyPDFLoader): {str(inner_e)}")
|
297 |
-
|
298 |
except Exception as e:
|
299 |
# docling μ€λ₯ νμΈ
|
300 |
error_str = str(e)
|
@@ -366,7 +369,7 @@ class AutoRAGChatApp:
|
|
366 |
if not os.path.exists(file_path):
|
367 |
logger.error(f"ν΄μ κ³μ° μ€ν¨ - νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
368 |
raise FileNotFoundError(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
369 |
-
|
370 |
try:
|
371 |
hasher = hashlib.md5()
|
372 |
with open(file_path, 'rb') as f:
|
@@ -393,7 +396,7 @@ class AutoRAGChatApp:
|
|
393 |
if not os.path.exists(file_path):
|
394 |
logger.warning(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
395 |
return False
|
396 |
-
|
397 |
# μΈλ±μ€μ νμΌ μ‘΄μ¬ μ¬λΆ νμΈ
|
398 |
if file_path not in self.file_index:
|
399 |
return False
|
@@ -480,13 +483,13 @@ class AutoRAGChatApp:
|
|
480 |
if file_path not in self.file_index:
|
481 |
logger.error(f"μΈλ±μ€μ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
482 |
raise KeyError(f"μΈλ±μ€μ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
483 |
-
|
484 |
chunks_path = self.file_index[file_path]['chunks_path']
|
485 |
-
|
486 |
if not os.path.exists(chunks_path):
|
487 |
logger.error(f"μ²ν¬ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {chunks_path}")
|
488 |
raise FileNotFoundError(f"μ²ν¬ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {chunks_path}")
|
489 |
-
|
490 |
try:
|
491 |
with open(chunks_path, 'rb') as f:
|
492 |
chunks = pickle.load(f)
|
@@ -511,15 +514,15 @@ class AutoRAGChatApp:
|
|
511 |
except Exception as e:
|
512 |
logger.error(f"PDF λλ ν 리 μμ± μ€ν¨: {e}")
|
513 |
raise
|
514 |
-
|
515 |
# λλ ν 리μΈμ§ νμΈ
|
516 |
if not os.path.isdir(self.pdf_directory):
|
517 |
logger.error(f"PDF κ²½λ‘κ° λλ ν λ¦¬κ° μλλλ€: {self.pdf_directory}")
|
518 |
raise ConfigurationError(f"PDF κ²½λ‘κ° λλ ν λ¦¬κ° μλλλ€: {self.pdf_directory}")
|
519 |
-
|
520 |
# PDF νμΌ μ‘΄μ¬ νμΈ
|
521 |
pdf_files = [f for f in os.listdir(self.pdf_directory) if f.lower().endswith('.pdf')]
|
522 |
-
|
523 |
if pdf_files:
|
524 |
logger.info(f"PDF λλ ν 리μμ {len(pdf_files)}κ°μ PDF νμΌμ μ°Ύμμ΅λλ€: {pdf_files}")
|
525 |
else:
|
@@ -530,7 +533,7 @@ class AutoRAGChatApp:
|
|
530 |
"documents",
|
531 |
os.path.join(os.getcwd(), "documents")
|
532 |
]
|
533 |
-
|
534 |
found_pdfs = False
|
535 |
for alt_path in alternative_paths:
|
536 |
if os.path.exists(alt_path) and os.path.isdir(alt_path):
|
@@ -540,11 +543,11 @@ class AutoRAGChatApp:
|
|
540 |
self.pdf_directory = os.path.abspath(alt_path)
|
541 |
found_pdfs = True
|
542 |
break
|
543 |
-
|
544 |
if not found_pdfs:
|
545 |
logger.warning(f"PDF λλ ν 리μ PDF νμΌμ΄ μμ΅λλ€: {self.pdf_directory}")
|
546 |
logger.info("PDF νμΌμ λλ ν 리μ μΆκ°ν΄μ£ΌμΈμ.")
|
547 |
-
|
548 |
except Exception as e:
|
549 |
logger.error(f"PDF λλ ν 리 κ²μ¦ μ€ μ€λ₯: {e}", exc_info=True)
|
550 |
raise
|
@@ -757,7 +760,7 @@ class AutoRAGChatApp:
|
|
757 |
def _process_vector_index(self, new_files: List[str], updated_files: List[str]) -> None:
|
758 |
"""
|
759 |
λ²‘ν° μΈλ±μ€ μ²λ¦¬
|
760 |
-
|
761 |
Args:
|
762 |
new_files: μλ‘ μΆκ°λ νμΌ λͺ©λ‘
|
763 |
updated_files: μ
λ°μ΄νΈλ νμΌ λͺ©λ‘
|
@@ -1118,36 +1121,32 @@ class AutoRAGChatApp:
|
|
1118 |
logger.error("Gradio λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύμ μ μμ΅λλ€. pip install gradioλ‘ μ€μΉνμΈμ.")
|
1119 |
print("Gradio λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύμ μ μμ΅λλ€. pip install gradioλ‘ μ€μΉνμΈμ.")
|
1120 |
return
|
1121 |
-
|
1122 |
app_instance = self
|
1123 |
try:
|
1124 |
with gr.Blocks(title="PDF λ¬Έμ κΈ°λ° RAG μ±λ΄") as app:
|
1125 |
gr.Markdown("# PDF λ¬Έμ κΈ°λ° RAG μ±λ΄")
|
1126 |
gr.Markdown(f"* μ¬μ© μ€μΈ LLM λͺ¨λΈ: **{LLM_MODEL}**")
|
1127 |
|
1128 |
-
|
1129 |
-
|
|
|
|
|
1130 |
gr.Markdown(f"* PDF λ¬Έμ ν΄λ: **{actual_pdf_dir}**")
|
|
|
1131 |
with gr.Row():
|
1132 |
with gr.Column(scale=1):
|
1133 |
-
# λ¬Έμ μν μΉμ
|
1134 |
status_box = gr.Textbox(
|
1135 |
label="λ¬Έμ μ²λ¦¬ μν",
|
1136 |
value=self._get_status_message(),
|
1137 |
lines=5,
|
1138 |
interactive=False
|
1139 |
)
|
1140 |
-
|
1141 |
-
# μΊμ κ΄λ¦¬ λ²νΌ
|
1142 |
refresh_button = gr.Button("λ¬Έμ μλ‘ μ½κΈ°", variant="primary")
|
1143 |
reset_button = gr.Button("μΊμ μ΄κΈ°ν", variant="stop")
|
1144 |
-
|
1145 |
-
# μν λ° μ€λ₯ νμ
|
1146 |
status_info = gr.Markdown(
|
1147 |
value=f"μμ€ν
μν: {'μ΄κΈ°νλ¨' if self.is_initialized else 'μ΄κΈ°νλμ§ μμ'}"
|
1148 |
)
|
1149 |
-
|
1150 |
-
# μ²λ¦¬λ νμΌ μ 보
|
1151 |
with gr.Accordion("μΊμ μΈλΆ μ 보", open=False):
|
1152 |
cache_info = gr.Textbox(
|
1153 |
label="μΊμλ νμΌ μ 보",
|
@@ -1157,25 +1156,20 @@ class AutoRAGChatApp:
|
|
1157 |
)
|
1158 |
|
1159 |
with gr.Column(scale=2):
|
1160 |
-
# μ±ν
μΈν°νμ΄μ€
|
1161 |
chatbot = gr.Chatbot(
|
1162 |
label="λν λ΄μ©",
|
1163 |
bubble_full_width=False,
|
1164 |
height=500,
|
1165 |
show_copy_button=True
|
1166 |
)
|
1167 |
-
|
1168 |
-
# μμ± λ
Ήμ UI μΆκ°
|
1169 |
with gr.Row():
|
1170 |
with gr.Column(scale=4):
|
1171 |
-
# μ§λ¬Έ μ
λ ₯κ³Ό μ μ‘ λ²νΌ
|
1172 |
query_box = gr.Textbox(
|
1173 |
label="μ§λ¬Έ",
|
1174 |
placeholder="μ²λ¦¬λ λ¬Έμ λ΄μ©μ λν΄ μ§λ¬ΈνμΈμ...",
|
1175 |
lines=2
|
1176 |
)
|
1177 |
with gr.Column(scale=1):
|
1178 |
-
# μμ± λ
Ήμ μ»΄ν¬λνΈ
|
1179 |
audio_input = gr.Audio(
|
1180 |
sources=["microphone"],
|
1181 |
type="numpy",
|
@@ -1186,153 +1180,118 @@ class AutoRAGChatApp:
|
|
1186 |
submit_btn = gr.Button("μ μ‘", variant="primary")
|
1187 |
clear_chat_button = gr.Button("λν μ΄κΈ°ν")
|
1188 |
|
1189 |
-
|
1190 |
-
|
1191 |
-
|
1192 |
-
|
1193 |
-
|
1194 |
-
|
1195 |
-
|
1196 |
-
|
1197 |
-
import soundfile as sf
|
1198 |
-
import tempfile
|
1199 |
-
import os
|
1200 |
-
|
1201 |
-
if audio is None:
|
1202 |
-
return "μμ±μ΄ λ
Ήμλμ§ μμμ΅λλ€."
|
1203 |
-
|
1204 |
-
# μ€λμ€ λ°μ΄ν°λ₯Ό μμ νμΌλ‘ μ μ₯
|
1205 |
-
sr, y = audio
|
1206 |
-
logger.info(f"μ€λμ€ λ
Ήμ λ°μ΄ν° μμ : μνλ μ΄νΈ={sr}Hz, κΈΈμ΄={len(y)}μν")
|
1207 |
-
if len(y) / sr < 1.0:
|
1208 |
-
return "λ
Ήμλ μμ±μ΄ λ무 μ§§μ΅λλ€. λ€μ μλν΄μ£ΌμΈμ."
|
1209 |
-
|
1210 |
-
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
|
1211 |
-
temp_path = temp_file.name
|
1212 |
-
sf.write(temp_path, y, sr, format="WAV")
|
1213 |
-
logger.info(f"μμ WAV νμΌ μ μ₯λ¨: {temp_path}")
|
1214 |
-
|
1215 |
-
# μμ± μΈμ μ€ν
|
1216 |
-
stt_client = ClovaSTT()
|
1217 |
-
with open(temp_path, "rb") as f:
|
1218 |
-
audio_bytes = f.read()
|
1219 |
-
result = stt_client.recognize(audio_bytes)
|
1220 |
-
|
1221 |
-
# μμ νμΌ μμ
|
1222 |
-
try:
|
1223 |
-
os.unlink(temp_path)
|
1224 |
-
logger.info("μμ μ€λμ€ νμΌ μμ λ¨")
|
1225 |
-
except Exception as e:
|
1226 |
-
logger.warning(f"μμ νμΌ μμ μ€ν¨: {e}")
|
1227 |
-
|
1228 |
-
if result["success"]:
|
1229 |
-
recognized_text = result["text"]
|
1230 |
-
logger.info(f"μμ±μΈμ μ±κ³΅: {recognized_text}")
|
1231 |
-
return recognized_text
|
1232 |
-
else:
|
1233 |
-
error_msg = f"μμ± μΈμ μ€ν¨: {result.get('error', 'μ μ μλ μ€λ₯')}"
|
1234 |
-
logger.error(error_msg)
|
1235 |
-
return error_msg
|
1236 |
-
|
1237 |
-
except ImportError as e:
|
1238 |
-
logger.error(f"νμν λΌμ΄λΈλ¬λ¦¬ λλ½: {e}")
|
1239 |
-
return "μμ±μΈμμ νοΏ½οΏ½οΏ½ν λΌμ΄λΈλ¬λ¦¬κ° μ€μΉλμ§ μμμ΅λλ€. pip install soundfile numpy requestsλ₯Ό μ€νν΄μ£ΌμΈμ."
|
1240 |
-
except Exception as e:
|
1241 |
-
logger.error(f"μμ± μ²λ¦¬ μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
1242 |
-
return f"μμ± μ²λ¦¬ μ€ μ€λ₯ λ°μ: {str(e)}"
|
1243 |
-
|
1244 |
-
# μλ‘ μΆκ°ν process_audio_and_submit ν¨μ
|
1245 |
-
def process_audio_and_submit(audio, chat_history):
|
1246 |
-
"""
|
1247 |
-
λ
Ήμ μ μ§ μ μμ± μΈμ ν μλμΌλ‘ μ§λ¬Έμ μ²λ¦¬νλ ν¨μ.
|
1248 |
-
μ
λ ₯:
|
1249 |
-
- audio: λ
Ήμ λ°μ΄ν° (gr.Audioμ κ°)
|
1250 |
-
- chat_history: νμ¬ λν κΈ°λ‘ (gr.Chatbotμ κ°)
|
1251 |
-
μΆλ ₯:
|
1252 |
-
- query_box: λΉ λ¬Έμμ΄ (μ§λ¬Έ μ
λ ₯λ μ΄κΈ°ν)
|
1253 |
-
- chatbot: μ
λ°μ΄νΈλ λν κΈ°λ‘
|
1254 |
-
"""
|
1255 |
-
recognized_text = process_audio(audio)
|
1256 |
-
|
1257 |
-
# μμ± μΈμ κ²°κ³Όκ° μ€λ₯ λ©μμ§μΈ κ²½μ° κ·Έλλ‘ λ°ν
|
1258 |
-
if not recognized_text or recognized_text.startswith("μμ± μΈμ μ€ν¨") or recognized_text.startswith(
|
1259 |
-
"μμ± μ²λ¦¬ μ€ μ€λ₯"):
|
1260 |
-
return recognized_text, chat_history
|
1261 |
-
|
1262 |
-
# μΈμλ ν
μ€νΈλ₯Ό μ¬μ©νμ¬ μ§λ¬Έ μ²λ¦¬
|
1263 |
-
return app_instance.process_query(recognized_text, chat_history)
|
1264 |
-
|
1265 |
-
# κΈ°μ‘΄ update_ui_after_refresh ν¨μ μμ (self λμ app_instance μ¬μ©)
|
1266 |
-
def update_ui_after_refresh(result):
|
1267 |
-
return (
|
1268 |
-
result, # μν λ©μμ§
|
1269 |
-
app_instance._get_status_message(), # μν λ°μ€ μ
λ°μ΄νΈ
|
1270 |
-
f"μμ€ν
μν: {'μ΄κΈ°νλ¨' if app_instance.is_initialized else 'μ΄κΈ°νλμ§ μμ'}", # μν μ 보 μ
λ°μ΄νΈ
|
1271 |
-
app_instance._get_cache_info() # μΊμ μ 보 μ
λ°μ΄νΈ
|
1272 |
-
)
|
1273 |
|
1274 |
-
|
1275 |
-
|
1276 |
-
audio_input.stop_recording(
|
1277 |
-
fn=process_audio_and_submit,
|
1278 |
-
inputs=[audio_input, chatbot],
|
1279 |
-
outputs=[query_box, chatbot]
|
1280 |
-
)
|
1281 |
|
1282 |
-
|
1283 |
-
|
1284 |
-
|
1285 |
-
|
1286 |
-
outputs=[query_box]
|
1287 |
-
)
|
1288 |
|
1289 |
-
|
1290 |
-
|
1291 |
-
|
1292 |
-
|
1293 |
-
outputs=[status_box, status_box, status_info, cache_info]
|
1294 |
-
)
|
1295 |
|
1296 |
-
|
1297 |
-
|
1298 |
-
|
1299 |
-
|
1300 |
-
return update_ui_after_refresh(f"{reset_result}\n\n{process_result}")
|
1301 |
|
1302 |
-
|
1303 |
-
|
1304 |
-
|
1305 |
-
|
1306 |
-
|
1307 |
|
1308 |
-
|
1309 |
-
|
1310 |
-
|
1311 |
-
|
1312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1313 |
)
|
1314 |
|
1315 |
-
|
1316 |
-
|
1317 |
-
|
1318 |
-
|
1319 |
-
|
1320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1321 |
|
1322 |
-
|
1323 |
-
clear_chat_button.click(
|
1324 |
-
fn=lambda: [],
|
1325 |
-
outputs=[chatbot]
|
1326 |
-
)
|
1327 |
|
1328 |
-
# μ± μ€ν
|
1329 |
-
app.launch(share=False)
|
1330 |
except Exception as e:
|
1331 |
logger.error(f"Gradio μ± μ€ν μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
1332 |
print(f"Gradio μ± μ€ν μ€ μ€λ₯ λ°μ: {e}")
|
1333 |
|
1334 |
|
1335 |
-
|
1336 |
def _get_status_message(self) -> str:
|
1337 |
"""
|
1338 |
νμ¬ μ²λ¦¬ μν λ©μμ§ μμ±
|
@@ -1448,7 +1407,6 @@ class AutoRAGChatApp:
|
|
1448 |
return file_info
|
1449 |
|
1450 |
|
1451 |
-
|
1452 |
if __name__ == "__main__":
|
1453 |
app = AutoRAGChatApp()
|
1454 |
app.launch_app()
|
|
|
14 |
from langchain.schema import Document
|
15 |
|
16 |
from config import (
|
17 |
+
PDF_DIRECTORY, CACHE_DIRECTORY, CHUNK_SIZE, CHUNK_OVERLAP,
|
18 |
LLM_MODEL, LOG_LEVEL, LOG_FILE, print_config, validate_config
|
19 |
)
|
20 |
from optimized_document_processor import OptimizedDocumentProcessor
|
21 |
from vector_store import VectorStore
|
22 |
|
23 |
import sys
|
24 |
+
|
25 |
print("===== Script starting =====")
|
26 |
sys.stdout.flush() # μ¦μ μΆλ ₯ κ°μ
|
27 |
|
|
|
32 |
print("Config loaded!")
|
33 |
sys.stdout.flush()
|
34 |
|
35 |
+
|
36 |
# λ‘κΉ
μ€μ κ°μ
|
37 |
def setup_logging():
|
38 |
"""μ ν리μΌμ΄μ
λ‘κΉ
μ€μ """
|
39 |
# λ‘κ·Έ λ 벨 μ€μ
|
40 |
log_level = getattr(logging, LOG_LEVEL.upper(), logging.INFO)
|
41 |
+
|
42 |
# λ‘κ·Έ ν¬λ§· μ€μ
|
43 |
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
44 |
formatter = logging.Formatter(log_format)
|
45 |
+
|
46 |
# λ£¨νΈ λ‘κ±° μ€μ
|
47 |
root_logger = logging.getLogger()
|
48 |
root_logger.setLevel(log_level)
|
49 |
+
|
50 |
# νΈλ€λ¬ μ΄κΈ°ν
|
51 |
# μ½μ νΈλ€λ¬
|
52 |
console_handler = logging.StreamHandler()
|
53 |
console_handler.setFormatter(formatter)
|
54 |
root_logger.addHandler(console_handler)
|
55 |
+
|
56 |
# νμΌ νΈλ€λ¬ (νμ μ)
|
57 |
try:
|
58 |
file_handler = RotatingFileHandler(
|
59 |
+
LOG_FILE,
|
60 |
+
maxBytes=10 * 1024 * 1024, # 10 MB
|
61 |
backupCount=5
|
62 |
)
|
63 |
file_handler.setFormatter(formatter)
|
64 |
root_logger.addHandler(file_handler)
|
65 |
except Exception as e:
|
66 |
console_handler.warning(f"λ‘κ·Έ νμΌ μ€μ μ€ν¨: {e}, μ½μ λ‘κΉ
λ§ μ¬μ©ν©λλ€.")
|
67 |
+
|
68 |
return logging.getLogger("AutoRAG")
|
69 |
|
70 |
+
|
71 |
# λ‘κ±° μ€μ
|
72 |
logger = setup_logging()
|
73 |
|
|
|
102 |
for warning in config_status["warnings"]:
|
103 |
logger.warning(f"μ€μ κ²½κ³ : {warning}")
|
104 |
|
|
|
|
|
|
|
105 |
# μμ ν μν¬νΈ
|
106 |
try:
|
107 |
from rag_chain import RAGChain
|
108 |
+
|
109 |
RAG_CHAIN_AVAILABLE = True
|
110 |
print("RAG μ²΄μΈ λͺ¨λ λ‘λ μ±κ³΅!")
|
111 |
except ImportError as e:
|
|
|
118 |
# ν΄λ°± RAG κ΄λ ¨ λͺ¨λλ 미리 νμΈ
|
119 |
try:
|
120 |
from fallback_rag_chain import FallbackRAGChain
|
121 |
+
|
122 |
FALLBACK_AVAILABLE = True
|
123 |
print("ν΄λ°± RAG μ²΄μΈ λͺ¨λ λ‘λ μ±κ³΅!")
|
124 |
except ImportError as e:
|
|
|
127 |
|
128 |
try:
|
129 |
from offline_fallback_rag import OfflineFallbackRAG
|
130 |
+
|
131 |
OFFLINE_FALLBACK_AVAILABLE = True
|
132 |
print("μ€νλΌμΈ ν΄λ°± RAG λͺ¨λ λ‘λ μ±κ³΅!")
|
133 |
except ImportError as e:
|
|
|
166 |
"""
|
167 |
try:
|
168 |
logger.info("AutoRAGChatApp μ΄κΈ°ν μμ")
|
169 |
+
|
170 |
# λ°μ΄ν° λλ ν 리 μ μ (μ€μ μμ κ°μ Έμ΄)
|
171 |
# μ λ κ²½λ‘λ‘ λ³ννμ¬ μ¬μ©
|
172 |
self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
|
|
|
176 |
self.vector_index_dir = os.path.join(self.cache_directory, "vector_index")
|
177 |
|
178 |
logger.info(f"μ€μ λ PDF λλ ν 리 (μ λ κ²½λ‘): {self.pdf_directory}")
|
179 |
+
|
180 |
# λλ ν 리 κ²μ¦
|
181 |
self._verify_pdf_directory()
|
182 |
+
|
183 |
# λλ ν 리 μμ±
|
184 |
self._ensure_directories_exist()
|
185 |
|
|
|
214 |
# μμ μ μλμΌλ‘ λ¬Έμ λ‘λ λ° μ²λ¦¬
|
215 |
logger.info("λ¬Έμ μλ λ‘λ λ° μ²λ¦¬ μμ...")
|
216 |
self.auto_process_documents()
|
217 |
+
|
218 |
logger.info("AutoRAGChatApp μ΄κΈ°ν μλ£")
|
219 |
+
|
220 |
except Exception as e:
|
221 |
logger.critical(f"μ ν리μΌμ΄μ
μ΄κΈ°ν μ€ μ¬κ°ν μ€λ₯: {e}", exc_info=True)
|
222 |
# κΈ°λ³Έ μν μ€μ μΌλ‘ μ΅μνμ κΈ°λ₯ μ μ§
|
|
|
236 |
self.chunks_dir,
|
237 |
self.vector_index_dir
|
238 |
]
|
239 |
+
|
240 |
for directory in directories:
|
241 |
try:
|
242 |
os.makedirs(directory, exist_ok=True)
|
|
|
257 |
if not os.path.exists(file_path):
|
258 |
logger.error(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
259 |
raise FileNotFoundError(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
260 |
+
|
261 |
try:
|
262 |
logger.info(f"doclingμΌλ‘ μ²λ¦¬ μλ: {file_path}")
|
263 |
|
|
|
290 |
except TimeoutError as te:
|
291 |
logger.warning(f"docling μ²λ¦¬ μκ° μ΄κ³Ό: {te}")
|
292 |
logger.info("PyPDFLoaderλ‘ λ체ν©λλ€.")
|
293 |
+
|
294 |
# PyPDFLoaderλ‘ λ체
|
295 |
try:
|
296 |
return self.document_processor.process_pdf(file_path, use_docling=False)
|
297 |
except Exception as inner_e:
|
298 |
logger.error(f"PyPDFLoader μ²λ¦¬ μ€λ₯: {inner_e}", exc_info=True)
|
299 |
raise DocumentProcessingError(f"PDF λ‘λ© μ€ν¨ (PyPDFLoader): {str(inner_e)}")
|
300 |
+
|
301 |
except Exception as e:
|
302 |
# docling μ€λ₯ νμΈ
|
303 |
error_str = str(e)
|
|
|
369 |
if not os.path.exists(file_path):
|
370 |
logger.error(f"ν΄μ κ³μ° μ€ν¨ - νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
371 |
raise FileNotFoundError(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
372 |
+
|
373 |
try:
|
374 |
hasher = hashlib.md5()
|
375 |
with open(file_path, 'rb') as f:
|
|
|
396 |
if not os.path.exists(file_path):
|
397 |
logger.warning(f"νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
398 |
return False
|
399 |
+
|
400 |
# μΈλ±μ€μ νμΌ μ‘΄μ¬ μ¬λΆ νμΈ
|
401 |
if file_path not in self.file_index:
|
402 |
return False
|
|
|
483 |
if file_path not in self.file_index:
|
484 |
logger.error(f"μΈλ±μ€μ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
485 |
raise KeyError(f"μΈλ±μ€μ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {file_path}")
|
486 |
+
|
487 |
chunks_path = self.file_index[file_path]['chunks_path']
|
488 |
+
|
489 |
if not os.path.exists(chunks_path):
|
490 |
logger.error(f"μ²ν¬ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {chunks_path}")
|
491 |
raise FileNotFoundError(f"μ²ν¬ νμΌμ΄ μ‘΄μ¬νμ§ μμ: {chunks_path}")
|
492 |
+
|
493 |
try:
|
494 |
with open(chunks_path, 'rb') as f:
|
495 |
chunks = pickle.load(f)
|
|
|
514 |
except Exception as e:
|
515 |
logger.error(f"PDF λλ ν 리 μμ± μ€ν¨: {e}")
|
516 |
raise
|
517 |
+
|
518 |
# λλ ν 리μΈμ§ νμΈ
|
519 |
if not os.path.isdir(self.pdf_directory):
|
520 |
logger.error(f"PDF κ²½λ‘κ° λλ ν λ¦¬κ° μλλλ€: {self.pdf_directory}")
|
521 |
raise ConfigurationError(f"PDF κ²½λ‘κ° λλ ν λ¦¬κ° μλλλ€: {self.pdf_directory}")
|
522 |
+
|
523 |
# PDF νμΌ μ‘΄μ¬ νμΈ
|
524 |
pdf_files = [f for f in os.listdir(self.pdf_directory) if f.lower().endswith('.pdf')]
|
525 |
+
|
526 |
if pdf_files:
|
527 |
logger.info(f"PDF λλ ν 리μμ {len(pdf_files)}κ°μ PDF νμΌμ μ°Ύμμ΅λλ€: {pdf_files}")
|
528 |
else:
|
|
|
533 |
"documents",
|
534 |
os.path.join(os.getcwd(), "documents")
|
535 |
]
|
536 |
+
|
537 |
found_pdfs = False
|
538 |
for alt_path in alternative_paths:
|
539 |
if os.path.exists(alt_path) and os.path.isdir(alt_path):
|
|
|
543 |
self.pdf_directory = os.path.abspath(alt_path)
|
544 |
found_pdfs = True
|
545 |
break
|
546 |
+
|
547 |
if not found_pdfs:
|
548 |
logger.warning(f"PDF λλ ν 리μ PDF νμΌμ΄ μμ΅λλ€: {self.pdf_directory}")
|
549 |
logger.info("PDF νμΌμ λλ ν 리μ μΆκ°ν΄μ£ΌμΈμ.")
|
550 |
+
|
551 |
except Exception as e:
|
552 |
logger.error(f"PDF λλ ν 리 κ²μ¦ μ€ μ€λ₯: {e}", exc_info=True)
|
553 |
raise
|
|
|
760 |
def _process_vector_index(self, new_files: List[str], updated_files: List[str]) -> None:
|
761 |
"""
|
762 |
λ²‘ν° μΈλ±μ€ μ²λ¦¬
|
763 |
+
|
764 |
Args:
|
765 |
new_files: μλ‘ μΆκ°λ νμΌ λͺ©λ‘
|
766 |
updated_files: μ
λ°μ΄νΈλ νμΌ λͺ©λ‘
|
|
|
1121 |
logger.error("Gradio λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύμ μ μμ΅λλ€. pip install gradioλ‘ μ€μΉνμΈμ.")
|
1122 |
print("Gradio λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύμ μ μμ΅λλ€. pip install gradioλ‘ μ€μΉνμΈμ.")
|
1123 |
return
|
1124 |
+
|
1125 |
app_instance = self
|
1126 |
try:
|
1127 |
with gr.Blocks(title="PDF λ¬Έμ κΈ°λ° RAG μ±λ΄") as app:
|
1128 |
gr.Markdown("# PDF λ¬Έμ κΈ°λ° RAG μ±λ΄")
|
1129 |
gr.Markdown(f"* μ¬μ© μ€μΈ LLM λͺ¨λΈ: **{LLM_MODEL}**")
|
1130 |
|
1131 |
+
actual_pdf_dir = (
|
1132 |
+
self.pdf_directory.replace("\\", "\\\\")
|
1133 |
+
if os.name == 'nt' else self.pdf_directory
|
1134 |
+
)
|
1135 |
gr.Markdown(f"* PDF λ¬Έμ ν΄λ: **{actual_pdf_dir}**")
|
1136 |
+
|
1137 |
with gr.Row():
|
1138 |
with gr.Column(scale=1):
|
|
|
1139 |
status_box = gr.Textbox(
|
1140 |
label="λ¬Έμ μ²λ¦¬ μν",
|
1141 |
value=self._get_status_message(),
|
1142 |
lines=5,
|
1143 |
interactive=False
|
1144 |
)
|
|
|
|
|
1145 |
refresh_button = gr.Button("λ¬Έμ μλ‘ μ½κΈ°", variant="primary")
|
1146 |
reset_button = gr.Button("μΊμ μ΄κΈ°ν", variant="stop")
|
|
|
|
|
1147 |
status_info = gr.Markdown(
|
1148 |
value=f"μμ€ν
μν: {'μ΄κΈ°νλ¨' if self.is_initialized else 'μ΄κΈ°νλμ§ μμ'}"
|
1149 |
)
|
|
|
|
|
1150 |
with gr.Accordion("μΊμ μΈλΆ μ 보", open=False):
|
1151 |
cache_info = gr.Textbox(
|
1152 |
label="μΊμλ νμΌ μ 보",
|
|
|
1156 |
)
|
1157 |
|
1158 |
with gr.Column(scale=2):
|
|
|
1159 |
chatbot = gr.Chatbot(
|
1160 |
label="λν λ΄μ©",
|
1161 |
bubble_full_width=False,
|
1162 |
height=500,
|
1163 |
show_copy_button=True
|
1164 |
)
|
|
|
|
|
1165 |
with gr.Row():
|
1166 |
with gr.Column(scale=4):
|
|
|
1167 |
query_box = gr.Textbox(
|
1168 |
label="μ§λ¬Έ",
|
1169 |
placeholder="μ²λ¦¬λ λ¬Έμ λ΄μ©μ λν΄ μ§λ¬ΈνμΈμ...",
|
1170 |
lines=2
|
1171 |
)
|
1172 |
with gr.Column(scale=1):
|
|
|
1173 |
audio_input = gr.Audio(
|
1174 |
sources=["microphone"],
|
1175 |
type="numpy",
|
|
|
1180 |
submit_btn = gr.Button("μ μ‘", variant="primary")
|
1181 |
clear_chat_button = gr.Button("λν μ΄κΈ°ν")
|
1182 |
|
1183 |
+
# VITO STTμ© μμ± μ²λ¦¬ ν¨μ
|
1184 |
+
def process_audio(audio):
|
1185 |
+
logger.info("μμ± μΈμ μ²λ¦¬ μμ...")
|
1186 |
+
try:
|
1187 |
+
from vito_stt import VitoSTT
|
1188 |
+
import soundfile as sf
|
1189 |
+
import tempfile
|
1190 |
+
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1191 |
|
1192 |
+
if audio is None:
|
1193 |
+
return "μμ±μ΄ λ
Ήμλμ§ μμμ΅λλ€."
|
|
|
|
|
|
|
|
|
|
|
1194 |
|
1195 |
+
sr, y = audio
|
1196 |
+
logger.info(f"μ€λμ€ λ
Ήμ λ°μ΄ν° μμ : μνλ μ΄νΈ={sr}Hz, κΈΈμ΄={len(y)}μν")
|
1197 |
+
if len(y) / sr < 1.0:
|
1198 |
+
return "λ
Ήμλ μμ±μ΄ λ무 μ§§μ΅λλ€. λ€μ μλν΄μ£ΌμΈμ."
|
|
|
|
|
1199 |
|
1200 |
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
1201 |
+
tmp_path = tmp.name
|
1202 |
+
sf.write(tmp_path, y, sr, format="WAV")
|
1203 |
+
logger.info(f"μμ WAV νμΌ μ μ₯λ¨: {tmp_path}")
|
|
|
|
|
1204 |
|
1205 |
+
vito = VitoSTT()
|
1206 |
+
with open(tmp_path, "rb") as f:
|
1207 |
+
audio_bytes = f.read()
|
1208 |
+
result = vito.transcribe_audio(audio_bytes, language="ko")
|
|
|
1209 |
|
1210 |
+
try:
|
1211 |
+
os.unlink(tmp_path)
|
1212 |
+
logger.info("μμ μ€λμ€ νμΌ μμ λ¨")
|
1213 |
+
except Exception as e:
|
1214 |
+
logger.warning(f"μμ νμΌ μμ μ€ν¨: {e}")
|
1215 |
|
1216 |
+
if result.get("success"):
|
1217 |
+
recognized_text = result.get("text", "")
|
1218 |
+
logger.info(f"μμ±μΈμ μ±κ³΅: {recognized_text}")
|
1219 |
+
return recognized_text
|
1220 |
+
else:
|
1221 |
+
error_msg = f"μμ± μΈμ μ€ν¨: {result.get('error', 'μ μ μλ μ€λ₯')}"
|
1222 |
+
logger.error(error_msg)
|
1223 |
+
return error_msg
|
1224 |
+
|
1225 |
+
except ImportError as e:
|
1226 |
+
logger.error(f"νμν λΌμ΄λΈλ¬λ¦¬ λλ½: {e}")
|
1227 |
+
return ("μμ±μΈμμ νμν λΌμ΄λΈλ¬λ¦¬κ° μ€μΉλμ§ μμμ΅λλ€. "
|
1228 |
+
"pip install soundfile numpy requests λ₯Ό μ€νν΄μ£ΌμΈμ.")
|
1229 |
+
except Exception as e:
|
1230 |
+
logger.error(f"μμ± μ²λ¦¬ μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
1231 |
+
return f"μμ± μ²λ¦¬ μ€ μ€λ₯ λ°μ: {str(e)}"
|
1232 |
+
|
1233 |
+
# μμ± μΈμ ν μλ μ§λ¬Έ μ²λ¦¬
|
1234 |
+
def process_audio_and_submit(audio, chat_history):
|
1235 |
+
recognized_text = process_audio(audio)
|
1236 |
+
if (not recognized_text
|
1237 |
+
or recognized_text.startswith("μμ± μΈμ μ€ν¨")
|
1238 |
+
or recognized_text.startswith("μμ± μ²λ¦¬ μ€ μ€λ₯")):
|
1239 |
+
return recognized_text, chat_history
|
1240 |
+
return app_instance.process_query(recognized_text, chat_history)
|
1241 |
+
|
1242 |
+
def update_ui_after_refresh(result):
|
1243 |
+
return (
|
1244 |
+
result,
|
1245 |
+
app_instance._get_status_message(),
|
1246 |
+
f"μμ€ν
μν: {'μ΄κΈ°νλ¨' if app_instance.is_initialized else 'μ΄κΈ°νλμ§ μμ'}",
|
1247 |
+
app_instance._get_cache_info()
|
1248 |
)
|
1249 |
|
1250 |
+
# μ΄λ²€νΈ νΈλ€λ¬ λ°μΈλ©
|
1251 |
+
audio_input.stop_recording(
|
1252 |
+
fn=process_audio_and_submit,
|
1253 |
+
inputs=[audio_input, chatbot],
|
1254 |
+
outputs=[query_box, chatbot]
|
1255 |
+
)
|
1256 |
+
audio_input.stop_recording(
|
1257 |
+
fn=process_audio,
|
1258 |
+
inputs=[audio_input],
|
1259 |
+
outputs=[query_box]
|
1260 |
+
)
|
1261 |
+
refresh_button.click(
|
1262 |
+
fn=lambda: update_ui_after_refresh(self.auto_process_documents()),
|
1263 |
+
inputs=[],
|
1264 |
+
outputs=[status_box, status_box, status_info, cache_info]
|
1265 |
+
)
|
1266 |
+
reset_button.click(
|
1267 |
+
fn=lambda: update_ui_after_refresh(
|
1268 |
+
f"{self.reset_cache()}\n\n{self.auto_process_documents()}"
|
1269 |
+
),
|
1270 |
+
inputs=[],
|
1271 |
+
outputs=[status_box, status_box, status_info, cache_info]
|
1272 |
+
)
|
1273 |
+
submit_btn.click(
|
1274 |
+
fn=self.process_query,
|
1275 |
+
inputs=[query_box, chatbot],
|
1276 |
+
outputs=[query_box, chatbot]
|
1277 |
+
)
|
1278 |
+
query_box.submit(
|
1279 |
+
fn=self.process_query,
|
1280 |
+
inputs=[query_box, chatbot],
|
1281 |
+
outputs=[query_box, chatbot]
|
1282 |
+
)
|
1283 |
+
clear_chat_button.click(
|
1284 |
+
fn=lambda: [],
|
1285 |
+
outputs=[chatbot]
|
1286 |
+
)
|
1287 |
|
1288 |
+
app.launch(share=False)
|
|
|
|
|
|
|
|
|
1289 |
|
|
|
|
|
1290 |
except Exception as e:
|
1291 |
logger.error(f"Gradio μ± μ€ν μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
1292 |
print(f"Gradio μ± μ€ν μ€ μ€λ₯ λ°μ: {e}")
|
1293 |
|
1294 |
|
|
|
1295 |
def _get_status_message(self) -> str:
|
1296 |
"""
|
1297 |
νμ¬ μ²λ¦¬ μν λ©μμ§ μμ±
|
|
|
1407 |
return file_info
|
1408 |
|
1409 |
|
|
|
1410 |
if __name__ == "__main__":
|
1411 |
app = AutoRAGChatApp()
|
1412 |
app.launch_app()
|
vito_stt.py
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
VITO APIλ₯Ό μ¬μ©ν μμ± μΈμ(STT) λͺ¨λ
|
4 |
+
"""
|
5 |
+
|
6 |
+
import os
|
7 |
+
import logging
|
8 |
+
import requests
|
9 |
+
import json
|
10 |
+
import time # time import μΆκ°
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
|
13 |
+
# νκ²½ λ³μ λ‘λ
|
14 |
+
load_dotenv()
|
15 |
+
|
16 |
+
# λ‘κ±° μ€μ (app.pyμ 곡μ νκ±°λ λ
립μ μΌλ‘ μ€μ κ°λ₯)
|
17 |
+
# μ¬κΈ°μλ λ
립μ μΈ λ‘κ±°λ₯Ό μ¬μ©ν©λλ€. νμμ app.pyμ λ‘κ±°λ₯Ό μ¬μ©νλλ‘ μμ ν μ μμ΅λλ€.
|
18 |
+
logger = logging.getLogger("VitoSTT")
|
19 |
+
# κΈ°λ³Έ λ‘κΉ
λ 벨 μ€μ (νΈλ€λ¬κ° μμΌλ©΄ μΆλ ₯μ΄ μλ μ μμΌλ―λ‘ κΈ°λ³Έ νΈλ€λ¬ μΆκ° κ³ λ €)
|
20 |
+
if not logger.hasHandlers():
|
21 |
+
handler = logging.StreamHandler()
|
22 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
23 |
+
handler.setFormatter(formatter)
|
24 |
+
logger.addHandler(handler)
|
25 |
+
logger.setLevel(logging.INFO) # κΈ°λ³Έ λ 벨 INFOλ‘ μ€μ
|
26 |
+
|
27 |
+
class VitoSTT:
|
28 |
+
"""VITO STT API λνΌ ν΄λμ€"""
|
29 |
+
|
30 |
+
def __init__(self):
|
31 |
+
"""VITO STT ν΄λμ€ μ΄κΈ°ν"""
|
32 |
+
self.client_id = os.getenv("VITO_CLIENT_ID")
|
33 |
+
self.client_secret = os.getenv("VITO_CLIENT_SECRET")
|
34 |
+
|
35 |
+
if not self.client_id or not self.client_secret:
|
36 |
+
logger.warning("VITO API μΈμ¦ μ λ³΄κ° .env νμΌμ μ€μ λμ§ μμμ΅λλ€.")
|
37 |
+
logger.warning("VITO_CLIENT_IDμ VITO_CLIENT_SECRETλ₯Ό νμΈνμΈμ.")
|
38 |
+
# μλ¬λ₯Ό λ°μμν€κ±°λ, κΈ°λ₯ μ¬μ© μμ μ 체ν¬νλλ‘ λ μ μμ΅λλ€.
|
39 |
+
# μ¬κΈ°μλ κ²½κ³ λ§ νκ³ λμ΄κ°λλ€.
|
40 |
+
else:
|
41 |
+
logger.info("VITO STT API ν΄λΌμ΄μΈνΈ ID/Secret λ‘λ μλ£.")
|
42 |
+
|
43 |
+
# API μλν¬μΈνΈ
|
44 |
+
self.token_url = "https://openapi.vito.ai/v1/authenticate"
|
45 |
+
self.stt_url = "https://openapi.vito.ai/v1/transcribe"
|
46 |
+
|
47 |
+
# μ‘μΈμ€ ν ν°
|
48 |
+
self.access_token = None
|
49 |
+
self._token_expires_at = 0 # ν ν° λ§λ£ μκ° μΆμ (μ νμ κ°μ )
|
50 |
+
|
51 |
+
def get_access_token(self):
|
52 |
+
"""VITO API μ‘μΈμ€ ν ν° νλ"""
|
53 |
+
# νμ¬ μκ°μ κ°μ Έμ ν ν° λ§λ£ μ¬λΆ νμΈ (μ νμ κ°μ )
|
54 |
+
# now = time.time()
|
55 |
+
# if self.access_token and now < self._token_expires_at:
|
56 |
+
# logger.debug("κΈ°μ‘΄ VITO API ν ν° μ¬μ©")
|
57 |
+
# return self.access_token
|
58 |
+
|
59 |
+
if not self.client_id or not self.client_secret:
|
60 |
+
logger.error("API ν€κ° μ€μ λμ§ μμ ν ν°μ νλν μ μμ΅λλ€.")
|
61 |
+
raise ValueError("VITO API μΈμ¦ μ λ³΄κ° μ€μ λμ§ μμμ΅λλ€.")
|
62 |
+
|
63 |
+
logger.info("VITO API μ‘μΈμ€ ν ν° μμ² μ€...")
|
64 |
+
try:
|
65 |
+
response = requests.post(
|
66 |
+
self.token_url,
|
67 |
+
data={"client_id": self.client_id, "client_secret": self.client_secret},
|
68 |
+
timeout=10 # νμμμ μ€μ
|
69 |
+
)
|
70 |
+
response.raise_for_status() # HTTP μ€λ₯ λ°μ μ μμΈ λ°μ
|
71 |
+
|
72 |
+
result = response.json()
|
73 |
+
self.access_token = result.get("access_token")
|
74 |
+
expires_in = result.get("expires_in", 3600) # λ§λ£ μκ° (μ΄), κΈ°λ³Έκ° 1μκ°
|
75 |
+
self._token_expires_at = time.time() + expires_in - 60 # 60μ΄ μ¬μ
|
76 |
+
|
77 |
+
if not self.access_token:
|
78 |
+
logger.error("VITO API μλ΅μμ ν ν°μ μ°Ύμ μ μμ΅λλ€.")
|
79 |
+
raise ValueError("VITO API ν ν°μ λ°μμ€μ§ λͺ»νμ΅λλ€.")
|
80 |
+
|
81 |
+
logger.info("VITO API μ‘μΈμ€ ν ν° νλ μ±κ³΅")
|
82 |
+
return self.access_token
|
83 |
+
except requests.exceptions.Timeout:
|
84 |
+
logger.error(f"VITO API ν ν° νλ μκ° μ΄κ³Ό: {self.token_url}")
|
85 |
+
raise TimeoutError("VITO API ν ν° νλ μκ° μ΄κ³Ό")
|
86 |
+
except requests.exceptions.RequestException as e:
|
87 |
+
logger.error(f"VITO API ν ν° νλ μ€ν¨: {e}")
|
88 |
+
if hasattr(e, 'response') and e.response is not None:
|
89 |
+
logger.error(f"μλ΅ μ½λ: {e.response.status_code}, λ΄μ©: {e.response.text}")
|
90 |
+
raise ConnectionError(f"VITO API ν ν° νλ μ€ν¨: {e}")
|
91 |
+
|
92 |
+
|
93 |
+
def transcribe_audio(self, audio_bytes, language="ko"):
|
94 |
+
"""
|
95 |
+
μ€λμ€ λ°μ΄νΈ λ°μ΄ν°λ₯Ό ν
μ€νΈλ‘ λ³ν
|
96 |
+
|
97 |
+
Args:
|
98 |
+
audio_bytes: μ€λμ€ νμΌ λ°μ΄νΈ λ°μ΄ν°
|
99 |
+
language: μΈμ΄ μ½λ (κΈ°λ³Έκ°: 'ko')
|
100 |
+
|
101 |
+
Returns:
|
102 |
+
μΈμλ ν
μ€νΈ λλ μ€λ₯ λ©μμ§λ₯Ό ν¬ν¨ν λμ
λ리
|
103 |
+
{'success': True, 'text': 'μΈμλ ν
μ€νΈ'}
|
104 |
+
{'success': False, 'error': 'μ€λ₯ λ©μμ§', 'details': 'μμΈ λ΄μ©'}
|
105 |
+
"""
|
106 |
+
if not self.client_id or not self.client_secret:
|
107 |
+
logger.error("API ν€κ° μ€μ λμ§ μμμ΅λλ€.")
|
108 |
+
return {"success": False, "error": "API ν€κ° μ€μ λμ§ μμμ΅λλ€."}
|
109 |
+
|
110 |
+
try:
|
111 |
+
# ν ν° νλ λλ κ°±μ
|
112 |
+
# (μ νμ κ°μ : λ§λ£ μκ° μ²΄ν¬ λ‘μ§ μΆκ° μ self._token_expires_at μ¬μ©)
|
113 |
+
if not self.access_token: # or time.time() >= self._token_expires_at:
|
114 |
+
logger.info("VITO API ν ν° νλ/κ°±μ μλ...")
|
115 |
+
self.get_access_token()
|
116 |
+
|
117 |
+
headers = {
|
118 |
+
"Authorization": f"Bearer {self.access_token}"
|
119 |
+
}
|
120 |
+
|
121 |
+
files = {
|
122 |
+
"file": ("audio_file", audio_bytes) # νμΌλͺ
ννλ‘ μ λ¬
|
123 |
+
}
|
124 |
+
|
125 |
+
# API μ€μ κ° (νμμ λ°λΌ μμ )
|
126 |
+
config = {
|
127 |
+
"diarization": {"use_verification": False},
|
128 |
+
"use_multi_channel": False,
|
129 |
+
"use_itn": True, # Inverse Text Normalization (μ«μ, λ μ§ λ± λ³ν)
|
130 |
+
"use_disfluency_filter": True, # νλ¬ (μ, μ...) μ κ±°
|
131 |
+
"use_profanity_filter": False, # λΉμμ΄ νν°λ§
|
132 |
+
"language": language,
|
133 |
+
# "type": "audio" # type νλΌλ―Έν°λ VITO λ¬Έμμ νμ μλ (μλ κ°μ§)
|
134 |
+
}
|
135 |
+
data = {"config": json.dumps(config)}
|
136 |
+
|
137 |
+
logger.info(f"VITO STT API ({self.stt_url}) μμ² μ μ‘ μ€...")
|
138 |
+
response = requests.post(
|
139 |
+
self.stt_url,
|
140 |
+
headers=headers,
|
141 |
+
files=files,
|
142 |
+
data=data,
|
143 |
+
timeout=20 # μ
λ‘λ νμμμ
|
144 |
+
)
|
145 |
+
response.raise_for_status()
|
146 |
+
|
147 |
+
result = response.json()
|
148 |
+
job_id = result.get("id")
|
149 |
+
|
150 |
+
if not job_id:
|
151 |
+
logger.error("VITO API μμ
IDλ₯Ό λ°μμ€μ§ λͺ»νμ΅λλ€.")
|
152 |
+
return {"success": False, "error": "VITO API μμ
IDλ₯Ό λ°μμ€μ§ λͺ»νμ΅λλ€."}
|
153 |
+
|
154 |
+
logger.info(f"VITO STT μμ
ID: {job_id}, κ²°κ³Ό νμΈ μμ...")
|
155 |
+
|
156 |
+
# κ²°κ³Ό νμΈ URL
|
157 |
+
transcript_url = f"{self.stt_url}/{job_id}"
|
158 |
+
max_tries = 15 # μ΅λ μλ νμ μ¦κ°
|
159 |
+
wait_time = 2 # λκΈ° μκ° μ¦κ° (μ΄)
|
160 |
+
|
161 |
+
for try_count in range(max_tries):
|
162 |
+
time.sleep(wait_time) # API λΆν κ°μ μν΄ λκΈ°
|
163 |
+
logger.debug(f"κ²°κ³Ό νμΈ μλ ({try_count + 1}/{max_tries}) - URL: {transcript_url}")
|
164 |
+
get_response = requests.get(
|
165 |
+
transcript_url,
|
166 |
+
headers=headers,
|
167 |
+
timeout=10 # κ²°κ³Ό νμΈ νμμμ
|
168 |
+
)
|
169 |
+
get_response.raise_for_status()
|
170 |
+
|
171 |
+
result = get_response.json()
|
172 |
+
status = result.get("status")
|
173 |
+
logger.debug(f"νμ¬ μν: {status}")
|
174 |
+
|
175 |
+
if status == "completed":
|
176 |
+
# κ²°κ³Ό μΆμΆ (utterances ꡬ쑰 νμΈ νμ)
|
177 |
+
utterances = result.get("results", {}).get("utterances", [])
|
178 |
+
if utterances:
|
179 |
+
# μ 체 ν
μ€νΈλ₯Ό νλλ‘ ν©μΉ¨
|
180 |
+
transcript = " ".join([seg.get("msg", "") for seg in utterances if seg.get("msg")]).strip()
|
181 |
+
logger.info(f"VITO STT μΈμ μ±κ³΅ (μΌλΆ): {transcript[:50]}...")
|
182 |
+
return {
|
183 |
+
"success": True,
|
184 |
+
"text": transcript
|
185 |
+
# "raw_result": result # νμμ μ 체 κ²°κ³Ό λ°ν
|
186 |
+
}
|
187 |
+
else:
|
188 |
+
logger.warning("VITO STT μλ£λμμΌλ κ²°κ³Ό utterancesκ° λΉμ΄μμ΅λλ€.")
|
189 |
+
return {"success": True, "text": ""} # μ±κ³΅μ΄μ§λ§ ν
μ€νΈ μμ
|
190 |
+
|
191 |
+
elif status == "failed":
|
192 |
+
error_msg = f"VITO API λ³ν μ€ν¨: {result.get('message', 'μ μ μλ μ€λ₯')}"
|
193 |
+
logger.error(error_msg)
|
194 |
+
return {"success": False, "error": error_msg, "details": result}
|
195 |
+
|
196 |
+
elif status == "transcribing":
|
197 |
+
logger.info(f"VITO API μ²λ¦¬ μ€... ({try_count + 1}/{max_tries})")
|
198 |
+
else: # registered, waiting λ± λ€λ₯Έ μν
|
199 |
+
logger.info(f"VITO API μν '{status}', λκΈ° μ€... ({try_count + 1}/{max_tries})")
|
200 |
+
|
201 |
+
|
202 |
+
logger.error(f"VITO API μλ΅ νμμμ ({max_tries * wait_time}μ΄ μ΄κ³Ό)")
|
203 |
+
return {"success": False, "error": "VITO API μλ΅ νμμμ"}
|
204 |
+
|
205 |
+
except requests.exceptions.HTTPError as e:
|
206 |
+
# ν ν° λ§λ£ μ€λ₯ μ²λ¦¬ (401 Unauthorized)
|
207 |
+
if e.response.status_code == 401:
|
208 |
+
logger.warning("VITO API ν ν°μ΄ λ§λ£λμκ±°λ μ ν¨νμ§ μμ΅λλ€. ν ν° μ¬λ°κΈ μλ...")
|
209 |
+
self.access_token = None # κΈ°μ‘΄ ν ν° λ¬΄ν¨ν
|
210 |
+
try:
|
211 |
+
# μ¬κ· νΈμΆ λμ , ν ν° μ¬λ°κΈ ν λ€μ μλνλ λ‘μ§ κ΅¬μ±
|
212 |
+
self.get_access_token()
|
213 |
+
logger.info("μ ν ν°μΌλ‘ μ¬μλν©λλ€.")
|
214 |
+
# μ¬μλλ μ΄ ν¨μλ₯Ό λ€μ νΈμΆνλ λμ , νΈμΆνλ μͺ½μμ μ²λ¦¬νλ κ²μ΄ λ μμ ν οΏ½οΏ½οΏ½ μμ
|
215 |
+
# μ¬κΈ°μλ ν λ² λ μλνλ λ‘μ§ μΆκ° (무ν 루ν λ°©μ§ νμ)
|
216 |
+
# return self.transcribe_audio(audio_bytes, language) # μ¬κ· νΈμΆ λ°©μ
|
217 |
+
# --- λΉμ¬κ· λ°©μ ---
|
218 |
+
headers["Authorization"] = f"Bearer {self.access_token}" # ν€λ μ
λ°μ΄νΈ
|
219 |
+
# POST μμ²λΆν° λ€μ μμ (μ½λ μ€λ³΅ λ°μ κ°λ₯μ± μμ)
|
220 |
+
# ... (POST μμ² λ° κ²°κ³Ό ν΄λ§ λ‘μ§ λ°λ³΅) ...
|
221 |
+
# κ°λ¨νκ²λ κ·Έλ₯ μ€ν¨ μ²λ¦¬νκ³ μμμμ μ¬μλ μ λ
|
222 |
+
return {"success": False, "error": "ν ν° λ§λ£ ν μ¬μλ νμ", "details": "ν ν° μ¬λ°κΈ μ±κ³΅"}
|
223 |
+
|
224 |
+
except Exception as token_e:
|
225 |
+
logger.error(f"ν ν° μ¬νλ μ€ν¨: {token_e}")
|
226 |
+
return {"success": False, "error": f"ν ν° μ¬νλ μ€ν¨: {str(token_e)}"}
|
227 |
+
|
228 |
+
else:
|
229 |
+
# 401 μΈ λ€λ₯Έ HTTP μ€λ₯
|
230 |
+
error_body = ""
|
231 |
+
try:
|
232 |
+
error_body = e.response.text
|
233 |
+
except Exception:
|
234 |
+
pass
|
235 |
+
logger.error(f"VITO API HTTP μ€λ₯: {e.response.status_code}, μλ΅: {error_body}")
|
236 |
+
return {
|
237 |
+
"success": False,
|
238 |
+
"error": f"API HTTP μ€λ₯: {e.response.status_code}",
|
239 |
+
"details": error_body
|
240 |
+
}
|
241 |
+
|
242 |
+
except requests.exceptions.Timeout:
|
243 |
+
logger.error("VITO API μμ² μκ° μ΄κ³Ό")
|
244 |
+
return {"success": False, "error": "API μμ² μκ° μ΄κ³Ό"}
|
245 |
+
except requests.exceptions.RequestException as e:
|
246 |
+
logger.error(f"VITO API μμ² μ€ λ€νΈμν¬ μ€λ₯ λ°μ: {str(e)}")
|
247 |
+
return {"success": False, "error": "API μμ² λ€νΈμν¬ μ€λ₯", "details": str(e)}
|
248 |
+
except Exception as e:
|
249 |
+
logger.error(f"μμ±μΈμ μ²λ¦¬ μ€ μμμΉ λͺ»ν μ€λ₯ λ°μ: {str(e)}", exc_info=True)
|
250 |
+
return {
|
251 |
+
"success": False,
|
252 |
+
"error": "μμ±μΈμ λ΄λΆ μ²λ¦¬ μ€ν¨",
|
253 |
+
"details": str(e)
|
254 |
+
}
|