jeongsoo's picture
fix
b0188f6
raw
history blame
17.5 kB
"""
RAG ๊ฒ€์ƒ‰ ์ฑ—๋ด‡ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (์„ธ์…˜ ์„ค์ • ์ˆ˜์ • ์ ์šฉ ๋ฐ TypeError ํ•ด๊ฒฐ)
"""
import os
import json
import logging
import tempfile
import threading
import datetime
import time # ์ถ”๊ฐ€
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
)
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', '')
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 or '์„ค์ •๋˜์ง€ ์•Š์Œ (ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ž๋™ ์„ค์ •)'}")
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
if not ADMIN_USERNAME:
ADMIN_USERNAME = 'admin'
logger.warning("ADMIN_USERNAME ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ๊ฐ’ 'admin'์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.")
if not ADMIN_PASSWORD:
ADMIN_PASSWORD = 'rag12345'
logger.warning("ADMIN_PASSWORD ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—†์–ด ๊ธฐ๋ณธ๊ฐ’ 'rag12345'๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.")
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ---
# MockComponent ์ •์˜ (์ž„ํฌํŠธ ์‹คํŒจ ์‹œ ๋Œ€์ฒด)
class MockComponent:
def __init__(self):
self.is_mock = True
def search(self, query, top_k=5, first_stage_k=None):
"""๋นˆ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."""
logger.warning(f"MockComponent.search ํ˜ธ์ถœ๋จ (์ฟผ๋ฆฌ: {query[:30]}...)")
return []
def __getattr__(self, name):
# Mock ๊ฐ์ฒด์˜ ์–ด๋–ค ์†์„ฑ์ด๋‚˜ ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ์‹œ ๊ฒฝ๊ณ  ๋กœ๊ทธ ์ถœ๋ ฅ ๋ฐ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
logger.warning(f"MockComponent์—์„œ '{name}' ์ ‘๊ทผ ์‹œ๋„๋จ (์‹ค์ œ ๋ชจ๋“ˆ ๋กœ๋“œ ์‹คํŒจ)")
# ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ์‹œ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•˜๋Š” ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
if name in ['add_documents', 'save', 'transcribe_audio', 'rag_generate', 'set_llm', 'get_current_llm_details', 'prepare_rag_context', 'csv_to_documents', 'text_to_documents', 'load_documents_from_directory']:
return lambda *args, **kwargs: logger.warning(f"Mocked method '{name}' called") or None
# ์†์„ฑ ์ ‘๊ทผ ์‹œ์—๋Š” None ๋ฐ˜ํ™˜
return None
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
MODULE_LOAD_SUCCESS = True
except ImportError as e:
logger.error(f"๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹คํŒจ: {e}. Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
# register_routes, register_device_routes๋Š” ์ž„ํฌํŠธ ์‹คํŒจ ์‹œ ์ •์˜๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์•„๋ž˜์—์„œ ์ฒ˜๋ฆฌ
MODULE_LOAD_SUCCESS = False
# ์ž„์‹œ๋กœ ๋นˆ ํ•จ์ˆ˜ ์ •์˜ (์•ฑ ์‹คํ–‰์€ ๋˜๋„๋ก)
def register_routes(*args, **kwargs): logger.error("register_routes ์ž„ํฌํŠธ ์‹คํŒจ")
def register_device_routes(*args, **kwargs): logger.error("register_device_routes ์ž„ํฌํŠธ ์‹คํŒจ")
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ๋ ---
# Flask ์•ฑ ์ดˆ๊ธฐํ™”
app = Flask(__name__)
# ์„ธ์…˜ ์„ค์ •
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345')
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
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__))
# static ํด๋” ๊ฒฝ๋กœ ์ˆ˜์ • (app ํด๋” ๋‚ด๋ถ€์˜ static)
app.config['STATIC_FOLDER'] = os.path.join(APP_ROOT, 'static')
app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
# data ๋ฐ index ๊ฒฝ๋กœ๋Š” app ํด๋” ์™ธ๋ถ€๋กœ ์„ค์ • (ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๊ธฐ์ค€)
app.config['DATA_FOLDER'] = os.path.join(os.path.dirname(APP_ROOT), 'data')
app.config['INDEX_PATH'] = os.path.join(os.path.dirname(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'}
# --- ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ---
llm_interface = None
stt_client = None
base_retriever = None
retriever = None
# app_ready ํ”Œ๋ž˜๊ทธ ๋Œ€์‹  threading.Event ์‚ฌ์šฉ
app_ready_event = threading.Event() # ์ดˆ๊ธฐ ์ƒํƒœ: False (set() ํ˜ธ์ถœ ์ „๊นŒ์ง€)
# --- ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” (try-except๋กœ ๊ฐ์‹ธ๊ธฐ) ---
try:
if MODULE_LOAD_SUCCESS: # ๋ชจ๋“ˆ ๋กœ๋“œ ์„ฑ๊ณต ์‹œ์—๋งŒ ์‹ค์ œ ์ดˆ๊ธฐํ™” ์‹œ๋„
llm_interface = LLMInterface(default_llm="openai")
stt_client = VitoSTT()
else: # ์‹คํŒจ ์‹œ Mock ๊ฐ์ฒด ์‚ฌ์šฉ
llm_interface = LLMInterface() # MockComponent
stt_client = VitoSTT() # MockComponent
except Exception as e:
logger.error(f"LLM ๋˜๋Š” STT ์ธํ„ฐํŽ˜์ด์Šค ์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
llm_interface = MockComponent() # ์˜ค๋ฅ˜ ์‹œ Mock ๊ฐ์ฒด ํ• ๋‹น
stt_client = MockComponent() # ์˜ค๋ฅ˜ ์‹œ Mock ๊ฐ์ฒด ํ• ๋‹น
# --- ์ธ์ฆ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ---
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
logger.info(f"----------- ์ธ์ฆ ํ•„์š” ํŽ˜์ด์ง€ ์ ‘๊ทผ ์‹œ๋„: {request.path} -----------")
logger.debug(f"ํ˜„์žฌ ํ”Œ๋ผ์Šคํฌ ์„ธ์…˜ ๊ฐ์ฒด: {session}") # DEBUG ๋ ˆ๋ฒจ๋กœ ๋ณ€๊ฒฝ
logger.info(f"ํ˜„์žฌ ์„ธ์…˜ ์ƒํƒœ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
logger.debug(f"์š”์ฒญ์˜ ์„ธ์…˜ ์ฟ ํ‚ค ๊ฐ’: {request.cookies.get('session', 'None')}") # DEBUG ๋ ˆ๋ฒจ๋กœ ๋ณ€๊ฒฝ
if not session.get('logged_in'): # .get() ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „
logger.warning(f"์„ธ์…˜์— 'logged_in' ์—†์Œ ๋˜๋Š” False. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜.")
return redirect(url_for('login', next=request.url))
logger.info(f"์ธ์ฆ ์„ฑ๊ณต: {session.get('username', 'unknown')} ์‚ฌ์šฉ์ž๊ฐ€ {request.path} ์ ‘๊ทผ")
return f(*args, **kwargs)
return decorated_function
# --- ์ธ์ฆ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ ---
# --- ํ—ฌํผ ํ•จ์ˆ˜ (app_routes.py์—๋„ ์žˆ์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋„ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ) ---
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
# --- ํ—ฌํผ ํ•จ์ˆ˜ ๋ ---
# --- ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ๊ด€๋ จ ํ•จ์ˆ˜ ---
def init_retriever():
"""๊ฒ€์ƒ‰๊ธฐ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ๋˜๋Š” ๋กœ๋“œ"""
global base_retriever, retriever
# ๋ชจ๋“ˆ ๋กœ๋“œ ์‹คํŒจ ์‹œ Mock ๊ฐ์ฒด ๋ฐ˜ํ™˜
if not MODULE_LOAD_SUCCESS:
logger.warning("ํ•„์ˆ˜ ๋ชจ๋“ˆ ๋กœ๋“œ ์‹คํŒจ๋กœ Mock ๊ฒ€์ƒ‰๊ธฐ ๋ฐ˜ํ™˜")
base_retriever = VectorRetriever() # MockComponent
retriever = ReRanker() # MockComponent
return retriever
index_path = app.config['INDEX_PATH']
data_path = app.config['DATA_FOLDER']
logger.info("--- init_retriever ์‹œ์ž‘ ---")
# 1. ๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ ๋กœ๋“œ ๋˜๋Š” ์ดˆ๊ธฐํ™”
try:
if os.path.exists(os.path.join(index_path, "documents.json")): # ์ €์žฅ ๋ฐฉ์‹์— ๋”ฐ๋ผ ํ™•์ธ ํŒŒ์ผ ๋ณ€๊ฒฝ ํ•„์š”
logger.info(f"์ธ๋ฑ์Šค ๋กœ๋“œ ์‹œ๋„: {index_path}")
base_retriever = VectorRetriever.load(index_path)
logger.info(f"์ธ๋ฑ์Šค ๋กœ๋“œ ์„ฑ๊ณต. ๋ฌธ์„œ {len(getattr(base_retriever, 'documents', []))}๊ฐœ")
else:
logger.info("์ธ๋ฑ์Šค ํŒŒ์ผ ์—†์Œ. ์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์‹œ๋„...")
base_retriever = VectorRetriever()
logger.info("์ƒˆ VectorRetriever ์ดˆ๊ธฐํ™” ์„ฑ๊ณต.")
except Exception as e:
logger.error(f"๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”/๋กœ๋“œ ์‹คํŒจ: {e}", exc_info=True)
base_retriever = MockComponent() # ์‹คํŒจ ์‹œ Mock ์‚ฌ์šฉ
retriever = MockComponent()
logger.info("Mock ๊ฒ€์ƒ‰๊ธฐ๋ฅผ ๋Œ€์ฒด๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
return retriever # ์ดˆ๊ธฐํ™” ์‹คํŒจํ•ด๋„ Mock ๊ฒ€์ƒ‰๊ธฐ ๋ฐ˜ํ™˜ (None ๋Œ€์‹ )
# 2. ๋ฐ์ดํ„ฐ ํด๋” ๋ฌธ์„œ ๋กœ๋“œ (๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ ๋น„์–ด์žˆ์„ ๋•Œ)
needs_loading = not hasattr(base_retriever, 'documents') or not getattr(base_retriever, 'documents', [])
if needs_loading and os.path.exists(data_path):
logger.info(f"๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ ๋น„์–ด์žˆ์–ด {data_path}์—์„œ ๋ฌธ์„œ ๋กœ๋“œ ์‹œ๋„...")
try:
# DocumentProcessor.load_documents_from_directory ํ˜ธ์ถœ ํ™•์ธ
if hasattr(DocumentProcessor, 'load_documents_from_directory'):
docs = DocumentProcessor.load_documents_from_directory(
directory=data_path,
extensions=[".txt", ".md", ".csv"],
recursive=True
)
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)
else:
logger.warning("DocumentProcessor์— load_documents_from_directory ๋ฉ”์†Œ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
except Exception as e_load_add:
logger.error(f"DATA_FOLDER ๋ฌธ์„œ ๋กœ๋“œ/์ถ”๊ฐ€ ์ค‘ ์˜ค๋ฅ˜: {e_load_add}", exc_info=True)
# 3. ์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”
logger.info("์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹œ๋„...")
try:
# custom_rerank_fn ์ •์˜
def custom_rerank_fn(query, results):
# ์ด ํ•จ์ˆ˜๋Š” ์‹ค์ œ ์žฌ์ˆœ์œ„ํ™” ๋กœ์ง์— ๋งž๊ฒŒ ๊ตฌํ˜„ ํ•„์š”
# ์˜ˆ์‹œ: ๋‹จ์ˆœํžˆ score ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ
results.sort(key=lambda x: x.get("score", 0) if isinstance(x, dict) else 0, reverse=True)
return results
# 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 retriever, base_retriever, llm_interface, stt_client, app_ready_event
logger.info("๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
start_init_time = time.time()
try:
# 1. LLM, STT ์ธํ„ฐํŽ˜์ด์Šค ์žฌํ™•์ธ (์ด๋ฏธ ์ดˆ๊ธฐํ™” ์‹œ๋„๋จ)
if llm_interface is None or isinstance(llm_interface, MockComponent):
logger.warning("LLM ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ Mock ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.")
# ํ•„์š”์‹œ ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ์ดˆ๊ธฐํ™” ์‹œ๋„ ๊ฐ€๋Šฅ
if stt_client is None or isinstance(stt_client, MockComponent):
logger.warning("STT ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ Mock ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.")
# 2. ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”
logger.info("๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹œ๋„ (background)...")
retriever = init_retriever() # init_retriever๊ฐ€ base_retriever๋„ ์„ค์ •
# ์„ฑ๊ณต ์—ฌ๋ถ€ ํ™•์ธ - retriever๊ฐ€ None์ด ์•„๋‹ˆ๋ฉด ์„ฑ๊ณต์œผ๋กœ ๊ฐ„์ฃผ
if retriever is not None:
if not isinstance(retriever, MockComponent):
logger.info("๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต (background)")
else:
logger.warning("Mock ๊ฒ€์ƒ‰๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด ์ œํ•œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
# ์ค‘์š”: ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ Mock ๊ฐ์ฒด์ด๋”๋ผ๋„ app_ready_event.set()์„ ํ˜ธ์ถœํ•˜์—ฌ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
app_ready_event.set()
logger.info("app_ready_event๊ฐ€ True๋กœ ์„ค์ •๋จ.")
else:
logger.error("๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹คํŒจ (background)")
# ์ค‘์š” ์ˆ˜์ •: ์‹คํŒจ ์‹œ์—๋„ app_ready_event๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋กœ๋”ฉ ํŽ˜์ด์ง€์—์„œ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
# ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ฑ ์ž์ฒด๋Š” ์‹คํ–‰๋˜์–ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๊ฐ€ ๋จ
app_ready_event.set()
logger.warning("๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์‹คํŒจํ–ˆ์ง€๋งŒ app_ready_event๋ฅผ True๋กœ ์„ค์ •ํ•˜์—ฌ ์•ฑ์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.")
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()
# ์ค‘์š” ์ˆ˜์ •: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ์—๋„ app_ready_event๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋กœ๋”ฉ ํŽ˜์ด์ง€์—์„œ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
app_ready_event.set()
logger.warning("์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์ง€๋งŒ app_ready_event๋ฅผ True๋กœ ์„ค์ •ํ•˜์—ฌ ์•ฑ์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.")
finally:
end_init_time = time.time()
logger.info(f"๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ. ์†Œ์š” ์‹œ๊ฐ„: {end_init_time - start_init_time:.2f}์ดˆ")
logger.info(f"์ตœ์ข… ์•ฑ ์ค€๋น„ ์ƒํƒœ (app_ready_event.is_set()): {app_ready_event.is_set()}")
# ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์‹œ์ž‘
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_event=app_ready_event,
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:
# ๋ผ์šฐํŠธ ๋“ฑ๋ก ์‹คํŒจ๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ์ด๋ฏ€๋กœ Critical ๋ ˆ๋ฒจ ์‚ฌ์šฉ ๊ณ ๋ ค
logger.critical(f"๋ผ์šฐํŠธ ๋“ฑ๋ก ์ค‘ ์น˜๋ช…์  ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
# ์•ฑ ์‹คํ–‰์„ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ์ตœ์†Œํ•œ์˜ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋กœ์ง ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
# --- ์ •์  ํŒŒ์ผ ์„œ๋น™ ---
# STATIC_FOLDER ์„ค์ •์„ ์‚ฌ์šฉํ•˜์—ฌ static ํด๋” ๊ฒฝ๋กœ ์ง€์ •
@app.route('/static/<path:path>')
def send_static(path):
static_folder = app.config.get('STATIC_FOLDER', 'static') # ์„ค์ •๊ฐ’ ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
# logger.debug(f"Serving static file: {path} from {static_folder}") # ๋””๋ฒ„๊น… ์‹œ ์ฃผ์„ ํ•ด์ œ
return send_from_directory(static_folder, path)
# --- ์š”์ฒญ ์ฒ˜๋ฆฌ ํ›… ---
@app.after_request
def after_request_func(response):
"""๋ชจ๋“  ์‘๋‹ต์— ๋Œ€ํ•ด ํ›„์ฒ˜๋ฆฌ ์ˆ˜ํ–‰ (์˜ˆ: ๋ณด์•ˆ ํ—ค๋” ์ถ”๊ฐ€)"""
# ์˜ˆ์‹œ: response.headers['X-Content-Type-Options'] = 'nosniff'
return response
# ์•ฑ ์‹คํ–‰ (๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ)
if __name__ == '__main__':
logger.info("Flask ์•ฑ์„ ์ง์ ‘ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (๊ฐœ๋ฐœ์šฉ ์„œ๋ฒ„).")
# port ๋ฒˆํ˜ธ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
port = int(os.environ.get("PORT", 7860)) # ๊ธฐ๋ณธ ํฌํŠธ 7860 ์‚ฌ์šฉ
logger.info(f"์„œ๋ฒ„๋ฅผ http://0.0.0.0:{port} ์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
# debug=True๋Š” ๊ฐœ๋ฐœ ์ค‘์—๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๋ฐฐํฌ ์‹œ์—๋Š” False๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐ
# use_reloader=False ์ถ”๊ฐ€ํ•˜์—ฌ ์ž๋™ ๋ฆฌ๋กœ๋“œ ๋น„ํ™œ์„ฑํ™” (๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ๋ฌธ์ œ ๋ฐฉ์ง€)
app.run(debug=False, host='0.0.0.0', port=port, use_reloader=False)