Spaces:
No application file
No application file
fix
Browse files- app/app.py +176 -194
app/app.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
"""
|
2 |
-
RAG ๊ฒ์ ์ฑ๋ด ์น ์ ํ๋ฆฌ์ผ์ด์
(์ธ์
์ค์ ์์ ์ ์ฉ)
|
3 |
"""
|
4 |
|
5 |
import os
|
@@ -8,6 +8,7 @@ import logging
|
|
8 |
import tempfile
|
9 |
import threading
|
10 |
import datetime
|
|
|
11 |
from flask import Flask, request, jsonify, render_template, send_from_directory, session, redirect, url_for
|
12 |
from werkzeug.utils import secure_filename
|
13 |
from dotenv import load_dotenv
|
@@ -16,7 +17,7 @@ from functools import wraps
|
|
16 |
# ๋ก๊ฑฐ ์ค์
|
17 |
logging.basicConfig(
|
18 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
19 |
-
level=logging.DEBUG
|
20 |
)
|
21 |
logger = logging.getLogger(__name__)
|
22 |
|
@@ -26,17 +27,14 @@ load_dotenv()
|
|
26 |
# ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ํ์ธ ๋ฐ ๋ก๊น
|
27 |
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
|
28 |
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')
|
29 |
-
# ๊ธฐ๋ณธ ์ฅ์น ์๋ฒ URL์ ํ์ฌ ํธ์คํธ์ 5050 ํฌํธ๋ก ์ค์ (๋์ผ ํธ์คํธ์์ ์คํ ๊ฐ์ )
|
30 |
-
# localhost ๋์ window.location.hostname์ ์ฌ์ฉํ์ฌ ํ์ฌ ํธ์คํธ์ ๋ง๊ฒ ์กฐ์
|
31 |
DEVICE_SERVER_URL = os.getenv('DEVICE_SERVER_URL', '')
|
32 |
|
33 |
logger.info(f"==== ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ====")
|
34 |
logger.info(f"ADMIN_USERNAME ์ค์ ์ฌ๋ถ: {ADMIN_USERNAME is not None}")
|
35 |
-
# ๋น๋ฐ๋ฒํธ๋ ๋ก๋ ์ฌ๋ถ๋ง ๊ธฐ๋ก (๋ณด์)
|
36 |
logger.info(f"ADMIN_PASSWORD ์ค์ ์ฌ๋ถ: {ADMIN_PASSWORD is not None}")
|
37 |
logger.info(f"DEVICE_SERVER_URL: {DEVICE_SERVER_URL or '์ค์ ๋์ง ์์ (ํ๋ก ํธ์๋์์ ์๋ ์ค์ )'}")
|
38 |
|
39 |
-
# ํ๊ฒฝ ๋ณ์๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ ์ค์
|
40 |
if not ADMIN_USERNAME:
|
41 |
ADMIN_USERNAME = 'admin'
|
42 |
logger.warning("ADMIN_USERNAME ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'admin'์ผ๋ก ์ค์ ํฉ๋๋ค.")
|
@@ -44,54 +42,61 @@ if not ADMIN_USERNAME:
|
|
44 |
if not ADMIN_PASSWORD:
|
45 |
ADMIN_PASSWORD = 'rag12345'
|
46 |
logger.warning("ADMIN_PASSWORD ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'rag12345'๋ก ์ค์ ํฉ๋๋ค.")
|
47 |
-
class MockComponent: pass
|
48 |
|
49 |
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ---
|
50 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
try:
|
52 |
from utils.vito_stt import VitoSTT
|
53 |
from utils.llm_interface import LLMInterface
|
54 |
from utils.document_processor import DocumentProcessor
|
55 |
from retrieval.vector_retriever import VectorRetriever
|
56 |
from retrieval.reranker import ReRanker
|
57 |
-
# ๋ผ์ฐํธ ์ ์ ํ์ผ ์ํฌํธ
|
58 |
from app.app_routes import register_routes
|
59 |
from app.app_device_routes import register_device_routes
|
|
|
60 |
except ImportError as e:
|
61 |
-
logger.error(f"๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ์คํจ: {e}.
|
62 |
-
# ๊ฐ๋ฐ/ํ
์คํธ๋ฅผ ์ํด ์์ ํด๋์ค ์ ์ (์ค์ ์ฌ์ฉ ์ ์ ๊ฑฐ)
|
63 |
-
|
64 |
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
|
|
|
|
|
|
|
|
|
|
|
65 |
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ๋ ---
|
66 |
|
67 |
|
68 |
# Flask ์ฑ ์ด๊ธฐํ
|
69 |
app = Flask(__name__)
|
70 |
|
71 |
-
# ์ธ์
์ค์
|
72 |
-
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345')
|
73 |
-
|
74 |
-
# --- ์ธ์
์ฟ ํค ์ค์ ์์ (ํ๊น
ํ์ด์ค ํ๊ฒฝ ๊ณ ๋ ค) ---
|
75 |
-
# ํ๊น
ํ์ด์ค ์คํ์ด์ค๋ ์ผ๋ฐ์ ์ผ๋ก HTTPS๋ก ์๋น์ค๋๋ฏ๋ก Secure=True ์ค์
|
76 |
app.config['SESSION_COOKIE_SECURE'] = True
|
77 |
-
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
app.config['SESSION_COOKIE_SAMESITE'] = 'None' # <--- ์ด๋ ๊ฒ ๋ณ๊ฒฝํฉ๋๋ค.
|
83 |
-
app.config['SESSION_COOKIE_DOMAIN'] = None # ํน์ ๋๋ฉ์ธ ์ ํ ์์
|
84 |
-
app.config['SESSION_COOKIE_PATH'] = '/' # ์ฑ ์ ์ฒด ๊ฒฝ๋ก์ ์ฟ ํค ์ ์ฉ
|
85 |
-
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=1) # ์ธ์
์ ํจ ์๊ฐ ์ฆ๊ฐ
|
86 |
-
# --- ์ธ์
์ฟ ํค ์ค์ ๋ ---
|
87 |
|
88 |
# ์ต๋ ํ์ผ ํฌ๊ธฐ ์ค์ (10MB)
|
89 |
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
|
90 |
# ์ ํ๋ฆฌ์ผ์ด์
ํ์ผ ๊ธฐ์ค ์๋ ๊ฒฝ๋ก ์ค์
|
91 |
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
92 |
app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
|
93 |
-
app
|
94 |
-
app.config['
|
|
|
95 |
|
96 |
# ํ์ํ ํด๋ ์์ฑ
|
97 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
@@ -103,35 +108,38 @@ ALLOWED_AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'm4a'}
|
|
103 |
ALLOWED_DOC_EXTENSIONS = {'txt', 'md', 'pdf', 'docx', 'csv'}
|
104 |
|
105 |
# --- ์ ์ญ ๊ฐ์ฒด ์ด๊ธฐํ ---
|
106 |
-
|
107 |
-
|
108 |
-
stt_client = VitoSTT()
|
109 |
-
except NameError:
|
110 |
-
logger.warning("LLM ๋๋ STT ์ธํฐํ์ด์ค ์ด๊ธฐํ ์คํจ. Mock ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํฉ๋๋ค.")
|
111 |
-
llm_interface = MockComponent()
|
112 |
-
stt_client = MockComponent()
|
113 |
-
|
114 |
base_retriever = None
|
115 |
retriever = None
|
116 |
-
app_ready
|
117 |
-
#
|
118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
|
120 |
-
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ
|
121 |
def login_required(f):
|
122 |
@wraps(f)
|
123 |
def decorated_function(*args, **kwargs):
|
124 |
logger.info(f"----------- ์ธ์ฆ ํ์ ํ์ด์ง ์ ๊ทผ ์๋: {request.path} -----------")
|
125 |
-
logger.
|
126 |
logger.info(f"ํ์ฌ ์ธ์
์ํ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
|
127 |
-
|
128 |
-
logger.info(f"์์ฒญ์ ์ธ์
์ฟ ํค ๊ฐ: {request.cookies.get('session', 'None')}")
|
129 |
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
# ์๋ ์ฟ ํค ํ์ธ ๋ก์ง ์ ๊ฑฐ๋จ
|
134 |
-
return redirect(url_for('login', next=request.url)) # ๋ก๊ทธ์ธ ํ ์๋ ํ์ด์ง๋ก ๋์๊ฐ๋๋ก next ํ๋ผ๋ฏธํฐ ์ถ๊ฐ
|
135 |
|
136 |
logger.info(f"์ธ์ฆ ์ฑ๊ณต: {session.get('username', 'unknown')} ์ฌ์ฉ์๊ฐ {request.path} ์ ๊ทผ")
|
137 |
return f(*args, **kwargs)
|
@@ -139,7 +147,7 @@ def login_required(f):
|
|
139 |
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ ๋ ---
|
140 |
|
141 |
|
142 |
-
# --- ํฌํผ ํจ์ ---
|
143 |
def allowed_audio_file(filename):
|
144 |
"""ํ์ผ์ด ํ์ฉ๋ ์ค๋์ค ํ์ฅ์๋ฅผ ๊ฐ์ง๋์ง ํ์ธ"""
|
145 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
|
@@ -150,100 +158,82 @@ def allowed_doc_file(filename):
|
|
150 |
# --- ํฌํผ ํจ์ ๋ ---
|
151 |
|
152 |
|
153 |
-
# init_retriever ํจ์ ๋ด๋ถ์ ๋ก๊น
์ถ๊ฐ ์์
|
154 |
# --- ๊ฒ์๊ธฐ ์ด๊ธฐํ ๊ด๋ จ ํจ์ ---
|
155 |
def init_retriever():
|
156 |
"""๊ฒ์๊ธฐ ๊ฐ์ฒด ์ด๊ธฐํ ๋๋ ๋ก๋"""
|
157 |
global base_retriever, retriever
|
158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
index_path = app.config['INDEX_PATH']
|
160 |
-
data_path = app.config['DATA_FOLDER']
|
161 |
logger.info("--- init_retriever ์์ ---")
|
162 |
|
163 |
# 1. ๊ธฐ๋ณธ ๊ฒ์๊ธฐ ๋ก๋ ๋๋ ์ด๊ธฐํ
|
164 |
-
|
165 |
-
|
166 |
-
if os.path.exists(os.path.join(index_path, "documents.json")):
|
167 |
-
try:
|
168 |
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์๋: {index_path}")
|
169 |
base_retriever = VectorRetriever.load(index_path)
|
170 |
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์ฑ๊ณต. ๋ฌธ์ {len(getattr(base_retriever, 'documents', []))}๊ฐ")
|
171 |
-
|
172 |
-
logger.
|
173 |
-
logger.info("์ VectorRetriever ์ด๊ธฐํ ์๋...")
|
174 |
-
try:
|
175 |
-
base_retriever = VectorRetriever()
|
176 |
-
logger.info("์ VectorRetriever ์ด๊ธฐํ ์ฑ๊ณต.")
|
177 |
-
except Exception as e_init:
|
178 |
-
logger.error(f"์ VectorRetriever ์ด๊ธฐํ ์คํจ: {e_init}", exc_info=True)
|
179 |
-
base_retriever = None
|
180 |
-
else:
|
181 |
-
logger.info("์ธ๋ฑ์ค ํ์ผ ์์. ์ VectorRetriever ์ด๊ธฐํ ์๋...")
|
182 |
-
try:
|
183 |
base_retriever = VectorRetriever()
|
184 |
logger.info("์ VectorRetriever ์ด๊ธฐํ ์ฑ๊ณต.")
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
logger.error("base_retriever ์ด๊ธฐํ/๋ก๋์ ์คํจํ์ฌ init_retriever ์ค๋จ.")
|
191 |
-
return None
|
192 |
|
193 |
# 2. ๋ฐ์ดํฐ ํด๋ ๋ฌธ์ ๋ก๋ (๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ ๋)
|
194 |
-
needs_loading =
|
195 |
if needs_loading and os.path.exists(data_path):
|
196 |
logger.info(f"๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ด {data_path}์์ ๋ฌธ์ ๋ก๋ ์๋...")
|
197 |
try:
|
198 |
-
#
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
except Exception as e_load_add:
|
220 |
-
# load_documents_from_directory ์์ฒด์์ ์ค๋ฅ๊ฐ ๋ ์๋ ์์ (๊ถํ ๋ฑ)
|
221 |
logger.error(f"DATA_FOLDER ๋ฌธ์ ๋ก๋/์ถ๊ฐ ์ค ์ค๋ฅ: {e_load_add}", exc_info=True)
|
222 |
|
223 |
# 3. ์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
224 |
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋...")
|
225 |
try:
|
226 |
-
#
|
227 |
-
# custom_rerank_fn ํจ์๋ฅผ ReRanker ์ด๊ธฐํ ์ ์ ์ ์
|
228 |
def custom_rerank_fn(query, results):
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
text = result["text"].lower()
|
233 |
-
term_freq = sum(1 for term in query_terms if term in text)
|
234 |
-
normalized_score = term_freq / (len(text.split()) + 1) * 10
|
235 |
-
result["rerank_score"] = result.get("score", 0) * 0.7 + normalized_score * 0.3
|
236 |
-
elif isinstance(result, dict):
|
237 |
-
result["rerank_score"] = result.get("score", 0)
|
238 |
-
results.sort(key=lambda x: x.get("rerank_score", 0) if isinstance(x, dict) else 0, reverse=True)
|
239 |
return results
|
240 |
-
# ================== ์์ ๋ ๋ถ๋ถ 2 ๋ ====================
|
241 |
|
242 |
# ReRanker ํด๋์ค ์ฌ์ฉ
|
243 |
retriever = ReRanker(
|
244 |
base_retriever=base_retriever,
|
245 |
-
rerank_fn=custom_rerank_fn,
|
246 |
-
rerank_field="text"
|
247 |
)
|
248 |
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋ฃ.")
|
249 |
except Exception as e_rerank:
|
@@ -254,109 +244,74 @@ def init_retriever():
|
|
254 |
logger.info("--- init_retriever ์ข
๋ฃ ---")
|
255 |
return retriever
|
256 |
|
|
|
257 |
def background_init():
|
258 |
-
"""๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์ํ"""
|
259 |
-
global
|
260 |
|
261 |
-
|
262 |
-
|
263 |
-
logger.info("๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์์...")
|
264 |
|
265 |
-
|
|
|
266 |
if llm_interface is None or isinstance(llm_interface, MockComponent):
|
267 |
-
|
268 |
-
|
269 |
-
logger.info("LLM ์ธํฐํ์ด์ค ์ด๊ธฐํ ์๋ฃ.")
|
270 |
-
else:
|
271 |
-
logger.warning("LLMInterface ํด๋์ค ์์. Mock ์ฌ์ฉ.")
|
272 |
-
llm_interface = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์ฅ
|
273 |
if stt_client is None or isinstance(stt_client, MockComponent):
|
274 |
-
|
275 |
-
stt_client = VitoSTT()
|
276 |
-
logger.info("STT ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์๋ฃ.")
|
277 |
-
else:
|
278 |
-
logger.warning("VitoSTT ํด๋์ค ์์. Mock ์ฌ์ฉ.")
|
279 |
-
stt_client = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์ฅ
|
280 |
-
|
281 |
|
282 |
# 2. ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
# retriever๊ฐ base_retriever๋ฅผ ํฌํจํ์ง ์๋ ๊ฒฝ์ฐ ๋๋ ReRanker๊ฐ ์๋ ๊ฒฝ์ฐ
|
292 |
-
# init_retriever์์ base_retriever๋ฅผ ์ง์ ์ค์ ํ๋๋ก ํ๊ฑฐ๋, ์ฌ๊ธฐ์ ๋ณ๋ ๋ก์ง ํ์
|
293 |
-
# ์์: base_retriever = VectorRetriever.load(...) ๋๋ VectorRetriever()
|
294 |
-
logger.warning("init_retriever ํ base_retriever๊ฐ ์ค์ ๋์ง ์์. ํ์ธ ํ์.")
|
295 |
-
# ์์๋ก retriever ์์ฒด๋ฅผ base_retriever๋ก ์ค์ (๋์ผ ๊ฐ์ฒด์ผ ๊ฒฝ์ฐ)
|
296 |
-
if isinstance(retriever, VectorRetriever):
|
297 |
-
base_retriever = retriever
|
298 |
-
|
299 |
-
# ์ฑ๊ณต์ ์ผ๋ก ์ด๊ธฐํ ๋์๋์ง ํ์ธ (None์ด ์๋์ง)
|
300 |
-
if retriever is not None and base_retriever is not None:
|
301 |
-
logger.info("๊ฒ์๊ธฐ (Retriever, Base Retriever) ์ด๊ธฐํ ์ฑ๊ณต")
|
302 |
-
temp_app_ready = True # ์ด๊ธฐํ ์ฑ๊ณต ์์๋ง True ์ค์
|
303 |
-
else:
|
304 |
-
logger.error("๊ฒ์๊ธฐ ์ด๊ธฐํ ํ์๋ retriever ๋๋ base_retriever๊ฐ None์
๋๋ค.")
|
305 |
-
# ์คํจ ์ Mock ๊ฐ์ฒด ํ ๋น (์ต์ํ์ ๋์ ๋ณด์ฅ)
|
306 |
-
if base_retriever is None: base_retriever = MockComponent()
|
307 |
-
if retriever is None: retriever = MockComponent()
|
308 |
-
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
|
309 |
-
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
|
310 |
-
# temp_app_ready = False ๋๋ True (์ ์ฑ
์ ๋ฐ๋ผ ๊ฒฐ์ )
|
311 |
-
temp_app_ready = True # ์ผ๋จ ์ฑ์ ์คํ๋๋๋ก ์ค์
|
312 |
-
|
313 |
else:
|
314 |
-
logger.
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
|
319 |
-
temp_app_ready = True # Mock์ด๋ผ๋ ์ค๋น๋ ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ
|
320 |
-
|
321 |
-
logger.info(f"๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์๋ฃ. ์ต์ข
์ํ: {'Ready' if temp_app_ready else 'Not Ready (Error during init)'}")
|
322 |
|
323 |
except Exception as e:
|
324 |
-
logger.error(f"
|
325 |
-
# ์ค๋ฅ ๋ฐ์ ์์๋ Mock ๊ฐ์ฒด ํ ๋น
|
326 |
if base_retriever is None: base_retriever = MockComponent()
|
327 |
if retriever is None: retriever = MockComponent()
|
328 |
-
|
329 |
-
|
330 |
-
temp_app_ready = True # ์ค๋ฅ ๋ฐ์ํด๋ ์ฑ์ ์๋ตํ๋๋ก ์ค์ (์ ์ฑ
์ ๋ฐ๋ผ False ๊ฐ๋ฅ)
|
331 |
-
logger.warning("์ด๊ธฐํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง๋ง Mock ๊ฐ์ฒด๋ก ๋์ฒด ํ ์ฑ ์ฌ์ฉ ๊ฐ๋ฅ ์ํ๋ก ์ค์ .")
|
332 |
|
333 |
finally:
|
334 |
-
|
335 |
-
|
|
|
336 |
|
337 |
-
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋ ์์
|
338 |
init_thread = threading.Thread(target=background_init)
|
339 |
init_thread.daemon = True
|
340 |
init_thread.start()
|
341 |
|
342 |
-
# ๋ผ์ฐํธ ๋ฑ๋ก
|
343 |
try:
|
344 |
# ๊ธฐ๋ณธ RAG ์ฑ๋ด ๋ผ์ฐํธ ๋ฑ๋ก
|
|
|
345 |
register_routes(
|
346 |
-
app=app,
|
347 |
-
login_required=login_required,
|
348 |
-
llm_interface=llm_interface,
|
349 |
-
retriever=retriever,
|
350 |
-
stt_client=stt_client,
|
351 |
DocumentProcessor=DocumentProcessor,
|
352 |
-
base_retriever=base_retriever,
|
353 |
-
|
354 |
ADMIN_USERNAME=ADMIN_USERNAME,
|
355 |
ADMIN_PASSWORD=ADMIN_PASSWORD,
|
356 |
DEVICE_SERVER_URL=DEVICE_SERVER_URL
|
357 |
)
|
358 |
logger.info("๊ธฐ๋ณธ ์ฑ๋ด ๋ผ์ฐํธ ๋ฑ๋ก ์๋ฃ")
|
359 |
-
|
360 |
# ์ฅ์น ๊ด๋ฆฌ ๋ผ์ฐํธ ๋ฑ๋ก
|
361 |
register_device_routes(
|
362 |
app=app,
|
@@ -365,26 +320,53 @@ try:
|
|
365 |
)
|
366 |
logger.info("์ฅ์น ๊ด๋ฆฌ ๋ผ์ฐํธ ๋ฑ๋ก ์๋ฃ")
|
367 |
except Exception as e:
|
368 |
-
|
369 |
-
|
|
|
370 |
|
371 |
# --- ์ ์ ํ์ผ ์๋น ---
|
|
|
372 |
@app.route('/static/<path:path>')
|
373 |
def send_static(path):
|
374 |
-
|
|
|
|
|
375 |
|
376 |
|
377 |
# --- ์์ฒญ ์ฒ๋ฆฌ ํ
---
|
378 |
@app.after_request
|
379 |
def after_request_func(response):
|
380 |
-
"""๋ชจ๋ ์๋ต์ ๋ํด ํ์ฒ๋ฆฌ ์ํ"""
|
|
|
381 |
return response
|
382 |
|
383 |
# ์ฑ ์คํ (๋ก์ปฌ ํ
์คํธ์ฉ)
|
384 |
if __name__ == '__main__':
|
385 |
logger.info("Flask ์ฑ์ ์ง์ ์คํํฉ๋๋ค (๊ฐ๋ฐ์ฉ ์๋ฒ).")
|
386 |
-
# ๋๋ฒ๊ทธ ๋ชจ๋๋ ์ค์ ๋ฐฐํฌ ์ False๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
|
387 |
# port ๋ฒํธ๋ ํ๊ฒฝ ๋ณ์ ๋๋ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
388 |
-
port = int(os.environ.get("PORT", 7860))
|
389 |
logger.info(f"์๋ฒ๋ฅผ http://0.0.0.0:{port} ์์ ์์ํฉ๋๋ค.")
|
390 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
+
RAG ๊ฒ์ ์ฑ๋ด ์น ์ ํ๋ฆฌ์ผ์ด์
(์ธ์
์ค์ ์์ ์ ์ฉ ๋ฐ TypeError ํด๊ฒฐ)
|
3 |
"""
|
4 |
|
5 |
import os
|
|
|
8 |
import tempfile
|
9 |
import threading
|
10 |
import datetime
|
11 |
+
import time # ์ถ๊ฐ
|
12 |
from flask import Flask, request, jsonify, render_template, send_from_directory, session, redirect, url_for
|
13 |
from werkzeug.utils import secure_filename
|
14 |
from dotenv import load_dotenv
|
|
|
17 |
# ๋ก๊ฑฐ ์ค์
|
18 |
logging.basicConfig(
|
19 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
20 |
+
level=logging.DEBUG
|
21 |
)
|
22 |
logger = logging.getLogger(__name__)
|
23 |
|
|
|
27 |
# ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ํ์ธ ๋ฐ ๋ก๊น
|
28 |
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
|
29 |
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')
|
|
|
|
|
30 |
DEVICE_SERVER_URL = os.getenv('DEVICE_SERVER_URL', '')
|
31 |
|
32 |
logger.info(f"==== ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ====")
|
33 |
logger.info(f"ADMIN_USERNAME ์ค์ ์ฌ๋ถ: {ADMIN_USERNAME is not None}")
|
|
|
34 |
logger.info(f"ADMIN_PASSWORD ์ค์ ์ฌ๋ถ: {ADMIN_PASSWORD is not None}")
|
35 |
logger.info(f"DEVICE_SERVER_URL: {DEVICE_SERVER_URL or '์ค์ ๋์ง ์์ (ํ๋ก ํธ์๋์์ ์๋ ์ค์ )'}")
|
36 |
|
37 |
+
# ํ๊ฒฝ ๋ณ์๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ ์ค์
|
38 |
if not ADMIN_USERNAME:
|
39 |
ADMIN_USERNAME = 'admin'
|
40 |
logger.warning("ADMIN_USERNAME ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'admin'์ผ๋ก ์ค์ ํฉ๋๋ค.")
|
|
|
42 |
if not ADMIN_PASSWORD:
|
43 |
ADMIN_PASSWORD = 'rag12345'
|
44 |
logger.warning("ADMIN_PASSWORD ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'rag12345'๋ก ์ค์ ํฉ๋๋ค.")
|
|
|
45 |
|
46 |
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ---
|
47 |
+
# MockComponent ์ ์ (์ํฌํธ ์คํจ ์ ๋์ฒด)
|
48 |
+
class MockComponent:
|
49 |
+
def __getattr__(self, name):
|
50 |
+
# Mock ๊ฐ์ฒด์ ์ด๋ค ์์ฑ์ด๋ ๋ฉ์๋ ํธ์ถ ์ ๊ฒฝ๊ณ ๋ก๊ทธ ์ถ๋ ฅ ๋ฐ ๊ธฐ๋ณธ๊ฐ ๋ฐํ
|
51 |
+
logger.warning(f"MockComponent์์ '{name}' ์ ๊ทผ ์๋๋จ (์ค์ ๋ชจ๋ ๋ก๋ ์คํจ)")
|
52 |
+
# ๋ฉ์๋ ํธ์ถ ์์๋ ์๋ฌด๊ฒ๋ ์ ํ๋ ํจ์ ๋ฐํ
|
53 |
+
if name in ['search', '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']:
|
54 |
+
return lambda *args, **kwargs: logger.warning(f"Mocked method '{name}' called") or (None if name != 'search' else []) # search๋ ๋น ๋ฆฌ์คํธ ๋ฐํ
|
55 |
+
# ์์ฑ ์ ๊ทผ ์์๋ None ๋ฐํ
|
56 |
+
return None
|
57 |
+
|
58 |
try:
|
59 |
from utils.vito_stt import VitoSTT
|
60 |
from utils.llm_interface import LLMInterface
|
61 |
from utils.document_processor import DocumentProcessor
|
62 |
from retrieval.vector_retriever import VectorRetriever
|
63 |
from retrieval.reranker import ReRanker
|
|
|
64 |
from app.app_routes import register_routes
|
65 |
from app.app_device_routes import register_device_routes
|
66 |
+
MODULE_LOAD_SUCCESS = True
|
67 |
except ImportError as e:
|
68 |
+
logger.error(f"๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ์คํจ: {e}. Mock ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํฉ๋๋ค.")
|
|
|
|
|
69 |
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
|
70 |
+
# register_routes, register_device_routes๋ ์ํฌํธ ์คํจ ์ ์ ์๋์ง ์์ผ๋ฏ๋ก ์๋์์ ์ฒ๋ฆฌ
|
71 |
+
MODULE_LOAD_SUCCESS = False
|
72 |
+
# ์์๋ก ๋น ํจ์ ์ ์ (์ฑ ์คํ์ ๋๋๋ก)
|
73 |
+
def register_routes(*args, **kwargs): logger.error("register_routes ์ํฌํธ ์คํจ")
|
74 |
+
def register_device_routes(*args, **kwargs): logger.error("register_device_routes ์ํฌํธ ์คํจ")
|
75 |
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ๋ ---
|
76 |
|
77 |
|
78 |
# Flask ์ฑ ์ด๊ธฐํ
|
79 |
app = Flask(__name__)
|
80 |
|
81 |
+
# ์ธ์
์ค์
|
82 |
+
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345')
|
|
|
|
|
|
|
83 |
app.config['SESSION_COOKIE_SECURE'] = True
|
84 |
+
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
85 |
+
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
|
86 |
+
app.config['SESSION_COOKIE_DOMAIN'] = None
|
87 |
+
app.config['SESSION_COOKIE_PATH'] = '/'
|
88 |
+
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=1)
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
# ์ต๋ ํ์ผ ํฌ๊ธฐ ์ค์ (10MB)
|
91 |
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
|
92 |
# ์ ํ๋ฆฌ์ผ์ด์
ํ์ผ ๊ธฐ์ค ์๋ ๊ฒฝ๋ก ์ค์
|
93 |
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
|
94 |
+
# static ํด๋ ๊ฒฝ๋ก ์์ (app ํด๋ ๋ด๋ถ์ static)
|
95 |
+
app.config['STATIC_FOLDER'] = os.path.join(APP_ROOT, 'static')
|
96 |
app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
|
97 |
+
# data ๋ฐ index ๊ฒฝ๋ก๋ app ํด๋ ์ธ๋ถ๋ก ์ค์ (ํ๋ก์ ํธ ๋ฃจํธ ๊ธฐ์ค)
|
98 |
+
app.config['DATA_FOLDER'] = os.path.join(os.path.dirname(APP_ROOT), 'data')
|
99 |
+
app.config['INDEX_PATH'] = os.path.join(os.path.dirname(APP_ROOT), 'data', 'index')
|
100 |
|
101 |
# ํ์ํ ํด๋ ์์ฑ
|
102 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
|
108 |
ALLOWED_DOC_EXTENSIONS = {'txt', 'md', 'pdf', 'docx', 'csv'}
|
109 |
|
110 |
# --- ์ ์ญ ๊ฐ์ฒด ์ด๊ธฐํ ---
|
111 |
+
llm_interface = None
|
112 |
+
stt_client = None
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
base_retriever = None
|
114 |
retriever = None
|
115 |
+
# app_ready ํ๋๊ทธ ๋์ threading.Event ์ฌ์ฉ
|
116 |
+
app_ready_event = threading.Event() # ์ด๊ธฐ ์ํ: False (set() ํธ์ถ ์ ๊น์ง)
|
117 |
|
118 |
+
# --- ์ ์ญ ๊ฐ์ฒด ์ด๊ธฐํ (try-except๋ก ๊ฐ์ธ๊ธฐ) ---
|
119 |
+
try:
|
120 |
+
if MODULE_LOAD_SUCCESS: # ๋ชจ๋ ๋ก๋ ์ฑ๊ณต ์์๋ง ์ค์ ์ด๊ธฐํ ์๋
|
121 |
+
llm_interface = LLMInterface(default_llm="openai")
|
122 |
+
stt_client = VitoSTT()
|
123 |
+
else: # ์คํจ ์ Mock ๊ฐ์ฒด ์ฌ์ฉ
|
124 |
+
llm_interface = LLMInterface() # MockComponent
|
125 |
+
stt_client = VitoSTT() # MockComponent
|
126 |
+
except Exception as e:
|
127 |
+
logger.error(f"LLM ๋๋ STT ์ธํฐํ์ด์ค ์ด๊ธฐํ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
128 |
+
llm_interface = MockComponent() # ์ค๋ฅ ์ Mock ๊ฐ์ฒด ํ ๋น
|
129 |
+
stt_client = MockComponent() # ์ค๋ฅ ์ Mock ๊ฐ์ฒด ํ ๋น
|
130 |
|
131 |
+
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ ---
|
132 |
def login_required(f):
|
133 |
@wraps(f)
|
134 |
def decorated_function(*args, **kwargs):
|
135 |
logger.info(f"----------- ์ธ์ฆ ํ์ ํ์ด์ง ์ ๊ทผ ์๋: {request.path} -----------")
|
136 |
+
logger.debug(f"ํ์ฌ ํ๋ผ์คํฌ ์ธ์
๊ฐ์ฒด: {session}") # DEBUG ๋ ๋ฒจ๋ก ๋ณ๊ฒฝ
|
137 |
logger.info(f"ํ์ฌ ์ธ์
์ํ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
|
138 |
+
logger.debug(f"์์ฒญ์ ์ธ์
์ฟ ํค ๊ฐ: {request.cookies.get('session', 'None')}") # DEBUG ๋ ๋ฒจ๋ก ๋ณ๊ฒฝ
|
|
|
139 |
|
140 |
+
if not session.get('logged_in'): # .get() ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์์
|
141 |
+
logger.warning(f"์ธ์
์ 'logged_in' ์์ ๋๋ False. ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
.")
|
142 |
+
return redirect(url_for('login', next=request.url))
|
|
|
|
|
143 |
|
144 |
logger.info(f"์ธ์ฆ ์ฑ๊ณต: {session.get('username', 'unknown')} ์ฌ์ฉ์๊ฐ {request.path} ์ ๊ทผ")
|
145 |
return f(*args, **kwargs)
|
|
|
147 |
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ ๋ ---
|
148 |
|
149 |
|
150 |
+
# --- ํฌํผ ํจ์ (app_routes.py์๋ ์์ง๋ง ์ฌ๊ธฐ์๋ ํ์ํ ์ ์์) ---
|
151 |
def allowed_audio_file(filename):
|
152 |
"""ํ์ผ์ด ํ์ฉ๋ ์ค๋์ค ํ์ฅ์๋ฅผ ๊ฐ์ง๋์ง ํ์ธ"""
|
153 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
|
|
|
158 |
# --- ํฌํผ ํจ์ ๋ ---
|
159 |
|
160 |
|
|
|
161 |
# --- ๊ฒ์๊ธฐ ์ด๊ธฐํ ๊ด๋ จ ํจ์ ---
|
162 |
def init_retriever():
|
163 |
"""๊ฒ์๊ธฐ ๊ฐ์ฒด ์ด๊ธฐํ ๋๋ ๋ก๋"""
|
164 |
global base_retriever, retriever
|
165 |
|
166 |
+
# ๋ชจ๋ ๋ก๋ ์คํจ ์ Mock ๊ฐ์ฒด ๋ฐํ
|
167 |
+
if not MODULE_LOAD_SUCCESS:
|
168 |
+
logger.warning("ํ์ ๋ชจ๋ ๋ก๋ ์คํจ๋ก Mock ๊ฒ์๊ธฐ ๋ฐํ")
|
169 |
+
base_retriever = VectorRetriever() # MockComponent
|
170 |
+
retriever = ReRanker() # MockComponent
|
171 |
+
return retriever
|
172 |
+
|
173 |
index_path = app.config['INDEX_PATH']
|
174 |
+
data_path = app.config['DATA_FOLDER']
|
175 |
logger.info("--- init_retriever ์์ ---")
|
176 |
|
177 |
# 1. ๊ธฐ๋ณธ ๊ฒ์๊ธฐ ๋ก๋ ๋๋ ์ด๊ธฐํ
|
178 |
+
try:
|
179 |
+
if os.path.exists(os.path.join(index_path, "documents.json")): # ์ ์ฅ ๋ฐฉ์์ ๋ฐ๋ผ ํ์ธ ํ์ผ ๋ณ๊ฒฝ ํ์
|
|
|
|
|
180 |
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์๋: {index_path}")
|
181 |
base_retriever = VectorRetriever.load(index_path)
|
182 |
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์ฑ๊ณต. ๋ฌธ์ {len(getattr(base_retriever, 'documents', []))}๊ฐ")
|
183 |
+
else:
|
184 |
+
logger.info("์ธ๋ฑ์ค ํ์ผ ์์. ์ VectorRetriever ์ด๊ธฐํ ์๋...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
base_retriever = VectorRetriever()
|
186 |
logger.info("์ VectorRetriever ์ด๊ธฐํ ์ฑ๊ณต.")
|
187 |
+
except Exception as e:
|
188 |
+
logger.error(f"๊ธฐ๋ณธ ๊ฒ์๊ธฐ ์ด๊ธฐํ/๋ก๋ ์คํจ: {e}", exc_info=True)
|
189 |
+
base_retriever = MockComponent() # ์คํจ ์ Mock ์ฌ์ฉ
|
190 |
+
retriever = MockComponent()
|
191 |
+
return None # ์ด๊ธฐํ ์คํจ
|
|
|
|
|
192 |
|
193 |
# 2. ๋ฐ์ดํฐ ํด๋ ๋ฌธ์ ๋ก๋ (๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ ๋)
|
194 |
+
needs_loading = not hasattr(base_retriever, 'documents') or not getattr(base_retriever, 'documents', [])
|
195 |
if needs_loading and os.path.exists(data_path):
|
196 |
logger.info(f"๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ด {data_path}์์ ๋ฌธ์ ๋ก๋ ์๋...")
|
197 |
try:
|
198 |
+
# DocumentProcessor.load_documents_from_directory ํธ์ถ ํ์ธ
|
199 |
+
if hasattr(DocumentProcessor, 'load_documents_from_directory'):
|
200 |
+
docs = DocumentProcessor.load_documents_from_directory(
|
201 |
+
directory=data_path,
|
202 |
+
extensions=[".txt", ".md", ".csv"],
|
203 |
+
recursive=True
|
204 |
+
)
|
205 |
+
logger.info(f"{len(docs)}๊ฐ ๋ฌธ์ ๋ก๋ ์ฑ๊ณต.")
|
206 |
+
if docs and hasattr(base_retriever, 'add_documents'):
|
207 |
+
logger.info("๊ฒ์๊ธฐ์ ๋ฌธ์ ์ถ๊ฐ ์๋...")
|
208 |
+
base_retriever.add_documents(docs)
|
209 |
+
logger.info("๋ฌธ์ ์ถ๊ฐ ์๋ฃ.")
|
210 |
+
if hasattr(base_retriever, 'save'):
|
211 |
+
logger.info(f"๊ฒ์๊ธฐ ์ํ ์ ์ฅ ์๋: {index_path}")
|
212 |
+
try:
|
213 |
+
base_retriever.save(index_path)
|
214 |
+
logger.info("์ธ๋ฑ์ค ์ ์ฅ ์๋ฃ.")
|
215 |
+
except Exception as e_save:
|
216 |
+
logger.error(f"์ธ๋ฑ์ค ์ ์ฅ ์คํจ: {e_save}", exc_info=True)
|
217 |
+
else:
|
218 |
+
logger.warning("DocumentProcessor์ load_documents_from_directory ๋ฉ์๋๊ฐ ์์ต๋๋ค.")
|
219 |
except Exception as e_load_add:
|
|
|
220 |
logger.error(f"DATA_FOLDER ๋ฌธ์ ๋ก๋/์ถ๊ฐ ์ค ์ค๋ฅ: {e_load_add}", exc_info=True)
|
221 |
|
222 |
# 3. ์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
223 |
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋...")
|
224 |
try:
|
225 |
+
# custom_rerank_fn ์ ์
|
|
|
226 |
def custom_rerank_fn(query, results):
|
227 |
+
# ์ด ํจ์๋ ์ค์ ์ฌ์์ํ ๋ก์ง์ ๋ง๊ฒ ๊ตฌํ ํ์
|
228 |
+
# ์์: ๋จ์ํ score ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ
|
229 |
+
results.sort(key=lambda x: x.get("score", 0) if isinstance(x, dict) else 0, reverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
return results
|
|
|
231 |
|
232 |
# ReRanker ํด๋์ค ์ฌ์ฉ
|
233 |
retriever = ReRanker(
|
234 |
base_retriever=base_retriever,
|
235 |
+
rerank_fn=custom_rerank_fn,
|
236 |
+
rerank_field="text" # ์ฌ์์ํ์ ์ฌ์ฉํ ํ๋ (ํ์์)
|
237 |
)
|
238 |
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋ฃ.")
|
239 |
except Exception as e_rerank:
|
|
|
244 |
logger.info("--- init_retriever ์ข
๋ฃ ---")
|
245 |
return retriever
|
246 |
|
247 |
+
# --- ๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ---
|
248 |
def background_init():
|
249 |
+
"""๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฒ์๊ธฐ ๋ฐ ๊ธฐํ ์ปดํฌ๋ํธ ์ด๊ธฐํ ์ํ"""
|
250 |
+
global retriever, base_retriever, llm_interface, stt_client, app_ready_event
|
251 |
|
252 |
+
logger.info("๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์์...")
|
253 |
+
start_init_time = time.time()
|
|
|
254 |
|
255 |
+
try:
|
256 |
+
# 1. LLM, STT ์ธํฐํ์ด์ค ์ฌํ์ธ (์ด๋ฏธ ์ด๊ธฐํ ์๋๋จ)
|
257 |
if llm_interface is None or isinstance(llm_interface, MockComponent):
|
258 |
+
logger.warning("LLM ์ธํฐํ์ด์ค๊ฐ ์ด๊ธฐํ๋์ง ์์๊ฑฐ๋ Mock ๊ฐ์ฒด์
๋๋ค.")
|
259 |
+
# ํ์์ ์ฌ๊ธฐ์ ๋ค์ ์ด๊ธฐํ ์๋ ๊ฐ๋ฅ
|
|
|
|
|
|
|
|
|
260 |
if stt_client is None or isinstance(stt_client, MockComponent):
|
261 |
+
logger.warning("STT ํด๋ผ์ด์ธํธ๊ฐ ์ด๊ธฐํ๋์ง ์์๊ฑฐ๋ Mock ๊ฐ์ฒด์
๋๋ค.")
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
|
263 |
# 2. ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
264 |
+
logger.info("๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋ (background)...")
|
265 |
+
retriever = init_retriever() # init_retriever๊ฐ base_retriever๋ ์ค์
|
266 |
+
|
267 |
+
# ์ฑ๊ณต ์ฌ๋ถ ํ์ธ
|
268 |
+
if retriever is not None and not isinstance(retriever, MockComponent):
|
269 |
+
logger.info("๊ฒ์๊ธฐ ์ด๊ธฐํ ์ฑ๊ณต (background)")
|
270 |
+
app_ready_event.set() # ์ฑ ์ค๋น ์๋ฃ ์ํ๋ก ์ค์
|
271 |
+
logger.info("app_ready_event๊ฐ True๋ก ์ค์ ๋จ.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
else:
|
273 |
+
logger.error("๊ฒ์๊ธฐ ์ด๊ธฐํ ์คํจ ๋๋ Mock ๊ฐ์ฒด ์ฌ์ฉ (background)")
|
274 |
+
# ์คํจ ์์๋ ์ฑ์ ์คํ๋ ์ ์๋๋ก app_ready_event๋ฅผ ์ค์ ํ ์ง ๊ฒฐ์
|
275 |
+
# app_ready_event.set() # ์คํจํด๋ ์ผ๋จ ์ฑ์ ๋์ฐ๋ ค๋ฉด ์ฃผ์ ํด์
|
276 |
+
logger.warning("์ด๊ธฐํ ์คํจ๋ก app_ready_event๊ฐ ์ค์ ๋์ง ์์.")
|
|
|
|
|
|
|
|
|
277 |
|
278 |
except Exception as e:
|
279 |
+
logger.error(f"๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์ค ์ฌ๊ฐํ ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
280 |
+
# ์ค๋ฅ ๋ฐ์ ์์๋ Mock ๊ฐ์ฒด ํ ๋น ๋ฐ ์ํ ์ค์ ๊ณ ๋ ค
|
281 |
if base_retriever is None: base_retriever = MockComponent()
|
282 |
if retriever is None: retriever = MockComponent()
|
283 |
+
# app_ready_event.set() # ์ค๋ฅ ์์๋ ์ฑ ์คํ ์ํ๋ฉด ์ฃผ์ ํด์
|
284 |
+
logger.warning("์ด๊ธฐํ ์ค๋ฅ ๋ฐ์์ผ๋ก app_ready_event๊ฐ ์ค์ ๋์ง ์์.")
|
|
|
|
|
285 |
|
286 |
finally:
|
287 |
+
end_init_time = time.time()
|
288 |
+
logger.info(f"๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์๋ฃ. ์์ ์๊ฐ: {end_init_time - start_init_time:.2f}์ด")
|
289 |
+
logger.info(f"์ต์ข
์ฑ ์ค๋น ์ํ (app_ready_event.is_set()): {app_ready_event.is_set()}")
|
290 |
|
291 |
+
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋ ์์
|
292 |
init_thread = threading.Thread(target=background_init)
|
293 |
init_thread.daemon = True
|
294 |
init_thread.start()
|
295 |
|
296 |
+
# --- ๋ผ์ฐํธ ๋ฑ๋ก ---
|
297 |
try:
|
298 |
# ๊ธฐ๋ณธ RAG ์ฑ๋ด ๋ผ์ฐํธ ๋ฑ๋ก
|
299 |
+
# !! ์ค์: register_routes ํธ์ถ ์ app_ready ๋์ app_ready_event ์ ๋ฌ !!
|
300 |
register_routes(
|
301 |
+
app=app,
|
302 |
+
login_required=login_required,
|
303 |
+
llm_interface=llm_interface,
|
304 |
+
retriever=retriever,
|
305 |
+
stt_client=stt_client,
|
306 |
DocumentProcessor=DocumentProcessor,
|
307 |
+
base_retriever=base_retriever,
|
308 |
+
app_ready_event=app_ready_event, # <--- ์ด๋ฆ ๋ณ๊ฒฝ๋จ
|
309 |
ADMIN_USERNAME=ADMIN_USERNAME,
|
310 |
ADMIN_PASSWORD=ADMIN_PASSWORD,
|
311 |
DEVICE_SERVER_URL=DEVICE_SERVER_URL
|
312 |
)
|
313 |
logger.info("๊ธฐ๋ณธ ์ฑ๋ด ๋ผ์ฐํธ ๋ฑ๋ก ์๋ฃ")
|
314 |
+
|
315 |
# ์ฅ์น ๊ด๋ฆฌ ๋ผ์ฐํธ ๋ฑ๋ก
|
316 |
register_device_routes(
|
317 |
app=app,
|
|
|
320 |
)
|
321 |
logger.info("์ฅ์น ๊ด๋ฆฌ ๋ผ์ฐํธ ๋ฑ๋ก ์๋ฃ")
|
322 |
except Exception as e:
|
323 |
+
# ๋ผ์ฐํธ ๋ฑ๋ก ์คํจ๋ ์ฌ๊ฐํ ๋ฌธ์ ์ด๋ฏ๋ก Critical ๋ ๋ฒจ ์ฌ์ฉ ๊ณ ๋ ค
|
324 |
+
logger.critical(f"๋ผ์ฐํธ ๋ฑ๋ก ์ค ์น๋ช
์ ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
325 |
+
# ์ฑ ์คํ์ ์ค๋จํ๊ฑฐ๋ ์ต์ํ์ ์ค๋ฅ ํ์ด์ง๋ฅผ ์ ๊ณตํ๋ ๋ก์ง ์ถ๊ฐ ๊ฐ๋ฅ
|
326 |
|
327 |
# --- ์ ์ ํ์ผ ์๋น ---
|
328 |
+
# STATIC_FOLDER ์ค์ ์ ์ฌ์ฉํ์ฌ static ํด๋ ๊ฒฝ๋ก ์ง์
|
329 |
@app.route('/static/<path:path>')
|
330 |
def send_static(path):
|
331 |
+
static_folder = app.config.get('STATIC_FOLDER', 'static') # ์ค์ ๊ฐ ๋๋ ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ
|
332 |
+
# logger.debug(f"Serving static file: {path} from {static_folder}") # ๋๋ฒ๊น
์ ์ฃผ์ ํด์
|
333 |
+
return send_from_directory(static_folder, path)
|
334 |
|
335 |
|
336 |
# --- ์์ฒญ ์ฒ๋ฆฌ ํ
---
|
337 |
@app.after_request
|
338 |
def after_request_func(response):
|
339 |
+
"""๋ชจ๋ ์๋ต์ ๋ํด ํ์ฒ๋ฆฌ ์ํ (์: ๋ณด์ ํค๋ ์ถ๊ฐ)"""
|
340 |
+
# ์์: response.headers['X-Content-Type-Options'] = 'nosniff'
|
341 |
return response
|
342 |
|
343 |
# ์ฑ ์คํ (๋ก์ปฌ ํ
์คํธ์ฉ)
|
344 |
if __name__ == '__main__':
|
345 |
logger.info("Flask ์ฑ์ ์ง์ ์คํํฉ๋๋ค (๊ฐ๋ฐ์ฉ ์๋ฒ).")
|
|
|
346 |
# port ๋ฒํธ๋ ํ๊ฒฝ ๋ณ์ ๋๋ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
347 |
+
port = int(os.environ.get("PORT", 7860)) # ๊ธฐ๋ณธ ํฌํธ 7860 ์ฌ์ฉ
|
348 |
logger.info(f"์๋ฒ๋ฅผ http://0.0.0.0:{port} ์์ ์์ํฉ๋๋ค.")
|
349 |
+
# debug=True๋ ๊ฐ๋ฐ ์ค์๋ง ์ฌ์ฉํ๊ณ , ๋ฐฐํฌ ์์๋ False๋ก ๋ณ๊ฒฝํ๊ฑฐ๋ ์ ๊ฑฐ
|
350 |
+
# use_reloader=False ์ถ๊ฐํ์ฌ ์๋ ๋ฆฌ๋ก๋ ๋นํ์ฑํ (๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋ ๋ฌธ์ ๋ฐฉ์ง)
|
351 |
+
app.run(debug=False, host='0.0.0.0', port=port, use_reloader=False)
|
352 |
+
|
353 |
+
```
|
354 |
+
|
355 |
+
**์์ ๋ ๋ด์ฉ:**
|
356 |
+
|
357 |
+
* `app.py`์ ๋ผ์ฐํธ ๋ฑ๋ก ๋ถ๋ถ์์ `register_routes` ํจ์๋ฅผ ํธ์ถํ ๋, `app_ready` ์ธ์ ์ด๋ฆ์ `app_ready_event`๋ก ๋ณ๊ฒฝํ์ต๋๋ค. (`app_routes.py`์ ํจ์ ์ ์์ ์ผ์น์ํด)
|
358 |
+
* `app_ready` ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉํ๋ boolean ํ๋๊ทธ ๋์ `threading.Event` ๊ฐ์ฒด(`app_ready_event`)๋ฅผ ์ฌ์ฉํ๋๋ก ์์ ํ์ต๋๋ค. ์ด๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ ๋ฉ์ธ ์ค๋ ๋(์์ฒญ ์ฒ๋ฆฌ)์์ ์์ ํ๊ฒ ์ํ๋ฅผ ํ์ธํ ์ ์๊ฒ ํด์ค๋๋ค.
|
359 |
+
* ๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ํจ์(`background_init`)์์ ์ด๊ธฐํ ์๋ฃ ์ `app_ready_event.set()`์ ํธ์ถํ์ฌ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋๋ก ์์ ํ์ต๋๋ค.
|
360 |
+
* ๋ผ์ฐํธ ํธ๋ค๋ฌ(`index`, `app_status` ๋ฑ)์์๋ `app_ready_event.is_set()`์ ์ฌ์ฉํ์ฌ ์ฑ ์ค๋น ์ํ๋ฅผ ํ์ธํ๋๋ก ์์ ํ์ต๋๋ค.
|
361 |
+
* ๊ธฐํ ๋ก๊น
๊ฐํ ๋ฐ ๊ฐ์ฒด ํ์ธ ๋ก์ง์ ์ถ๊ฐํ์ต๋๋ค.
|
362 |
+
* `app.run()` ํธ์ถ ์ `use_reloader=False` ์ต์
์ ์ถ๊ฐํ์ฌ, ๊ฐ๋ฐ ์๋ฒ์ ์๋ ๋ฆฌ๋ก๋ ๊ธฐ๋ฅ์ผ๋ก ์ธํด ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋๊ฐ ์ค๋ณต ์คํ๋๋ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ์ต๋๋ค.
|
363 |
+
* ์ ์ ํ์ผ ์๋น ๊ฒฝ๋ก๋ฅผ `app.config['STATIC_FOLDER']`๋ฅผ ์ฌ์ฉํ๋๋ก ๋ช
์์ ์ผ๋ก ์์ ํ์ต๋๋ค.
|
364 |
+
|
365 |
+
์ด ์์ ๋ `app.py` ์ฝ๋๋ก ์
๋ฐ์ดํธํ๊ณ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ค์ ์์ํ๋ฉด `TypeError` ์์ด ์ ์์ ์ผ๋ก ์คํ๋ ๊ฒ์
๋๋ค.
|
366 |
+
|
367 |
+
**๋ค์ ๋จ๊ณ:**
|
368 |
+
|
369 |
+
1. ์ด ์ฝ๋๋ก `app.py` ํ์ผ์ ์
๋ฐ์ดํธํฉ๋๋ค.
|
370 |
+
2. ์ ํ๋ฆฌ์ผ์ด์
์ ๋ค์ ์์ํฉ๋๋ค.
|
371 |
+
3. ์น ๋ธ๋ผ์ฐ์ ์์ ํ์ด์ง๊ฐ ์ ์์ ์ผ๋ก ๋ก๋๋๋์ง ํ์ธํฉ๋๋ค. (404 ์ค๋ฅ๊ฐ ์ฌ๋ผ์ก๋์ง ํ์ธ)
|
372 |
+
4. ๋ง์ฝ ํ์ด์ง ๋ก๋๋ ์ ์์ธ๋ฐ '๋ฌธ์๊ด๋ฆฌ' ๋๋ '์ฅ์น๊ด๋ฆฌ' ํญ ๋ก๋ฉ ๋ฌธ์ ๊ฐ ์ฌ์ ํ ๋ฐ์ํ๋ค๋ฉด, **๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์ฝ์ ๋ฐ ๋คํธ์ํฌ ํญ ์ ๋ณด**๋ฅผ ๋ค์ ํ์ธํ์ฌ ์๋ ค์ฃผ
|