RAG_voice / app.py
jeongsoo's picture
Add application file
67649a2
"""
์Œ์„ฑ์ธ์‹ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ RAG ์ฑ—๋ด‡ ์•ฑ
"""
import os
import time
import tempfile
from typing import List, Dict, Tuple, Any, Optional
import hashlib
import pickle
import json
# ๊ธฐ์กด ์ž„ํฌํŠธ
from config import (
PDF_DIRECTORY, CHUNK_SIZE, CHUNK_OVERLAP, LLM_MODEL,
STT_LANGUAGE, IS_HUGGINGFACE, OPENAI_API_KEY, USE_OPENAI
)
from optimized_document_processor import OptimizedDocumentProcessor
from vector_store import VectorStore
from langchain.schema import Document
# ํด๋กœ๋ฐ” STT ๋ชจ๋“ˆ ์ž„ํฌํŠธ
from clova_stt import ClovaSTT
# ์•ˆ์ „ํ•œ ์ž„ํฌํŠธ
try:
print("RAG ์ฒด์ธ ๋ชจ๋“ˆ ๋กœ๋“œ ์‹œ๋„...")
from rag_chain import RAGChain
# RAGChain ํด๋ž˜์Šค๊ฐ€ ์ œ๋Œ€๋กœ ์ž„ํฌํŠธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
RAG_CHAIN_AVAILABLE = True
print("์™ธ๋ถ€ RAG ์ฒด์ธ ๋ชจ๋“ˆ ๋กœ๋“œ ์„ฑ๊ณต")
except Exception as e:
print(f"์™ธ๋ถ€ RAG ์ฒด์ธ ๋กœ๋“œ ์‹คํŒจ: {e}")
print("๋‚ด์žฅ RAG ์ฒด์ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
# ๋‚ด์žฅ RAG ์ฒด์ธ ๊ตฌํ˜„ ์‚ฌ์šฉ
RAG_CHAIN_AVAILABLE = True
# ํ•„์š”ํ•œ langchain ์ž„ํฌํŠธ
try:
from langchain_openai import ChatOpenAI
from langchain_community.chat_models import ChatOllama
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# SimpleRAGChain ๋‚ด์žฅ ํด๋ž˜์Šค ์ •์˜
class RAGChain:
"""์•ฑ์— ๋‚ด์žฅ๋œ ๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ"""
def __init__(self, vector_store):
print("๋‚ด์žฅ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์ค‘...")
self.vector_store = vector_store
# ํ™˜๊ฒฝ ์„ค์ • ์ž„ํฌํŠธ
from config import OPENAI_API_KEY, LLM_MODEL, USE_OPENAI, TOP_K_RETRIEVAL, IS_HUGGINGFACE
# OLLAMA_HOST๋Š” HuggingFace ํ™˜๊ฒฝ์—์„œ๋Š” ์ •์˜๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์กฐ๊ฑด๋ถ€ ๊ฐ€์ ธ์˜ค๊ธฐ
OLLAMA_HOST = "http://localhost:11434" # ๊ธฐ๋ณธ๊ฐ’
if not IS_HUGGINGFACE and not USE_OPENAI:
try:
from config import OLLAMA_HOST
print(f"Ollama ํ˜ธ์ŠคํŠธ: {OLLAMA_HOST}")
except ImportError:
print("OLLAMA_HOST ์„ค์ •์„ ์ฐพ์„ ์ˆ˜ ์—†์–ด ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ")
try:
# LLM ์ดˆ๊ธฐํ™”
if USE_OPENAI or IS_HUGGINGFACE:
self.llm = ChatOpenAI(
model_name=LLM_MODEL,
temperature=0.2,
api_key=OPENAI_API_KEY,
)
print(f"OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™”: {LLM_MODEL}")
else:
try:
self.llm = ChatOllama(
model=LLM_MODEL,
temperature=0.2,
base_url=OLLAMA_HOST,
)
print(f"Ollama ๋ชจ๋ธ ์ดˆ๊ธฐํ™”: {LLM_MODEL}")
except Exception as e:
print(f"Ollama ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}, OpenAI ๋ชจ๋ธ๋กœ ๋Œ€์ฒด")
self.llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0.2,
api_key=OPENAI_API_KEY,
)
# ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
template = """
๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
์งˆ๋ฌธ: {question}
์ฐธ๊ณ  ์ •๋ณด:
{context}
์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ "์ œ๊ณต๋œ ๋ฌธ์„œ์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”.
๋‹ต๋ณ€์€ ์ •ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋˜, ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๊ทผ๊ฑฐ๋ฅผ ์ฐพ์•„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
์ฐธ๊ณ  ์ •๋ณด์˜ ์ถœ์ฒ˜๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”.
"""
self.prompt = PromptTemplate.from_template(template)
# ์ฒด์ธ ๊ตฌ์„ฑ
self.chain = (
{"context": self._retrieve, "question": RunnablePassthrough()}
| self.prompt
| self.llm
| StrOutputParser()
)
print("๋‚ด์žฅ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
except Exception as e:
print(f"LLM ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
import traceback
traceback.print_exc()
raise
def _retrieve(self, query):
"""๋ฌธ์„œ ๊ฒ€์ƒ‰"""
try:
from config import TOP_K_RETRIEVAL
docs = self.vector_store.similarity_search(query, k=TOP_K_RETRIEVAL)
# ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
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")
return "\n".join(context_parts)
except Exception as e:
print(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
import traceback
traceback.print_exc()
return "๋ฌธ์„œ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
def run(self, query):
"""์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ"""
try:
return self.chain.invoke(query)
except Exception as e:
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์˜ค๋ฅ˜: {e}")
import traceback
traceback.print_exc()
return f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
except Exception as inner_e:
print(f"๋‚ด์žฅ RAG ์ฒด์ธ ์ •์˜ ์‹คํŒจ: {inner_e}")
import traceback
traceback.print_exc()
RAG_CHAIN_AVAILABLE = False
class AutoRAGChatApp:
"""
documents ํด๋”์˜ PDF ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์Œ์„ฑ์ธ์‹ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” RAG ์ฑ—๋ด‡
"""
def __init__(self):
"""
RAG ์ฑ—๋ด‡ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
"""
print("=" * 50)
print("์Œ์„ฑ์ธ์‹ RAG ์ฑ—๋ด‡ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™” ์‹œ์ž‘")
print("=" * 50)
# ๋ฐ์ดํ„ฐ ๋””๋ ‰ํ† ๋ฆฌ ์ •์˜
self.pdf_directory = PDF_DIRECTORY
self.cache_directory = "cached_data"
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")
# ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
os.makedirs(self.pdf_directory, exist_ok=True)
os.makedirs(self.cache_directory, exist_ok=True)
os.makedirs(self.chunks_dir, exist_ok=True)
os.makedirs(self.vector_index_dir, exist_ok=True)
print(f"PDF ๋ฌธ์„œ ๋””๋ ‰ํ† ๋ฆฌ: '{self.pdf_directory}'")
print(f"์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ: '{self.cache_directory}'")
# ์ปดํฌ๋„ŒํŠธ ์ดˆ๊ธฐํ™”
self.document_processor = OptimizedDocumentProcessor(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP
)
# ๋ฒกํ„ฐ ์ €์žฅ์†Œ ์ดˆ๊ธฐํ™”
self.vector_store = VectorStore(use_milvus=False)
# ๋ฌธ์„œ ์ธ๋ฑ์Šค ๋กœ๋“œ
self.file_index = self._load_file_index()
# ๊ธฐ๋ณธ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”
self.documents = []
self.processed_files = []
self.is_initialized = False
self.rag_chain = None
# ํด๋กœ๋ฐ” STT ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
self.stt_client = ClovaSTT()
print("์Œ์„ฑ์ธ์‹(STT) ๊ธฐ๋Šฅ์ด ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
# RAG ์ฒด์ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ์„ฑ ํ™•์ธ
print(f"RAG ์ฒด์ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ: {RAG_CHAIN_AVAILABLE}")
# ์‹œ์ž‘ ์‹œ ์ž๋™์œผ๋กœ ๋ฌธ์„œ ๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌ
print("๋ฌธ์„œ ์ž๋™ ๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌ ์‹œ์ž‘...")
result = self.auto_process_documents()
print(f"์ดˆ๊ธฐํ™” ์™„๋ฃŒ ์ƒํƒœ: {self.is_initialized}")
print("=" * 50)
def _fallback_response(self, query: str) -> str:
"""
RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ ์‘๋‹ต ์ƒ์„ฑ
Args:
query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
Returns:
๊ธฐ๋ณธ ์‘๋‹ต ํ…์ŠคํŠธ
"""
try:
# ๋ฒกํ„ฐ ๊ฒ€์ƒ‰์ด๋ผ๋„ ์‹คํ–‰
if self.vector_store and self.vector_store.vector_store:
try:
docs = self.vector_store.similarity_search(query, k=3)
if docs:
context = "\n\n".join([doc.page_content for doc in docs])
response = f"""
์งˆ๋ฌธ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. RAG ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ๋ฌธ์„œ์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค:
{context}
RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
"""
return response.strip()
except Exception as e:
print(f"๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์‹คํŒจ: {e}")
# ๊ธฐ๋ณธ ์‘๋‹ต
return "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. RAG ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์งˆ๋ฌธ์— ์‘๋‹ตํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ์ˆ ์ ์ธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ ์ค‘์ž…๋‹ˆ๋‹ค."
except Exception as e:
print(f"๊ธฐ๋ณธ ์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ: {e}")
return "์‹œ์Šคํ…œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”."
def _process_pdf_file(self, file_path: str) -> List[Document]:
"""
PDF ํŒŒ์ผ ์ฒ˜๋ฆฌ - docling ์‹คํŒจ ์‹œ PyPDFLoader ์‚ฌ์šฉ
Args:
file_path: ์ฒ˜๋ฆฌํ•  PDF ํŒŒ์ผ ๊ฒฝ๋กœ
Returns:
์ฒ˜๋ฆฌ๋œ ๋ฌธ์„œ ์ฒญํฌ ๋ฆฌ์ŠคํŠธ
"""
try:
print(f"docling์œผ๋กœ ์ฒ˜๋ฆฌ ์‹œ๋„: {file_path}")
# docling ์‚ฌ์šฉ ์‹œ๋„
try:
# 10์ดˆ ํƒ€์ž„์•„์›ƒ ์„ค์ • (์˜ต์…˜)
import signal
def timeout_handler(signum, frame):
raise TimeoutError("docling ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
# ๋ฆฌ๋ˆ…์Šค/๋งฅ์—์„œ๋งŒ ์ž‘๋™ (์œˆ๋„์šฐ์—์„œ๋Š” ๋ฌด์‹œ๋จ)
try:
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(60) # 60์ดˆ ํƒ€์ž„์•„์›ƒ
except:
pass
# docling์œผ๋กœ ์ฒ˜๋ฆฌ ์‹œ๋„
chunks = self.document_processor.process_pdf(file_path, use_docling=True)
# ํƒ€์ž„์•„์›ƒ ์ทจ์†Œ
try:
signal.alarm(0)
except:
pass
return chunks
except Exception as e:
# docling ์˜ค๋ฅ˜ ํ™•์ธ
error_str = str(e)
if "Invalid code point" in error_str or "RuntimeError" in error_str:
print(f"docling ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ (์ฝ”๋“œ ํฌ์ธํŠธ ๋ฌธ์ œ): {error_str}")
print("PyPDFLoader๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.")
else:
print(f"docling ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {error_str}")
print("PyPDFLoader๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.")
# PyPDFLoader๋กœ ๋Œ€์ฒด
try:
return self.document_processor.process_pdf(file_path, use_docling=False)
except Exception as inner_e:
print(f"PyPDFLoader ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {inner_e}")
raise # ๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ
except Exception as e:
print(f"PDF ์ฒ˜๋ฆฌ ์ค‘ ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜: {e}")
# ๋นˆ ์ฒญํฌ๋ผ๋„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ „์ฒด ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘๋‹จ๋˜์ง€ ์•Š๋„๋ก ํ•จ
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 Exception as e:
print(f"์ธ๋ฑ์Šค ํŒŒ์ผ ๋กœ๋“œ ์‹คํŒจ: {e}")
return {}
return {}
def _save_file_index(self) -> None:
"""
ํŒŒ์ผ ์ธ๋ฑ์Šค ์ €์žฅ
"""
with open(self.index_file, 'w', encoding='utf-8') as f:
json.dump(self.file_index, f, ensure_ascii=False, indent=2)
def _calculate_file_hash(self, file_path: str) -> str:
"""
ํŒŒ์ผ ํ•ด์‹œ ๊ณ„์‚ฐ
Args:
file_path: ํŒŒ์ผ ๊ฒฝ๋กœ
Returns:
MD5 ํ•ด์‹œ๊ฐ’
"""
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()
def _is_file_processed(self, file_path: str) -> bool:
"""
ํŒŒ์ผ์ด ์ด๋ฏธ ์ฒ˜๋ฆฌ๋˜์—ˆ๊ณ  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ
Args:
file_path: ํŒŒ์ผ ๊ฒฝ๋กœ
Returns:
์ฒ˜๋ฆฌ ์—ฌ๋ถ€
"""
if file_path not in self.file_index:
return False
# ํ˜„์žฌ ํ•ด์‹œ๊ฐ’ ๊ณ„์‚ฐ
current_hash = self._calculate_file_hash(file_path)
# ์ €์žฅ๋œ ํ•ด์‹œ๊ฐ’๊ณผ ๋น„๊ต
if self.file_index[file_path]['hash'] != current_hash:
print(f"ํŒŒ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€: {file_path}")
return False
# ์ฒญํฌ ํŒŒ์ผ ์กด์žฌ ํ™•์ธ
chunks_path = self.file_index[file_path]['chunks_path']
if not os.path.exists(chunks_path):
return False
return True
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: ๋ฌธ์„œ ์ฒญํฌ ๋ฆฌ์ŠคํŠธ
"""
# ํ•ด์‹œ ๊ณ„์‚ฐ
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)
}
# ์ธ๋ฑ์Šค ์ €์žฅ
self._save_file_index()
print(f"์ฒญํฌ ์ €์žฅ ์™„๋ฃŒ: {file_path} ({len(chunks)}๊ฐœ ์ฒญํฌ)")
def _load_chunks(self, file_path: str) -> List[Document]:
"""
์ €์žฅ๋œ ์ฒญํฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
Args:
file_path: ํŒŒ์ผ ๊ฒฝ๋กœ
Returns:
๋ฌธ์„œ ์ฒญํฌ ๋ฆฌ์ŠคํŠธ
"""
chunks_path = self.file_index[file_path]['chunks_path']
with open(chunks_path, 'rb') as f:
chunks = pickle.load(f)
print(f"์ฒญํฌ ๋กœ๋“œ ์™„๋ฃŒ: {file_path} ({len(chunks)}๊ฐœ ์ฒญํฌ)")
return chunks
def auto_process_documents(self) -> str:
"""
documents ํด๋”์˜ PDF ํŒŒ์ผ ์ž๋™ ์ฒ˜๋ฆฌ
Returns:
์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
"""
try:
start_time = time.time()
# PDF ํŒŒ์ผ ๋ชฉ๋ก ์ˆ˜์ง‘
pdf_files = []
for filename in os.listdir(self.pdf_directory):
if filename.lower().endswith('.pdf'):
pdf_files.append(os.path.join(self.pdf_directory, filename))
if not pdf_files:
print(f"'{self.pdf_directory}' ํด๋”์— PDF ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
return f"'{self.pdf_directory}' ํด๋”์— PDF ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค."
print(f"๋ฐœ๊ฒฌ๋œ PDF ํŒŒ์ผ: {len(pdf_files)}๊ฐœ")
# ํด๋” ๋‚ด PDF ํŒŒ์ผ ์ฒ˜๋ฆฌ
new_files = []
updated_files = []
cached_files = []
failed_files = []
all_chunks = []
for file_path in pdf_files:
if self._is_file_processed(file_path):
# ์บ์‹œ์—์„œ ์ฒญํฌ ๋กœ๋“œ
chunks = self._load_chunks(file_path)
all_chunks.extend(chunks)
cached_files.append(file_path)
self.processed_files.append(os.path.basename(file_path))
else:
# ์ƒˆ ํŒŒ์ผ ๋˜๋Š” ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ ์ฒ˜๋ฆฌ
print(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:
print(f"'{file_path}' ์ฒ˜๋ฆฌ ์‹คํŒจ: ์ถ”์ถœ๋œ ์ฒญํฌ ์—†์Œ")
failed_files.append(file_path)
except Exception as e:
print(f"'{file_path}' ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
failed_files.append(file_path)
# ๋ชจ๋“  ์ฒญํฌ ์ €์žฅ
self.documents = all_chunks
processing_time = time.time() - start_time
print(f"๋ฌธ์„œ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {len(all_chunks)}๊ฐœ ์ฒญํฌ, {processing_time:.2f}์ดˆ")
# ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ €์žฅ ๊ฒฝ๋กœ ํ™•์ธ
if os.path.exists(self.vector_index_dir) and any(os.listdir(self.vector_index_dir)):
# ๊ธฐ์กด ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ๋กœ๋“œ
try:
print("์ €์žฅ๋œ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ๋กœ๋“œ ์ค‘...")
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:
print("๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์—…๋ฐ์ดํŠธ ์ค‘...")
self.vector_store.add_documents(self.documents)
print("๋ฒกํ„ฐ ์ธ๋ฑ์Šค ๋กœ๋“œ ์™„๋ฃŒ")
else:
print("๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋ฅผ ๋กœ๋“œํ–ˆ์œผ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์Œ, ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
self.vector_store.create_or_load(self.documents)
except Exception as e:
print(f"๋ฒกํ„ฐ ์ธ๋ฑ์Šค ๋กœ๋“œ ์‹คํŒจ, ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: {e}")
# ์˜ค๋ฅ˜ ์ƒ์„ธ ์ •๋ณด ์ถœ๋ ฅ
import traceback
traceback.print_exc()
# ์ƒˆ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ƒ์„ฑ
self.vector_store.create_or_load(self.documents)
else:
# ์ƒˆ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ƒ์„ฑ
print("์ƒˆ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ƒ์„ฑ ์ค‘...")
self.vector_store.create_or_load(self.documents)
# ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ €์žฅ
if self.vector_store and self.vector_store.vector_store is not None:
try:
print(f"๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ €์žฅ ์ค‘: {self.vector_index_dir}")
save_result = self.vector_store.save_local(self.vector_index_dir)
print(f"๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ €์žฅ ์™„๋ฃŒ: {self.vector_index_dir}")
except Exception as e:
print(f"๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์ €์žฅ ์‹คํŒจ: {e}")
# ์˜ค๋ฅ˜ ์ƒ์„ธ ์ •๋ณด ์ถœ๋ ฅ
import traceback
traceback.print_exc()
else:
print("๋ฒกํ„ฐ ์ธ๋ฑ์Šค๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
# RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”
try:
print("RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹œ๋„...")
if RAG_CHAIN_AVAILABLE:
print("RAG_CHAIN_AVAILABLE=True, ์ดˆ๊ธฐํ™” ์ง„ํ–‰")
# ์ง์ ‘ RAG ์ฒด์ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ดˆ๊ธฐํ™”
self.rag_chain = RAGChain(self.vector_store)
print("RAG ์ฒด์ธ ๊ฐ์ฒด ์ƒ์„ฑ ์™„๋ฃŒ")
# ํ…Œ์ŠคํŠธ ์ฟผ๋ฆฌ ์‹คํ–‰ํ•˜์—ฌ ์ฒด์ธ์ด ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธ
try:
test_response = self.rag_chain.run("ํ…Œ์ŠคํŠธ ์ฟผ๋ฆฌ์ž…๋‹ˆ๋‹ค.")
print(f"RAG ์ฒด์ธ ํ…Œ์ŠคํŠธ ์„ฑ๊ณต: ์‘๋‹ต ๊ธธ์ด {len(test_response)}")
self.is_initialized = True
except Exception as test_e:
print(f"RAG ์ฒด์ธ ํ…Œ์ŠคํŠธ ์‹คํŒจ: {test_e}")
import traceback
traceback.print_exc()
self.is_initialized = False
return f"RAG ์ฒด์ธ ํ…Œ์ŠคํŠธ ์‹คํŒจ: {test_e}"
else:
print("RAG_CHAIN_AVAILABLE=False, ์ดˆ๊ธฐํ™” ๋ถˆ๊ฐ€")
self.is_initialized = False
return "RAG ์ฒด์ธ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
# ์ตœ์ข… ์ƒํƒœ ํ™•์ธ ๋ฐ ๋กœ๊ทธ
print(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ๊ฒฐ๊ณผ: is_initialized={self.is_initialized}")
if self.is_initialized:
print("RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต!")
else:
print("RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจํ–ˆ์ง€๋งŒ ์˜ˆ์™ธ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ.")
return "RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: ์›์ธ ๋ถˆ๋ช…"
except Exception as e:
print(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
import traceback
traceback.print_exc()
self.is_initialized = False
return f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}"
total_time = time.time() - start_time
status_message = (
f"๋ฌธ์„œ ์ฒ˜๋ฆฌ ์™„๋ฃŒ!\n"
f"- ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ: {len(self.processed_files)}๊ฐœ\n"
f"- ์บ์‹œ๋œ ํŒŒ์ผ: {len(cached_files)}๊ฐœ\n"
f"- ์ƒˆ ํŒŒ์ผ: {len(new_files)}๊ฐœ\n"
f"- ์—…๋ฐ์ดํŠธ๋œ ํŒŒ์ผ: {len(updated_files)}๊ฐœ\n"
f"- ์‹คํŒจํ•œ ํŒŒ์ผ: {len(failed_files)}๊ฐœ\n"
f"- ์ด ์ฒญํฌ ์ˆ˜: {len(self.documents)}๊ฐœ\n"
f"- ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {total_time:.2f}์ดˆ\n"
f"- RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”: {'์„ฑ๊ณต' if self.is_initialized else '์‹คํŒจ'}\n"
f"์ด์ œ ์งˆ๋ฌธํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"
)
print(status_message)
return status_message
except Exception as e:
self.is_initialized = False
error_message = f"๋ฌธ์„œ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(error_message)
import traceback
traceback.print_exc()
return error_message
def reset_cache(self) -> str:
"""
์บ์‹œ ์ดˆ๊ธฐํ™”
Returns:
๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
"""
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)
# ์ธ๋ฑ์Šค ์ดˆ๊ธฐํ™”
self.file_index = {}
self._save_file_index()
# ๋ฒกํ„ฐ ์ธ๋ฑ์Šค ์‚ญ์ œ
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)
self.documents = []
self.processed_files = []
self.is_initialized = False
return "์บ์‹œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์‹คํ–‰ ์‹œ ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ๋‹ค์‹œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค."
except Exception as e:
return f"์บ์‹œ ์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
def process_query(self, query: str, chat_history: List[List[str]]) -> Tuple[str, List[List[str]]]:
"""
์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ
Args:
query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
chat_history: ๋Œ€ํ™” ๊ธฐ๋ก (๋ฆฌ์ŠคํŠธ ํ˜•์‹)
Returns:
์‘๋‹ต ๋ฐ ์—…๋ฐ์ดํŠธ๋œ ๋Œ€ํ™” ๊ธฐ๋ก
"""
if not query: # ๋น„์–ด์žˆ๋Š” ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ
return "", chat_history
if not self.is_initialized or self.rag_chain is None:
print("ํ…์ŠคํŠธ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ ๋กœ๋“œ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
response = "๋ฌธ์„œ ๋กœ๋“œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ž๋™ ๋กœ๋“œ๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค."
new_history = list(chat_history)
new_history.append([query, response])
# ์ž๋™ ๋กœ๋“œ ์‹œ๋„
try:
init_result = self.auto_process_documents()
print(f"[DEBUG] ์ž๋™ ๋กœ๋“œ ํ›„ is_initialized = {self.is_initialized}, RAG ์ฒด์ธ ์กด์žฌ = {self.rag_chain is not None}")
if not self.is_initialized or self.rag_chain is None:
response = f"๋ฌธ์„œ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. 'documents' ํด๋”์— PDF ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.\n์˜ค๋ฅ˜ ์ •๋ณด: {init_result}"
new_history = list(chat_history)
new_history.append([query, response])
return "", new_history
except Exception as e:
response = f"๋ฌธ์„œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
new_history = list(chat_history)
new_history.append([query, response])
return "", new_history
else:
print("ํ…์ŠคํŠธ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ๊ฐ€ ์ด๋ฏธ ๋กœ๋“œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
try:
# RAG ์ฒด์ธ ์‹คํ–‰ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
start_time = time.time()
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘: ์ฟผ๋ฆฌ = '{query}'")
if self.is_initialized and self.rag_chain is not None:
response = self.rag_chain.run(query)
else:
print("RAG ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์Œ: ๊ธฐ๋ณธ ์‘๋‹ต ์‚ฌ์šฉ")
response = self._fallback_response(query)
end_time = time.time()
query_time = end_time - start_time
print(f"์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {query_time:.2f}์ดˆ")
print(f"์‘๋‹ต: {response[:100]}..." if len(response) > 100 else f"์‘๋‹ต: {response}")
# ๋ฉ”์‹œ์ง€ ํ˜•์‹์— ๋งž๊ฒŒ ์ถ”๊ฐ€
new_history = list(chat_history)
new_history.append([query, response])
return "", new_history
except Exception as e:
error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {error_msg}")
import traceback
traceback.print_exc()
new_history = list(chat_history)
new_history.append([query, error_msg])
return "", new_history
def process_voice_query(self, audio, chat_history: List[List[str]]) -> List[List[str]]:
"""
์Œ์„ฑ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ
Args:
audio: ๋…น์Œ๋œ ์˜ค๋””์˜ค ๋ฐ์ดํ„ฐ (numpy ๋ฐฐ์—ด: (์ƒ˜ํ”Œ, ์ฑ„๋„))
chat_history: ๋Œ€ํ™” ๊ธฐ๋ก
Returns:
์—…๋ฐ์ดํŠธ๋œ ๋Œ€ํ™” ๊ธฐ๋ก
"""
if audio is None:
return chat_history
try:
import numpy as np
import scipy.io.wavfile as wav
# numpy ๋ฐฐ์—ด์„ WAV ํŒŒ์ผ๋กœ ์ €์žฅ
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
temp_path = temp_file.name
# ์ƒ˜ํ”Œ๋ง ๋ ˆ์ดํŠธ์™€ ์˜ค๋””์˜ค ๋ฐ์ดํ„ฐ๋ฅผ WAV ํŒŒ์ผ๋กœ ์ €์žฅ
sr, data = audio
# 16๋น„ํŠธ PCM ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
wav.write(temp_path, sr, data.astype(np.int16))
print(f"[STT] ์ž„์‹œ ์˜ค๋””์˜ค ํŒŒ์ผ ์ƒ์„ฑ: {temp_path}")
# config.py์—์„œ ์„ค์ •ํ•œ ์–ธ์–ด ์ฝ”๋“œ๋กœ STT ์‹คํ–‰
result = self.stt_client.recognize_file(temp_path, language=STT_LANGUAGE)
# ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
try:
os.unlink(temp_path)
print("[STT] ์ž„์‹œ ์˜ค๋””์˜ค ํŒŒ์ผ ์‚ญ์ œ๋จ")
except Exception as e:
print(f"[STT] ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ ์‹คํŒจ: {e}")
# STT ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
if "error" in result:
error_msg = f"์Œ์„ฑ์ธ์‹ ์˜ค๋ฅ˜: {result.get('error')}"
print(f"[STT] {error_msg}")
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
return new_history
# ์ธ์‹๋œ ํ…์ŠคํŠธ ์ถ”์ถœ
recognized_text = result.get("text", "")
if not recognized_text:
error_msg = "์Œ์„ฑ์„ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”."
print("[STT] ์ธ์‹๋œ ํ…์ŠคํŠธ ์—†์Œ")
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
return new_history
print(f"[STT] ์ธ์‹๋œ ํ…์ŠคํŠธ: {recognized_text}")
# ์ธ์‹๋œ ํ…์ŠคํŠธ๋กœ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ (์Œ์„ฑ ๋ฉ”์‹œ์ง€ ์ ‘๋‘์–ด ์ถ”๊ฐ€)
query = f"๐ŸŽค {recognized_text}"
print(f"[DEBUG] is_initialized = {self.is_initialized}, RAG ์ฒด์ธ ์กด์žฌ = {self.rag_chain is not None}")
# RAG ์ฒด์ธ ์‹คํ–‰ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
if not self.is_initialized or self.rag_chain is None:
print("์Œ์„ฑ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ ๋กœ๋“œ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
response = "๋ฌธ์„œ ๋กœ๋“œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ž๋™ ๋กœ๋“œ๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค."
new_history = list(chat_history)
new_history.append([query, response])
# ์ž๋™ ๋กœ๋“œ ์‹œ๋„
try:
init_result = self.auto_process_documents()
print(f"[DEBUG] ์ž๋™ ๋กœ๋“œ ํ›„ is_initialized = {self.is_initialized}, RAG ์ฒด์ธ ์กด์žฌ = {self.rag_chain is not None}")
if not self.is_initialized or self.rag_chain is None:
response = f"๋ฌธ์„œ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. 'documents' ํด๋”์— PDF ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.\n์˜ค๋ฅ˜ ์ •๋ณด: {init_result}"
new_history = list(chat_history)
new_history.append([query, response])
return new_history
except Exception as e:
response = f"๋ฌธ์„œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
new_history = list(chat_history)
new_history.append([query, response])
return new_history
else:
print("์Œ์„ฑ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ๊ฐ€ ์ด๋ฏธ ๋กœ๋“œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
try:
# RAG ์ฒด์ธ ์‹คํ–‰ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
start_time = time.time()
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘: ์ฟผ๋ฆฌ = '{query}'")
if self.is_initialized and self.rag_chain is not None:
response = self.rag_chain.run(query)
else:
print("RAG ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์Œ: ๊ธฐ๋ณธ ์‘๋‹ต ์‚ฌ์šฉ")
response = self._fallback_response(query)
end_time = time.time()
query_time = end_time - start_time
print(f"์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {query_time:.2f}์ดˆ")
print(f"์‘๋‹ต: {response[:100]}..." if len(response) > 100 else f"์‘๋‹ต: {response}")
# ๋ฉ”์‹œ์ง€ ํ˜•์‹์— ๋งž๊ฒŒ ์ถ”๊ฐ€
new_history = list(chat_history) # ๊ธฐ์กด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌ
new_history.append([query, response]) # ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€
return new_history
except Exception as e:
error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {error_msg}")
import traceback
traceback.print_exc()
# ๋ฉ”์‹œ์ง€ ํ˜•์‹์— ๋งž๊ฒŒ ์ถ”๊ฐ€
new_history = list(chat_history) # ๊ธฐ์กด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌ
new_history.append([query, error_msg]) # ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€
return new_history
except Exception as e:
error_msg = f"์Œ์„ฑ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(f"[STT] {error_msg}")
import traceback
traceback.print_exc()
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
return new_history
def launch_app(self) -> None:
"""
์Œ์„ฑ์ธ์‹ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ Gradio ์•ฑ ์‹คํ–‰ (์ž๋™ ์Œ์„ฑ์ฒ˜๋ฆฌ ๊ฐœ์„ )
"""
import gradio as gr
import time
# ์Œ์„ฑ ์ธ์‹ ํ›„ ์ž๋™ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
def process_audio_auto(audio, chat_history):
"""๋…น์Œ ์™„๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ STT ์ฒ˜๋ฆฌ ํ›„ ์งˆ์˜ ์ฒ˜๋ฆฌ"""
if audio is None:
return chat_history, gr.update(interactive=True), "๋…น์Œ ํ›„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค", ""
# ์ฒ˜๋ฆฌ ์ค‘ ์ƒํƒœ ๋ฉ”์‹œ์ง€
processing_msg = "์Œ์„ฑ ์ฒ˜๋ฆฌ ์ค‘..."
try:
import numpy as np
import scipy.io.wavfile as wav
# ์ƒํƒœ ์—…๋ฐ์ดํŠธ
yield chat_history, gr.update(interactive=False), processing_msg, "์Œ์„ฑ์„ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ ์ค‘..."
# numpy ๋ฐฐ์—ด์„ WAV ํŒŒ์ผ๋กœ ์ €์žฅ
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
temp_path = temp_file.name
# ์ƒ˜ํ”Œ๋ง ๋ ˆ์ดํŠธ์™€ ์˜ค๋””์˜ค ๋ฐ์ดํ„ฐ๋ฅผ WAV ํŒŒ์ผ๋กœ ์ €์žฅ
sr, data = audio
# 16๋น„ํŠธ PCM ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
wav.write(temp_path, sr, data.astype(np.int16))
print(f"[STT] ์ž„์‹œ ์˜ค๋””์˜ค ํŒŒ์ผ ์ƒ์„ฑ: {temp_path}")
# config.py์—์„œ ์„ค์ •ํ•œ ์–ธ์–ด ์ฝ”๋“œ๋กœ STT ์‹คํ–‰
result = self.stt_client.recognize_file(temp_path, language=STT_LANGUAGE)
# ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
try:
os.unlink(temp_path)
print("[STT] ์ž„์‹œ ์˜ค๋””์˜ค ํŒŒ์ผ ์‚ญ์ œ๋จ")
except Exception as e:
print(f"[STT] ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ ์‹คํŒจ: {e}")
# STT ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
if "error" in result:
error_msg = f"์Œ์„ฑ์ธ์‹ ์˜ค๋ฅ˜: {result.get('error')}"
print(f"[STT] {error_msg}")
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
yield new_history, gr.update(interactive=True), error_msg, ""
return
# ์ธ์‹๋œ ํ…์ŠคํŠธ ์ถ”์ถœ
recognized_text = result.get("text", "")
if not recognized_text:
error_msg = "์Œ์„ฑ์„ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”."
print("[STT] ์ธ์‹๋œ ํ…์ŠคํŠธ ์—†์Œ")
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
yield new_history, gr.update(interactive=True), error_msg, ""
return
print(f"[STT] ์ธ์‹๋œ ํ…์ŠคํŠธ: {recognized_text}")
# ์ƒํƒœ ์—…๋ฐ์ดํŠธ
yield chat_history, gr.update(
interactive=False), f"์ธ์‹๋œ ํ…์ŠคํŠธ: {recognized_text}\n\n์‘๋‹ต ์ƒ์„ฑ ์ค‘...", "์‘๋‹ต ์ƒ์„ฑ ์ค‘..."
# ์ธ์‹๋œ ํ…์ŠคํŠธ๋กœ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ (์Œ์„ฑ ๋ฉ”์‹œ์ง€ ์ ‘๋‘์–ด ์ถ”๊ฐ€)
query = f"๐ŸŽค {recognized_text}"
print(f"[DEBUG] is_initialized = {self.is_initialized}, RAG ์ฒด์ธ ์กด์žฌ = {self.rag_chain is not None}")
# RAG ์ฒด์ธ ์‹คํ–‰ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
if not self.is_initialized or self.rag_chain is None:
print("์Œ์„ฑ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ ๋กœ๋“œ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
response = "๋ฌธ์„œ ๋กœ๋“œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ž๋™ ๋กœ๋“œ๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค."
new_history = list(chat_history)
new_history.append([query, response])
# ์ž๋™ ๋กœ๋“œ ์‹œ๋„
try:
init_result = self.auto_process_documents()
print(
f"[DEBUG] ์ž๋™ ๋กœ๋“œ ํ›„ is_initialized = {self.is_initialized}, RAG ์ฒด์ธ ์กด์žฌ = {self.rag_chain is not None}")
if not self.is_initialized or self.rag_chain is None:
response = f"๋ฌธ์„œ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. 'documents' ํด๋”์— PDF ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.\n์˜ค๋ฅ˜ ์ •๋ณด: {init_result}"
new_history = list(chat_history)
new_history.append([query, response])
yield new_history, gr.update(interactive=True), response, ""
return
except Exception as e:
response = f"๋ฌธ์„œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
new_history = list(chat_history)
new_history.append([query, response])
yield new_history, gr.update(interactive=True), response, ""
return
else:
print("์Œ์„ฑ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ: ๋ฌธ์„œ๊ฐ€ ์ด๋ฏธ ๋กœ๋“œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
try:
# RAG ์ฒด์ธ ์‹คํ–‰ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
start_time = time.time()
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘: ์ฟผ๋ฆฌ = '{query}'")
if self.is_initialized and self.rag_chain is not None:
response = self.rag_chain.run(query)
else:
print("RAG ์ฒด์ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์Œ: ๊ธฐ๋ณธ ์‘๋‹ต ์‚ฌ์šฉ")
response = self._fallback_response(query)
end_time = time.time()
query_time = end_time - start_time
print(f"์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {query_time:.2f}์ดˆ")
print(f"์‘๋‹ต: {response[:100]}..." if len(response) > 100 else f"์‘๋‹ต: {response}")
# ๋ฉ”์‹œ์ง€ ํ˜•์‹์— ๋งž๊ฒŒ ์ถ”๊ฐ€
new_history = list(chat_history) # ๊ธฐ์กด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌ
new_history.append([query, response]) # ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€
# ์ตœ์ข… ์ƒํƒœ ์—…๋ฐ์ดํŠธ
yield new_history, gr.update(interactive=True), f"์ฒ˜๋ฆฌ ์™„๋ฃŒ: {recognized_text}", ""
except Exception as e:
error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {error_msg}")
import traceback
traceback.print_exc()
# ๋ฉ”์‹œ์ง€ ํ˜•์‹์— ๋งž๊ฒŒ ์ถ”๊ฐ€
new_history = list(chat_history) # ๊ธฐ์กด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌ
new_history.append([query, error_msg]) # ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€
yield new_history, gr.update(interactive=True), error_msg, ""
except Exception as e:
error_msg = f"์Œ์„ฑ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(f"[STT] {error_msg}")
import traceback
traceback.print_exc()
new_history = list(chat_history)
new_history.append(["์Œ์„ฑ ๋ฉ”์‹œ์ง€", error_msg])
yield new_history, gr.update(interactive=True), error_msg, ""
with gr.Blocks(title="์Œ์„ฑ์ธ์‹ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ PDF ๋ฌธ์„œ ๊ธฐ๋ฐ˜ RAG ์ฑ—๋ด‡") as app:
gr.Markdown("# ์Œ์„ฑ์ธ์‹ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ PDF ๋ฌธ์„œ ๊ธฐ๋ฐ˜ RAG ์ฑ—๋ด‡")
gr.Markdown(f"* ์‚ฌ์šฉ ์ค‘์ธ LLM ๋ชจ๋ธ: **{LLM_MODEL}**")
gr.Markdown(f"* PDF ๋ฌธ์„œ ํด๋”: **{self.pdf_directory}**")
gr.Markdown("* ๋„ค์ด๋ฒ„ ํด๋กœ๋ฐ” ์Œ์„ฑ์ธ์‹ API ํ†ตํ•ฉ")
with gr.Row():
with gr.Column(scale=1):
# ๋ฌธ์„œ ์ƒํƒœ ์„น์…˜
status_box = gr.Textbox(
label="๋ฌธ์„œ ์ฒ˜๋ฆฌ ์ƒํƒœ",
value=f"์ฒ˜๋ฆฌ๋œ ๋ฌธ์„œ ({len(self.processed_files)}๊ฐœ): {', '.join(self.processed_files)}",
lines=5,
interactive=False
)
# ์บ์‹œ ๊ด€๋ฆฌ ๋ฒ„ํŠผ
refresh_button = gr.Button("๋ฌธ์„œ ์ƒˆ๋กœ ์ฝ๊ธฐ", variant="primary")
reset_button = gr.Button("์บ์‹œ ์ดˆ๊ธฐํ™”", variant="stop")
# ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ ์ •๋ณด
with gr.Accordion("์บ์‹œ ์„ธ๋ถ€ ์ •๋ณด", open=False):
file_info = ""
for file_path, info in self.file_index.items():
file_info += f"- {os.path.basename(file_path)}: {info['chunks_count']}๊ฐœ ์ฒญํฌ\n"
cache_info = gr.Textbox(
label="์บ์‹œ๋œ ํŒŒ์ผ ์ •๋ณด",
value=file_info or "์บ์‹œ๋œ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.",
lines=5,
interactive=False
)
with gr.Column(scale=2):
# ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค
chatbot = gr.Chatbot(
label="๋Œ€ํ™” ๋‚ด์šฉ",
height=500,
show_copy_button=True
)
with gr.Tabs() as input_tabs:
# ํ…์ŠคํŠธ ์ž…๋ ฅ ํƒญ
with gr.Tab("ํ…์ŠคํŠธ ์ž…๋ ฅ"):
# ํ…์ŠคํŠธ ์ž…๋ ฅ๊ณผ ์ „์†ก ๋ฒ„ํŠผ์„ ์ˆ˜ํ‰์œผ๋กœ ๋ฐฐ์น˜
with gr.Row():
query_box = gr.Textbox(
label="์งˆ๋ฌธ",
placeholder="์ฒ˜๋ฆฌ๋œ ๋ฌธ์„œ ๋‚ด์šฉ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•˜์„ธ์š”...",
lines=2,
scale=4
)
submit_btn = gr.Button("์ „์†ก", variant="primary", scale=1)
# ์Œ์„ฑ ์ž…๋ ฅ ํƒญ (์ž๋™ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ )
with gr.Tab("์Œ์„ฑ ์ž…๋ ฅ"):
# ์ƒํƒœ ํ‘œ์‹œ
processing_status = gr.Textbox(
label="์ฒ˜๋ฆฌ ์ƒํƒœ",
placeholder="๋…น์Œ ํ›„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค",
lines=3,
interactive=False,
visible=True
)
# ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ์ถ”๊ฐ€
progress_bar = gr.Textbox(
label="์ง„ํ–‰ ์ƒํ™ฉ",
value="",
visible=True
)
# ์ž๋™ ์ฒ˜๋ฆฌ ์˜ค๋””์˜ค ์ปดํฌ๋„ŒํŠธ
audio_input_auto = gr.Audio(
label="๋งˆ์ดํฌ ์ž…๋ ฅ (๋…น์Œ ํ›„ ์ž๋™ ์ฒ˜๋ฆฌ)",
sources=["microphone"],
type="numpy",
format="wav",
streaming=False
)
# ๋Œ€ํ™” ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ
clear_chat_button = gr.Button("๋Œ€ํ™” ์ดˆ๊ธฐํ™”")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
refresh_button.click(
fn=self.auto_process_documents,
inputs=[],
outputs=[status_box]
)
reset_button.click(
fn=lambda: (self.reset_cache(), self.auto_process_documents()),
inputs=[],
outputs=[status_box]
)
# ํ…์ŠคํŠธ ์ „์†ก ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
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]
)
# ์ž๋™ ์Œ์„ฑ์ฒ˜๋ฆฌ ์ด๋ฒคํŠธ (๋…น์Œ์ด ๋๋‚˜๋ฉด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ ์‹œ์ž‘)
audio_input_auto.stop_recording(
fn=process_audio_auto,
inputs=[audio_input_auto, chatbot],
outputs=[chatbot, audio_input_auto, processing_status, progress_bar]
)
# ๋Œ€ํ™” ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ
clear_chat_button.click(
fn=lambda: [], # ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
outputs=[chatbot]
)
# ์•ฑ ์‹คํ–‰
app.launch(share=False)
if __name__ == "__main__":
app = AutoRAGChatApp()
app.launch_app()