jeongsoo commited on
Commit
637ba57
Β·
1 Parent(s): 27a9fab
Files changed (3) hide show
  1. app_part2.py +0 -209
  2. app_part3.py +0 -163
  3. app_revised.py +0 -268
app_part2.py DELETED
@@ -1,209 +0,0 @@
1
- # --- μž„λ² λ”© κ΄€λ ¨ 헬퍼 ν•¨μˆ˜ ---
2
- def save_embeddings(base_retriever, file_path):
3
- """μž„λ² λ”© 데이터λ₯Ό μ••μΆ•ν•˜μ—¬ νŒŒμΌμ— μ €μž₯"""
4
- try:
5
- # μ €μž₯ 디렉토리가 μ—†μœΌλ©΄ 생성
6
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
7
-
8
- # νƒ€μž„μŠ€νƒ¬ν”„ μΆ”κ°€
9
- save_data = {
10
- 'timestamp': datetime.now().isoformat(),
11
- 'retriever': base_retriever
12
- }
13
-
14
- # μ••μΆ•ν•˜μ—¬ μ €μž₯ (μš©λŸ‰ 쀄이기)
15
- with gzip.open(file_path, 'wb') as f:
16
- pickle.dump(save_data, f)
17
-
18
- logger.info(f"μž„λ² λ”© 데이터λ₯Ό {file_path}에 μ••μΆ•ν•˜μ—¬ μ €μž₯ν–ˆμŠ΅λ‹ˆλ‹€.")
19
- return True
20
- except Exception as e:
21
- logger.error(f"μž„λ² λ”© μ €μž₯ 쀑 였λ₯˜ λ°œμƒ: {e}")
22
- return False
23
-
24
- def load_embeddings(file_path, max_age_days=30):
25
- """μ €μž₯된 μž„λ² λ”© 데이터λ₯Ό νŒŒμΌμ—μ„œ λ‘œλ“œ"""
26
- try:
27
- if not os.path.exists(file_path):
28
- logger.info(f"μ €μž₯된 μž„λ² λ”© 파일({file_path})이 μ—†μŠ΅λ‹ˆλ‹€.")
29
- return None
30
-
31
- # μ••μΆ• 파일 λ‘œλ“œ
32
- with gzip.open(file_path, 'rb') as f:
33
- data = pickle.load(f)
34
-
35
- # νƒ€μž„μŠ€νƒ¬ν”„ 확인 (λ„ˆλ¬΄ 였래된 λ°μ΄ν„°λŠ” μ‚¬μš©ν•˜μ§€ μ•ŠμŒ)
36
- saved_time = datetime.fromisoformat(data['timestamp'])
37
- age = (datetime.now() - saved_time).days
38
-
39
- if age > max_age_days:
40
- logger.info(f"μ €μž₯된 μž„λ² λ”©μ΄ {age}일둜 λ„ˆλ¬΄ μ˜€λž˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μƒˆλ‘œ μƒμ„±ν•©λ‹ˆλ‹€.")
41
- return None
42
-
43
- logger.info(f"{file_path}μ—μ„œ μž„λ² λ”© 데이터λ₯Ό λ‘œλ“œν–ˆμŠ΅λ‹ˆλ‹€. (생성일: {saved_time})")
44
- return data['retriever']
45
- except Exception as e:
46
- logger.error(f"μž„λ² λ”© λ‘œλ“œ 쀑 였λ₯˜ λ°œμƒ: {e}")
47
- return None
48
-
49
- def init_retriever():
50
- """검색기 객체 μ΄ˆκΈ°ν™” λ˜λŠ” λ‘œλ“œ"""
51
- global base_retriever, retriever
52
-
53
- # μž„λ² λ”© μΊμ‹œ 파일 경둜
54
- cache_path = os.path.join(app.config['INDEX_PATH'], "cached_embeddings.gz")
55
-
56
- # λ¨Όμ € μ €μž₯된 μž„λ² λ”© 데이터 λ‘œλ“œ μ‹œλ„
57
- cached_retriever = load_embeddings(cache_path)
58
-
59
- if cached_retriever:
60
- logger.info("μΊμ‹œλœ μž„λ² λ”© 데이터λ₯Ό μ„±κ³΅μ μœΌλ‘œ λ‘œλ“œν–ˆμŠ΅λ‹ˆλ‹€.")
61
- base_retriever = cached_retriever
62
- else:
63
- # μΊμ‹œλœ 데이터가 μ—†μœΌλ©΄ κΈ°μ‘΄ λ°©μ‹μœΌλ‘œ μ΄ˆκΈ°ν™”
64
- index_path = app.config['INDEX_PATH']
65
-
66
- # VectorRetriever λ‘œλ“œ λ˜λŠ” μ΄ˆκΈ°ν™”
67
- if os.path.exists(os.path.join(index_path, "documents.json")):
68
- try:
69
- logger.info(f"κΈ°μ‘΄ 벑터 인덱슀λ₯Ό '{index_path}'μ—μ„œ λ‘œλ“œν•©λ‹ˆλ‹€...")
70
- base_retriever = VectorRetriever.load(index_path)
71
- logger.info(f"{len(base_retriever.documents) if hasattr(base_retriever, 'documents') else 0}개 λ¬Έμ„œκ°€ λ‘œλ“œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
72
- except Exception as e:
73
- logger.error(f"인덱슀 λ‘œλ“œ 쀑 였λ₯˜ λ°œμƒ: {e}. μƒˆ 검색기λ₯Ό μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€.")
74
- base_retriever = VectorRetriever()
75
- else:
76
- logger.info("κΈ°μ‘΄ 인덱슀λ₯Ό 찾을 수 μ—†μ–΄ μƒˆ 검색기λ₯Ό μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€...")
77
- base_retriever = VectorRetriever()
78
-
79
- # 데이터 ν΄λ”μ˜ λ¬Έμ„œ λ‘œλ“œ
80
- data_path = app.config['DATA_FOLDER']
81
- if (not hasattr(base_retriever, 'documents') or not base_retriever.documents) and os.path.exists(data_path):
82
- logger.info(f"{data_path}μ—μ„œ λ¬Έμ„œλ₯Ό λ‘œλ“œν•©λ‹ˆλ‹€...")
83
- try:
84
- docs = DocumentProcessor.load_documents_from_directory(
85
- data_path,
86
- extensions=[".txt", ".md", ".csv"],
87
- recursive=True
88
- )
89
- if docs and hasattr(base_retriever, 'add_documents'):
90
- logger.info(f"{len(docs)}개 λ¬Έμ„œλ₯Ό 검색기에 μΆ”κ°€ν•©λ‹ˆλ‹€...")
91
- base_retriever.add_documents(docs)
92
-
93
- if hasattr(base_retriever, 'save'):
94
- logger.info(f"검색기 μƒνƒœλ₯Ό '{index_path}'에 μ €μž₯ν•©λ‹ˆλ‹€...")
95
- try:
96
- base_retriever.save(index_path)
97
- logger.info("인덱슀 μ €μž₯ μ™„λ£Œ")
98
-
99
- # μƒˆλ‘œ μƒμ„±λœ 검색기 캐싱
100
- if hasattr(base_retriever, 'documents') and base_retriever.documents:
101
- save_embeddings(base_retriever, cache_path)
102
- logger.info(f"검색기λ₯Ό μΊμ‹œ 파일 {cache_path}에 μ €μž₯ μ™„λ£Œ")
103
- except Exception as e:
104
- logger.error(f"인덱슀 μ €μž₯ 쀑 였λ₯˜ λ°œμƒ: {e}")
105
- except Exception as e:
106
- logger.error(f"DATA_FOLDERμ—μ„œ λ¬Έμ„œ λ‘œλ“œ 쀑 였λ₯˜: {e}")
107
-
108
- # μž¬μˆœμœ„ν™” 검색기 μ΄ˆκΈ°ν™”
109
- logger.info("μž¬μˆœμœ„ν™” 검색기λ₯Ό μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€...")
110
- try:
111
- # 자체 κ΅¬ν˜„λœ μž¬μˆœμœ„ν™” ν•¨μˆ˜
112
- def custom_rerank_fn(query, results):
113
- query_terms = set(query.lower().split())
114
- for result in results:
115
- if isinstance(result, dict) and "text" in result:
116
- text = result["text"].lower()
117
- term_freq = sum(1 for term in query_terms if term in text)
118
- normalized_score = term_freq / (len(text.split()) + 1) * 10
119
- result["rerank_score"] = result.get("score", 0) * 0.7 + normalized_score * 0.3
120
- elif isinstance(result, dict):
121
- result["rerank_score"] = result.get("score", 0)
122
- results.sort(key=lambda x: x.get("rerank_score", 0) if isinstance(x, dict) else 0, reverse=True)
123
- return results
124
-
125
- # ReRanker 클래슀 μ‚¬μš©
126
- retriever = ReRanker(
127
- base_retriever=base_retriever,
128
- rerank_fn=custom_rerank_fn,
129
- rerank_field="text"
130
- )
131
- logger.info("μž¬μˆœμœ„ν™” 검색기 μ΄ˆκΈ°ν™” μ™„λ£Œ")
132
- except Exception as e:
133
- logger.error(f"μž¬μˆœμœ„ν™” 검색기 μ΄ˆκΈ°ν™” μ‹€νŒ¨: {e}")
134
- retriever = base_retriever # μ‹€νŒ¨ μ‹œ κΈ°λ³Έ 검색기 μ‚¬μš©
135
-
136
- return retriever
137
-
138
- def background_init():
139
- """λ°±κ·ΈλΌμš΄λ“œμ—μ„œ 검색기 μ΄ˆκΈ°ν™” μˆ˜ν–‰"""
140
- global app_ready, retriever, base_retriever
141
-
142
- # μ¦‰μ‹œ μ•± μ‚¬μš© κ°€λŠ₯ μƒνƒœλ‘œ μ„€μ •
143
- app_ready = True
144
- logger.info("앱을 μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯ μƒνƒœλ‘œ μ„€μ • (app_ready=True)")
145
-
146
- try:
147
- # κΈ°λ³Έ 검색기 μ΄ˆκΈ°ν™” (λ³΄ν—˜)
148
- if base_retriever is None:
149
- base_retriever = MockComponent()
150
- if hasattr(base_retriever, 'documents'):
151
- base_retriever.documents = []
152
-
153
- # μž„μ‹œ retriever μ„€μ •
154
- if retriever is None:
155
- retriever = MockComponent()
156
- if not hasattr(retriever, 'search'):
157
- retriever.search = lambda query, **kwargs: []
158
-
159
- # μΊμ‹œλœ μž„λ² λ”© λ‘œλ“œ μ‹œλ„
160
- cache_path = os.path.join(app.config['INDEX_PATH'], "cached_embeddings.gz")
161
- cached_retriever = load_embeddings(cache_path)
162
-
163
- if cached_retriever:
164
- # μΊμ‹œλœ 데이터가 있으면 λ°”λ‘œ μ‚¬μš©
165
- base_retriever = cached_retriever
166
-
167
- # κ°„λ‹¨ν•œ μž¬μˆœμœ„ν™” ν•¨μˆ˜
168
- def simple_rerank(query, results):
169
- if results:
170
- for result in results:
171
- if isinstance(result, dict):
172
- result["rerank_score"] = result.get("score", 0)
173
- results.sort(key=lambda x: x.get("rerank_score", 0) if isinstance(x, dict) else 0, reverse=True)
174
- return results
175
-
176
- # μž¬μˆœμœ„ν™” 검색기 μ΄ˆκΈ°ν™”
177
- retriever = ReRanker(
178
- base_retriever=base_retriever,
179
- rerank_fn=simple_rerank,
180
- rerank_field="text"
181
- )
182
-
183
- logger.info("μΊμ‹œλœ μž„λ² λ”©μœΌλ‘œ 검색기 μ΄ˆκΈ°ν™” μ™„λ£Œ (λΉ λ₯Έ μ‹œμž‘)")
184
- else:
185
- # μΊμ‹œλœ 데이터가 μ—†μœΌλ©΄ 전체 μ΄ˆκΈ°ν™” μ§„ν–‰
186
- logger.info("μΊμ‹œλœ μž„λ² λ”©μ΄ μ—†μ–΄ 전체 μ΄ˆκΈ°ν™” μ‹œμž‘")
187
- retriever = init_retriever()
188
- logger.info("전체 μ΄ˆκΈ°ν™” μ™„λ£Œ")
189
-
190
- logger.info("μ•± μ΄ˆκΈ°ν™” μ™„λ£Œ (λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈ 쀀비됨)")
191
- except Exception as e:
192
- logger.error(f"μ•± λ°±κ·ΈλΌμš΄λ“œ μ΄ˆκΈ°ν™” 쀑 μ‹¬κ°ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
193
- # μ΄ˆκΈ°ν™” μ‹€νŒ¨ μ‹œ κΈ°λ³Έ 객체 생성
194
- if base_retriever is None:
195
- base_retriever = MockComponent()
196
- if hasattr(base_retriever, 'documents'):
197
- base_retriever.documents = []
198
- if retriever is None:
199
- retriever = MockComponent()
200
- if not hasattr(retriever, 'search'):
201
- retriever.search = lambda query, **kwargs: []
202
-
203
- logger.warning("μ΄ˆκΈ°ν™” 쀑 였λ₯˜κ°€ μžˆμ§€λ§Œ 앱은 계속 μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.")
204
-
205
- # λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œ μ‹œμž‘
206
- init_thread = threading.Thread(target=background_init)
207
- init_thread.daemon = True
208
- init_thread.start()
209
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_part3.py DELETED
@@ -1,163 +0,0 @@
1
- # --- Flask 라우트 μ •μ˜ ---
2
-
3
- @app.route('/login', methods=['GET', 'POST'])
4
- def login():
5
- error = None
6
- next_url = request.args.get('next')
7
- logger.info(f"-------------- 둜그인 νŽ˜μ΄μ§€ 접속 (Next: {next_url}) --------------")
8
- logger.info(f"Method: {request.method}")
9
-
10
- if request.method == 'POST':
11
- logger.info("둜그인 μ‹œλ„ λ°›μŒ")
12
- username = request.form.get('username', '')
13
- password = request.form.get('password', '')
14
- logger.info(f"μž…λ ₯된 μ‚¬μš©μžλͺ…: {username}")
15
- logger.info(f"λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ μ—¬λΆ€: {len(password) > 0}")
16
-
17
- # ν™˜κ²½ λ³€μˆ˜ λ˜λŠ” κΈ°λ³Έκ°’κ³Ό 비ꡐ
18
- valid_username = ADMIN_USERNAME
19
- valid_password = ADMIN_PASSWORD
20
- logger.info(f"κ²€μ¦μš© μ‚¬μš©μžλͺ…: {valid_username}")
21
- logger.info(f"κ²€μ¦μš© λΉ„λ°€λ²ˆν˜Έ 쑴재 μ—¬λΆ€: {valid_password is not None and len(valid_password) > 0}")
22
-
23
- if username == valid_username and password == valid_password:
24
- logger.info(f"둜그인 성곡: {username}")
25
- # μ„Έμ…˜ μ„€μ • μ „ ν˜„μž¬ μ„Έμ…˜ μƒνƒœ λ‘œκΉ…
26
- logger.debug(f"μ„Έμ…˜ μ„€μ • μ „: {session}")
27
-
28
- # μ„Έμ…˜μ— 둜그인 정보 μ €μž₯
29
- session.permanent = True
30
- session['logged_in'] = True
31
- session['username'] = username
32
- session.modified = True
33
-
34
- logger.info(f"μ„Έμ…˜ μ„€μ • ν›„: {session}")
35
- logger.info("μ„Έμ…˜ μ„€μ • μ™„λ£Œ, λ¦¬λ””λ ‰μ…˜ μ‹œλ„")
36
-
37
- # 둜그인 성곡 ν›„ λ¦¬λ””λ ‰μ…˜
38
- redirect_to = next_url or url_for('index')
39
- logger.info(f"λ¦¬λ””λ ‰μ…˜ λŒ€μƒ: {redirect_to}")
40
- response = redirect(redirect_to)
41
- return response
42
- else:
43
- logger.warning("둜그인 μ‹€νŒ¨: 아이디 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έ 뢈일치")
44
- if username != valid_username: logger.warning("μ‚¬μš©μžλͺ… 뢈일치")
45
- if password != valid_password: logger.warning("λΉ„λ°€λ²ˆν˜Έ 뢈일치")
46
- error = '아이디 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έκ°€ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.'
47
- else:
48
- logger.info("둜그인 νŽ˜μ΄μ§€ GET μš”μ²­")
49
- if 'logged_in' in session:
50
- logger.info("이미 둜그인된 μ‚¬μš©μž, 메인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜")
51
- return redirect(url_for('index'))
52
-
53
- logger.info("---------- 둜그인 νŽ˜μ΄μ§€ λ Œλ”λ§ ----------")
54
- return render_template('login.html', error=error, next=next_url)
55
-
56
-
57
- @app.route('/logout')
58
- def logout():
59
- logger.info("-------------- λ‘œκ·Έμ•„μ›ƒ μš”μ²­ --------------")
60
- logger.info(f"λ‘œκ·Έμ•„μ›ƒ μ „ μ„Έμ…˜ μƒνƒœ: {session}")
61
-
62
- if 'logged_in' in session:
63
- username = session.get('username', 'unknown')
64
- logger.info(f"μ‚¬μš©μž {username} λ‘œκ·Έμ•„μ›ƒ 처리 μ‹œμž‘")
65
- session.pop('logged_in', None)
66
- session.pop('username', None)
67
- session.modified = True
68
- logger.info(f"μ„Έμ…˜ 정보 μ‚­μ œ μ™„λ£Œ. ν˜„μž¬ μ„Έμ…˜: {session}")
69
- else:
70
- logger.warning("λ‘œκ·ΈμΈλ˜μ§€ μ•Šμ€ μƒνƒœμ—μ„œ λ‘œκ·Έμ•„μ›ƒ μ‹œλ„")
71
-
72
- logger.info("둜그인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜")
73
- response = redirect(url_for('login'))
74
- return response
75
-
76
-
77
- @app.route('/')
78
- @login_required
79
- def index():
80
- """메인 νŽ˜μ΄μ§€"""
81
- global app_ready
82
-
83
- # μ•± μ€€λΉ„ μƒνƒœ 확인 - 30초 이상 μ§€λ‚¬μœΌλ©΄ κ°•μ œλ‘œ ready μƒνƒœλ‘œ λ³€κ²½
84
- current_time = datetime.now()
85
- start_time = datetime.fromtimestamp(os.path.getmtime(__file__))
86
- time_diff = (current_time - start_time).total_seconds()
87
-
88
- if not app_ready and time_diff > 30:
89
- logger.warning(f"앱이 30초 이상 μ΄ˆκΈ°ν™” 쀑 μƒνƒœμž…λ‹ˆλ‹€. κ°•μ œλ‘œ ready μƒνƒœλ‘œ λ³€κ²½ν•©λ‹ˆλ‹€.")
90
- app_ready = True
91
-
92
- if not app_ready:
93
- logger.info("앱이 아직 μ€€λΉ„λ˜μ§€ μ•Šμ•„ λ‘œλ”© νŽ˜μ΄μ§€ ν‘œμ‹œ")
94
- return render_template('loading.html'), 503 # μ„œλΉ„μŠ€ μ€€λΉ„ μ•ˆλ¨ μƒνƒœ μ½”λ“œ
95
-
96
- logger.info("메인 νŽ˜μ΄μ§€ μš”μ²­")
97
- return render_template('index.html')
98
-
99
-
100
- @app.route('/api/status')
101
- @login_required
102
- def app_status():
103
- """μ•± μ΄ˆκΈ°ν™” μƒνƒœ 확인 API"""
104
- logger.info(f"μ•± μƒνƒœ 확인 μš”μ²­: {'Ready' if app_ready else 'Not Ready'}")
105
- return jsonify({"ready": app_ready})
106
-
107
-
108
- @app.route('/api/llm', methods=['GET', 'POST'])
109
- @login_required
110
- def llm_api():
111
- """μ‚¬μš© κ°€λŠ₯ν•œ LLM λͺ©λ‘ 및 선택 API"""
112
- global llm_interface
113
-
114
- if not app_ready:
115
- return jsonify({"error": "앱이 아직 μ΄ˆκΈ°ν™” μ€‘μž…λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 503
116
-
117
- if request.method == 'GET':
118
- logger.info("LLM λͺ©λ‘ μš”μ²­")
119
- try:
120
- current_details = llm_interface.get_current_llm_details() if hasattr(llm_interface, 'get_current_llm_details') else {"id": "unknown", "name": "Unknown"}
121
- supported_llms_dict = llm_interface.SUPPORTED_LLMS if hasattr(llm_interface, 'SUPPORTED_LLMS') else {}
122
- supported_list = [{
123
- "name": name, "id": id, "current": id == current_details.get("id")
124
- } for name, id in supported_llms_dict.items()]
125
-
126
- return jsonify({
127
- "supported_llms": supported_list,
128
- "current_llm": current_details
129
- })
130
- except Exception as e:
131
- logger.error(f"LLM 정보 쑰회 였λ₯˜: {e}")
132
- return jsonify({"error": "LLM 정보 쑰회 쀑 였λ₯˜ λ°œμƒ"}), 500
133
-
134
- elif request.method == 'POST':
135
- data = request.get_json()
136
- if not data or 'llm_id' not in data:
137
- return jsonify({"error": "LLM IDκ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."}), 400
138
-
139
- llm_id = data['llm_id']
140
- logger.info(f"LLM λ³€κ²½ μš”μ²­: {llm_id}")
141
-
142
- try:
143
- if not hasattr(llm_interface, 'set_llm') or not hasattr(llm_interface, 'llm_clients'):
144
- raise NotImplementedError("LLM μΈν„°νŽ˜μ΄μŠ€μ— ν•„μš”ν•œ λ©”μ†Œλ“œ/속성 μ—†μŒ")
145
-
146
- if llm_id not in llm_interface.llm_clients:
147
- return jsonify({"error": f"μ§€μ›λ˜μ§€ μ•ŠλŠ” LLM ID: {llm_id}"}), 400
148
-
149
- success = llm_interface.set_llm(llm_id)
150
- if success:
151
- new_details = llm_interface.get_current_llm_details()
152
- logger.info(f"LLM이 '{new_details.get('name', llm_id)}'둜 λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
153
- return jsonify({
154
- "success": True,
155
- "message": f"LLM이 '{new_details.get('name', llm_id)}'둜 λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
156
- "current_llm": new_details
157
- })
158
- else:
159
- logger.error(f"LLM λ³€κ²½ μ‹€νŒ¨ (ID: {llm_id})")
160
- return jsonify({"error": "LLM λ³€κ²½ 쀑 λ‚΄λΆ€ 였λ₯˜ λ°œμƒ"}), 500
161
- except Exception as e:
162
- logger.error(f"LLM λ³€κ²½ 처리 쀑 였λ₯˜: {e}", exc_info=True)
163
- return jsonify({"error": f"LLM λ³€κ²½ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_revised.py DELETED
@@ -1,268 +0,0 @@
1
- """
2
- RAG 검색 챗봇 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ (μž₯치 관리 κΈ°λŠ₯ 톡합)
3
- """
4
-
5
- import os
6
- import logging
7
- import threading
8
- from datetime import datetime, timedelta
9
- from flask import Flask, send_from_directory, jsonify
10
- from dotenv import load_dotenv
11
- from functools import wraps
12
- from flask_cors import CORS
13
-
14
- # 둜거 μ„€μ •
15
- logging.basicConfig(
16
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
- level=logging.DEBUG
18
- )
19
- logger = logging.getLogger(__name__)
20
-
21
- # ν™˜κ²½ λ³€μˆ˜ λ‘œλ“œ
22
- load_dotenv()
23
-
24
- # ν™˜κ²½ λ³€μˆ˜ λ‘œλ“œ μƒνƒœ 확인 및 λ‘œκΉ…
25
- ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
26
- ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')
27
- DEVICE_SERVER_URL = os.getenv('DEVICE_SERVER_URL', 'http://localhost:5050')
28
-
29
- logger.info(f"==== ν™˜κ²½ λ³€μˆ˜ λ‘œλ“œ μƒνƒœ ====")
30
- logger.info(f"ADMIN_USERNAME μ„€μ • μ—¬λΆ€: {ADMIN_USERNAME is not None}")
31
- logger.info(f"ADMIN_PASSWORD μ„€μ • μ—¬λΆ€: {ADMIN_PASSWORD is not None}")
32
- logger.info(f"DEVICE_SERVER_URL: {DEVICE_SERVER_URL}")
33
-
34
- # ν™˜κ²½ λ³€μˆ˜κ°€ μ—†μœΌλ©΄ κΈ°λ³Έκ°’ μ„€μ •
35
- if not ADMIN_USERNAME:
36
- ADMIN_USERNAME = 'admin'
37
- logger.warning("ADMIN_USERNAME ν™˜κ²½λ³€μˆ˜κ°€ μ—†μ–΄ κΈ°λ³Έκ°’ 'admin'으둜 μ„€μ •ν•©λ‹ˆλ‹€.")
38
-
39
- if not ADMIN_PASSWORD:
40
- ADMIN_PASSWORD = 'rag12345'
41
- logger.warning("ADMIN_PASSWORD ν™˜κ²½λ³€μˆ˜κ°€ μ—†μ–΄ κΈ°λ³Έκ°’ 'rag12345'둜 μ„€μ •ν•©λ‹ˆλ‹€.")
42
-
43
- class MockComponent: pass
44
-
45
- # --- 둜컬 λͺ¨λ“ˆ μž„ν¬νŠΈ ---
46
- try:
47
- from utils.vito_stt import VitoSTT
48
- from utils.llm_interface import LLMInterface
49
- from utils.document_processor import DocumentProcessor
50
- from retrieval.vector_retriever import VectorRetriever
51
- from retrieval.reranker import ReRanker
52
- except ImportError as e:
53
- logger.error(f"둜컬 λͺ¨λ“ˆ μž„ν¬νŠΈ μ‹€νŒ¨: {e}. utils 및 retrieval νŒ¨ν‚€μ§€κ°€ μ˜¬λ°”λ₯Έ κ²½λ‘œμ— μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.")
54
- VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
55
- # --- 둜컬 λͺ¨λ“ˆ μž„ν¬νŠΈ 끝 ---
56
-
57
-
58
- # Flask μ•± μ΄ˆκΈ°ν™”
59
- app = Flask(__name__)
60
-
61
- # CORS μ„€μ • - λͺ¨λ“  λ„λ©”μΈμ—μ„œμ˜ μš”μ²­ ν—ˆμš©
62
- CORS(app, supports_credentials=True)
63
-
64
- # μ„Έμ…˜ μ„€μ •
65
- app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345')
66
-
67
- # --- μ„Έμ…˜ μΏ ν‚€ μ„€μ • ---
68
- app.config['SESSION_COOKIE_SECURE'] = True
69
- app.config['SESSION_COOKIE_HTTPONLY'] = True
70
- app.config['SESSION_COOKIE_SAMESITE'] = 'None'
71
- app.config['SESSION_COOKIE_DOMAIN'] = None
72
- app.config['SESSION_COOKIE_PATH'] = '/'
73
- app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=1)
74
- # --- μ„Έμ…˜ μΏ ν‚€ μ„€μ • 끝 ---
75
-
76
- # μ΅œλŒ€ 파일 크기 μ„€μ • (10MB)
77
- app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
78
- # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 파일 κΈ°μ€€ μƒλŒ€ 경둜 μ„€μ •
79
- APP_ROOT = os.path.dirname(os.path.abspath(__file__))
80
- app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
81
- app.config['DATA_FOLDER'] = os.path.join(APP_ROOT, '..', 'data')
82
- app.config['INDEX_PATH'] = os.path.join(APP_ROOT, '..', 'data', 'index')
83
-
84
- # ν•„μš”ν•œ 폴더 생성
85
- os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
86
- os.makedirs(app.config['DATA_FOLDER'], exist_ok=True)
87
- os.makedirs(app.config['INDEX_PATH'], exist_ok=True)
88
-
89
- # --- μ „μ—­ 객체 μ΄ˆκΈ°ν™” ---
90
- try:
91
- llm_interface = LLMInterface(default_llm="openai")
92
- stt_client = VitoSTT()
93
- except NameError:
94
- logger.warning("LLM λ˜λŠ” STT μΈν„°νŽ˜μ΄μŠ€ μ΄ˆκΈ°ν™” μ‹€νŒ¨. Mock 객체λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.")
95
- llm_interface = MockComponent()
96
- stt_client = MockComponent()
97
-
98
- base_retriever = None
99
- retriever = None
100
- app_ready = False # μ•± μ΄ˆκΈ°ν™” μƒνƒœ ν”Œλž˜κ·Έ
101
- # --- μ „μ—­ 객체 μ΄ˆκΈ°ν™” 끝 ---
102
-
103
-
104
- # --- 인증 λ°μ½”λ ˆμ΄ν„° ---
105
- def login_required(f):
106
- @wraps(f)
107
- def decorated_function(*args, **kwargs):
108
- from flask import request, session, redirect, url_for
109
-
110
- logger.info(f"----------- 인증 ν•„μš” νŽ˜μ΄μ§€ μ ‘κ·Ό μ‹œλ„: {request.path} -----------")
111
- logger.info(f"ν˜„μž¬ ν”ŒλΌμŠ€ν¬ μ„Έμ…˜ 객체: {session}")
112
- logger.info(f"ν˜„μž¬ μ„Έμ…˜ μƒνƒœ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
113
- logger.info(f"μš”μ²­μ˜ μ„Έμ…˜ μΏ ν‚€ κ°’: {request.cookies.get('session', 'None')}")
114
-
115
- # API μš”μ²­μ΄κ³  ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ μ˜€λŠ” 경우 인증 λ¬΄μ‹œ (μž„μ‹œ 쑰치)
116
- if request.path.startswith('/api/device/'):
117
- logger.info(f"μž₯치 API μš”μ²­: {request.path} - 인증 μ œμ™Έ")
118
- return f(*args, **kwargs)
119
-
120
- # Flask μ„Έμ…˜μ— 'logged_in' ν‚€κ°€ μžˆλŠ”μ§€ 직접 확인
121
- if 'logged_in' not in session:
122
- logger.warning(f"ν”ŒλΌμŠ€ν¬ μ„Έμ…˜μ— 'logged_in' μ—†μŒ. 둜그인 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜.")
123
- return redirect(url_for('login', next=request.url))
124
-
125
- logger.info(f"인증 성곡: {session.get('username', 'unknown')} μ‚¬μš©μžκ°€ {request.path} μ ‘κ·Ό")
126
- return f(*args, **kwargs)
127
- return decorated_function
128
- # --- 인증 λ°μ½”λ ˆμ΄ν„° 끝 ---
129
-
130
- # --- 였λ₯˜ ν•Έλ“€λŸ¬ μΆ”κ°€ ---
131
- @app.errorhandler(404)
132
- def not_found(e):
133
- # ν΄λΌμ΄μ–ΈνŠΈκ°€ JSON을 κΈ°λŒ€ν•˜λŠ” API 호좜인 경우 JSON 응닡
134
- if request.path.startswith('/api/'):
135
- return jsonify({"success": False, "error": "μš”μ²­ν•œ API μ—”λ“œν¬μΈνŠΈλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."}), 404
136
- # 일반 μ›Ή νŽ˜μ΄μ§€ μš”μ²­μΈ 경우 HTML 응닡
137
- return "νŽ˜μ΄μ§€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", 404
138
-
139
- @app.errorhandler(500)
140
- def internal_error(e):
141
- # ν΄λΌμ΄μ–ΈνŠΈκ°€ JSON을 κΈ°λŒ€ν•˜λŠ” API 호좜인 경우 JSON 응닡
142
- if request.path.startswith('/api/'):
143
- return jsonify({"success": False, "error": "μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."}), 500
144
- # 일반 μ›Ή νŽ˜μ΄μ§€ μš”μ²­μΈ 경우 HTML 응닡
145
- return "μ„œλ²„ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", 500
146
- # --- 였λ₯˜ ν•Έλ“€λŸ¬ 끝 ---
147
-
148
-
149
- # --- 정적 파일 μ„œλΉ™ ---
150
- @app.route('/static/<path:path>')
151
- def send_static(path):
152
- return send_from_directory('static', path)
153
-
154
-
155
- # --- λ°±κ·ΈλΌμš΄λ“œ μ΄ˆκΈ°ν™” ν•¨μˆ˜ ---
156
- def background_init():
157
- """λ°±κ·ΈλΌμš΄λ“œμ—μ„œ 검색기 μ΄ˆκΈ°ν™” μˆ˜ν–‰"""
158
- global app_ready, retriever, base_retriever
159
-
160
- # μ¦‰μ‹œ μ•± μ‚¬μš© κ°€λŠ₯ μƒνƒœλ‘œ μ„€μ •
161
- app_ready = True
162
- logger.info("앱을 μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯ μƒνƒœλ‘œ μ„€μ • (app_ready=True)")
163
-
164
- try:
165
- from app.init_retriever import init_retriever
166
-
167
- # κΈ°λ³Έ 검색기 μ΄ˆκΈ°ν™” (λ³΄ν—˜)
168
- if base_retriever is None:
169
- base_retriever = MockComponent()
170
- if hasattr(base_retriever, 'documents'):
171
- base_retriever.documents = []
172
-
173
- # μž„μ‹œ retriever μ„€μ •
174
- if retriever is None:
175
- retriever = MockComponent()
176
- if not hasattr(retriever, 'search'):
177
- retriever.search = lambda query, **kwargs: []
178
-
179
- # μž„λ² λ”© μΊμ‹œ 파일 경둜
180
- cache_path = os.path.join(app.config['INDEX_PATH'], "cached_embeddings.gz")
181
-
182
- # μΊμ‹œλœ μž„λ² λ”© λ‘œλ“œ μ‹œλ„
183
- try:
184
- from app.init_retriever import load_embeddings
185
- cached_retriever = load_embeddings(cache_path)
186
-
187
- if cached_retriever:
188
- # μΊμ‹œλœ 데이터가 있으면 λ°”λ‘œ μ‚¬μš©
189
- base_retriever = cached_retriever
190
-
191
- # μž¬μˆœμœ„ν™” 검색기 μ΄ˆκΈ°ν™”
192
- retriever = ReRanker(
193
- base_retriever=base_retriever,
194
- rerank_fn=lambda query, results: results,
195
- rerank_field="text"
196
- )
197
-
198
- logger.info("μΊμ‹œλœ μž„λ² λ”©μœΌλ‘œ 검색기 μ΄ˆκΈ°ν™” μ™„λ£Œ (λΉ λ₯Έ μ‹œμž‘)")
199
- else:
200
- # μΊμ‹œλœ 데이터가 μ—†μœΌλ©΄ 전체 μ΄ˆκΈ°ν™” μ§„ν–‰
201
- logger.info("μΊμ‹œλœ μž„λ² λ”©μ΄ μ—†μ–΄ 전체 μ΄ˆκΈ°ν™” μ‹œμž‘")
202
- retriever = init_retriever(app, base_retriever, retriever, ReRanker)
203
- logger.info("전체 μ΄ˆκΈ°ν™” μ™„λ£Œ")
204
- except ImportError:
205
- logger.warning("μž„λ² λ”© μΊμ‹œ λͺ¨λ“ˆμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. 전체 μ΄ˆκΈ°ν™”λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€.")
206
- retriever = init_retriever(app, base_retriever, retriever, ReRanker)
207
-
208
- logger.info("μ•± μ΄ˆκΈ°ν™” μ™„λ£Œ (λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈ 쀀비됨)")
209
- except Exception as e:
210
- logger.error(f"μ•± λ°±κ·ΈλΌμš΄λ“œ μ΄ˆκΈ°ν™” 쀑 μ‹¬κ°ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
211
- # μ΄ˆκΈ°ν™” μ‹€νŒ¨ μ‹œ κΈ°λ³Έ 객체 생성
212
- if base_retriever is None:
213
- base_retriever = MockComponent()
214
- if hasattr(base_retriever, 'documents'):
215
- base_retriever.documents = []
216
- if retriever is None:
217
- retriever = MockComponent()
218
- if not hasattr(retriever, 'search'):
219
- retriever.search = lambda query, **kwargs: []
220
-
221
- logger.warning("μ΄ˆκΈ°ν™” 쀑 였λ₯˜κ°€ μžˆμ§€λ§Œ 앱은 계속 μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.")
222
-
223
-
224
- # --- 라우트 등둝 ---
225
- def register_all_routes():
226
- try:
227
- # κΈ°λ³Έ 라우트 등둝
228
- from app.app_routes import register_routes
229
- register_routes(
230
- app, login_required, llm_interface, retriever, stt_client,
231
- DocumentProcessor, base_retriever, app_ready,
232
- ADMIN_USERNAME, ADMIN_PASSWORD, DEVICE_SERVER_URL
233
- )
234
-
235
- # μž₯치 관리 라우트 등둝
236
- from app.app_device_routes import register_device_routes
237
- register_device_routes(app, login_required, DEVICE_SERVER_URL)
238
-
239
- logger.info("λͺ¨λ“  라우트 등둝 μ™„λ£Œ")
240
- except ImportError as e:
241
- logger.error(f"라우트 λͺ¨λ“ˆ μž„ν¬νŠΈ μ‹€νŒ¨: {e}")
242
- except Exception as e:
243
- logger.error(f"라우트 등둝 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
244
-
245
-
246
- # --- μ•± μ΄ˆκΈ°ν™” 및 μ‹€ν–‰ ---
247
- def initialize_app():
248
- # λ°±κ·ΈλΌμš΄λ“œ μ΄ˆκΈ°ν™” μŠ€λ ˆλ“œ μ‹œμž‘
249
- init_thread = threading.Thread(target=background_init)
250
- init_thread.daemon = True
251
- init_thread.start()
252
-
253
- # 라우트 등둝
254
- register_all_routes()
255
-
256
- logger.info("μ•± μ΄ˆκΈ°ν™” μ™„λ£Œ")
257
-
258
-
259
- # μ•± μ΄ˆκΈ°ν™” μ‹€ν–‰
260
- initialize_app()
261
-
262
-
263
- # --- μ•± μ‹€ν–‰ (직접 μ‹€ν–‰ μ‹œ) ---
264
- if __name__ == '__main__':
265
- logger.info("Flask 앱을 직접 μ‹€ν–‰ν•©λ‹ˆλ‹€ (개발용 μ„œλ²„).")
266
- port = int(os.environ.get("PORT", 7860))
267
- logger.info(f"μ„œλ²„λ₯Ό http://0.0.0.0:{port} μ—μ„œ μ‹œμž‘ν•©λ‹ˆλ‹€.")
268
- app.run(debug=True, host='0.0.0.0', port=port)