RAG3_Voice / app.py
jeongsoo's picture
app
b4a2320
"""
디버깅을 μœ„ν•œ μ½”λ“œ μΆ”κ°€ - 경둜 κ΄€λ ¨ 문제 ν•΄κ²°
"""
import os
import time
import hashlib
import pickle
import json
import logging
import glob
from typing import List, Dict, Tuple, Any, Optional
from logging.handlers import RotatingFileHandler
from pathlib import Path
from langchain.schema import Document
from config import (
PDF_DIRECTORY, CACHE_DIRECTORY, CHUNK_SIZE, CHUNK_OVERLAP,
LLM_MODEL, LOG_LEVEL, LOG_FILE, print_config, validate_config
)
from optimized_document_processor import OptimizedDocumentProcessor
from vector_store import VectorStore
import sys
print("===== Script starting =====")
sys.stdout.flush() # μ¦‰μ‹œ 좜λ ₯ κ°•μ œ
# μ£Όμš” ν•¨μˆ˜/λ©”μ„œλ“œ 호좜 전후에도 디버깅 좜λ ₯ μΆ”κ°€
print("Loading config...")
sys.stdout.flush()
# from config import ... λ“±μ˜ μ½”λ“œ
print("Config loaded!")
sys.stdout.flush()
# λ‘œκΉ… μ„€μ • κ°œμ„ 
def setup_logging():
"""μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‘œκΉ… μ„€μ •"""
# 둜그 레벨 μ„€μ •
log_level = getattr(logging, LOG_LEVEL.upper(), logging.INFO)
# 둜그 포맷 μ„€μ •
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(log_format)
# 루트 둜거 μ„€μ •
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# ν•Έλ“€λŸ¬ μ΄ˆκΈ°ν™”
# μ½˜μ†” ν•Έλ“€λŸ¬
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
# 파일 ν•Έλ“€λŸ¬ (νšŒμ „μ‹)
try:
file_handler = RotatingFileHandler(
LOG_FILE,
maxBytes=10*1024*1024, # 10 MB
backupCount=5
)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
except Exception as e:
console_handler.warning(f"둜그 파일 μ„€μ • μ‹€νŒ¨: {e}, μ½˜μ†” λ‘œκΉ…λ§Œ μ‚¬μš©ν•©λ‹ˆλ‹€.")
return logging.getLogger("AutoRAG")
# 둜거 μ„€μ •
logger = setup_logging()
# ν˜„μž¬ μž‘μ—… 디렉토리 확인을 μœ„ν•œ 디버깅 μ½”λ“œ
current_dir = os.getcwd()
logger.info(f"ν˜„μž¬ μž‘μ—… 디렉토리: {current_dir}")
# μ„€μ •λœ PDF 디렉토리 확인
abs_pdf_dir = os.path.abspath(PDF_DIRECTORY)
logger.info(f"μ„€μ •λœ PDF 디렉토리: {PDF_DIRECTORY}")
logger.info(f"μ ˆλŒ€ 경둜둜 λ³€ν™˜λœ PDF 디렉토리: {abs_pdf_dir}")
# PDF 디렉토리 쑴재 확인
if os.path.exists(abs_pdf_dir):
logger.info(f"PDF 디렉토리가 μ‘΄μž¬ν•©λ‹ˆλ‹€: {abs_pdf_dir}")
# 디렉토리 λ‚΄μš© 확인
pdf_files = glob.glob(os.path.join(abs_pdf_dir, "*.pdf"))
logger.info(f"디렉토리 λ‚΄ PDF 파일 λͺ©λ‘: {pdf_files}")
else:
logger.error(f"PDF 디렉토리가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: {abs_pdf_dir}")
# μƒμœ„ 디렉토리 λ‚΄μš© 확인
parent_dir = os.path.dirname(abs_pdf_dir)
logger.info(f"μƒμœ„ 디렉토리: {parent_dir}")
if os.path.exists(parent_dir):
dir_contents = os.listdir(parent_dir)
logger.info(f"μƒμœ„ 디렉토리 λ‚΄μš©: {dir_contents}")
# μ„€μ • μƒνƒœ 확인
logger.info("μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„€μ • 검증 쀑...")
config_status = validate_config()
if config_status["status"] != "valid":
for warning in config_status["warnings"]:
logger.warning(f"μ„€μ • κ²½κ³ : {warning}")
# μ•ˆμ „ν•œ μž„ν¬νŠΈ
try:
from rag_chain import RAGChain
RAG_CHAIN_AVAILABLE = True
print("RAG 체인 λͺ¨λ“ˆ λ‘œλ“œ 성곡!")
except ImportError as e:
logger.warning(f"RAG 체인 λͺ¨λ“ˆμ„ λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€: {e}")
RAG_CHAIN_AVAILABLE = False
except Exception as e:
logger.warning(f"RAG 체인 λͺ¨λ“ˆ λ‘œλ“œ 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {e}")
RAG_CHAIN_AVAILABLE = False
# 폴백 RAG κ΄€λ ¨ λͺ¨λ“ˆλ„ 미리 확인
try:
from fallback_rag_chain import FallbackRAGChain
FALLBACK_AVAILABLE = True
print("폴백 RAG 체인 λͺ¨λ“ˆ λ‘œλ“œ 성곡!")
except ImportError as e:
logger.warning(f"폴백 RAG 체인 λͺ¨λ“ˆμ„ λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€: {e}")
FALLBACK_AVAILABLE = False
try:
from offline_fallback_rag import OfflineFallbackRAG
OFFLINE_FALLBACK_AVAILABLE = True
print("μ˜€ν”„λΌμΈ 폴백 RAG λͺ¨λ“ˆ λ‘œλ“œ 성곡!")
except ImportError as e:
logger.warning(f"μ˜€ν”„λΌμΈ 폴백 RAG λͺ¨λ“ˆμ„ λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€: {e}")
OFFLINE_FALLBACK_AVAILABLE = False
class DocumentProcessingError(Exception):
"""λ¬Έμ„œ 처리 쀑 λ°œμƒν•˜λŠ” μ˜ˆμ™Έ"""
pass
class VectorStoreError(Exception):
"""벑터 μŠ€ν† μ–΄ μž‘μ—… 쀑 λ°œμƒν•˜λŠ” μ˜ˆμ™Έ"""
pass
class RAGInitializationError(Exception):
"""RAG 체인 μ΄ˆκΈ°ν™” 쀑 λ°œμƒν•˜λŠ” μ˜ˆμ™Έ"""
pass
class ConfigurationError(Exception):
"""μ„€μ • κ΄€λ ¨ 였λ₯˜"""
pass
class AutoRAGChatApp:
"""
documents ν΄λ”μ˜ PDF νŒŒμΌμ„ μžλ™μœΌλ‘œ μ²˜λ¦¬ν•˜λŠ” RAG 챗봇
"""
def __init__(self):
"""
RAG 챗봇 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ΄ˆκΈ°ν™”
"""
try:
logger.info("AutoRAGChatApp μ΄ˆκΈ°ν™” μ‹œμž‘")
# 데이터 디렉토리 μ •μ˜ (μ„€μ •μ—μ„œ κ°€μ Έμ˜΄)
# μ ˆλŒ€ 경둜둜 λ³€ν™˜ν•˜μ—¬ μ‚¬μš©
self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
self.cache_directory = os.path.abspath(CACHE_DIRECTORY)
self.index_file = os.path.join(self.cache_directory, "file_index.json")
self.chunks_dir = os.path.join(self.cache_directory, "chunks")
self.vector_index_dir = os.path.join(self.cache_directory, "vector_index")
logger.info(f"μ„€μ •λœ PDF 디렉토리 (μ ˆλŒ€ 경둜): {self.pdf_directory}")
# 디렉토리 검증
self._verify_pdf_directory()
# 디렉토리 생성
self._ensure_directories_exist()
logger.info(f"PDF λ¬Έμ„œ 디렉토리: '{self.pdf_directory}'")
logger.info(f"μΊμ‹œ 디렉토리: '{self.cache_directory}'")
# μ»΄ν¬λ„ŒνŠΈ μ΄ˆκΈ°ν™”
try:
self.document_processor = OptimizedDocumentProcessor(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP
)
except Exception as e:
logger.error(f"λ¬Έμ„œ 처리기 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {e}")
raise DocumentProcessingError(f"λ¬Έμ„œ 처리기 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {str(e)}")
# 벑터 μ €μž₯μ†Œ μ΄ˆκΈ°ν™”
try:
self.vector_store = VectorStore(use_milvus=False)
except Exception as e:
logger.error(f"벑터 μ €μž₯μ†Œ μ΄ˆκΈ°ν™” μ‹€νŒ¨: {e}")
raise VectorStoreError(f"벑터 μ €μž₯μ†Œ μ΄ˆκΈ°ν™” μ‹€νŒ¨: {str(e)}")
# λ¬Έμ„œ 인덱슀 λ‘œλ“œ
self.file_index = self._load_file_index()
# κΈ°λ³Έ λ³€μˆ˜ μ΄ˆκΈ°ν™”
self.documents = []
self.processed_files = []
self.is_initialized = False
# μ‹œμž‘ μ‹œ μžλ™μœΌλ‘œ λ¬Έμ„œ λ‘œλ“œ 및 처리
logger.info("λ¬Έμ„œ μžλ™ λ‘œλ“œ 및 처리 μ‹œμž‘...")
self.auto_process_documents()
logger.info("AutoRAGChatApp μ΄ˆκΈ°ν™” μ™„λ£Œ")
except Exception as e:
logger.critical(f"μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ΄ˆκΈ°ν™” 쀑 μ‹¬κ°ν•œ 였λ₯˜: {e}", exc_info=True)
# κΈ°λ³Έ μƒνƒœ μ„€μ •μœΌλ‘œ μ΅œμ†Œν•œμ˜ κΈ°λŠ₯ μœ μ§€
self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
self.documents = []
self.processed_files = []
self.is_initialized = False
self.file_index = {}
def _ensure_directories_exist(self) -> None:
"""
ν•„μš”ν•œ 디렉토리가 μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜κ³  생성
"""
directories = [
self.pdf_directory,
self.cache_directory,
self.chunks_dir,
self.vector_index_dir
]
for directory in directories:
try:
os.makedirs(directory, exist_ok=True)
except Exception as e:
logger.error(f"디렉토리 생성 μ‹€νŒ¨ '{directory}': {e}")
raise OSError(f"디렉토리 생성 μ‹€νŒ¨ '{directory}': {str(e)}")
def _process_pdf_file(self, file_path: str) -> List[Document]:
"""
PDF 파일 처리 - docling μ‹€νŒ¨ μ‹œ PyPDFLoader μ‚¬μš©
Args:
file_path: μ²˜λ¦¬ν•  PDF 파일 경둜
Returns:
처리된 λ¬Έμ„œ 청크 리슀트
"""
if not os.path.exists(file_path):
logger.error(f"파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
raise FileNotFoundError(f"파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
try:
logger.info(f"docling으둜 처리 μ‹œλ„: {file_path}")
# docling μ‚¬μš© μ‹œλ„
try:
# 10초 νƒ€μž„μ•„μ›ƒ μ„€μ • (μ˜΅μ…˜)
import signal
def timeout_handler(signum, frame):
raise TimeoutError("docling 처리 μ‹œκ°„ 초과 (60초)")
# λ¦¬λˆ…μŠ€/λ§₯μ—μ„œλ§Œ μž‘λ™ (μœˆλ„μš°μ—μ„œλŠ” λ¬΄μ‹œλ¨)
try:
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(60) # 60초 νƒ€μž„μ•„μ›ƒ
except (AttributeError, ValueError) as se:
logger.warning(f"μ‹œκ·Έλ„ μ„€μ • μ‹€νŒ¨ (μœˆλ„μš° ν™˜κ²½μΌ 수 있음): {se}")
# docling으둜 처리 μ‹œλ„
chunks = self.document_processor.process_pdf(file_path, use_docling=True)
# νƒ€μž„μ•„μ›ƒ μ·¨μ†Œ
try:
signal.alarm(0)
except (AttributeError, ValueError):
pass
return chunks
except TimeoutError as te:
logger.warning(f"docling 처리 μ‹œκ°„ 초과: {te}")
logger.info("PyPDFLoader둜 λŒ€μ²΄ν•©λ‹ˆλ‹€.")
# PyPDFLoader둜 λŒ€μ²΄
try:
return self.document_processor.process_pdf(file_path, use_docling=False)
except Exception as inner_e:
logger.error(f"PyPDFLoader 처리 였λ₯˜: {inner_e}", exc_info=True)
raise DocumentProcessingError(f"PDF λ‘œλ”© μ‹€νŒ¨ (PyPDFLoader): {str(inner_e)}")
except Exception as e:
# docling 였λ₯˜ 확인
error_str = str(e)
if "Invalid code point" in error_str or "RuntimeError" in error_str:
logger.warning(f"docling 처리 였λ₯˜ (μ½”λ“œ 포인트 문제): {error_str}")
logger.info("PyPDFLoader둜 λŒ€μ²΄ν•©λ‹ˆλ‹€.")
else:
logger.warning(f"docling 처리 였λ₯˜: {error_str}")
logger.info("PyPDFLoader둜 λŒ€μ²΄ν•©λ‹ˆλ‹€.")
# PyPDFLoader둜 λŒ€μ²΄
try:
return self.document_processor.process_pdf(file_path, use_docling=False)
except Exception as inner_e:
logger.error(f"PyPDFLoader 처리 였λ₯˜: {inner_e}", exc_info=True)
raise DocumentProcessingError(f"PDF λ‘œλ”© μ‹€νŒ¨ (PyPDFLoader): {str(inner_e)}")
except DocumentProcessingError:
# 이미 λž˜ν•‘λœ μ˜ˆμ™ΈλŠ” κ·ΈλŒ€λ‘œ 전달
raise
except Exception as e:
logger.error(f"PDF 처리 쀑 μ‹¬κ°ν•œ 였λ₯˜: {e}", exc_info=True)
# 빈 청크라도 λ°˜ν™˜ν•˜μ—¬ 전체 μ²˜λ¦¬κ°€ μ€‘λ‹¨λ˜μ§€ μ•Šλ„λ‘ 함
logger.warning(f"'{file_path}' 처리 μ‹€νŒ¨λ‘œ 빈 청크 λͺ©λ‘ λ°˜ν™˜")
return []
def _load_file_index(self) -> Dict[str, Dict[str, Any]]:
"""
파일 인덱슀 λ‘œλ“œ
Returns:
파일 경둜 -> 메타데이터 λ§€ν•‘
"""
if os.path.exists(self.index_file):
try:
with open(self.index_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
logger.error(f"인덱슀 파일 JSON νŒŒμ‹± μ‹€νŒ¨: {e}")
logger.warning("μ†μƒλœ 인덱슀 파일, μƒˆλ‘œμš΄ 인덱슀λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.")
return {}
except Exception as e:
logger.error(f"인덱슀 파일 λ‘œλ“œ μ‹€νŒ¨: {e}")
return {}
return {}
def _save_file_index(self) -> None:
"""
파일 인덱슀 μ €μž₯
"""
try:
with open(self.index_file, 'w', encoding='utf-8') as f:
json.dump(self.file_index, f, ensure_ascii=False, indent=2)
logger.debug("파일 인덱슀 μ €μž₯ μ™„λ£Œ")
except Exception as e:
logger.error(f"파일 인덱슀 μ €μž₯ μ‹€νŒ¨: {e}")
raise IOError(f"파일 인덱슀 μ €μž₯ μ‹€νŒ¨: {str(e)}")
def _calculate_file_hash(self, file_path: str) -> str:
"""
파일 ν•΄μ‹œ 계산
Args:
file_path: 파일 경둜
Returns:
MD5 ν•΄μ‹œκ°’
"""
if not os.path.exists(file_path):
logger.error(f"ν•΄μ‹œ 계산 μ‹€νŒ¨ - 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
raise FileNotFoundError(f"파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
try:
hasher = hashlib.md5()
with open(file_path, 'rb') as f:
buf = f.read(65536)
while len(buf) > 0:
hasher.update(buf)
buf = f.read(65536)
return hasher.hexdigest()
except Exception as e:
logger.error(f"파일 ν•΄μ‹œ 계산 쀑 였λ₯˜: {e}")
raise IOError(f"파일 ν•΄μ‹œ 계산 μ‹€νŒ¨: {str(e)}")
def _is_file_processed(self, file_path: str) -> bool:
"""
파일이 이미 μ²˜λ¦¬λ˜μ—ˆκ³  λ³€κ²½λ˜μ§€ μ•Šμ•˜λŠ”μ§€ 확인
Args:
file_path: 파일 경둜
Returns:
처리 μ—¬λΆ€
"""
# 파일 쑴재 확인
if not os.path.exists(file_path):
logger.warning(f"파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
return False
# μΈλ±μŠ€μ— 파일 쑴재 μ—¬λΆ€ 확인
if file_path not in self.file_index:
return False
try:
# ν˜„μž¬ ν•΄μ‹œκ°’ 계산
current_hash = self._calculate_file_hash(file_path)
# μ €μž₯된 ν•΄μ‹œκ°’κ³Ό 비ꡐ
if self.file_index[file_path]['hash'] != current_hash:
logger.info(f"파일 λ³€κ²½ 감지: {file_path}")
return False
# 청크 파일 쑴재 확인
chunks_path = self.file_index[file_path]['chunks_path']
if not os.path.exists(chunks_path):
logger.warning(f"청크 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {chunks_path}")
return False
return True
except Exception as e:
logger.error(f"파일 처리 μƒνƒœ 확인 쀑 였λ₯˜: {e}")
return False
def _get_chunks_path(self, file_hash: str) -> str:
"""
청크 파일 경둜 생성
Args:
file_hash: 파일 ν•΄μ‹œκ°’
Returns:
청크 파일 경둜
"""
return os.path.join(self.chunks_dir, f"{file_hash}.pkl")
def _save_chunks(self, file_path: str, chunks: List[Document]) -> None:
"""
청크 데이터 μ €μž₯
Args:
file_path: 원본 파일 경둜
chunks: λ¬Έμ„œ 청크 리슀트
"""
try:
# ν•΄μ‹œ 계산
file_hash = self._calculate_file_hash(file_path)
# 청크 파일 경둜
chunks_path = self._get_chunks_path(file_hash)
# 청크 데이터 μ €μž₯
with open(chunks_path, 'wb') as f:
pickle.dump(chunks, f)
# 인덱슀 μ—…λ°μ΄νŠΈ
self.file_index[file_path] = {
'hash': file_hash,
'chunks_path': chunks_path,
'last_processed': time.time(),
'chunks_count': len(chunks),
'file_size': os.path.getsize(file_path),
'file_name': os.path.basename(file_path)
}
# 인덱슀 μ €μž₯
self._save_file_index()
logger.info(f"청크 μ €μž₯ μ™„λ£Œ: {file_path} ({len(chunks)}개 청크)")
except Exception as e:
logger.error(f"청크 μ €μž₯ μ‹€νŒ¨: {e}", exc_info=True)
raise IOError(f"청크 μ €μž₯ μ‹€νŒ¨: {str(e)}")
def _load_chunks(self, file_path: str) -> List[Document]:
"""
μ €μž₯된 청크 데이터 λ‘œλ“œ
Args:
file_path: 파일 경둜
Returns:
λ¬Έμ„œ 청크 리슀트
"""
if file_path not in self.file_index:
logger.error(f"μΈλ±μŠ€μ— 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
raise KeyError(f"μΈλ±μŠ€μ— 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {file_path}")
chunks_path = self.file_index[file_path]['chunks_path']
if not os.path.exists(chunks_path):
logger.error(f"청크 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {chunks_path}")
raise FileNotFoundError(f"청크 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {chunks_path}")
try:
with open(chunks_path, 'rb') as f:
chunks = pickle.load(f)
logger.info(f"청크 λ‘œλ“œ μ™„λ£Œ: {file_path} ({len(chunks)}개 청크)")
return chunks
except pickle.UnpicklingError as e:
logger.error(f"청크 파일 역직렬화 μ‹€νŒ¨: {e}")
raise IOError(f"청크 파일 손상: {str(e)}")
except Exception as e:
logger.error(f"청크 λ‘œλ“œ μ‹€νŒ¨: {e}", exc_info=True)
raise IOError(f"청크 λ‘œλ“œ μ‹€νŒ¨: {str(e)}")
def _verify_pdf_directory(self):
"""PDF 디렉토리 검증 및 파일 쑴재 확인"""
try:
# 디렉토리 쑴재 확인
if not os.path.exists(self.pdf_directory):
try:
logger.warning(f"PDF 디렉토리가 μ‘΄μž¬ν•˜μ§€ μ•Šμ•„ μƒμ„±ν•©λ‹ˆλ‹€: {self.pdf_directory}")
os.makedirs(self.pdf_directory, exist_ok=True)
except Exception as e:
logger.error(f"PDF 디렉토리 생성 μ‹€νŒ¨: {e}")
raise
# 디렉토리인지 확인
if not os.path.isdir(self.pdf_directory):
logger.error(f"PDF κ²½λ‘œκ°€ 디렉토리가 μ•„λ‹™λ‹ˆλ‹€: {self.pdf_directory}")
raise ConfigurationError(f"PDF κ²½λ‘œκ°€ 디렉토리가 μ•„λ‹™λ‹ˆλ‹€: {self.pdf_directory}")
# PDF 파일 쑴재 확인
pdf_files = [f for f in os.listdir(self.pdf_directory) if f.lower().endswith('.pdf')]
if pdf_files:
logger.info(f"PDF λ””λ ‰ν† λ¦¬μ—μ„œ {len(pdf_files)}개의 PDF νŒŒμΌμ„ μ°Ύμ•˜μŠ΅λ‹ˆλ‹€: {pdf_files}")
else:
# μ—¬λŸ¬ κ²½λ‘œμ—μ„œ PDF 파일 탐색 μ‹œλ„
alternative_paths = [
"./documents",
"../documents",
"documents",
os.path.join(os.getcwd(), "documents")
]
found_pdfs = False
for alt_path in alternative_paths:
if os.path.exists(alt_path) and os.path.isdir(alt_path):
alt_pdf_files = [f for f in os.listdir(alt_path) if f.lower().endswith('.pdf')]
if alt_pdf_files:
logger.warning(f"λŒ€μ²΄ 경둜 '{alt_path}'μ—μ„œ PDF νŒŒμΌμ„ μ°Ύμ•˜μŠ΅λ‹ˆλ‹€. 이 경둜λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.")
self.pdf_directory = os.path.abspath(alt_path)
found_pdfs = True
break
if not found_pdfs:
logger.warning(f"PDF 디렉토리에 PDF 파일이 μ—†μŠ΅λ‹ˆλ‹€: {self.pdf_directory}")
logger.info("PDF νŒŒμΌμ„ 디렉토리에 μΆ”κ°€ν•΄μ£Όμ„Έμš”.")
except Exception as e:
logger.error(f"PDF 디렉토리 검증 쀑 였λ₯˜: {e}", exc_info=True)
raise
def auto_process_documents(self) -> str:
"""
documents ν΄λ”μ˜ PDF 파일 μžλ™ 처리
Returns:
처리 κ²°κ³Ό λ©”μ‹œμ§€
"""
try:
start_time = time.time()
# PDF 파일 λͺ©λ‘ μˆ˜μ§‘μ„ κ°œμ„ ν•˜μ—¬ λ‹€μ–‘ν•œ 경둜 처리
try:
pdf_files = []
# μ„€μ •λœ λ””λ ‰ν† λ¦¬μ—μ„œ PDF 파일 μ°ΎκΈ°
logger.info(f"PDF 파일 검색 경둜: {self.pdf_directory}")
if os.path.exists(self.pdf_directory) and os.path.isdir(self.pdf_directory):
# 디렉토리 λ‚΄μš© 좜λ ₯ (λ””λ²„κΉ…μš©)
dir_contents = os.listdir(self.pdf_directory)
logger.info(f"디렉토리 λ‚΄μš©: {dir_contents}")
# PDF 파일만 필터링
for filename in os.listdir(self.pdf_directory):
if filename.lower().endswith('.pdf'):
file_path = os.path.join(self.pdf_directory, filename)
if os.path.isfile(file_path): # μ‹€μ œ νŒŒμΌμΈμ§€ 확인
pdf_files.append(file_path)
logger.info(f"PDF 파일 찾음: {file_path}")
# 발견된 λͺ¨λ“  파일 둜그
logger.info(f"발견된 λͺ¨λ“  PDF 파일: {pdf_files}")
except FileNotFoundError:
logger.error(f"PDF 디렉토리λ₯Ό 찾을 수 μ—†μŒ: {self.pdf_directory}")
return f"'{self.pdf_directory}' 디렉토리λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. 디렉토리가 μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”."
except PermissionError:
logger.error(f"PDF 디렉토리 μ ‘κ·Ό κΆŒν•œ μ—†μŒ: {self.pdf_directory}")
return f"'{self.pdf_directory}' 디렉토리에 μ ‘κ·Όν•  수 μ—†μŠ΅λ‹ˆλ‹€. κΆŒν•œμ„ ν™•μΈν•˜μ„Έμš”."
if not pdf_files:
logger.warning(f"'{self.pdf_directory}' 폴더에 PDF 파일이 μ—†μŠ΅λ‹ˆλ‹€.")
return f"'{self.pdf_directory}' 폴더에 PDF 파일이 μ—†μŠ΅λ‹ˆλ‹€."
logger.info(f"발견된 PDF 파일: {len(pdf_files)}개")
# 폴더 λ‚΄ PDF 파일 처리
new_files = []
updated_files = []
cached_files = []
failed_files = []
all_chunks = []
for file_path in pdf_files:
try:
if self._is_file_processed(file_path):
# μΊμ‹œμ—μ„œ 청크 λ‘œλ“œ
try:
chunks = self._load_chunks(file_path)
all_chunks.extend(chunks)
cached_files.append(file_path)
self.processed_files.append(os.path.basename(file_path))
except Exception as e:
logger.error(f"μΊμ‹œλœ 청크 λ‘œλ“œ μ‹€νŒ¨: {e}")
# νŒŒμΌμ„ λ‹€μ‹œ 처리
logger.info(f"μΊμ‹œ μ‹€νŒ¨λ‘œ 파일 재처리: {file_path}")
chunks = self._process_pdf_file(file_path)
if chunks:
self._save_chunks(file_path, chunks)
all_chunks.extend(chunks)
updated_files.append(file_path)
self.processed_files.append(os.path.basename(file_path))
else:
failed_files.append(file_path)
else:
# μƒˆ 파일 λ˜λŠ” λ³€κ²½λœ 파일 처리
logger.info(f"처리 쀑: {file_path}")
try:
# κ°œμ„ λœ PDF 처리 λ©”μ„œλ“œ μ‚¬μš©
chunks = self._process_pdf_file(file_path)
if chunks: # 청크가 μžˆλŠ” κ²½μš°μ—λ§Œ μ €μž₯
# 청크 μ €μž₯
self._save_chunks(file_path, chunks)
all_chunks.extend(chunks)
if file_path in self.file_index:
updated_files.append(file_path)
else:
new_files.append(file_path)
self.processed_files.append(os.path.basename(file_path))
else:
logger.warning(f"'{file_path}' 처리 μ‹€νŒ¨: μΆ”μΆœλœ 청크 μ—†μŒ")
failed_files.append(file_path)
except Exception as e:
logger.error(f"'{file_path}' 처리 쀑 였λ₯˜: {e}", exc_info=True)
failed_files.append(file_path)
except Exception as e:
logger.error(f"'{file_path}' 파일 처리 루프 쀑 였λ₯˜: {e}", exc_info=True)
failed_files.append(file_path)
# λͺ¨λ“  청크 μ €μž₯
self.documents = all_chunks
processing_time = time.time() - start_time
logger.info(f"λ¬Έμ„œ 처리 μ™„λ£Œ: {len(all_chunks)}개 청크, {processing_time:.2f}초")
# 벑터 인덱슀 처리
try:
self._process_vector_index(new_files, updated_files)
except Exception as e:
logger.error(f"벑터 인덱슀 처리 μ‹€νŒ¨: {e}", exc_info=True)
return f"λ¬Έμ„œλŠ” μ²˜λ¦¬λ˜μ—ˆμœΌλ‚˜ 벑터 인덱슀 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
# RAG 체인 μ΄ˆκΈ°ν™”
if RAG_CHAIN_AVAILABLE:
try:
logger.info("RAGChain으둜 μ΄ˆκΈ°ν™”λ₯Ό μ‹œλ„ν•©λ‹ˆλ‹€.")
self.rag_chain = RAGChain(self.vector_store)
self.is_initialized = True
logger.info("RAG 체인 μ΄ˆκΈ°ν™” 성곡")
except Exception as e:
logger.error(f"RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {e}", exc_info=True)
# FallbackRAGChain으둜 λŒ€μ²΄ μ‹œλ„
try:
logger.info("FallbackRAGChain으둜 λŒ€μ²΄ν•©λ‹ˆλ‹€...")
from fallback_rag_chain import FallbackRAGChain
self.rag_chain = FallbackRAGChain(self.vector_store)
self.is_initialized = True
logger.info("폴백 RAG 체인 μ΄ˆκΈ°ν™” 성곡")
except Exception as fallback_e:
logger.error(f"폴백 RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {fallback_e}", exc_info=True)
# SimpleRAGChain μ‹œλ„ (μ΅œν›„μ˜ μˆ˜λ‹¨)
try:
logger.info("SimpleRAGChain으둜 λŒ€μ²΄ν•©λ‹ˆλ‹€...")
from simple_rag_chain import SimpleRAGChain
# API 정보 κ°€μ Έμ˜€κΈ°
try:
from config import DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT
logger.info(f"μ„€μ • νŒŒμΌμ—μ„œ DeepSeek API 정보λ₯Ό λ‘œλ“œν–ˆμŠ΅λ‹ˆλ‹€: λͺ¨λΈ={DEEPSEEK_MODEL}")
except ImportError:
# μ„€μ • νŒŒμΌμ—μ„œ κ°€μ Έμ˜¬ 수 μ—†λŠ” 경우 ν™˜κ²½ λ³€μˆ˜ 확인
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT",
"https://api.deepseek.com/v1/chat/completions")
logger.info(f"ν™˜κ²½ λ³€μˆ˜μ—μ„œ DeepSeek API 정보λ₯Ό λ‘œλ“œν–ˆμŠ΅λ‹ˆλ‹€: λͺ¨λΈ={DEEPSEEK_MODEL}")
# SimpleRAGChain μ΄ˆκΈ°ν™” μ‹œλ„
self.rag_chain = SimpleRAGChain(self.vector_store)
self.is_initialized = True
logger.info("SimpleRAGChain μ΄ˆκΈ°ν™” 성곡")
except Exception as simple_e:
logger.error(f"λͺ¨λ“  RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {simple_e}", exc_info=True)
return f"λ¬Έμ„œμ™€ 벑터 μΈλ±μŠ€λŠ” μ²˜λ¦¬λ˜μ—ˆμœΌλ‚˜ RAG 체인 μ΄ˆκΈ°ν™”μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
else:
# RAGChain을 μ‚¬μš©ν•  수 μ—†λŠ” 경우
try:
logger.info("κΈ°λ³Έ RAG Chain을 μ‚¬μš©ν•  수 μ—†μ–΄ λŒ€μ²΄ 버전을 μ‹œλ„ν•©λ‹ˆλ‹€...")
# FallbackRAGChain μ‹œλ„
try:
from fallback_rag_chain import FallbackRAGChain
self.rag_chain = FallbackRAGChain(self.vector_store)
self.is_initialized = True
logger.info("폴백 RAG 체인 μ΄ˆκΈ°ν™” 성곡")
except Exception as fallback_e:
logger.error(f"폴백 RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {fallback_e}", exc_info=True)
# SimpleRAGChain μ‹œλ„ (μ΅œν›„μ˜ μˆ˜λ‹¨)
try:
from simple_rag_chain import SimpleRAGChain
self.rag_chain = SimpleRAGChain(self.vector_store)
self.is_initialized = True
logger.info("SimpleRAGChain μ΄ˆκΈ°ν™” 성곡")
except Exception as simple_e:
logger.error(f"λͺ¨λ“  RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {simple_e}", exc_info=True)
return f"λ¬Έμ„œμ™€ 벑터 μΈλ±μŠ€λŠ” μ²˜λ¦¬λ˜μ—ˆμœΌλ‚˜ RAG 체인 μ΄ˆκΈ°ν™”μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€"
except Exception as e:
logger.error(f"RAG 체인 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {e}", exc_info=True)
return f"λ¬Έμ„œμ™€ 벑터 μΈλ±μŠ€λŠ” μ²˜λ¦¬λ˜μ—ˆμœΌλ‚˜ RAG 체인 μ΄ˆκΈ°ν™”μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
# 성곡 λ©”μ‹œμ§€ 생성
result_message = f"""λ¬Έμ„œ 처리 μ™„λ£Œ!
- 처리된 파일: {len(pdf_files)}개
- μΊμ‹œλœ 파일: {len(cached_files)}개
- μƒˆ 파일: {len(new_files)}개
- μ—…λ°μ΄νŠΈλœ 파일: {len(updated_files)}개
- μ‹€νŒ¨ν•œ 파일: {len(failed_files)}개
- 총 청크 수: {len(all_chunks)}개
- 처리 μ‹œκ°„: {processing_time:.2f}초
이제 μ§ˆλ¬Έν•  μ€€λΉ„κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€!"""
return result_message
except Exception as e:
error_message = f"λ¬Έμ„œ 처리 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
logger.error(error_message, exc_info=True)
return error_message
def _process_vector_index(self, new_files: List[str], updated_files: List[str]) -> None:
"""
벑터 인덱슀 처리
Args:
new_files: μƒˆλ‘œ μΆ”κ°€λœ 파일 λͺ©λ‘
updated_files: μ—…λ°μ΄νŠΈλœ 파일 λͺ©λ‘
"""
# 벑터 인덱슀 μ €μž₯ 경둜 확인
if os.path.exists(self.vector_index_dir) and any(os.listdir(self.vector_index_dir)):
# κΈ°μ‘΄ 벑터 인덱슀 λ‘œλ“œ
try:
logger.info("μ €μž₯된 벑터 인덱슀 λ‘œλ“œ 쀑...")
vector_store_loaded = self.vector_store.load_local(self.vector_index_dir)
# 인덱슀 λ‘œλ“œ 성곡 확인
if self.vector_store.vector_store is not None:
# μƒˆ λ¬Έμ„œλ‚˜ λ³€κ²½λœ λ¬Έμ„œκ°€ 있으면 인덱슀 μ—…λ°μ΄νŠΈ
if new_files or updated_files:
logger.info("벑터 인덱슀 μ—…λ°μ΄νŠΈ 쀑...")
self.vector_store.add_documents(self.documents)
logger.info("벑터 인덱슀 λ‘œλ“œ μ™„λ£Œ")
else:
logger.warning("벑터 인덱슀λ₯Ό λ‘œλ“œν–ˆμœΌλ‚˜ μœ νš¨ν•˜μ§€ μ•ŠμŒ, μƒˆλ‘œ μƒμ„±ν•©λ‹ˆλ‹€.")
self.vector_store.create_or_load(self.documents)
except Exception as e:
logger.error(f"벑터 인덱슀 λ‘œλ“œ μ‹€νŒ¨, μƒˆλ‘œ μƒμ„±ν•©λ‹ˆλ‹€: {e}", exc_info=True)
# μƒˆ 벑터 인덱슀 생성
self.vector_store.create_or_load(self.documents)
else:
# μƒˆ 벑터 인덱슀 생성
logger.info("μƒˆ 벑터 인덱슀 생성 쀑...")
self.vector_store.create_or_load(self.documents)
# 벑터 인덱슀 μ €μž₯
if self.vector_store and self.vector_store.vector_store is not None:
try:
logger.info(f"벑터 인덱슀 μ €μž₯ 쀑: {self.vector_index_dir}")
save_result = self.vector_store.save_local(self.vector_index_dir)
logger.info(f"벑터 인덱슀 μ €μž₯ μ™„λ£Œ: {self.vector_index_dir}")
except Exception as e:
logger.error(f"벑터 인덱슀 μ €μž₯ μ‹€νŒ¨: {e}", exc_info=True)
raise VectorStoreError(f"벑터 인덱슀 μ €μž₯ μ‹€νŒ¨: {str(e)}")
else:
logger.warning("벑터 μΈλ±μŠ€κ°€ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•„ μ €μž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
def reset_cache(self) -> str:
"""
μΊμ‹œ μ΄ˆκΈ°ν™”
Returns:
κ²°κ³Ό λ©”μ‹œμ§€
"""
try:
# 청크 파일 μ‚­μ œ
try:
for filename in os.listdir(self.chunks_dir):
file_path = os.path.join(self.chunks_dir, filename)
if os.path.isfile(file_path):
os.remove(file_path)
logger.info("청크 μΊμ‹œ 파일 μ‚­μ œ μ™„λ£Œ")
except Exception as e:
logger.error(f"청크 파일 μ‚­μ œ 쀑 였λ₯˜: {e}")
return f"청크 파일 μ‚­μ œ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
# 인덱슀 μ΄ˆκΈ°ν™”
self.file_index = {}
try:
self._save_file_index()
logger.info("파일 인덱슀 μ΄ˆκΈ°ν™” μ™„λ£Œ")
except Exception as e:
logger.error(f"인덱슀 파일 μ΄ˆκΈ°ν™” 쀑 였λ₯˜: {e}")
return f"인덱슀 파일 μ΄ˆκΈ°ν™” 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
# 벑터 인덱슀 μ‚­μ œ
try:
for filename in os.listdir(self.vector_index_dir):
file_path = os.path.join(self.vector_index_dir, filename)
if os.path.isfile(file_path):
os.remove(file_path)
logger.info("벑터 인덱슀 파일 μ‚­μ œ μ™„λ£Œ")
except Exception as e:
logger.error(f"벑터 인덱슀 파일 μ‚­μ œ 쀑 였λ₯˜: {e}")
return f"벑터 인덱슀 파일 μ‚­μ œ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
self.documents = []
self.processed_files = []
self.is_initialized = False
logger.info("μΊμ‹œ μ΄ˆκΈ°ν™” μ™„λ£Œ")
return "μΊμ‹œκ°€ μ΄ˆκΈ°ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ μ‹€ν–‰ μ‹œ λͺ¨λ“  λ¬Έμ„œκ°€ λ‹€μ‹œ μ²˜λ¦¬λ©λ‹ˆλ‹€."
except Exception as e:
error_msg = f"μΊμ‹œ μ΄ˆκΈ°ν™” 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
logger.error(error_msg, exc_info=True)
return error_msg
def process_query(self, query: str, chat_history: List[Tuple[str, str]]) -> Tuple[str, List[Tuple[str, str]]]:
"""
μ‚¬μš©μž 쿼리 처리
Args:
query: μ‚¬μš©μž 질문
chat_history: λŒ€ν™” 기둝
Returns:
응닡 및 μ—…λ°μ΄νŠΈλœ λŒ€ν™” 기둝
"""
if not query or not query.strip():
response = "질문이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ§ˆλ¬Έμ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”."
chat_history.append((query, response))
return "", chat_history
if not self.is_initialized:
response = "λ¬Έμ„œ λ‘œλ“œκ°€ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μžλ™ λ‘œλ“œλ₯Ό μ‹œλ„ν•©λ‹ˆλ‹€."
chat_history.append((query, response))
# μžλ™ λ‘œλ“œ μ‹œλ„
try:
init_result = self.auto_process_documents()
if not self.is_initialized:
response = f"λ¬Έμ„œλ₯Ό λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€. 'documents' 폴더에 PDF 파일이 μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”. μ΄ˆκΈ°ν™” κ²°κ³Ό: {init_result}"
chat_history.append((query, response))
return "", chat_history
except Exception as e:
response = f"λ¬Έμ„œ λ‘œλ“œ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
logger.error(f"μžλ™ λ‘œλ“œ μ‹€νŒ¨: {e}", exc_info=True)
chat_history.append((query, response))
return "", chat_history
try:
# RAG 체인 μ‹€ν–‰ 및 응닡 생성
start_time = time.time()
logger.info(f"쿼리 처리 μ‹œμž‘: {query}")
# rag_chain이 μ΄ˆκΈ°ν™”λ˜μ—ˆλŠ”μ§€ 확인
if not hasattr(self, 'rag_chain') or self.rag_chain is None:
raise RAGInitializationError("RAG 체인이 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€")
# 1. λ¨Όμ € ν‘œμ€€ RAG 체인으둜 μ‹œλ„
try:
response = self.rag_chain.run(query)
logger.info(f"κΈ°λ³Έ RAG 체인으둜 응닡 생성 성곡")
except Exception as rag_error:
logger.error(f"κΈ°λ³Έ RAG 체인 μ‹€ν–‰ μ‹€νŒ¨: {rag_error}, λŒ€μ•ˆ μ‹œλ„")
# 2. DeepSeek API 직접 호좜 μ‹œλ„ (RAG 체인 우회)
try:
# DeepSeek API 정보 κ°€μ Έμ˜€κΈ°
try:
from config import DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT
except ImportError:
# μ„€μ • λͺ¨λ“ˆμ—μ„œ κ°€μ Έμ˜¬ 수 μ—†λŠ” 경우 κΈ°λ³Έκ°’ μ„€μ •
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT",
"https://api.deepseek.com/v1/chat/completions")
# 직접 API 호좜 ν•¨μˆ˜ μ •μ˜ (μ™ΈλΆ€ λͺ¨λ“ˆ μ˜μ‘΄μ„± 제거)
def direct_api_call(query, context, api_key, model_name, endpoint, max_retries=3, timeout=60):
"""DeepSeek API 직접 호좜 ν•¨μˆ˜"""
import requests
import json
import time
# ν”„λ‘¬ν”„νŠΈ 길이 μ œν•œ
if len(context) > 6000:
context = context[:2500] + "\n...(μ€‘λž΅)...\n" + context[-2500:]
# ν”„λ‘¬ν”„νŠΈ ꡬ성
prompt = f"""
λ‹€μŒ 정보λ₯Ό 기반으둜 μ§ˆλ¬Έμ— μ •ν™•ν•˜κ²Œ λ‹΅λ³€ν•΄μ£Όμ„Έμš”.
질문: {query}
μ°Έκ³  정보:
{context}
μ°Έκ³  정보에 닡이 있으면 λ°˜λ“œμ‹œ κ·Έ 정보λ₯Ό 기반으둜 λ‹΅λ³€ν•˜μ„Έμš”.
μ°Έκ³  정보에 닡이 μ—†λŠ” κ²½μš°μ—λŠ” 일반적인 지식을 ν™œμš©ν•˜μ—¬ λ‹΅λ³€ν•  수 μžˆμ§€λ§Œ, "제곡된 λ¬Έμ„œμ—λŠ” 이 정보가 μ—†μœΌλ‚˜, μΌλ°˜μ μœΌλ‘œλŠ”..." μ‹μœΌλ‘œ μ‹œμž‘ν•˜μ„Έμš”.
닡변은 μ •ν™•ν•˜κ³  κ°„κ²°ν•˜κ²Œ μ œκ³΅ν•˜λ˜, κ°€λŠ₯ν•œ μ°Έκ³  μ •λ³΄μ—μ„œ κ·Όκ±°λ₯Ό μ°Ύμ•„ μ„€λͺ…ν•΄μ£Όμ„Έμš”.
μ°Έκ³  μ •λ³΄μ˜ μΆœμ²˜λ„ ν•¨κ»˜ μ•Œλ €μ£Όμ„Έμš”.
"""
# API μš”μ²­ μ‹œλ„
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": model_name,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3,
"max_tokens": 1000
}
# μž¬μ‹œλ„ 둜직
retry_delay = 1.0
for attempt in range(max_retries):
try:
logger.info(f"DeepSeek API 직접 호좜 μ‹œλ„ ({attempt + 1}/{max_retries})...")
response = requests.post(
endpoint,
headers=headers,
json=payload,
timeout=timeout
)
if response.status_code == 200:
result = response.json()
content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
logger.info(f"DeepSeek API 직접 호좜 성곡")
return content
else:
logger.warning(f"API 였λ₯˜: μƒνƒœ μ½”λ“œ {response.status_code}")
# μš”μ²­ ν•œλ„μΈ 경우 더 였래 λŒ€κΈ°
if response.status_code == 429:
retry_delay = min(retry_delay * 3, 15)
else:
retry_delay = min(retry_delay * 2, 10)
if attempt < max_retries - 1:
logger.info(f"{retry_delay}초 ν›„ μž¬μ‹œλ„...")
time.sleep(retry_delay)
except Exception as e:
logger.error(f"API 호좜 였λ₯˜: {e}")
if attempt < max_retries - 1:
logger.info(f"{retry_delay}초 ν›„ μž¬μ‹œλ„...")
time.sleep(retry_delay)
retry_delay = min(retry_delay * 2, 10)
# λͺ¨λ“  μ‹œλ„ μ‹€νŒ¨
raise Exception("μ΅œλŒ€ μž¬μ‹œλ„ 횟수 초과")
# 벑터 검색 μˆ˜ν–‰
if self.vector_store and hasattr(self.vector_store, "similarity_search"):
logger.info("벑터 검색 μˆ˜ν–‰...")
docs = self.vector_store.similarity_search(query, k=5)
# 검색 κ²°κ³Ό μ»¨ν…μŠ€νŠΈ ꡬ성
context_parts = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", "μ•Œ 수 μ—†λŠ” 좜처")
page = doc.metadata.get("page", "")
source_info = f"{source}"
if page:
source_info += f" (νŽ˜μ΄μ§€: {page})"
context_parts.append(f"[참고자료 {i}] - 좜처: {source_info}\n{doc.page_content}\n")
context = "\n".join(context_parts)
# 직접 API 호좜
logger.info("DeepSeek API 직접 호좜 μ‹œλ„...")
response = direct_api_call(
query,
context,
DEEPSEEK_API_KEY,
DEEPSEEK_MODEL,
DEEPSEEK_ENDPOINT,
max_retries=3,
timeout=120
)
logger.info("DeepSeek API 직접 호좜 성곡")
else:
raise Exception("벑터 μŠ€ν† μ–΄κ°€ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€")
except Exception as direct_api_error:
logger.error(f"DeepSeek API 직접 호좜 μ‹€νŒ¨: {direct_api_error}, 검색 κ²°κ³Ό λ°˜ν™˜")
# 3. 검색 κ²°κ³Όλ§Œμ΄λΌλ„ λ°˜ν™˜
try:
# 벑터 검색 μˆ˜ν–‰
if self.vector_store and hasattr(self.vector_store, "similarity_search"):
docs = self.vector_store.similarity_search(query, k=5)
# 검색 κ²°κ³Ό μ»¨ν…μŠ€νŠΈ ꡬ성
context_parts = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", "μ•Œ 수 μ—†λŠ” 좜처")
page = doc.metadata.get("page", "")
source_info = f"{source}"
if page:
source_info += f" (νŽ˜μ΄μ§€: {page})"
context_parts.append(f"[참고자료 {i}] - 좜처: {source_info}\n{doc.page_content}\n")
context = "\n".join(context_parts)
# κ°„λ‹¨ν•œ 응닡 생성
predefined_answers = {
"λŒ€ν•œλ―Όκ΅­μ˜ μˆ˜λ„": "λŒ€ν•œλ―Όκ΅­μ˜ μˆ˜λ„λŠ” μ„œμšΈμž…λ‹ˆλ‹€.",
"μˆ˜λ„": "λŒ€ν•œλ―Όκ΅­μ˜ μˆ˜λ„λŠ” μ„œμšΈμž…λ‹ˆλ‹€.",
"λˆ„κ΅¬μ•Ό": "μ €λŠ” RAG 기반 μ§ˆμ˜μ‘λ‹΅ μ‹œμŠ€ν…œμž…λ‹ˆλ‹€. λ¬Έμ„œλ₯Ό κ²€μƒ‰ν•˜κ³  κ΄€λ ¨ 정보λ₯Ό μ°Ύμ•„λ“œλ¦½λ‹ˆλ‹€.",
"μ•ˆλ…•": "μ•ˆλ…•ν•˜μ„Έμš”! 무엇을 λ„μ™€λ“œλ¦΄κΉŒμš”?",
"뭐해": "μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ— λ‹΅λ³€ν•˜κΈ° μœ„ν•΄ λ¬Έμ„œλ₯Ό κ²€μƒ‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 무엇을 μ•Œλ €λ“œλ¦΄κΉŒμš”?"
}
# μ§ˆλ¬Έμ— λ§žλŠ” 미리 μ •μ˜λœ 응닡이 μžˆλŠ”μ§€ 확인
for key, answer in predefined_answers.items():
if key in query.lower():
response = answer
logger.info(f"미리 μ •μ˜λœ 응닡 제곡: {key}")
break
else:
# 미리 μ •μ˜λœ 응닡이 μ—†μœΌλ©΄ 검색 결과만 ν‘œμ‹œ
response = f"""
API μ„œλ²„ 연결에 λ¬Έμ œκ°€ μžˆμ–΄ 검색 결과만 ν‘œμ‹œν•©λ‹ˆλ‹€.
질문: {query}
κ²€μƒ‰λœ κ΄€λ ¨ λ¬Έμ„œ:
{context}
[μ°Έκ³ ] API μ—°κ²° 문제둜 인해 μžλ™ μš”μ•½μ΄ μ œκ³΅λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•˜κ±°λ‚˜ λ‹€λ₯Έ μ§ˆλ¬Έμ„ ν•΄λ³΄μ„Έμš”.
"""
logger.info("검색 결과만 ν‘œμ‹œ")
else:
response = f"API μ—°κ²° 및 벑터 검색에 λͺ¨λ‘ μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. μ‹œμŠ€ν…œ κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”."
except Exception as fallback_error:
logger.error(f"μ΅œμ’… 폴백 응닡 생성 μ‹€νŒ¨: {fallback_error}")
# 4. μ΅œν›„μ˜ 방법: 였λ₯˜ λ©”μ‹œμ§€λ₯Ό μ‘λ‹΅μœΌλ‘œ λ°˜ν™˜
if "Connection error" in str(rag_error) or "timeout" in str(rag_error).lower():
response = f"""
API μ„œλ²„ 연결에 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.
질문: {query}
[μ°Έκ³ ] ν˜„μž¬ DeepSeek API μ„œλ²„μ™€μ˜ 연결이 μ›ν™œν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 이둜 인해 μ§ˆλ¬Έμ— λŒ€ν•œ 응닡을 μ œκ³΅ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
"""
else:
response = f"쿼리 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(rag_error)}"
end_time = time.time()
query_time = end_time - start_time
logger.info(f"쿼리 처리 μ™„λ£Œ: {query_time:.2f}초")
chat_history.append((query, response))
return "", chat_history
except RAGInitializationError as e:
error_msg = f"RAG μ‹œμŠ€ν…œ μ΄ˆκΈ°ν™” 였λ₯˜: {str(e)}. 'documents' 폴더에 PDF 파일이 μžˆλŠ”μ§€ ν™•μΈν•˜κ³ , μž¬μ‹œμž‘ν•΄ λ³΄μ„Έμš”."
logger.error(f"쿼리 처리 쀑 RAG μ΄ˆκΈ°ν™” 였λ₯˜: {e}", exc_info=True)
chat_history.append((query, error_msg))
return "", chat_history
except (VectorStoreError, DocumentProcessingError) as e:
error_msg = f"λ¬Έμ„œ 처리 μ‹œμŠ€ν…œ 였λ₯˜: {str(e)}. λ¬Έμ„œ ν˜•μ‹μ΄ μ˜¬λ°”λ₯Έμ§€ 확인해 λ³΄μ„Έμš”."
logger.error(f"쿼리 처리 쀑 λ¬Έμ„œ/벑터 μŠ€ν† μ–΄ 였λ₯˜: {e}", exc_info=True)
chat_history.append((query, error_msg))
return "", chat_history
except Exception as e:
error_msg = f"쿼리 처리 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
logger.error(f"쿼리 처리 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {e}", exc_info=True)
chat_history.append((query, error_msg))
return "", chat_history
def launch_app(self) -> None:
"""
Gradio μ•± μ‹€ν–‰
"""
try:
import gradio as gr
except ImportError:
logger.error("Gradio 라이브러리λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. pip install gradio둜 μ„€μΉ˜ν•˜μ„Έμš”.")
print("Gradio 라이브러리λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. pip install gradio둜 μ„€μΉ˜ν•˜μ„Έμš”.")
return
# λ‚΄λΆ€ ν•¨μˆ˜λ“€μ΄ ν˜„μž¬ μΈμŠ€ν„΄μŠ€(self)에 μ ‘κ·Όν•  수 μžˆλ„λ‘ ν΄λ‘œμ € λ³€μˆ˜λ‘œ μ •μ˜
app_instance = self
try:
with gr.Blocks(title="PDF λ¬Έμ„œ 기반 RAG 챗봇") as app:
gr.Markdown("# PDF λ¬Έμ„œ 기반 RAG 챗봇")
gr.Markdown(f"* μ‚¬μš© 쀑인 LLM λͺ¨λΈ: **{LLM_MODEL}**")
# μ—¬κΈ°λ₯Ό μˆ˜μ •: μ‹€μ œ 경둜 ν‘œμ‹œ
actual_pdf_dir = self.pdf_directory.replace('\\', '\\\\') if os.name == 'nt' else self.pdf_directory
gr.Markdown(f"* PDF λ¬Έμ„œ 폴더: **{actual_pdf_dir}**")
with gr.Row():
with gr.Column(scale=1):
# λ¬Έμ„œ μƒνƒœ μ„Ήμ…˜
status_box = gr.Textbox(
label="λ¬Έμ„œ 처리 μƒνƒœ",
value=self._get_status_message(),
lines=5,
interactive=False
)
# μΊμ‹œ 관리 λ²„νŠΌ
refresh_button = gr.Button("λ¬Έμ„œ μƒˆλ‘œ 읽기", variant="primary")
reset_button = gr.Button("μΊμ‹œ μ΄ˆκΈ°ν™”", variant="stop")
# μƒνƒœ 및 였λ₯˜ ν‘œμ‹œ
status_info = gr.Markdown(
value=f"μ‹œμŠ€ν…œ μƒνƒœ: {'μ΄ˆκΈ°ν™”λ¨' if self.is_initialized else 'μ΄ˆκΈ°ν™”λ˜μ§€ μ•ŠμŒ'}"
)
# 처리된 파일 정보
with gr.Accordion("μΊμ‹œ μ„ΈλΆ€ 정보", open=False):
cache_info = gr.Textbox(
label="μΊμ‹œλœ 파일 정보",
value=self._get_cache_info(),
lines=5,
interactive=False
)
with gr.Column(scale=2):
# μ±„νŒ… μΈν„°νŽ˜μ΄μŠ€
chatbot = gr.Chatbot(
label="λŒ€ν™” λ‚΄μš©",
bubble_full_width=False,
height=500,
show_copy_button=True
)
# μŒμ„± λ…ΉμŒ UI μΆ”κ°€
with gr.Row():
with gr.Column(scale=4):
# 질문 μž…λ ₯κ³Ό 전솑 λ²„νŠΌ
query_box = gr.Textbox(
label="질문",
placeholder="처리된 λ¬Έμ„œ λ‚΄μš©μ— λŒ€ν•΄ μ§ˆλ¬Έν•˜μ„Έμš”...",
lines=2
)
with gr.Column(scale=1):
# μŒμ„± λ…ΉμŒ μ»΄ν¬λ„ŒνŠΈ
audio_input = gr.Audio(
sources=["microphone"],
type="numpy",
label="μŒμ„±μœΌλ‘œ μ§ˆλ¬Έν•˜κΈ°"
)
with gr.Row():
submit_btn = gr.Button("전솑", variant="primary")
clear_chat_button = gr.Button("λŒ€ν™” μ΄ˆκΈ°ν™”")
# μŒμ„± 인식 처리 ν•¨μˆ˜
# app.py λ‚΄ process_audio ν•¨μˆ˜ 보강
# Gradio μ•± 내에 μžˆλŠ” μŒμ„± 인식 처리 ν•¨μˆ˜ (원본)
def process_audio(audio):
logger.info("μŒμ„± 인식 처리 μ‹œμž‘...")
try:
from clova_stt import ClovaSTT
import numpy as np
import soundfile as sf
import tempfile
import os
if audio is None:
return "μŒμ„±μ΄ λ…ΉμŒλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
# μ˜€λ””μ˜€ 데이터λ₯Ό μž„μ‹œ 파일둜 μ €μž₯
sr, y = audio
logger.info(f"μ˜€λ””μ˜€ λ…ΉμŒ 데이터 μˆ˜μ‹ : μƒ˜ν”Œλ ˆμ΄νŠΈ={sr}Hz, 길이={len(y)}μƒ˜ν”Œ")
if len(y) / sr < 1.0:
return "λ…ΉμŒλœ μŒμ„±μ΄ λ„ˆλ¬΄ μ§§μŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
temp_path = temp_file.name
sf.write(temp_path, y, sr, format="WAV")
logger.info(f"μž„μ‹œ WAV 파일 μ €μž₯됨: {temp_path}")
# μŒμ„± 인식 μ‹€ν–‰
stt_client = ClovaSTT()
with open(temp_path, "rb") as f:
audio_bytes = f.read()
result = stt_client.recognize(audio_bytes)
# μž„μ‹œ 파일 μ‚­μ œ
try:
os.unlink(temp_path)
logger.info("μž„μ‹œ μ˜€λ””μ˜€ 파일 μ‚­μ œλ¨")
except Exception as e:
logger.warning(f"μž„μ‹œ 파일 μ‚­μ œ μ‹€νŒ¨: {e}")
if result["success"]:
recognized_text = result["text"]
logger.info(f"μŒμ„±μΈμ‹ 성곡: {recognized_text}")
return recognized_text
else:
error_msg = f"μŒμ„± 인식 μ‹€νŒ¨: {result.get('error', 'μ•Œ 수 μ—†λŠ” 였λ₯˜')}"
logger.error(error_msg)
return error_msg
except ImportError as e:
logger.error(f"ν•„μš”ν•œ 라이브러리 λˆ„λ½: {e}")
return "μŒμ„±μΈμ‹μ— ν•„μš”ν•œ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. pip install soundfile numpy requestsλ₯Ό μ‹€ν–‰ν•΄μ£Όμ„Έμš”."
except Exception as e:
logger.error(f"μŒμ„± 처리 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return f"μŒμ„± 처리 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
# μƒˆλ‘œ μΆ”κ°€ν•  process_audio_and_submit ν•¨μˆ˜
def process_audio_and_submit(audio, chat_history):
"""
λ…ΉμŒ μ •μ§€ μ‹œ μŒμ„± 인식 ν›„ μžλ™μœΌλ‘œ μ§ˆλ¬Έμ„ μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜.
μž…λ ₯:
- audio: λ…ΉμŒ 데이터 (gr.Audio의 κ°’)
- chat_history: ν˜„μž¬ λŒ€ν™” 기둝 (gr.Chatbot의 κ°’)
좜λ ₯:
- query_box: 빈 λ¬Έμžμ—΄ (질문 μž…λ ₯λž€ μ΄ˆκΈ°ν™”)
- chatbot: μ—…λ°μ΄νŠΈλœ λŒ€ν™” 기둝
"""
recognized_text = process_audio(audio)
# μŒμ„± 인식 κ²°κ³Όκ°€ 였λ₯˜ λ©”μ‹œμ§€μΈ 경우 κ·ΈλŒ€λ‘œ λ°˜ν™˜
if not recognized_text or recognized_text.startswith("μŒμ„± 인식 μ‹€νŒ¨") or recognized_text.startswith(
"μŒμ„± 처리 쀑 였λ₯˜"):
return recognized_text, chat_history
# μΈμ‹λœ ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ 질문 처리
return app_instance.process_query(recognized_text, chat_history)
# κΈ°μ‘΄ update_ui_after_refresh ν•¨μˆ˜ μˆ˜μ • (self λŒ€μ‹  app_instance μ‚¬μš©)
def update_ui_after_refresh(result):
return (
result, # μƒνƒœ λ©”μ‹œμ§€
app_instance._get_status_message(), # μƒνƒœ λ°•μŠ€ μ—…λ°μ΄νŠΈ
f"μ‹œμŠ€ν…œ μƒνƒœ: {'μ΄ˆκΈ°ν™”λ¨' if app_instance.is_initialized else 'μ΄ˆκΈ°ν™”λ˜μ§€ μ•ŠμŒ'}", # μƒνƒœ 정보 μ—…λ°μ΄νŠΈ
app_instance._get_cache_info() # μΊμ‹œ 정보 μ—…λ°μ΄νŠΈ
)
# --- Gradio 이벀트 ν•Έλ“€λŸ¬ μ„€μ • ---
# 예: audio_input μ»΄ν¬λ„ŒνŠΈμ˜ stop_recording 이벀트λ₯Ό μ•„λž˜μ™€ 같이 μˆ˜μ •
audio_input.stop_recording(
fn=process_audio_and_submit,
inputs=[audio_input, chatbot],
outputs=[query_box, chatbot]
)
# μŒμ„± 인식 κ²°κ³Όλ₯Ό 질문 μƒμžμ— μ—…λ°μ΄νŠΈ
audio_input.stop_recording(
fn=process_audio,
inputs=[audio_input],
outputs=[query_box]
)
# λ¬Έμ„œ μƒˆλ‘œ 읽기 λ²„νŠΌ
refresh_button.click(
fn=lambda: update_ui_after_refresh(self.auto_process_documents()),
inputs=[],
outputs=[status_box, status_box, status_info, cache_info]
)
# μΊμ‹œ μ΄ˆκΈ°ν™” λ²„νŠΌ
def reset_and_process():
reset_result = self.reset_cache()
process_result = self.auto_process_documents()
return update_ui_after_refresh(f"{reset_result}\n\n{process_result}")
reset_button.click(
fn=reset_and_process,
inputs=[],
outputs=[status_box, status_box, status_info, cache_info]
)
# 전솑 λ²„νŠΌ 클릭 이벀트
submit_btn.click(
fn=self.process_query,
inputs=[query_box, chatbot],
outputs=[query_box, chatbot]
)
# μ—”ν„°ν‚€ μž…λ ₯ 이벀트
query_box.submit(
fn=self.process_query,
inputs=[query_box, chatbot],
outputs=[query_box, chatbot]
)
# λŒ€ν™” μ΄ˆκΈ°ν™” λ²„νŠΌ
clear_chat_button.click(
fn=lambda: [],
outputs=[chatbot]
)
# μ•± μ‹€ν–‰
app.launch(share=False)
except Exception as e:
logger.error(f"Gradio μ•± μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
print(f"Gradio μ•± μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {e}")
def _get_status_message(self) -> str:
"""
ν˜„μž¬ 처리 μƒνƒœ λ©”μ‹œμ§€ 생성
Returns:
μƒνƒœ λ©”μ‹œμ§€
"""
if not self.processed_files:
return "처리된 λ¬Έμ„œκ°€ μ—†μŠ΅λ‹ˆλ‹€. 'λ¬Έμ„œ μƒˆλ‘œ 읽기' λ²„νŠΌμ„ ν΄λ¦­ν•˜μ„Έμš”."
# DeepSeek API μƒνƒœ 확인
from config import USE_DEEPSEEK, DEEPSEEK_API_KEY, DEEPSEEK_MODEL
model_info = ""
if USE_DEEPSEEK and DEEPSEEK_API_KEY:
# DeepSeek API ν…ŒμŠ€νŠΈ μˆ˜ν–‰
try:
# ν…ŒμŠ€νŠΈ ν•¨μˆ˜ κ°€μ Έμ˜€κΈ° μ‹œλ„
try:
from deepseek_utils import test_deepseek_api
# DeepSeek μ„€μ • κ°€μ Έμ˜€κΈ°
from config import DEEPSEEK_ENDPOINT
# API ν…ŒμŠ€νŠΈ
test_result = test_deepseek_api(DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL)
if test_result["success"]:
model_info = f"\nDeepSeek API μƒνƒœ: 정상 ({DEEPSEEK_MODEL})"
else:
model_info = f"\nDeepSeek API μƒνƒœ: 였λ₯˜ - {test_result['message']}"
except ImportError:
# 직접 ν…ŒμŠ€νŠΈ μ‹€ν–‰
import requests
import json
# DeepSeek μ„€μ • κ°€μ Έμ˜€κΈ°
from config import DEEPSEEK_ENDPOINT
# ν…ŒμŠ€νŠΈμš© κ°„λ‹¨ν•œ ν”„λ‘¬ν”„νŠΈ
test_prompt = "Hello, please respond with a short greeting."
# API μš”μ²­ 헀더 및 데이터
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
}
payload = {
"model": DEEPSEEK_MODEL,
"messages": [{"role": "user", "content": test_prompt}],
"temperature": 0.7,
"max_tokens": 50
}
# API μš”μ²­ 전솑
try:
response = requests.post(
DEEPSEEK_ENDPOINT,
headers=headers,
data=json.dumps(payload),
timeout=5 # 5초 νƒ€μž„μ•„μ›ƒ (UI λ°˜μ‘μ„± μœ μ§€)
)
# 응닡 확인
if response.status_code == 200:
model_info = f"\nDeepSeek API μƒνƒœ: 정상 ({DEEPSEEK_MODEL})"
else:
error_message = response.text[:100]
model_info = f"\nDeepSeek API μƒνƒœ: 였λ₯˜ (μƒνƒœ μ½”λ“œ: {response.status_code})"
except Exception as e:
model_info = f"\nDeepSeek API μƒνƒœ: μ—°κ²° μ‹€νŒ¨ ({str(e)[:100]})"
except Exception as e:
model_info = f"\nDeepSeek API μƒνƒœ 확인 μ‹€νŒ¨: {str(e)[:100]}"
return f"처리된 λ¬Έμ„œ ({len(self.processed_files)}개): {', '.join(self.processed_files)}{model_info}"
def _get_cache_info(self) -> str:
"""
μΊμ‹œ μ„ΈλΆ€ 정보 λ©”μ‹œμ§€ 생성
Returns:
μΊμ‹œ 정보 λ©”μ‹œμ§€
"""
if not self.file_index:
return "μΊμ‹œλœ 파일이 μ—†μŠ΅λ‹ˆλ‹€."
file_info = ""
for file_path, info in self.file_index.items():
file_name = info.get('file_name', os.path.basename(file_path))
chunks_count = info.get('chunks_count', 0)
file_size = info.get('file_size', 0)
last_processed = info.get('last_processed', 0)
# 파일 크기λ₯Ό μ‚¬λžŒμ΄ 읽기 μ‰¬μš΄ ν˜•νƒœλ‘œ λ³€ν™˜
if file_size < 1024:
size_str = f"{file_size} bytes"
elif file_size < 1024 * 1024:
size_str = f"{file_size / 1024:.1f} KB"
else:
size_str = f"{file_size / (1024 * 1024):.1f} MB"
# λ§ˆμ§€λ§‰ 처리 μ‹œκ°„μ„ λ‚ μ§œ/μ‹œκ°„ ν˜•μ‹μœΌλ‘œ λ³€ν™˜
if last_processed:
from datetime import datetime
last_time = datetime.fromtimestamp(last_processed).strftime('%Y-%m-%d %H:%M:%S')
else:
last_time = "μ•Œ 수 μ—†μŒ"
file_info += f"- {file_name}: {chunks_count}개 청크, {size_str}, λ§ˆμ§€λ§‰ 처리: {last_time}\n"
return file_info
if __name__ == "__main__":
app = AutoRAGChatApp()
app.launch_app()