RAG_AgenticServer / app /app_revised.py
jeongsoo's picture
init
64371be
"""
RAG ๊ฒ€์ƒ‰ ์ฑ—๋ด‡ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (์žฅ์น˜ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ํ†ตํ•ฉ)
"""
import os
import logging
import threading
from datetime import datetime, timedelta
from flask import Flask, send_from_directory, jsonify
from dotenv import load_dotenv
from functools import wraps
from flask_cors import CORS
# ๋กœ๊ฑฐ ์„ค์ •
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', '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
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ---
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
except ImportError as e:
logger.error(f"๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹คํŒจ: {e}. utils ๋ฐ retrieval ํŒจํ‚ค์ง€๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.")
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
# --- ๋กœ์ปฌ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ๋ ---
# Flask ์•ฑ ์ดˆ๊ธฐํ™”
app = Flask(__name__)
# CORS ์„ค์ • - ๋ชจ๋“  ๋„๋ฉ”์ธ์—์„œ์˜ ์š”์ฒญ ํ—ˆ์šฉ
CORS(app, supports_credentials=True)
# ์„ธ์…˜ ์„ค์ •
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'] = 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)
# --- ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ---
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):
from flask import request, session, redirect, url_for
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')}")
# API ์š”์ฒญ์ด๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ์˜ค๋Š” ๊ฒฝ์šฐ ์ธ์ฆ ๋ฌด์‹œ (์ž„์‹œ ์กฐ์น˜)
if request.path.startswith('/api/device/'):
logger.info(f"์žฅ์น˜ API ์š”์ฒญ: {request.path} - ์ธ์ฆ ์ œ์™ธ")
return f(*args, **kwargs)
# Flask ์„ธ์…˜์— 'logged_in' ํ‚ค๊ฐ€ ์žˆ๋Š”์ง€ ์ง์ ‘ ํ™•์ธ
if 'logged_in' not in session:
logger.warning(f"ํ”Œ๋ผ์Šคํฌ ์„ธ์…˜์— 'logged_in' ์—†์Œ. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜.")
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.errorhandler(404)
def not_found(e):
# ํด๋ผ์ด์–ธํŠธ๊ฐ€ JSON์„ ๊ธฐ๋Œ€ํ•˜๋Š” API ํ˜ธ์ถœ์ธ ๊ฒฝ์šฐ JSON ์‘๋‹ต
if request.path.startswith('/api/'):
return jsonify({"success": False, "error": "์š”์ฒญํ•œ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."}), 404
# ์ผ๋ฐ˜ ์›น ํŽ˜์ด์ง€ ์š”์ฒญ์ธ ๊ฒฝ์šฐ HTML ์‘๋‹ต
return "ํŽ˜์ด์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 404
@app.errorhandler(500)
def internal_error(e):
# ํด๋ผ์ด์–ธํŠธ๊ฐ€ JSON์„ ๊ธฐ๋Œ€ํ•˜๋Š” API ํ˜ธ์ถœ์ธ ๊ฒฝ์šฐ JSON ์‘๋‹ต
if request.path.startswith('/api/'):
return jsonify({"success": False, "error": "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."}), 500
# ์ผ๋ฐ˜ ์›น ํŽ˜์ด์ง€ ์š”์ฒญ์ธ ๊ฒฝ์šฐ HTML ์‘๋‹ต
return "์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", 500
# --- ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ ๋ ---
# --- ์ •์  ํŒŒ์ผ ์„œ๋น™ ---
@app.route('/static/<path:path>')
def send_static(path):
return send_from_directory('static', path)
# --- ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ---
def background_init():
"""๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์ˆ˜ํ–‰"""
global app_ready, retriever, base_retriever
# ์ฆ‰์‹œ ์•ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์ƒํƒœ๋กœ ์„ค์ •
app_ready = True
logger.info("์•ฑ์„ ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์ƒํƒœ๋กœ ์„ค์ • (app_ready=True)")
try:
from app.init_retriever import init_retriever
# ๊ธฐ๋ณธ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” (๋ณดํ—˜)
if base_retriever is None:
base_retriever = MockComponent()
if hasattr(base_retriever, 'documents'):
base_retriever.documents = []
# ์ž„์‹œ retriever ์„ค์ •
if retriever is None:
retriever = MockComponent()
if not hasattr(retriever, 'search'):
retriever.search = lambda query, **kwargs: []
# ์ž„๋ฒ ๋”ฉ ์บ์‹œ ํŒŒ์ผ ๊ฒฝ๋กœ
cache_path = os.path.join(app.config['INDEX_PATH'], "cached_embeddings.gz")
# ์บ์‹œ๋œ ์ž„๋ฒ ๋”ฉ ๋กœ๋“œ ์‹œ๋„
try:
from app.init_retriever import load_embeddings
cached_retriever = load_embeddings(cache_path)
if cached_retriever:
# ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ์‚ฌ์šฉ
base_retriever = cached_retriever
# ์žฌ์ˆœ์œ„ํ™” ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™”
retriever = ReRanker(
base_retriever=base_retriever,
rerank_fn=lambda query, results: results,
rerank_field="text"
)
logger.info("์บ์‹œ๋œ ์ž„๋ฒ ๋”ฉ์œผ๋กœ ๊ฒ€์ƒ‰๊ธฐ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ (๋น ๋ฅธ ์‹œ์ž‘)")
else:
# ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ „์ฒด ์ดˆ๊ธฐํ™” ์ง„ํ–‰
logger.info("์บ์‹œ๋œ ์ž„๋ฒ ๋”ฉ์ด ์—†์–ด ์ „์ฒด ์ดˆ๊ธฐํ™” ์‹œ์ž‘")
retriever = init_retriever(app, base_retriever, retriever, ReRanker)
logger.info("์ „์ฒด ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
except ImportError:
logger.warning("์ž„๋ฒ ๋”ฉ ์บ์‹œ ๋ชจ๋“ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์ดˆ๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.")
retriever = init_retriever(app, base_retriever, retriever, ReRanker)
logger.info("์•ฑ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ (๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ์ค€๋น„๋จ)")
except Exception as e:
logger.error(f"์•ฑ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์ค‘ ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
# ์ดˆ๊ธฐํ™” ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ ๊ฐ์ฒด ์ƒ์„ฑ
if base_retriever is None:
base_retriever = MockComponent()
if hasattr(base_retriever, 'documents'):
base_retriever.documents = []
if retriever is None:
retriever = MockComponent()
if not hasattr(retriever, 'search'):
retriever.search = lambda query, **kwargs: []
logger.warning("์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์ง€๋งŒ ์•ฑ์€ ๊ณ„์† ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.")
# --- ๋ผ์šฐํŠธ ๋“ฑ๋ก ---
def register_all_routes():
try:
# ๊ธฐ๋ณธ ๋ผ์šฐํŠธ ๋“ฑ๋ก
from app.app_routes import register_routes
register_routes(
app, login_required, llm_interface, retriever, stt_client,
DocumentProcessor, base_retriever, app_ready,
ADMIN_USERNAME, ADMIN_PASSWORD, DEVICE_SERVER_URL
)
# ์žฅ์น˜ ๊ด€๋ฆฌ ๋ผ์šฐํŠธ ๋“ฑ๋ก
from app.app_device_routes import register_device_routes
register_device_routes(app, login_required, DEVICE_SERVER_URL)
logger.info("๋ชจ๋“  ๋ผ์šฐํŠธ ๋“ฑ๋ก ์™„๋ฃŒ")
except ImportError as e:
logger.error(f"๋ผ์šฐํŠธ ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹คํŒจ: {e}")
except Exception as e:
logger.error(f"๋ผ์šฐํŠธ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
# --- ์•ฑ ์ดˆ๊ธฐํ™” ๋ฐ ์‹คํ–‰ ---
def initialize_app():
# ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ดˆ๊ธฐํ™” ์Šค๋ ˆ๋“œ ์‹œ์ž‘
init_thread = threading.Thread(target=background_init)
init_thread.daemon = True
init_thread.start()
# ๋ผ์šฐํŠธ ๋“ฑ๋ก
register_all_routes()
logger.info("์•ฑ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
# ์•ฑ ์ดˆ๊ธฐํ™” ์‹คํ–‰
initialize_app()
# --- ์•ฑ ์‹คํ–‰ (์ง์ ‘ ์‹คํ–‰ ์‹œ) ---
if __name__ == '__main__':
logger.info("Flask ์•ฑ์„ ์ง์ ‘ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (๊ฐœ๋ฐœ์šฉ ์„œ๋ฒ„).")
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)