Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,8 +3,10 @@ import os
|
|
| 3 |
#os.environ["PYDANTIC_SKIP_VALIDATING_CORE_SCHEMAS"] = "1"
|
| 4 |
# --------------------------------------------------------------------------
|
| 5 |
|
| 6 |
-
from flask import Flask, render_template, jsonify, request
|
| 7 |
-
from flask_socketio import SocketIO
|
|
|
|
|
|
|
| 8 |
import threading
|
| 9 |
import sqlite3
|
| 10 |
import gc
|
|
@@ -58,6 +60,9 @@ region_paths = {
|
|
| 58 |
# --- 프롬프트 ---
|
| 59 |
lexi_prompts = leximind_prompts.PromptLibrary()
|
| 60 |
|
|
|
|
|
|
|
|
|
|
| 61 |
# --- RAG 객체 ---
|
| 62 |
region_rag_objects = {}
|
| 63 |
|
|
@@ -124,33 +129,187 @@ def load_rag_objects():
|
|
| 124 |
# --- 웹 ---
|
| 125 |
@app.route('/')
|
| 126 |
def index():
|
| 127 |
-
return render_template('
|
| 128 |
|
| 129 |
-
#
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
title = reg.get('title', '')
|
| 145 |
-
if title:
|
| 146 |
-
filters["regulation_part"].append(title)
|
| 147 |
-
|
| 148 |
-
Rag_Results = search_DB_from_multiple_regions(query, regions, region_rag_objects)
|
| 149 |
-
AImessage = RegAI(query, Rag_Results, ResultFile_FolderAddress)
|
| 150 |
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
return jsonify(message=AImessage)
|
| 154 |
|
| 155 |
# --- 법규 리스트 ---
|
| 156 |
@app.route('/get_reg_list', methods=['POST'])
|
|
@@ -280,7 +439,6 @@ def Gemma3_AI_Translate(query_txt):
|
|
| 280 |
# --- 검색 ---
|
| 281 |
def search_DB_from_multiple_regions(query, selected_regions, region_rag_objects):
|
| 282 |
selected_regions = selected_regions or list(region_rag_objects.keys())
|
| 283 |
-
query = Gemma3_AI_Translate(query)
|
| 284 |
logger.info(f"번역된 쿼리: {query}")
|
| 285 |
|
| 286 |
combined_results = []
|
|
@@ -336,6 +494,60 @@ def RegAI(query, Rag_Results, ResultFile_FolderAddress):
|
|
| 336 |
|
| 337 |
return AI_Result
|
| 338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
# --- 실행 ---
|
| 340 |
if __name__ == '__main__':
|
| 341 |
# 로컬 개발용
|
|
|
|
| 3 |
#os.environ["PYDANTIC_SKIP_VALIDATING_CORE_SCHEMAS"] = "1"
|
| 4 |
# --------------------------------------------------------------------------
|
| 5 |
|
| 6 |
+
from flask import Flask, render_template, jsonify, request, Response
|
| 7 |
+
from flask_socketio import SocketIO, emit
|
| 8 |
+
import uuid
|
| 9 |
+
|
| 10 |
import threading
|
| 11 |
import sqlite3
|
| 12 |
import gc
|
|
|
|
| 60 |
# --- 프롬프트 ---
|
| 61 |
lexi_prompts = leximind_prompts.PromptLibrary()
|
| 62 |
|
| 63 |
+
# 세션별 요청 추적을 위한 딕셔너리
|
| 64 |
+
active_sessions = {}
|
| 65 |
+
|
| 66 |
# --- RAG 객체 ---
|
| 67 |
region_rag_objects = {}
|
| 68 |
|
|
|
|
| 129 |
# --- 웹 ---
|
| 130 |
@app.route('/')
|
| 131 |
def index():
|
| 132 |
+
return render_template('chat_v02.html')
|
| 133 |
|
| 134 |
+
# 전역 변수에 기본값 추가
|
| 135 |
+
Search_each_all_mode = True # 기본값을 클라이언트에서 제어 가능
|
| 136 |
+
|
| 137 |
+
@socketio.on('search_query')
|
| 138 |
+
def handle_search_query(data):
|
| 139 |
+
global Filtered_search
|
| 140 |
+
global filters
|
| 141 |
+
global Search_each_all_mode
|
| 142 |
+
|
| 143 |
+
# 세션 ID 생성
|
| 144 |
+
session_id = str(uuid.uuid4())
|
| 145 |
+
active_sessions[session_id] = True
|
| 146 |
+
|
| 147 |
+
# 클라이언트에 session_id 전달
|
| 148 |
+
emit('search_started', {'session_id': session_id})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
+
try:
|
| 151 |
+
# 클라이언트에서 전송된 검색 모드 사용
|
| 152 |
+
Search_each_all_mode = data.get('searchEachMode', True)
|
| 153 |
+
|
| 154 |
+
query = data.get('query', '')
|
| 155 |
+
regions = data.get('regions', [])
|
| 156 |
+
selected_regulations = data.get('selectedRegulations', [])
|
| 157 |
+
|
| 158 |
+
emit('search_status', {'status': 'processing', 'message': '검색 요청을 처리하는 중입니다...'})
|
| 159 |
+
|
| 160 |
+
logger.info(f"선택된 지역 : {regions}")
|
| 161 |
+
logger.info(f"선택된 법규 : {selected_regulations}")
|
| 162 |
+
if Search_each_all_mode:
|
| 163 |
+
logger.info(f"검색 모드 : 각각 검색")
|
| 164 |
+
else:
|
| 165 |
+
logger.info(f"검색 모드 : 통합 검색")
|
| 166 |
+
|
| 167 |
+
# 법규 타입별로 필터 구분
|
| 168 |
+
filters = {
|
| 169 |
+
"regulation_part": [],
|
| 170 |
+
"regulation_section": [],
|
| 171 |
+
"chapter_section": [],
|
| 172 |
+
"jo": []
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
# 번역 진행 상황 알림
|
| 176 |
+
emit('search_status', {'status': 'translating', 'message': '질문을 번역하는 중입니다...'})
|
| 177 |
+
|
| 178 |
+
if session_id not in active_sessions:
|
| 179 |
+
emit('search_cancelled', {'message': '검색이 취소되었습니다.'})
|
| 180 |
+
emit('search_status', {'status': 'processing', 'message': 'Ready to search'})
|
| 181 |
+
return
|
| 182 |
+
|
| 183 |
+
Translated_query = Gemma3_AI_Translate(query)
|
| 184 |
+
emit('search_status', {'status': 'translated', 'message': f'번역 완료: {Translated_query}'})
|
| 185 |
+
logger.info(f"Query: Original query : {query}")
|
| 186 |
+
logger.info(f"Query: Translated_query : {Translated_query}")
|
| 187 |
+
|
| 188 |
+
if selected_regulations:
|
| 189 |
+
Filtered_search = True
|
| 190 |
+
cont_selected_num = 0
|
| 191 |
+
|
| 192 |
+
# 파일로 저장
|
| 193 |
+
output_path = os.path.join(current_dir, "merged_ai_messages.txt")
|
| 194 |
+
|
| 195 |
+
if os.path.exists(output_path):
|
| 196 |
+
os.remove(output_path)
|
| 197 |
+
print(f"기존 파일 삭제 완료: {output_path}")
|
| 198 |
+
|
| 199 |
+
if Search_each_all_mode:
|
| 200 |
+
# 각각 검색 모드
|
| 201 |
+
emit('search_status', {'status': 'searching', 'message': f'선택된 {len(selected_regulations)}개 법규를 각각 검색 중...'})
|
| 202 |
+
|
| 203 |
+
for i, regulation in enumerate(selected_regulations):
|
| 204 |
+
if session_id not in active_sessions:
|
| 205 |
+
emit('search_cancelled', {'message': '검색이 취소되었습니다.'})
|
| 206 |
+
emit('search_status', {'status': 'processing', 'message': 'Ready to search'})
|
| 207 |
+
return
|
| 208 |
+
|
| 209 |
+
regulation_title = regulation.get('title', '')
|
| 210 |
+
regulation_id = regulation.get('id', '')
|
| 211 |
+
regulation_type = regulation.get('type', 'part') # 타입 정보 추출
|
| 212 |
+
|
| 213 |
+
emit('search_status', {
|
| 214 |
+
'status': 'searching_regulation',
|
| 215 |
+
'message': f'법규 {i+1}/{len(selected_regulations)}: [{regulation_type.upper()}] {regulation_title} 검색 중...',
|
| 216 |
+
'progress': (i / len(selected_regulations)) * 100
|
| 217 |
+
})
|
| 218 |
+
|
| 219 |
+
# 법규 타입별 필터 생성
|
| 220 |
+
current_filters = create_filter_by_type(regulation_type, regulation_title)
|
| 221 |
+
print(f"[{regulation_type}] 필터에 추가: {regulation_title}")
|
| 222 |
+
print(f"생성된 필터: {current_filters}")
|
| 223 |
+
|
| 224 |
+
Rag_Results = search_DB_from_multiple_regions(Translated_query, regions, region_rag_objects, current_filters)
|
| 225 |
+
|
| 226 |
+
if session_id not in active_sessions:
|
| 227 |
+
emit('search_cancelled', {'message': '검색이 취소되었습니다.'})
|
| 228 |
+
emit('search_status', {'status': 'processing', 'message': 'Ready to search'})
|
| 229 |
+
return
|
| 230 |
+
|
| 231 |
+
emit('search_status', {
|
| 232 |
+
'status': 'ai_processing',
|
| 233 |
+
'message': f'AI가 [{regulation_type.upper()}] {regulation_title}에 대한 답변을 생성 중...'
|
| 234 |
+
})
|
| 235 |
+
|
| 236 |
+
AImessage = RegAI(query, Rag_Results, ResultFile_FolderAddress)
|
| 237 |
+
logger.info(f"Answer: {AImessage}")
|
| 238 |
+
|
| 239 |
+
if session_id not in active_sessions:
|
| 240 |
+
emit('search_cancelled', {'message': '검색이 취소되었습니다.'})
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
# 각 법규별 결과를 실시간으로 전송 (타입 정보 포함)
|
| 244 |
+
emit('regulation_result', {
|
| 245 |
+
'regulation_title': f"[{regulation_type.upper()}] {regulation_title}",
|
| 246 |
+
'regulation_index': i + 1,
|
| 247 |
+
'total_regulations': len(selected_regulations),
|
| 248 |
+
'regulation_type': regulation_type,
|
| 249 |
+
'result': AImessage
|
| 250 |
+
})
|
| 251 |
+
|
| 252 |
+
# 파일에 저장
|
| 253 |
+
if isinstance(AImessage, str) and AImessage.strip():
|
| 254 |
+
with open(output_path, "a", encoding="utf-8") as f:
|
| 255 |
+
cont_selected_num += 1
|
| 256 |
+
from datetime import datetime
|
| 257 |
+
stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 258 |
+
f.write(f"\n--- [{stamp}] message #{cont_selected_num} --- Regulation Type: {regulation_type} --- Regulation Name : {regulation_title} ---\n {AImessage}")
|
| 259 |
+
|
| 260 |
+
emit('search_complete', {'status': 'completed', 'message': '모든 법규 검색이 완료되었습니다.'})
|
| 261 |
+
else:
|
| 262 |
+
# 통합 검색 모드 - 타입별로 그룹화
|
| 263 |
+
grouped_regulations = group_regulations_by_type(selected_regulations)
|
| 264 |
+
emit('search_status', {'status': 'searching', 'message': f'선택된 {len(selected_regulations)}개 법규를 타입별로 통합하여 검색 중...'})
|
| 265 |
+
|
| 266 |
+
# 타입별로 필터 생성
|
| 267 |
+
combined_filters = create_combined_filters(grouped_regulations)
|
| 268 |
+
logger.info(f"통합 필터: {combined_filters}")
|
| 269 |
+
|
| 270 |
+
Rag_Results = search_DB_from_multiple_regions(Translated_query, regions, region_rag_objects, combined_filters)
|
| 271 |
+
|
| 272 |
+
if session_id in active_sessions:
|
| 273 |
+
emit('search_status', {'status': 'ai_processing', 'message': 'AI가 통합 답변을 생성 중...'})
|
| 274 |
+
AImessage = RegAI(query, Rag_Results, ResultFile_FolderAddress)
|
| 275 |
+
logger.info(f"Answer: {AImessage}")
|
| 276 |
+
|
| 277 |
+
if session_id in active_sessions:
|
| 278 |
+
emit('search_result', {'result': AImessage})
|
| 279 |
+
emit('search_complete', {'status': 'completed', 'message': '통합 검색이 완료되었습니다.'})
|
| 280 |
+
|
| 281 |
+
else:
|
| 282 |
+
Filtered_search = False
|
| 283 |
+
emit('search_status', {'status': 'searching_all', 'message': '전체 법규에서 검색 중...'})
|
| 284 |
+
|
| 285 |
+
# 필터 없이 검색
|
| 286 |
+
Rag_Results = search_DB_from_multiple_regions(Translated_query, regions, region_rag_objects, None)
|
| 287 |
+
|
| 288 |
+
if session_id in active_sessions:
|
| 289 |
+
emit('search_status', {'status': 'ai_processing', 'message': 'AI가 답변을 생성 중...'})
|
| 290 |
+
AImessage = RegAI(query, Rag_Results, ResultFile_FolderAddress)
|
| 291 |
+
logger.info(f"Answer: {AImessage}")
|
| 292 |
+
|
| 293 |
+
if session_id in active_sessions:
|
| 294 |
+
emit('search_result', {'result': AImessage})
|
| 295 |
+
emit('search_complete', {'status': 'completed', 'message': '검색이 완료되었습니다.'})
|
| 296 |
+
|
| 297 |
+
except Exception as e:
|
| 298 |
+
print(f"검색 오류: {e}")
|
| 299 |
+
emit('search_error', {'error': str(e), 'message': '검색 중 오류가 발생했습니다.'})
|
| 300 |
+
finally:
|
| 301 |
+
# 세션 정리
|
| 302 |
+
if session_id in active_sessions:
|
| 303 |
+
del active_sessions[session_id]
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
@socketio.on('cancel_search')
|
| 307 |
+
def handle_cancel_search(data):
|
| 308 |
+
session_id = data.get('session_id')
|
| 309 |
+
if session_id and session_id in active_sessions:
|
| 310 |
+
del active_sessions[session_id]
|
| 311 |
+
emit('search_cancelled', {'message': '검색이 취소되었습니다.'})
|
| 312 |
|
|
|
|
| 313 |
|
| 314 |
# --- 법규 리스트 ---
|
| 315 |
@app.route('/get_reg_list', methods=['POST'])
|
|
|
|
| 439 |
# --- 검색 ---
|
| 440 |
def search_DB_from_multiple_regions(query, selected_regions, region_rag_objects):
|
| 441 |
selected_regions = selected_regions or list(region_rag_objects.keys())
|
|
|
|
| 442 |
logger.info(f"번역된 쿼리: {query}")
|
| 443 |
|
| 444 |
combined_results = []
|
|
|
|
| 494 |
|
| 495 |
return AI_Result
|
| 496 |
|
| 497 |
+
# 법규 타입별 필터 생성 함수
|
| 498 |
+
def create_filter_by_type(regulation_type, regulation_title):
|
| 499 |
+
"""법규 타입에 따라 적절한 필터 딕셔너리 생성"""
|
| 500 |
+
filter_dict = {
|
| 501 |
+
"regulation_part": [],
|
| 502 |
+
"regulation_section": [],
|
| 503 |
+
"chapter_section": [],
|
| 504 |
+
"jo": []
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
# 타입별 매핑
|
| 508 |
+
type_mapping = {
|
| 509 |
+
"part": "regulation_part",
|
| 510 |
+
"section": "regulation_section",
|
| 511 |
+
"chapter": "chapter_section",
|
| 512 |
+
"jo": "jo"
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
filter_key = type_mapping.get(regulation_type, "regulation_part")
|
| 516 |
+
filter_dict[filter_key].append(regulation_title)
|
| 517 |
+
|
| 518 |
+
return filter_dict
|
| 519 |
+
|
| 520 |
+
# 법규들을 타입별로 그룹화하는 함수
|
| 521 |
+
def group_regulations_by_type(selected_regulations):
|
| 522 |
+
"""선택된 법규들을 타입별로 그룹화"""
|
| 523 |
+
grouped = {
|
| 524 |
+
"part": [],
|
| 525 |
+
"section": [],
|
| 526 |
+
"chapter": [],
|
| 527 |
+
"jo": []
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
for regulation in selected_regulations:
|
| 531 |
+
regulation_type = regulation.get('type', 'part')
|
| 532 |
+
regulation_title = regulation.get('title', '')
|
| 533 |
+
|
| 534 |
+
if regulation_title and regulation_type in grouped:
|
| 535 |
+
grouped[regulation_type].append(regulation_title)
|
| 536 |
+
|
| 537 |
+
return grouped
|
| 538 |
+
|
| 539 |
+
# 통합 필터 생성 함수
|
| 540 |
+
def create_combined_filters(grouped_regulations):
|
| 541 |
+
"""그룹화된 법규들로부터 통합 필터 생성"""
|
| 542 |
+
filters = {
|
| 543 |
+
"regulation_part": grouped_regulations["part"],
|
| 544 |
+
"regulation_section": grouped_regulations["section"],
|
| 545 |
+
"chapter_section": grouped_regulations["chapter"],
|
| 546 |
+
"jo": grouped_regulations["jo"]
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
return filters
|
| 550 |
+
|
| 551 |
# --- 실행 ---
|
| 552 |
if __name__ == '__main__':
|
| 553 |
# 로컬 개발용
|