jeongsoo's picture
init
d93e680
raw
history blame
18.9 kB
"""
RAG ๊ฒ€์ƒ‰ ์ฑ—๋ด‡ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (์„ธ์…˜ ์„ค์ • ์ˆ˜์ • ์ ์šฉ)
"""
import os
import json
import logging
import tempfile
import threading
import datetime
from flask import Flask, request, jsonify, render_template, send_from_directory, session, redirect, url_for
from werkzeug.utils import secure_filename
from dotenv import load_dotenv
from functools import wraps
# ๋กœ๊ฑฐ ์„ค์ •
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG # INFO์—์„œ DEBUG๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋” ์ƒ์„ธํ•œ ๋กœ๊ทธ ํ™•์ธ
)
logger = logging.getLogger(__name__)
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ ๋ฐ ๋กœ๊น…
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')
DEVICE_SERVER_URL = os.getenv('DEVICE_SERVER_URL', 'http://localhost:5050')
logger.info(f"==== ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ ์ƒํƒœ ====")
logger.info(f"ADMIN_USERNAME ์„ค์ • ์—ฌ๋ถ€: {ADMIN_USERNAME is not None}")
# ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋กœ๋“œ ์—ฌ๋ถ€๋งŒ ๊ธฐ๋ก (๋ณด์•ˆ)
logger.info(f"ADMIN_PASSWORD ์„ค์ • ์—ฌ๋ถ€: {ADMIN_PASSWORD is not None}")
logger.info(f"DEVICE_SERVER_URL: {DEVICE_SERVER_URL}")
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • (๊ฐœ๋ฐœ์šฉ, ๋ฐฐํฌ ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ๊ถŒ์žฅ)
if not ADMIN_USERNAME:
ADMIN_USERNAME = 'admin'
logger.warning("ADMIN_USERNAME ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ๊ฐ’ 'admin'์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.")
if not ADMIN_PASSWORD:
ADMIN_PASSWORD = 'rag12345'
logger.warning("ADMIN_PASSWORD ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ๊ฐ’ 'rag12345'๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.")
class MockComponent: pass
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ---
# ์‹ค์ œ ๊ฒฝ๋กœ์— ๋งž๊ฒŒ utils, retrieval ํด๋”๊ฐ€ ์กด์žฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
try:
from utils.vito_stt import VitoSTT
from utils.llm_interface import LLMInterface
from utils.document_processor import DocumentProcessor
from retrieval.vector_retriever import VectorRetriever
from retrieval.reranker import ReRanker
# ๋ผ์šฐํŠธ ์ •์˜ ํŒŒ์ผ ์ž„ํฌํŠธ
from app.app_routes import register_routes
from app.app_device_routes import register_device_routes
except ImportError as e:
logger.error(f"๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹คํŒจ: {e}. utils ๋ฐ retrieval ํŒจํ‚ค์ง€๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.")
# ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ž„์‹œ ํด๋ž˜์Šค ์ •์˜ (์‹ค์ œ ์‚ฌ์šฉ ์‹œ ์ œ๊ฑฐ)
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ๋ ---
# Flask ์•ฑ ์ดˆ๊ธฐํ™”
app = Flask(__name__)
# ์„ธ์…˜ ์„ค์ • - ๊ณ ์ •๋œ ์‹œํฌ๋ฆฟ ํ‚ค ์‚ฌ์šฉ (์‹ค์ œ ๋ฐฐํฌ ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ์œผ๋กœ ๊ด€๋ฆฌ ๊ถŒ์žฅ)
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345') # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์šฐ์„  ์‚ฌ์šฉ
# --- ์„ธ์…˜ ์ฟ ํ‚ค ์„ค์ • ์ˆ˜์ • (ํ—ˆ๊น…ํŽ˜์ด์Šค ํ™˜๊ฒฝ ๊ณ ๋ ค) ---
# ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ HTTPS๋กœ ์„œ๋น„์Šค๋˜๋ฏ€๋กœ Secure=True ์„ค์ •
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True # JavaScript์—์„œ ์ฟ ํ‚ค ์ ‘๊ทผ ๋ฐฉ์ง€ (๋ณด์•ˆ ๊ฐ•ํ™”)
# SameSite='Lax'๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ๋” ์•ˆ์ „ํ•˜๊ณ  ํ˜ธํ™˜์„ฑ์ด ์ข‹์Œ.
# ๋งŒ์•ฝ ์•ฑ์ด ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ iframe ๋‚ด์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๋ฉด 'None'์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•จ.
# (๋‹จ, 'None'์œผ๋กœ ์„ค์ • ์‹œ ๋ฐ˜๋“œ์‹œ Secure=True์—ฌ์•ผ ํ•จ)
# ๋กœ๊ทธ ๋ถ„์„ ๊ฒฐ๊ณผ iframe ํ™˜๊ฒฝ์œผ๋กœ ํ™•์ธ๋˜์–ด 'None'์œผ๋กœ ๋ณ€๊ฒฝ
app.config['SESSION_COOKIE_SAMESITE'] = 'None' # <--- ์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
app.config['SESSION_COOKIE_DOMAIN'] = None # ํŠน์ • ๋„๋ฉ”์ธ ์ œํ•œ ์—†์Œ
app.config['SESSION_COOKIE_PATH'] = '/' # ์•ฑ ์ „์ฒด ๊ฒฝ๋กœ์— ์ฟ ํ‚ค ์ ์šฉ
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=1) # ์„ธ์…˜ ์œ ํšจ ์‹œ๊ฐ„ ์ฆ๊ฐ€
# --- ์„ธ์…˜ ์ฟ ํ‚ค ์„ค์ • ๋ ---
# ์ตœ๋Œ€ ํŒŒ์ผ ํฌ๊ธฐ ์„ค์ • (10MB)
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŒŒ์ผ ๊ธฐ์ค€ ์ƒ๋Œ€ ๊ฒฝ๋กœ ์„ค์ •
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
app.config['DATA_FOLDER'] = os.path.join(APP_ROOT, '..', 'data')
app.config['INDEX_PATH'] = os.path.join(APP_ROOT, '..', 'data', 'index')
# ํ•„์š”ํ•œ ํด๋” ์ƒ์„ฑ
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['DATA_FOLDER'], exist_ok=True)
os.makedirs(app.config['INDEX_PATH'], exist_ok=True)
# ํ—ˆ์šฉ๋˜๋Š” ์˜ค๋””์˜ค/๋ฌธ์„œ ํŒŒ์ผ ํ™•์žฅ์ž
ALLOWED_AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'm4a'}
ALLOWED_DOC_EXTENSIONS = {'txt', 'md', 'pdf', 'docx', 'csv'}
# --- ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ---
try:
llm_interface = LLMInterface(default_llm="openai")
stt_client = VitoSTT()
except NameError:
logger.warning("LLM ๋˜๋Š” STT ์ธํ„ฐํŽ˜์ด์Šค ์ดˆ๊ธฐํ™” ์‹คํŒจ. Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
llm_interface = MockComponent()
stt_client = MockComponent()
base_retriever = None
retriever = None
app_ready = False # ์•ฑ ์ดˆ๊ธฐํ™” ์ƒํƒœ ํ”Œ๋ž˜๊ทธ
# --- ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ๋ ---
# --- ์ธ์ฆ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ (์ˆ˜์ •๋จ) ---
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
logger.info(f"----------- ์ธ์ฆ ํ•„์š” ํŽ˜์ด์ง€ ์ ‘๊ทผ ์‹œ๋„: {request.path} -----------")
logger.info(f"ํ˜„์žฌ ํ”Œ๋ผ์Šคํฌ ์„ธ์…˜ ๊ฐ์ฒด: {session}")
logger.info(f"ํ˜„์žฌ ์„ธ์…˜ ์ƒํƒœ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
# ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ณด๋‚ธ ์‹ค์ œ ์ฟ ํ‚ค ํ™•์ธ (๋””๋ฒ„๊น…์šฉ)
logger.info(f"์š”์ฒญ์˜ ์„ธ์…˜ ์ฟ ํ‚ค ๊ฐ’: {request.cookies.get('session', 'None')}")
# Flask ์„ธ์…˜์— 'logged_in' ํ‚ค๊ฐ€ ์žˆ๋Š”์ง€ ์ง์ ‘ ํ™•์ธ
if 'logged_in' not in session:
logger.warning(f"ํ”Œ๋ผ์Šคํฌ ์„ธ์…˜์— 'logged_in' ์—†์Œ. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜.")
# ์ˆ˜๋™ ์ฟ ํ‚ค ํ™•์ธ ๋กœ์ง ์ œ๊ฑฐ๋จ
return redirect(url_for('login', next=request.url)) # ๋กœ๊ทธ์ธ ํ›„ ์›๋ž˜ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๋„๋ก next ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
logger.info(f"์ธ์ฆ ์„ฑ๊ณต: {session.get('username', 'unknown')} ์‚ฌ์šฉ์ž๊ฐ€ {request.path} ์ ‘๊ทผ")
return f(*args, **kwargs)
return decorated_function
# --- ์ธ์ฆ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ ---
# --- ํ—ฌํผ ํ•จ์ˆ˜ ---
def allowed_audio_file(filename):
"""ํŒŒ์ผ์ด ํ—ˆ์šฉ๋œ ์˜ค๋””์˜ค ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง€๋Š”์ง€ ํ™•์ธ"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
def allowed_doc_file(filename):
"""ํŒŒ์ผ์ด ํ—ˆ์šฉ๋œ ๋ฌธ์„œ ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง€๋Š”์ง€ ํ™•์ธ"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_DOC_EXTENSIONS
# --- ํ—ฌํผ ํ•จ์ˆ˜ ๋ ---
# init_retriever ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ๋กœ๊น… ์ถ”๊ฐ€ ์˜ˆ์‹œ
# --- ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ๊ด€๋ จ ํ•จ์ˆ˜ ---
def init_retriever():
"""๊ฒ€์ƒ‰๊ธฐ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ๋˜๋Š” ๋กœ๋“œ"""
global base_retriever, retriever
index_path = app.config['INDEX_PATH']
data_path = app.config['DATA_FOLDER'] # data_path ์ •์˜ ํ™•์ธ
logger.info("--- init_retriever ์‹œ์ž‘ ---")
# 1. ๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ ๋กœ๋“œ ๋˜๋Š” ์ดˆ๊ธฐํ™”
# ... (VectorRetriever ๋กœ๋“œ ๋˜๋Š” ์ดˆ๊ธฐํ™” ๋กœ์ง์€ ์ด์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€) ...
# VectorRetriever ์ดˆ๊ธฐํ™”/๋กœ๋“œ ์‹คํŒจ ์‹œ base_retriever = None ๋ฐ return None ์ฒ˜๋ฆฌ ํฌํ•จ
if os.path.exists(os.path.join(index_path, "documents.json")):
try:
logger.info(f"์ธ๋ฑ์Šค ๋กœ๋“œ ์‹œ๋„: {index_path}")
base_retriever = VectorRetriever.load(index_path)
logger.info(f"์ธ๋ฑ์Šค ๋กœ๋“œ ์„ฑ๊ณต. ๋ฌธ์„œ {len(getattr(base_retriever, 'documents', []))}๊ฐœ")
except Exception as e:
logger.error(f"์ธ๋ฑ์Šค ๋กœ๋“œ ์‹คํŒจ: {e}", exc_info=True)
logger.info("์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์‹œ๋„...")
try:
base_retriever = VectorRetriever()
logger.info("์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์„ฑ๊ณต.")
except Exception as e_init:
logger.error(f"์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e_init}", exc_info=True)
base_retriever = None
else:
logger.info("์ธ๋ฑ์Šค ํŒŒ์ผ ์—†์Œ. ์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์‹œ๋„...")
try:
base_retriever = VectorRetriever()
logger.info("์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์„ฑ๊ณต.")
except Exception as e_init:
logger.error(f"์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e_init}", exc_info=True)
base_retriever = None
if base_retriever is None:
logger.error("base_retriever ์ดˆ๊ธฐํ™”/๋กœ๋“œ์— ์‹คํŒจํ•˜์—ฌ init_retriever ์ค‘๋‹จ.")
return None
# 2. ๋ฐ์ดํ„ฐ ํด๋” ๋ฌธ์„œ ๋กœ๋“œ (๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ ๋น„์–ด์žˆ์„ ๋•Œ)
needs_loading = (not hasattr(base_retriever, 'documents') or not getattr(base_retriever, 'documents', None)) # None ์ฒดํฌ ์ถ”๊ฐ€
if needs_loading and os.path.exists(data_path):
logger.info(f"๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ ๋น„์–ด์žˆ์–ด {data_path}์—์„œ ๋ฌธ์„œ ๋กœ๋“œ ์‹œ๋„...")
try:
# ================== ์ˆ˜์ •๋œ ๋ถ€๋ถ„ 1 ์‹œ์ž‘ ==================
# DocumentProcessor.load_documents_from_directory ํ˜ธ์ถœ ์‹œ ์˜ฌ๋ฐ”๋ฅธ ์ธ์ž ์ „๋‹ฌ
docs = DocumentProcessor.load_documents_from_directory(
directory=data_path, # <-- ๊ฒฝ๋กœ ๋ณ€์ˆ˜ ์‚ฌ์šฉ
extensions=[".txt", ".md", ".csv"], # <-- ํ•„์š”ํ•œ ํ™•์žฅ์ž ์ „๋‹ฌ
recursive=True # <-- ์žฌ๊ท€ ํƒ์ƒ‰ ์—ฌ๋ถ€ ์ „๋‹ฌ
)
# ================== ์ˆ˜์ •๋œ ๋ถ€๋ถ„ 1 ๋ ====================
logger.info(f"{len(docs)}๊ฐœ ๋ฌธ์„œ ๋กœ๋“œ ์„ฑ๊ณต.")
if docs and hasattr(base_retriever, 'add_documents'):
logger.info("๊ฒ€์ƒ‰๊ธฐ์— ๋ฌธ์„œ ์ถ”๊ฐ€ ์‹œ๋„...")
base_retriever.add_documents(docs)
logger.info("๋ฌธ์„œ ์ถ”๊ฐ€ ์™„๋ฃŒ.")
if hasattr(base_retriever, 'save'):
logger.info(f"๊ฒ€์ƒ‰๊ธฐ ์ƒํƒœ ์ €์žฅ ์‹œ๋„: {index_path}")
try:
base_retriever.save(index_path)
logger.info("์ธ๋ฑ์Šค ์ €์žฅ ์™„๋ฃŒ.")
except Exception as e_save:
logger.error(f"์ธ๋ฑ์Šค ์ €์žฅ ์‹คํŒจ: {e_save}", exc_info=True)
except Exception as e_load_add:
# load_documents_from_directory ์ž์ฒด์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚  ์ˆ˜๋„ ์žˆ์Œ (๊ถŒํ•œ ๋“ฑ)
logger.error(f"DATA_FOLDER ๋ฌธ์„œ ๋กœ๋“œ/์ถ”๊ฐ€ ์ค‘ ์˜ค๋ฅ˜: {e_load_add}", exc_info=True)
# 3. ์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”
logger.info("์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹œ๋„...")
try:
# ================== ์ˆ˜์ •๋œ ๋ถ€๋ถ„ 2 ์‹œ์ž‘ ==================
# custom_rerank_fn ํ•จ์ˆ˜๋ฅผ ReRanker ์ดˆ๊ธฐํ™” ์ „์— ์ •์˜
def custom_rerank_fn(query, results):
query_terms = set(query.lower().split())
for result in results:
if isinstance(result, dict) and "text" in result:
text = result["text"].lower()
term_freq = sum(1 for term in query_terms if term in text)
normalized_score = term_freq / (len(text.split()) + 1) * 10
result["rerank_score"] = result.get("score", 0) * 0.7 + normalized_score * 0.3
elif isinstance(result, dict):
result["rerank_score"] = result.get("score", 0)
results.sort(key=lambda x: x.get("rerank_score", 0) if isinstance(x, dict) else 0, reverse=True)
return results
# ================== ์ˆ˜์ •๋œ ๋ถ€๋ถ„ 2 ๋ ====================
# ReRanker ํด๋ž˜์Šค ์‚ฌ์šฉ
retriever = ReRanker(
base_retriever=base_retriever,
rerank_fn=custom_rerank_fn, # ์ด์ œ ํ•จ์ˆ˜๊ฐ€ ์ •์˜๋˜์—ˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
rerank_field="text"
)
logger.info("์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
except Exception as e_rerank:
logger.error(f"์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e_rerank}", exc_info=True)
logger.warning("์žฌ์ˆœ์œ„ํ™” ์‹คํŒจ, ๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ๋ฅผ retriever๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
retriever = base_retriever # fallback
logger.info("--- init_retriever ์ข…๋ฃŒ ---")
return retriever
def background_init():
"""๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์ˆ˜ํ–‰"""
global app_ready, retriever, base_retriever, llm_interface, stt_client
temp_app_ready = False # ์ž„์‹œ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ
try:
logger.info("๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
# 1. LLM, STT ์ธํ„ฐํŽ˜์ด์Šค ์ดˆ๊ธฐํ™” (ํ•„์š” ์‹œ)
if llm_interface is None or isinstance(llm_interface, MockComponent):
if 'LLMInterface' in globals() and LLMInterface != MockComponent:
llm_interface = LLMInterface(default_llm="openai")
logger.info("LLM ์ธํ„ฐํŽ˜์ด์Šค ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
else:
logger.warning("LLMInterface ํด๋ž˜์Šค ์—†์Œ. Mock ์‚ฌ์šฉ.")
llm_interface = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์žฅ
if stt_client is None or isinstance(stt_client, MockComponent):
if 'VitoSTT' in globals() and VitoSTT != MockComponent:
stt_client = VitoSTT()
logger.info("STT ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
else:
logger.warning("VitoSTT ํด๋ž˜์Šค ์—†์Œ. Mock ์‚ฌ์šฉ.")
stt_client = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์žฅ
# 2. ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”
if 'VectorRetriever' in globals() and VectorRetriever != MockComponent:
logger.info("์‹ค์ œ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹œ๋„...")
# init_retriever๊ฐ€ base_retriever์™€ retriever๋ฅผ ๋ชจ๋‘ ์„ค์ •ํ•œ๋‹ค๊ณ  ๊ฐ€์ •
retriever = init_retriever()
# init_retriever ๋‚ด๋ถ€์—์„œ base_retriever๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์—ฌ๊ธฐ์„œ ์„ค์ •
if hasattr(retriever, 'base_retriever') and base_retriever is None:
base_retriever = retriever.base_retriever
elif base_retriever is None:
# retriever๊ฐ€ base_retriever๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ๋˜๋Š” ReRanker๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ
# init_retriever์—์„œ base_retriever๋ฅผ ์ง์ ‘ ์„ค์ •ํ•˜๋„๋ก ํ•˜๊ฑฐ๋‚˜, ์—ฌ๊ธฐ์„œ ๋ณ„๋„ ๋กœ์ง ํ•„์š”
# ์˜ˆ์‹œ: base_retriever = VectorRetriever.load(...) ๋˜๋Š” VectorRetriever()
logger.warning("init_retriever ํ›„ base_retriever๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ. ํ™•์ธ ํ•„์š”.")
# ์ž„์‹œ๋กœ retriever ์ž์ฒด๋ฅผ base_retriever๋กœ ์„ค์ • (๋™์ผ ๊ฐ์ฒด์ผ ๊ฒฝ์šฐ)
if isinstance(retriever, VectorRetriever):
base_retriever = retriever
# ์„ฑ๊ณต์ ์œผ๋กœ ์ดˆ๊ธฐํ™” ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ (None์ด ์•„๋‹Œ์ง€)
if retriever is not None and base_retriever is not None:
logger.info("๊ฒ€์ƒ‰๊ธฐ (Retriever, Base Retriever) ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
temp_app_ready = True # ์ดˆ๊ธฐํ™” ์„ฑ๊ณต ์‹œ์—๋งŒ True ์„ค์ •
else:
logger.error("๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ํ›„์—๋„ retriever ๋˜๋Š” base_retriever๊ฐ€ None์ž…๋‹ˆ๋‹ค.")
# ์‹คํŒจ ์‹œ Mock ๊ฐ์ฒด ํ• ๋‹น (์ตœ์†Œํ•œ์˜ ๋™์ž‘ ๋ณด์žฅ)
if base_retriever is None: base_retriever = MockComponent()
if retriever is None: retriever = MockComponent()
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
# temp_app_ready = False ๋˜๋Š” True (์ •์ฑ…์— ๋”ฐ๋ผ ๊ฒฐ์ •)
temp_app_ready = True # ์ผ๋‹จ ์•ฑ์€ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •
else:
logger.warning("VectorRetriever ํด๋ž˜์Šค ์—†์Œ. Mock ๊ฒ€์ƒ‰๊ธฐ ์‚ฌ์šฉ.")
base_retriever = MockComponent()
retriever = MockComponent()
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
temp_app_ready = True # Mock์ด๋ผ๋„ ์ค€๋น„๋Š” ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ
logger.info(f"๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ. ์ตœ์ข… ์ƒํƒœ: {'Ready' if temp_app_ready else 'Not Ready (Error during init)'}")
except Exception as e:
logger.error(f"์•ฑ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์ค‘ ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
# ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ์—๋„ Mock ๊ฐ์ฒด ํ• ๋‹น ์‹œ๋„
if base_retriever is None: base_retriever = MockComponent()
if retriever is None: retriever = MockComponent()
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
temp_app_ready = True # ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•ด๋„ ์•ฑ์€ ์‘๋‹ตํ•˜๋„๋ก ์„ค์ • (์ •์ฑ…์— ๋”ฐ๋ผ False ๊ฐ€๋Šฅ)
logger.warning("์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์ง€๋งŒ Mock ๊ฐ์ฒด๋กœ ๋Œ€์ฒด ํ›„ ์•ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์ƒํƒœ๋กœ ์„ค์ •.")
finally:
# ์ตœ์ข…์ ์œผ๋กœ app_ready ์ƒํƒœ ์—…๋ฐ์ดํŠธ
app_ready = temp_app_ready
# ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์‹œ์ž‘ ๋ถ€๋ถ„์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€
init_thread = threading.Thread(target=background_init)
init_thread.daemon = True
init_thread.start()
# ๋ผ์šฐํŠธ ๋“ฑ๋ก
try:
# ๊ธฐ๋ณธ RAG ์ฑ—๋ด‡ ๋ผ์šฐํŠธ ๋“ฑ๋ก
register_routes(
app=app,
login_required=login_required,
llm_interface=llm_interface,
retriever=retriever,
stt_client=stt_client,
DocumentProcessor=DocumentProcessor,
base_retriever=base_retriever,
app_ready=app_ready,
ADMIN_USERNAME=ADMIN_USERNAME,
ADMIN_PASSWORD=ADMIN_PASSWORD,
DEVICE_SERVER_URL=DEVICE_SERVER_URL
)
logger.info("๊ธฐ๋ณธ ์ฑ—๋ด‡ ๋ผ์šฐํŠธ ๋“ฑ๋ก ์™„๋ฃŒ")
# ์žฅ์น˜ ๊ด€๋ฆฌ ๋ผ์šฐํŠธ ๋“ฑ๋ก
register_device_routes(
app=app,
login_required=login_required,
DEVICE_SERVER_URL=DEVICE_SERVER_URL
)
logger.info("์žฅ์น˜ ๊ด€๋ฆฌ ๋ผ์šฐํŠธ ๋“ฑ๋ก ์™„๋ฃŒ")
except Exception as e:
logger.error(f"๋ผ์šฐํŠธ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
# --- ์ •์  ํŒŒ์ผ ์„œ๋น™ ---
@app.route('/static/<path:path>')
def send_static(path):
return send_from_directory('static', path)
# --- ์š”์ฒญ ์ฒ˜๋ฆฌ ํ›… ---
@app.after_request
def after_request_func(response):
"""๋ชจ๋“  ์‘๋‹ต์— ๋Œ€ํ•ด ํ›„์ฒ˜๋ฆฌ ์ˆ˜ํ–‰"""
return response
# ์•ฑ ์‹คํ–‰ (๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ)
if __name__ == '__main__':
logger.info("Flask ์•ฑ์„ ์ง์ ‘ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (๊ฐœ๋ฐœ์šฉ ์„œ๋ฒ„).")
# ๋””๋ฒ„๊ทธ ๋ชจ๋“œ๋Š” ์‹ค์ œ ๋ฐฐํฌ ์‹œ False๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
# port ๋ฒˆํ˜ธ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
port = int(os.environ.get("PORT", 7860))
logger.info(f"์„œ๋ฒ„๋ฅผ http://0.0.0.0:{port} ์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
app.run(debug=True, host='0.0.0.0', port=port)