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 |
+
}
|