jeongsoo commited on
Commit
1f59ca4
·
1 Parent(s): c94ff24

Add greeting function to app.py

Browse files
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 환경 변수
2
+ .env
3
+
4
+ # 캐시 및 임시 파일
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.so
8
+ .Python
9
+ env/
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+
25
+ # 폴더
26
+ documents/
27
+ faiss_index/
28
+ cached_data/
29
+ preprocessed_index/
30
+ **/__pycache__/
31
+
32
+ # 프로젝트 특화 파일
33
+ parts_extraction_cache.json
34
+ .venv/
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/RAG3_voice.iml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="Python 3.10 (RAG3_voice)" jdkType="Python SDK" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ <component name="PyDocumentationSettings">
11
+ <option name="format" value="PLAIN" />
12
+ <option name="myDocStringFormat" value="Plain" />
13
+ </component>
14
+ </module>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.10 (RAG3_voice)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (RAG3_voice)" project-jdk-type="Python SDK" />
7
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/RAG3_voice.iml" filepath="$PROJECT_DIR$/.idea/RAG3_voice.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ <mapping directory="$PROJECT_DIR$/RAG3_Voice" vcs="Git" />
6
+ </component>
7
+ </project>
app.py ADDED
@@ -0,0 +1,1454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 디버깅을 위한 코드 추가 - 경로 관련 문제 해결
3
+ """
4
+ import os
5
+ import time
6
+ import hashlib
7
+ import pickle
8
+ import json
9
+ import logging
10
+ import glob
11
+ from typing import List, Dict, Tuple, Any, Optional
12
+ from logging.handlers import RotatingFileHandler
13
+ from pathlib import Path
14
+ from langchain.schema import Document
15
+
16
+ from config import (
17
+ PDF_DIRECTORY, CACHE_DIRECTORY, CHUNK_SIZE, CHUNK_OVERLAP,
18
+ LLM_MODEL, LOG_LEVEL, LOG_FILE, print_config, validate_config
19
+ )
20
+ from optimized_document_processor import OptimizedDocumentProcessor
21
+ from vector_store import VectorStore
22
+
23
+ import sys
24
+ print("===== Script starting =====")
25
+ sys.stdout.flush() # 즉시 출력 강제
26
+
27
+ # 주요 함수/메서드 호출 전후에도 디버깅 출력 추가
28
+ print("Loading config...")
29
+ sys.stdout.flush()
30
+ # from config import ... 등의 코드
31
+ print("Config loaded!")
32
+ sys.stdout.flush()
33
+
34
+ # 로깅 설정 개선
35
+ def setup_logging():
36
+ """애플리케이션 로깅 설정"""
37
+ # 로그 레벨 설정
38
+ log_level = getattr(logging, LOG_LEVEL.upper(), logging.INFO)
39
+
40
+ # 로그 포맷 설정
41
+ log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
42
+ formatter = logging.Formatter(log_format)
43
+
44
+ # 루트 로거 설정
45
+ root_logger = logging.getLogger()
46
+ root_logger.setLevel(log_level)
47
+
48
+ # 핸들러 초기화
49
+ # 콘솔 핸들러
50
+ console_handler = logging.StreamHandler()
51
+ console_handler.setFormatter(formatter)
52
+ root_logger.addHandler(console_handler)
53
+
54
+ # 파일 핸들러 (회전식)
55
+ try:
56
+ file_handler = RotatingFileHandler(
57
+ LOG_FILE,
58
+ maxBytes=10*1024*1024, # 10 MB
59
+ backupCount=5
60
+ )
61
+ file_handler.setFormatter(formatter)
62
+ root_logger.addHandler(file_handler)
63
+ except Exception as e:
64
+ console_handler.warning(f"로그 파일 설정 실패: {e}, 콘솔 로깅만 사용합니다.")
65
+
66
+ return logging.getLogger("AutoRAG")
67
+
68
+ # 로거 설정
69
+ logger = setup_logging()
70
+
71
+ # 현재 작업 디렉토리 확인을 위한 디버깅 코드
72
+ current_dir = os.getcwd()
73
+ logger.info(f"현재 작업 디렉토리: {current_dir}")
74
+
75
+ # 설정된 PDF 디렉토리 확인
76
+ abs_pdf_dir = os.path.abspath(PDF_DIRECTORY)
77
+ logger.info(f"설정된 PDF 디렉토리: {PDF_DIRECTORY}")
78
+ logger.info(f"절대 경로로 변환된 PDF 디렉토리: {abs_pdf_dir}")
79
+
80
+ # PDF 디렉토리 존재 확인
81
+ if os.path.exists(abs_pdf_dir):
82
+ logger.info(f"PDF 디렉토리가 존재합니다: {abs_pdf_dir}")
83
+ # 디렉토리 내용 확인
84
+ pdf_files = glob.glob(os.path.join(abs_pdf_dir, "*.pdf"))
85
+ logger.info(f"디렉토리 내 PDF 파일 목록: {pdf_files}")
86
+ else:
87
+ logger.error(f"PDF 디렉토리가 존재하지 않습니다: {abs_pdf_dir}")
88
+ # 상위 디렉토리 내용 확인
89
+ parent_dir = os.path.dirname(abs_pdf_dir)
90
+ logger.info(f"상위 디렉토리: {parent_dir}")
91
+ if os.path.exists(parent_dir):
92
+ dir_contents = os.listdir(parent_dir)
93
+ logger.info(f"상위 디렉토리 내용: {dir_contents}")
94
+
95
+ # 설정 상태 확인
96
+ logger.info("애플리케이션 설정 검증 중...")
97
+ config_status = validate_config()
98
+ if config_status["status"] != "valid":
99
+ for warning in config_status["warnings"]:
100
+ logger.warning(f"설정 경고: {warning}")
101
+
102
+
103
+
104
+
105
+ # 안전한 임포트
106
+ try:
107
+ from rag_chain import RAGChain
108
+ RAG_CHAIN_AVAILABLE = True
109
+ print("RAG 체인 모듈 로드 성공!")
110
+ except ImportError as e:
111
+ logger.warning(f"RAG 체인 모듈을 로드할 수 없습니다: {e}")
112
+ RAG_CHAIN_AVAILABLE = False
113
+ except Exception as e:
114
+ logger.warning(f"RAG 체인 모듈 로드 중 예상치 못한 오류: {e}")
115
+ RAG_CHAIN_AVAILABLE = False
116
+
117
+ # 폴백 RAG 관련 모듈도 미리 확인
118
+ try:
119
+ from fallback_rag_chain import FallbackRAGChain
120
+ FALLBACK_AVAILABLE = True
121
+ print("폴백 RAG 체인 모듈 로드 성공!")
122
+ except ImportError as e:
123
+ logger.warning(f"폴백 RAG 체인 모듈을 로드할 수 없습니다: {e}")
124
+ FALLBACK_AVAILABLE = False
125
+
126
+ try:
127
+ from offline_fallback_rag import OfflineFallbackRAG
128
+ OFFLINE_FALLBACK_AVAILABLE = True
129
+ print("오프라인 폴백 RAG 모듈 로드 성공!")
130
+ except ImportError as e:
131
+ logger.warning(f"오프라인 폴백 RAG 모듈을 로드할 수 없습니다: {e}")
132
+ OFFLINE_FALLBACK_AVAILABLE = False
133
+
134
+
135
+ class DocumentProcessingError(Exception):
136
+ """문서 처리 중 발생하는 예외"""
137
+ pass
138
+
139
+
140
+ class VectorStoreError(Exception):
141
+ """벡터 스토어 작업 중 발생하는 예외"""
142
+ pass
143
+
144
+
145
+ class RAGInitializationError(Exception):
146
+ """RAG 체인 초기화 중 발생하는 예외"""
147
+ pass
148
+
149
+
150
+ class ConfigurationError(Exception):
151
+ """설정 관련 오류"""
152
+ pass
153
+
154
+
155
+ class AutoRAGChatApp:
156
+ """
157
+ documents 폴더의 PDF 파일을 자동으로 처리하는 RAG 챗봇
158
+ """
159
+
160
+ def __init__(self):
161
+ """
162
+ RAG 챗봇 애플리케이션 초기화
163
+ """
164
+ try:
165
+ logger.info("AutoRAGChatApp 초기화 시작")
166
+
167
+ # 데이터 디렉토리 정의 (설정에서 가져옴)
168
+ # 절대 경로로 변환하여 사용
169
+ self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
170
+ self.cache_directory = os.path.abspath(CACHE_DIRECTORY)
171
+ self.index_file = os.path.join(self.cache_directory, "file_index.json")
172
+ self.chunks_dir = os.path.join(self.cache_directory, "chunks")
173
+ self.vector_index_dir = os.path.join(self.cache_directory, "vector_index")
174
+
175
+ logger.info(f"설정된 PDF 디렉토리 (절대 경로): {self.pdf_directory}")
176
+
177
+ # 디렉토리 검증
178
+ self._verify_pdf_directory()
179
+
180
+ # 디렉토리 생성
181
+ self._ensure_directories_exist()
182
+
183
+ logger.info(f"PDF 문서 디렉토리: '{self.pdf_directory}'")
184
+ logger.info(f"캐시 디렉토리: '{self.cache_directory}'")
185
+
186
+ # 컴포넌트 초기화
187
+ try:
188
+ self.document_processor = OptimizedDocumentProcessor(
189
+ chunk_size=CHUNK_SIZE,
190
+ chunk_overlap=CHUNK_OVERLAP
191
+ )
192
+ except Exception as e:
193
+ logger.error(f"문서 처리기 초기화 실패: {e}")
194
+ raise DocumentProcessingError(f"문서 처리기 초기화 실패: {str(e)}")
195
+
196
+ # 벡터 저장소 초기화
197
+ try:
198
+ self.vector_store = VectorStore(use_milvus=False)
199
+ except Exception as e:
200
+ logger.error(f"벡터 저장소 초기화 실패: {e}")
201
+ raise VectorStoreError(f"벡터 저장소 초기화 실패: {str(e)}")
202
+
203
+ # 문서 인덱스 로드
204
+ self.file_index = self._load_file_index()
205
+
206
+ # 기본 변수 초기화
207
+ self.documents = []
208
+ self.processed_files = []
209
+ self.is_initialized = False
210
+
211
+ # 시작 시 자동으로 문서 로드 및 처리
212
+ logger.info("문서 자동 로드 및 처리 시작...")
213
+ self.auto_process_documents()
214
+
215
+ logger.info("AutoRAGChatApp 초기화 완료")
216
+
217
+ except Exception as e:
218
+ logger.critical(f"애플리케이션 초기화 중 심각한 오류: {e}", exc_info=True)
219
+ # 기본 상태 설정으로 최소한의 기능 유지
220
+ self.pdf_directory = os.path.abspath(PDF_DIRECTORY)
221
+ self.documents = []
222
+ self.processed_files = []
223
+ self.is_initialized = False
224
+ self.file_index = {}
225
+
226
+ def _ensure_directories_exist(self) -> None:
227
+ """
228
+ 필요한 디렉토리가 존재하는지 확인하고 생성
229
+ """
230
+ directories = [
231
+ self.pdf_directory,
232
+ self.cache_directory,
233
+ self.chunks_dir,
234
+ self.vector_index_dir
235
+ ]
236
+
237
+ for directory in directories:
238
+ try:
239
+ os.makedirs(directory, exist_ok=True)
240
+ except Exception as e:
241
+ logger.error(f"디렉토리 생성 실패 '{directory}': {e}")
242
+ raise OSError(f"디렉토리 생성 실패 '{directory}': {str(e)}")
243
+
244
+ def _process_pdf_file(self, file_path: str) -> List[Document]:
245
+ """
246
+ PDF 파일 처리 - docling 실패 시 PyPDFLoader 사용
247
+
248
+ Args:
249
+ file_path: 처리할 PDF 파일 경로
250
+
251
+ Returns:
252
+ 처리된 문서 청크 리스트
253
+ """
254
+ if not os.path.exists(file_path):
255
+ logger.error(f"파일이 존재하지 않음: {file_path}")
256
+ raise FileNotFoundError(f"파일이 존재하지 않음: {file_path}")
257
+
258
+ try:
259
+ logger.info(f"docling으로 처리 시도: {file_path}")
260
+
261
+ # docling 사용 시도
262
+ try:
263
+ # 10초 타임아웃 설정 (옵션)
264
+ import signal
265
+
266
+ def timeout_handler(signum, frame):
267
+ raise TimeoutError("docling 처리 시간 초과 (60초)")
268
+
269
+ # 리눅스/맥에서만 작동 (윈도우에서는 무시됨)
270
+ try:
271
+ signal.signal(signal.SIGALRM, timeout_handler)
272
+ signal.alarm(60) # 60초 타임아웃
273
+ except (AttributeError, ValueError) as se:
274
+ logger.warning(f"시그널 설정 실패 (윈도우 환경일 수 있음): {se}")
275
+
276
+ # docling으로 처리 시도
277
+ chunks = self.document_processor.process_pdf(file_path, use_docling=True)
278
+
279
+ # 타임아웃 취소
280
+ try:
281
+ signal.alarm(0)
282
+ except (AttributeError, ValueError):
283
+ pass
284
+
285
+ return chunks
286
+
287
+ except TimeoutError as te:
288
+ logger.warning(f"docling 처리 시간 초과: {te}")
289
+ logger.info("PyPDFLoader로 대체합니다.")
290
+
291
+ # PyPDFLoader로 대체
292
+ try:
293
+ return self.document_processor.process_pdf(file_path, use_docling=False)
294
+ except Exception as inner_e:
295
+ logger.error(f"PyPDFLoader 처리 오류: {inner_e}", exc_info=True)
296
+ raise DocumentProcessingError(f"PDF 로딩 실패 (PyPDFLoader): {str(inner_e)}")
297
+
298
+ except Exception as e:
299
+ # docling 오류 확인
300
+ error_str = str(e)
301
+ if "Invalid code point" in error_str or "RuntimeError" in error_str:
302
+ logger.warning(f"docling 처리 오류 (코드 포인트 문제): {error_str}")
303
+ logger.info("PyPDFLoader로 대체합니다.")
304
+ else:
305
+ logger.warning(f"docling 처리 오류: {error_str}")
306
+ logger.info("PyPDFLoader로 대체합니다.")
307
+
308
+ # PyPDFLoader로 대체
309
+ try:
310
+ return self.document_processor.process_pdf(file_path, use_docling=False)
311
+ except Exception as inner_e:
312
+ logger.error(f"PyPDFLoader 처리 오류: {inner_e}", exc_info=True)
313
+ raise DocumentProcessingError(f"PDF 로딩 실패 (PyPDFLoader): {str(inner_e)}")
314
+
315
+ except DocumentProcessingError:
316
+ # 이미 래핑된 예외는 그대로 전달
317
+ raise
318
+ except Exception as e:
319
+ logger.error(f"PDF 처리 중 심각한 오류: {e}", exc_info=True)
320
+ # 빈 청크라도 반환하여 전체 처리가 중단되지 않도록 함
321
+ logger.warning(f"'{file_path}' 처리 실패로 빈 청크 목록 반환")
322
+ return []
323
+
324
+ def _load_file_index(self) -> Dict[str, Dict[str, Any]]:
325
+ """
326
+ 파일 인덱스 로드
327
+
328
+ Returns:
329
+ 파일 경로 -> 메타데이터 매핑
330
+ """
331
+ if os.path.exists(self.index_file):
332
+ try:
333
+ with open(self.index_file, 'r', encoding='utf-8') as f:
334
+ return json.load(f)
335
+ except json.JSONDecodeError as e:
336
+ logger.error(f"인덱스 파일 JSON 파싱 실패: {e}")
337
+ logger.warning("손상된 인덱스 파일, 새로운 인덱스를 생성합니다.")
338
+ return {}
339
+ except Exception as e:
340
+ logger.error(f"인덱스 파일 로드 실패: {e}")
341
+ return {}
342
+ return {}
343
+
344
+ def _save_file_index(self) -> None:
345
+ """
346
+ 파일 인덱스 저장
347
+ """
348
+ try:
349
+ with open(self.index_file, 'w', encoding='utf-8') as f:
350
+ json.dump(self.file_index, f, ensure_ascii=False, indent=2)
351
+ logger.debug("파일 인덱스 저장 완료")
352
+ except Exception as e:
353
+ logger.error(f"파일 인덱스 저장 실패: {e}")
354
+ raise IOError(f"파일 인덱스 저장 실패: {str(e)}")
355
+
356
+ def _calculate_file_hash(self, file_path: str) -> str:
357
+ """
358
+ 파일 해시 계산
359
+
360
+ Args:
361
+ file_path: 파일 경로
362
+
363
+ Returns:
364
+ MD5 해시값
365
+ """
366
+ if not os.path.exists(file_path):
367
+ logger.error(f"해시 계산 실패 - 파일이 존재하지 않음: {file_path}")
368
+ raise FileNotFoundError(f"파일이 존재하지 않음: {file_path}")
369
+
370
+ try:
371
+ hasher = hashlib.md5()
372
+ with open(file_path, 'rb') as f:
373
+ buf = f.read(65536)
374
+ while len(buf) > 0:
375
+ hasher.update(buf)
376
+ buf = f.read(65536)
377
+ return hasher.hexdigest()
378
+ except Exception as e:
379
+ logger.error(f"파일 해시 계산 중 오류: {e}")
380
+ raise IOError(f"파일 해시 계산 실패: {str(e)}")
381
+
382
+ def _is_file_processed(self, file_path: str) -> bool:
383
+ """
384
+ 파일이 이미 처리되었고 변경되지 않았는지 확인
385
+
386
+ Args:
387
+ file_path: 파일 경로
388
+
389
+ Returns:
390
+ 처리 여부
391
+ """
392
+ # 파일 존재 확인
393
+ if not os.path.exists(file_path):
394
+ logger.warning(f"파일이 존재하지 않음: {file_path}")
395
+ return False
396
+
397
+ # 인덱스에 파일 존재 여부 확인
398
+ if file_path not in self.file_index:
399
+ return False
400
+
401
+ try:
402
+ # 현재 해시값 계산
403
+ current_hash = self._calculate_file_hash(file_path)
404
+
405
+ # 저장된 해시값과 비교
406
+ if self.file_index[file_path]['hash'] != current_hash:
407
+ logger.info(f"파일 변경 감지: {file_path}")
408
+ return False
409
+
410
+ # 청크 파일 존재 확인
411
+ chunks_path = self.file_index[file_path]['chunks_path']
412
+ if not os.path.exists(chunks_path):
413
+ logger.warning(f"청크 파일이 존재하지 않음: {chunks_path}")
414
+ return False
415
+
416
+ return True
417
+ except Exception as e:
418
+ logger.error(f"파일 처리 상태 확인 중 오류: {e}")
419
+ return False
420
+
421
+ def _get_chunks_path(self, file_hash: str) -> str:
422
+ """
423
+ 청크 파일 경로 생성
424
+
425
+ Args:
426
+ file_hash: 파일 해시값
427
+
428
+ Returns:
429
+ 청크 파일 경로
430
+ """
431
+ return os.path.join(self.chunks_dir, f"{file_hash}.pkl")
432
+
433
+ def _save_chunks(self, file_path: str, chunks: List[Document]) -> None:
434
+ """
435
+ 청크 데이터 저장
436
+
437
+ Args:
438
+ file_path: 원본 파일 경로
439
+ chunks: 문서 청크 리스트
440
+ """
441
+ try:
442
+ # 해시 계산
443
+ file_hash = self._calculate_file_hash(file_path)
444
+
445
+ # 청크 파일 경로
446
+ chunks_path = self._get_chunks_path(file_hash)
447
+
448
+ # 청크 데이터 저장
449
+ with open(chunks_path, 'wb') as f:
450
+ pickle.dump(chunks, f)
451
+
452
+ # 인덱스 업데이트
453
+ self.file_index[file_path] = {
454
+ 'hash': file_hash,
455
+ 'chunks_path': chunks_path,
456
+ 'last_processed': time.time(),
457
+ 'chunks_count': len(chunks),
458
+ 'file_size': os.path.getsize(file_path),
459
+ 'file_name': os.path.basename(file_path)
460
+ }
461
+
462
+ # 인덱스 저장
463
+ self._save_file_index()
464
+
465
+ logger.info(f"청크 저장 완료: {file_path} ({len(chunks)}개 청크)")
466
+ except Exception as e:
467
+ logger.error(f"청크 저장 실패: {e}", exc_info=True)
468
+ raise IOError(f"청크 저장 실패: {str(e)}")
469
+
470
+ def _load_chunks(self, file_path: str) -> List[Document]:
471
+ """
472
+ 저장된 청크 데이터 로드
473
+
474
+ Args:
475
+ file_path: 파일 경로
476
+
477
+ Returns:
478
+ 문서 청크 리스트
479
+ """
480
+ if file_path not in self.file_index:
481
+ logger.error(f"인덱스에 파일이 존재하지 않음: {file_path}")
482
+ raise KeyError(f"인덱스에 파일이 존재하지 않음: {file_path}")
483
+
484
+ chunks_path = self.file_index[file_path]['chunks_path']
485
+
486
+ if not os.path.exists(chunks_path):
487
+ logger.error(f"청크 파일이 존재하지 않음: {chunks_path}")
488
+ raise FileNotFoundError(f"청크 파일이 존재하지 않음: {chunks_path}")
489
+
490
+ try:
491
+ with open(chunks_path, 'rb') as f:
492
+ chunks = pickle.load(f)
493
+
494
+ logger.info(f"청크 로드 완료: {file_path} ({len(chunks)}개 청크)")
495
+ return chunks
496
+ except pickle.UnpicklingError as e:
497
+ logger.error(f"청크 파일 역직렬화 실패: {e}")
498
+ raise IOError(f"청크 파일 손상: {str(e)}")
499
+ except Exception as e:
500
+ logger.error(f"청크 로드 실패: {e}", exc_info=True)
501
+ raise IOError(f"청크 로드 실패: {str(e)}")
502
+
503
+ def _verify_pdf_directory(self):
504
+ """PDF 디렉토리 검증 및 파일 존재 확인"""
505
+ try:
506
+ # 디렉토리 존재 확인
507
+ if not os.path.exists(self.pdf_directory):
508
+ try:
509
+ logger.warning(f"PDF 디렉토리가 존재하지 않아 생성합니다: {self.pdf_directory}")
510
+ os.makedirs(self.pdf_directory, exist_ok=True)
511
+ except Exception as e:
512
+ logger.error(f"PDF 디렉토리 생성 실패: {e}")
513
+ raise
514
+
515
+ # 디렉토리인지 확인
516
+ if not os.path.isdir(self.pdf_directory):
517
+ logger.error(f"PDF 경로가 디렉토리가 아닙니다: {self.pdf_directory}")
518
+ raise ConfigurationError(f"PDF 경로가 디렉토리가 아닙니다: {self.pdf_directory}")
519
+
520
+ # PDF 파일 존재 확인
521
+ pdf_files = [f for f in os.listdir(self.pdf_directory) if f.lower().endswith('.pdf')]
522
+
523
+ if pdf_files:
524
+ logger.info(f"PDF 디렉토리에서 {len(pdf_files)}개의 PDF 파일을 찾았습니다: {pdf_files}")
525
+ else:
526
+ # 여러 경로에서 PDF 파일 탐색 시도
527
+ alternative_paths = [
528
+ "./documents",
529
+ "../documents",
530
+ "documents",
531
+ os.path.join(os.getcwd(), "documents")
532
+ ]
533
+
534
+ found_pdfs = False
535
+ for alt_path in alternative_paths:
536
+ if os.path.exists(alt_path) and os.path.isdir(alt_path):
537
+ alt_pdf_files = [f for f in os.listdir(alt_path) if f.lower().endswith('.pdf')]
538
+ if alt_pdf_files:
539
+ logger.warning(f"대체 경로 '{alt_path}'에서 PDF 파일을 찾았습니다. 이 경로를 사용합니다.")
540
+ self.pdf_directory = os.path.abspath(alt_path)
541
+ found_pdfs = True
542
+ break
543
+
544
+ if not found_pdfs:
545
+ logger.warning(f"PDF 디렉토리에 PDF 파일이 없습니다: {self.pdf_directory}")
546
+ logger.info("PDF 파일을 디렉토리에 추가해주세요.")
547
+
548
+ except Exception as e:
549
+ logger.error(f"PDF 디렉토리 검증 중 오류: {e}", exc_info=True)
550
+ raise
551
+
552
+ def auto_process_documents(self) -> str:
553
+ """
554
+ documents 폴더의 PDF 파일 자동 처리
555
+
556
+ Returns:
557
+ 처리 결과 메시지
558
+ """
559
+ try:
560
+ start_time = time.time()
561
+
562
+ # PDF 파일 목록 수집을 개선하여 다양한 경로 처리
563
+ try:
564
+ pdf_files = []
565
+
566
+ # 설정된 디렉토리에서 PDF 파일 찾기
567
+ logger.info(f"PDF 파일 검색 경로: {self.pdf_directory}")
568
+
569
+ if os.path.exists(self.pdf_directory) and os.path.isdir(self.pdf_directory):
570
+ # 디렉토리 내용 출력 (디버깅용)
571
+ dir_contents = os.listdir(self.pdf_directory)
572
+ logger.info(f"디렉토리 내용: {dir_contents}")
573
+
574
+ # PDF 파일만 필터링
575
+ for filename in os.listdir(self.pdf_directory):
576
+ if filename.lower().endswith('.pdf'):
577
+ file_path = os.path.join(self.pdf_directory, filename)
578
+ if os.path.isfile(file_path): # 실제 파일인지 확인
579
+ pdf_files.append(file_path)
580
+ logger.info(f"PDF 파일 찾음: {file_path}")
581
+
582
+ # 발견된 모든 파일 로그
583
+ logger.info(f"발견된 모든 PDF 파일: {pdf_files}")
584
+
585
+ except FileNotFoundError:
586
+ logger.error(f"PDF 디렉토리를 찾을 수 없음: {self.pdf_directory}")
587
+ return f"'{self.pdf_directory}' 디렉토리를 찾을 수 없습니다. 디렉토리가 존재하는지 확인하세요."
588
+ except PermissionError:
589
+ logger.error(f"PDF 디렉토리 접근 권한 없음: {self.pdf_directory}")
590
+ return f"'{self.pdf_directory}' 디렉토리에 접근할 수 없습니다. 권한을 확인하세요."
591
+
592
+ if not pdf_files:
593
+ logger.warning(f"'{self.pdf_directory}' 폴더에 PDF 파일이 없습니다.")
594
+ return f"'{self.pdf_directory}' 폴더에 PDF 파일이 없습니다."
595
+
596
+ logger.info(f"발견된 PDF 파일: {len(pdf_files)}개")
597
+
598
+ # 폴더 내 PDF 파일 처리
599
+ new_files = []
600
+ updated_files = []
601
+ cached_files = []
602
+ failed_files = []
603
+ all_chunks = []
604
+
605
+ for file_path in pdf_files:
606
+ try:
607
+ if self._is_file_processed(file_path):
608
+ # 캐시에서 청크 로드
609
+ try:
610
+ chunks = self._load_chunks(file_path)
611
+ all_chunks.extend(chunks)
612
+ cached_files.append(file_path)
613
+ self.processed_files.append(os.path.basename(file_path))
614
+ except Exception as e:
615
+ logger.error(f"캐시된 청크 로드 실패: {e}")
616
+ # 파일을 다시 처리
617
+ logger.info(f"캐시 실패로 파일 재처리: {file_path}")
618
+ chunks = self._process_pdf_file(file_path)
619
+ if chunks:
620
+ self._save_chunks(file_path, chunks)
621
+ all_chunks.extend(chunks)
622
+ updated_files.append(file_path)
623
+ self.processed_files.append(os.path.basename(file_path))
624
+ else:
625
+ failed_files.append(file_path)
626
+ else:
627
+ # 새 파일 또는 변경된 파일 처리
628
+ logger.info(f"처리 중: {file_path}")
629
+
630
+ try:
631
+ # 개선된 PDF 처리 메서드 사용
632
+ chunks = self._process_pdf_file(file_path)
633
+
634
+ if chunks: # 청크가 있는 경우에만 저장
635
+ # 청크 저장
636
+ self._save_chunks(file_path, chunks)
637
+
638
+ all_chunks.extend(chunks)
639
+ if file_path in self.file_index:
640
+ updated_files.append(file_path)
641
+ else:
642
+ new_files.append(file_path)
643
+
644
+ self.processed_files.append(os.path.basename(file_path))
645
+ else:
646
+ logger.warning(f"'{file_path}' 처리 실패: 추출된 청크 없음")
647
+ failed_files.append(file_path)
648
+ except Exception as e:
649
+ logger.error(f"'{file_path}' 처리 중 오류: {e}", exc_info=True)
650
+ failed_files.append(file_path)
651
+ except Exception as e:
652
+ logger.error(f"'{file_path}' 파일 처리 루프 중 오류: {e}", exc_info=True)
653
+ failed_files.append(file_path)
654
+
655
+ # 모든 청크 저장
656
+ self.documents = all_chunks
657
+
658
+ processing_time = time.time() - start_time
659
+ logger.info(f"문서 처리 완료: {len(all_chunks)}개 청크, {processing_time:.2f}초")
660
+
661
+ # 벡터 인덱스 처리
662
+ try:
663
+ self._process_vector_index(new_files, updated_files)
664
+ except Exception as e:
665
+ logger.error(f"벡터 인덱스 처리 실패: {e}", exc_info=True)
666
+ return f"문서는 처리되었으나 벡터 인덱스 생성에 실패했습니다: {str(e)}"
667
+
668
+ # RAG 체인 초기화
669
+ if RAG_CHAIN_AVAILABLE:
670
+ try:
671
+ logger.info("RAGChain으로 초기화를 시도합니다.")
672
+ self.rag_chain = RAGChain(self.vector_store)
673
+ self.is_initialized = True
674
+ logger.info("RAG 체인 초기화 성공")
675
+ except Exception as e:
676
+ logger.error(f"RAG 체인 초기화 실패: {e}", exc_info=True)
677
+
678
+ # FallbackRAGChain으로 대체 시도
679
+ try:
680
+ logger.info("FallbackRAGChain으로 대체합니다...")
681
+ from fallback_rag_chain import FallbackRAGChain
682
+ self.rag_chain = FallbackRAGChain(self.vector_store)
683
+ self.is_initialized = True
684
+ logger.info("폴백 RAG 체인 초기화 성공")
685
+ except Exception as fallback_e:
686
+ logger.error(f"폴백 RAG 체인 초기화 실패: {fallback_e}", exc_info=True)
687
+
688
+ # SimpleRAGChain 시도 (최후의 수단)
689
+ try:
690
+ logger.info("SimpleRAGChain으로 대체합니다...")
691
+ from simple_rag_chain import SimpleRAGChain
692
+
693
+ # API 정보 가져오기
694
+ try:
695
+ from config import DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT
696
+ logger.info(f"설정 파일에서 DeepSeek API 정보를 로드했습니다: 모델={DEEPSEEK_MODEL}")
697
+ except ImportError:
698
+ # 설정 파일에서 가져올 수 없는 경우 환경 변수 확인
699
+ DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
700
+ DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
701
+ DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT",
702
+ "https://api.deepseek.com/v1/chat/completions")
703
+ logger.info(f"환경 변수에서 DeepSeek API 정보를 로드했습니다: 모델={DEEPSEEK_MODEL}")
704
+
705
+ # SimpleRAGChain 초기화 시도
706
+ self.rag_chain = SimpleRAGChain(self.vector_store)
707
+ self.is_initialized = True
708
+ logger.info("SimpleRAGChain 초기화 성공")
709
+ except Exception as simple_e:
710
+ logger.error(f"모든 RAG 체인 초기화 실패: {simple_e}", exc_info=True)
711
+ return f"문서와 벡터 인덱스는 처리되었으나 RAG 체인 초기화에 실패했습니다: {str(e)}"
712
+ else:
713
+ # RAGChain을 사용할 수 없는 경우
714
+ try:
715
+ logger.info("기본 RAG Chain을 사용할 수 없어 대체 버전을 시도합니다...")
716
+
717
+ # FallbackRAGChain 시도
718
+ try:
719
+ from fallback_rag_chain import FallbackRAGChain
720
+ self.rag_chain = FallbackRAGChain(self.vector_store)
721
+ self.is_initialized = True
722
+ logger.info("폴백 RAG 체인 초기화 성공")
723
+ except Exception as fallback_e:
724
+ logger.error(f"폴백 RAG 체인 초기화 실패: {fallback_e}", exc_info=True)
725
+
726
+ # SimpleRAGChain 시도 (최후의 수단)
727
+ try:
728
+ from simple_rag_chain import SimpleRAGChain
729
+ self.rag_chain = SimpleRAGChain(self.vector_store)
730
+ self.is_initialized = True
731
+ logger.info("SimpleRAGChain 초기화 성공")
732
+ except Exception as simple_e:
733
+ logger.error(f"모든 RAG 체인 초기화 실패: {simple_e}", exc_info=True)
734
+ return f"문서와 벡터 인덱스는 처리되었으나 RAG 체인 초기화에 실패했습니다"
735
+ except Exception as e:
736
+ logger.error(f"RAG 체인 초기화 실패: {e}", exc_info=True)
737
+ return f"문서와 벡터 인덱스는 처리되었으나 RAG 체인 초기화에 실패했습니다: {str(e)}"
738
+
739
+ # 성공 메시지 생성
740
+ result_message = f"""문서 처리 완료!
741
+ - 처리된 파일: {len(pdf_files)}개
742
+ - 캐시된 파일: {len(cached_files)}개
743
+ - 새 파일: {len(new_files)}개
744
+ - 업데이트된 파일: {len(updated_files)}개
745
+ - 실패한 파일: {len(failed_files)}개
746
+ - 총 청크 수: {len(all_chunks)}개
747
+ - 처리 시간: {processing_time:.2f}초
748
+ 이제 질문할 준비가 되었습니다!"""
749
+
750
+ return result_message
751
+
752
+ except Exception as e:
753
+ error_message = f"문서 처리 중 오류 발생: {str(e)}"
754
+ logger.error(error_message, exc_info=True)
755
+ return error_message
756
+
757
+ def _process_vector_index(self, new_files: List[str], updated_files: List[str]) -> None:
758
+ """
759
+ 벡터 인덱스 처리
760
+
761
+ Args:
762
+ new_files: 새로 추가된 파일 목록
763
+ updated_files: 업데이트된 파일 목록
764
+ """
765
+ # 벡터 인덱스 저장 경로 확인
766
+ if os.path.exists(self.vector_index_dir) and any(os.listdir(self.vector_index_dir)):
767
+ # 기존 벡터 인덱스 로드
768
+ try:
769
+ logger.info("저장된 벡터 인덱스 로드 중...")
770
+ vector_store_loaded = self.vector_store.load_local(self.vector_index_dir)
771
+
772
+ # 인덱스 로드 성공 확인
773
+ if self.vector_store.vector_store is not None:
774
+ # 새 문서나 변경된 문서가 있으면 인덱스 업데이트
775
+ if new_files or updated_files:
776
+ logger.info("벡터 인덱스 업데이트 중...")
777
+ self.vector_store.add_documents(self.documents)
778
+
779
+ logger.info("벡터 인덱스 로드 완료")
780
+ else:
781
+ logger.warning("벡터 인덱스를 로드했으나 유효하지 않음, 새로 생성합니다.")
782
+ self.vector_store.create_or_load(self.documents)
783
+
784
+ except Exception as e:
785
+ logger.error(f"벡터 인덱스 로드 실패, 새로 생성합니다: {e}", exc_info=True)
786
+ # 새 벡터 인덱스 생성
787
+ self.vector_store.create_or_load(self.documents)
788
+ else:
789
+ # 새 벡터 인덱스 생성
790
+ logger.info("새 벡터 인덱스 생성 중...")
791
+ self.vector_store.create_or_load(self.documents)
792
+
793
+ # 벡터 인덱스 저장
794
+ if self.vector_store and self.vector_store.vector_store is not None:
795
+ try:
796
+ logger.info(f"벡터 인덱스 저장 중: {self.vector_index_dir}")
797
+ save_result = self.vector_store.save_local(self.vector_index_dir)
798
+ logger.info(f"벡터 인덱스 저장 완료: {self.vector_index_dir}")
799
+ except Exception as e:
800
+ logger.error(f"벡터 인덱스 저장 실패: {e}", exc_info=True)
801
+ raise VectorStoreError(f"벡터 인덱스 저장 실패: {str(e)}")
802
+ else:
803
+ logger.warning("벡터 인덱스가 초기화되지 않아 저장하지 않습니다.")
804
+
805
+ def reset_cache(self) -> str:
806
+ """
807
+ 캐시 초기화
808
+
809
+ Returns:
810
+ 결과 메시지
811
+ """
812
+ try:
813
+ # 청크 파일 삭제
814
+ try:
815
+ for filename in os.listdir(self.chunks_dir):
816
+ file_path = os.path.join(self.chunks_dir, filename)
817
+ if os.path.isfile(file_path):
818
+ os.remove(file_path)
819
+ logger.info("청크 캐시 파일 삭제 완료")
820
+ except Exception as e:
821
+ logger.error(f"청크 파일 삭제 중 오류: {e}")
822
+ return f"청크 파일 삭제 중 오류 발생: {str(e)}"
823
+
824
+ # 인덱스 초기화
825
+ self.file_index = {}
826
+ try:
827
+ self._save_file_index()
828
+ logger.info("파일 인덱스 초기화 완료")
829
+ except Exception as e:
830
+ logger.error(f"인덱스 파일 초기화 중 오류: {e}")
831
+ return f"인덱스 파일 초기화 중 ��류 발생: {str(e)}"
832
+
833
+ # 벡터 인덱스 삭제
834
+ try:
835
+ for filename in os.listdir(self.vector_index_dir):
836
+ file_path = os.path.join(self.vector_index_dir, filename)
837
+ if os.path.isfile(file_path):
838
+ os.remove(file_path)
839
+ logger.info("벡터 인덱스 파일 삭제 완료")
840
+ except Exception as e:
841
+ logger.error(f"벡터 인덱스 파일 삭제 중 오류: {e}")
842
+ return f"벡터 인덱스 파일 삭제 중 오류 발생: {str(e)}"
843
+
844
+ self.documents = []
845
+ self.processed_files = []
846
+ self.is_initialized = False
847
+
848
+ logger.info("캐시 초기화 완료")
849
+ return "캐시가 초기화되었습니다. 다음 실행 시 모든 문서가 다시 처리됩니다."
850
+ except Exception as e:
851
+ error_msg = f"캐시 초기화 중 오류 발생: {str(e)}"
852
+ logger.error(error_msg, exc_info=True)
853
+ return error_msg
854
+
855
+ def process_query(self, query: str, chat_history: List[Tuple[str, str]]) -> Tuple[str, List[Tuple[str, str]]]:
856
+ """
857
+ 사용자 쿼리 처리
858
+
859
+ Args:
860
+ query: 사용자 질문
861
+ chat_history: 대화 기록
862
+
863
+ Returns:
864
+ 응답 및 업데이트된 대화 기록
865
+ """
866
+ if not query or not query.strip():
867
+ response = "질문이 비어 있습니다. 질문을 입력해 주세요."
868
+ chat_history.append((query, response))
869
+ return "", chat_history
870
+
871
+ if not self.is_initialized:
872
+ response = "문서 로드가 초기화되지 않았습니다. 자동 로드를 시도합니다."
873
+ chat_history.append((query, response))
874
+
875
+ # 자동 로드 시도
876
+ try:
877
+ init_result = self.auto_process_documents()
878
+ if not self.is_initialized:
879
+ response = f"문서를 로드할 수 없습니다. 'documents' 폴더에 PDF 파일이 있는지 확인하세요. 초기화 결과: {init_result}"
880
+ chat_history.append((query, response))
881
+ return "", chat_history
882
+ except Exception as e:
883
+ response = f"문서 로드 중 오류 발생: {str(e)}"
884
+ logger.error(f"자동 로드 실패: {e}", exc_info=True)
885
+ chat_history.append((query, response))
886
+ return "", chat_history
887
+
888
+ try:
889
+ # RAG 체인 실행 및 응답 생성
890
+ start_time = time.time()
891
+ logger.info(f"쿼리 처리 시작: {query}")
892
+
893
+ # rag_chain이 초기화되었는지 확인
894
+ if not hasattr(self, 'rag_chain') or self.rag_chain is None:
895
+ raise RAGInitializationError("RAG 체인이 초기화되지 않았습니다")
896
+
897
+ # 1. 먼저 표준 RAG 체인으로 시도
898
+ try:
899
+ response = self.rag_chain.run(query)
900
+ logger.info(f"기본 RAG 체인으로 응답 생성 성공")
901
+ except Exception as rag_error:
902
+ logger.error(f"기본 RAG 체인 실행 실패: {rag_error}, 대안 시도")
903
+
904
+ # 2. DeepSeek API 직접 호출 시도 (RAG 체인 우회)
905
+ try:
906
+ # DeepSeek API 정보 가져오기
907
+ try:
908
+ from config import DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT
909
+ except ImportError:
910
+ # 설정 모듈에서 가져올 수 없는 경우 기본값 설정
911
+ DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
912
+ DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
913
+ DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT",
914
+ "https://api.deepseek.com/v1/chat/completions")
915
+
916
+ # 직접 API 호출 함수 정의 (외부 모듈 의존성 제거)
917
+ def direct_api_call(query, context, api_key, model_name, endpoint, max_retries=3, timeout=60):
918
+ """DeepSeek API 직접 호출 함수"""
919
+ import requests
920
+ import json
921
+ import time
922
+
923
+ # 프롬프트 길이 제한
924
+ if len(context) > 6000:
925
+ context = context[:2500] + "\n...(중략)...\n" + context[-2500:]
926
+
927
+ # 프롬프트 구성
928
+ prompt = f"""
929
+ 다음 정보를 기반으로 질문에 정확하게 답변해주세요.
930
+
931
+ 질문: {query}
932
+
933
+ 참고 정보:
934
+ {context}
935
+
936
+ 참고 정보에 답이 있으면 반드시 그 정보를 기반으로 답변하세요.
937
+ 참고 정보에 ��이 없는 경우에는 일반적인 지식을 활용하여 답변할 수 있지만, "제공된 문서에는 이 정보가 없으나, 일반적으로는..." 식으로 시작하세요.
938
+ 답변은 정확하고 간결하게 제공하되, 가능한 참고 정보에서 근거를 찾아 설명해주세요.
939
+ 참고 정보의 출처도 함께 알려주세요.
940
+ """
941
+
942
+ # API 요청 시도
943
+ headers = {
944
+ "Content-Type": "application/json",
945
+ "Authorization": f"Bearer {api_key}"
946
+ }
947
+
948
+ payload = {
949
+ "model": model_name,
950
+ "messages": [{"role": "user", "content": prompt}],
951
+ "temperature": 0.3,
952
+ "max_tokens": 1000
953
+ }
954
+
955
+ # 재시도 로직
956
+ retry_delay = 1.0
957
+ for attempt in range(max_retries):
958
+ try:
959
+ logger.info(f"DeepSeek API 직접 호출 시도 ({attempt + 1}/{max_retries})...")
960
+ response = requests.post(
961
+ endpoint,
962
+ headers=headers,
963
+ json=payload,
964
+ timeout=timeout
965
+ )
966
+
967
+ if response.status_code == 200:
968
+ result = response.json()
969
+ content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
970
+ logger.info(f"DeepSeek API 직접 호출 성공")
971
+ return content
972
+ else:
973
+ logger.warning(f"API 오류: 상태 코드 {response.status_code}")
974
+ # 요청 한도인 경우 더 오래 대기
975
+ if response.status_code == 429:
976
+ retry_delay = min(retry_delay * 3, 15)
977
+ else:
978
+ retry_delay = min(retry_delay * 2, 10)
979
+
980
+ if attempt < max_retries - 1:
981
+ logger.info(f"{retry_delay}초 후 재시도...")
982
+ time.sleep(retry_delay)
983
+ except Exception as e:
984
+ logger.error(f"API 호출 오류: {e}")
985
+ if attempt < max_retries - 1:
986
+ logger.info(f"{retry_delay}초 후 재시도...")
987
+ time.sleep(retry_delay)
988
+ retry_delay = min(retry_delay * 2, 10)
989
+
990
+ # 모든 시도 실패
991
+ raise Exception("최대 재시도 횟수 초과")
992
+
993
+ # 벡터 검색 수행
994
+ if self.vector_store and hasattr(self.vector_store, "similarity_search"):
995
+ logger.info("벡터 검색 수행...")
996
+ docs = self.vector_store.similarity_search(query, k=5)
997
+
998
+ # 검색 결과 컨텍스트 구성
999
+ context_parts = []
1000
+ for i, doc in enumerate(docs, 1):
1001
+ source = doc.metadata.get("source", "알 수 없는 출처")
1002
+ page = doc.metadata.get("page", "")
1003
+ source_info = f"{source}"
1004
+ if page:
1005
+ source_info += f" (페이지: {page})"
1006
+ context_parts.append(f"[참고자료 {i}] - 출처: {source_info}\n{doc.page_content}\n")
1007
+ context = "\n".join(context_parts)
1008
+
1009
+ # 직접 API 호출
1010
+ logger.info("DeepSeek API 직접 호출 시도...")
1011
+ response = direct_api_call(
1012
+ query,
1013
+ context,
1014
+ DEEPSEEK_API_KEY,
1015
+ DEEPSEEK_MODEL,
1016
+ DEEPSEEK_ENDPOINT,
1017
+ max_retries=3,
1018
+ timeout=120
1019
+ )
1020
+ logger.info("DeepSeek API 직접 호출 성공")
1021
+ else:
1022
+ raise Exception("벡터 스토어가 초기화되지 않았습니다")
1023
+
1024
+ except Exception as direct_api_error:
1025
+ logger.error(f"DeepSeek API 직접 호출 실패: {direct_api_error}, 검색 결과 반환")
1026
+
1027
+ # 3. 검색 결과만이라도 반환
1028
+ try:
1029
+ # 벡터 검색 수행
1030
+ if self.vector_store and hasattr(self.vector_store, "similarity_search"):
1031
+ docs = self.vector_store.similarity_search(query, k=5)
1032
+
1033
+ # 검색 결과 컨텍스트 구성
1034
+ context_parts = []
1035
+ for i, doc in enumerate(docs, 1):
1036
+ source = doc.metadata.get("source", "알 수 없는 출처")
1037
+ page = doc.metadata.get("page", "")
1038
+ source_info = f"{source}"
1039
+ if page:
1040
+ source_info += f" (페이지: {page})"
1041
+ context_parts.append(f"[참고자료 {i}] - 출처: {source_info}\n{doc.page_content}\n")
1042
+ context = "\n".join(context_parts)
1043
+
1044
+ # 간단한 응답 생성
1045
+ predefined_answers = {
1046
+ "대한민국의 수도": "대한민국의 수도는 서울입니다.",
1047
+ "수도": "대한민국의 수도는 서울입니다.",
1048
+ "누구야": "저는 RAG 기반 질의응답 시스템입니다. 문서를 검색하고 관련 정보를 찾아드립니다.",
1049
+ "안녕": "안녕하세요! 무엇을 도와드릴까요?",
1050
+ "뭐해": "사용자의 질문에 답변하기 위해 문서를 검색하고 있습니다. 무엇을 알려드릴까요?"
1051
+ }
1052
+
1053
+ # 질문에 맞는 미리 정의된 응답이 있는지 확인
1054
+ for key, answer in predefined_answers.items():
1055
+ if key in query.lower():
1056
+ response = answer
1057
+ logger.info(f"미리 정의된 응답 제공: {key}")
1058
+ break
1059
+ else:
1060
+ # 미리 정의된 응답이 없으면 검색 결과만 표시
1061
+ response = f"""
1062
+ API 서버 연결에 문제가 있어 검색 결과만 표시합니다.
1063
+
1064
+ 질문: {query}
1065
+
1066
+ 검색된 관련 문서:
1067
+ {context}
1068
+
1069
+ [참고] API 연결 문제로 인해 자동 요약이 제공되지 않습니다. 다시 시도하거나 다른 질문을 해보세요.
1070
+ """
1071
+ logger.info("검색 결과만 표시")
1072
+ else:
1073
+ response = f"API 연결 및 벡터 검색에 모두 실패했습니다. 시스템 관리자에게 문의하세요."
1074
+ except Exception as fallback_error:
1075
+ logger.error(f"최종 폴백 응답 생성 실패: {fallback_error}")
1076
+
1077
+ # 4. 최후의 방법: 오류 메시지를 응답으로 반환
1078
+ if "Connection error" in str(rag_error) or "timeout" in str(rag_error).lower():
1079
+ response = f"""
1080
+ API 서버 연결에 문제가 있습니다. 잠시 후 다시 시도해주세요.
1081
+
1082
+ 질문: {query}
1083
+
1084
+ [참고] 현재 DeepSeek API 서버와의 연결이 원활하지 않습니다. 이로 인해 질문에 대한 응답을 제공할 수 없습니다.
1085
+ """
1086
+ else:
1087
+ response = f"쿼리 처리 중 오류가 발생했습니다: {str(rag_error)}"
1088
+
1089
+ end_time = time.time()
1090
+ query_time = end_time - start_time
1091
+ logger.info(f"쿼리 처리 완료: {query_time:.2f}초")
1092
+
1093
+ chat_history.append((query, response))
1094
+ return "", chat_history
1095
+ except RAGInitializationError as e:
1096
+ error_msg = f"RAG 시스템 초기화 오류: {str(e)}. 'documents' 폴더에 PDF 파일이 있는지 확인하고, 재시작해 보세요."
1097
+ logger.error(f"쿼리 처리 중 RAG 초기화 오류: {e}", exc_info=True)
1098
+ chat_history.append((query, error_msg))
1099
+ return "", chat_history
1100
+ except (VectorStoreError, DocumentProcessingError) as e:
1101
+ error_msg = f"문서 처리 시스템 오류: {str(e)}. 문서 형식이 올바른지 확인해 보세요."
1102
+ logger.error(f"쿼리 처리 중 문서/벡터 스토어 오류: {e}", exc_info=True)
1103
+ chat_history.append((query, error_msg))
1104
+ return "", chat_history
1105
+ except Exception as e:
1106
+ error_msg = f"쿼리 처리 중 오류 발생: {str(e)}"
1107
+ logger.error(f"쿼리 처리 중 예상치 못한 오류: {e}", exc_info=True)
1108
+ chat_history.append((query, error_msg))
1109
+ return "", chat_history
1110
+
1111
+ def launch_app(self) -> None:
1112
+ """
1113
+ Gradio 앱 실행
1114
+ """
1115
+ try:
1116
+ import gradio as gr
1117
+ except ImportError:
1118
+ logger.error("Gradio 라이브러리를 찾을 수 없습니다. pip install gradio로 설치하세요.")
1119
+ print("Gradio 라이브러리를 찾을 수 없습니다. pip install gradio로 설치하세요.")
1120
+ return
1121
+ # 내부 함수들이 현재 인스턴스(self)에 접근할 수 있도록 클로저 변수로 정의
1122
+ app_instance = self
1123
+ try:
1124
+ with gr.Blocks(title="PDF 문서 기반 RAG 챗봇") as app:
1125
+ gr.Markdown("# PDF 문서 기반 RAG 챗봇")
1126
+ gr.Markdown(f"* 사용 중인 LLM 모델: **{LLM_MODEL}**")
1127
+
1128
+ # 여기를 수정: 실제 경로 표시
1129
+ actual_pdf_dir = self.pdf_directory.replace('\\', '\\\\') if os.name == 'nt' else self.pdf_directory
1130
+ gr.Markdown(f"* PDF 문서 폴더: **{actual_pdf_dir}**")
1131
+ with gr.Row():
1132
+ with gr.Column(scale=1):
1133
+ # 문서 상태 섹션
1134
+ status_box = gr.Textbox(
1135
+ label="문서 처리 상태",
1136
+ value=self._get_status_message(),
1137
+ lines=5,
1138
+ interactive=False
1139
+ )
1140
+
1141
+ # 캐시 관리 버튼
1142
+ refresh_button = gr.Button("문서 새로 읽기", variant="primary")
1143
+ reset_button = gr.Button("캐시 초기화", variant="stop")
1144
+
1145
+ # 상태 및 오류 표시
1146
+ status_info = gr.Markdown(
1147
+ value=f"시스템 상태: {'초기화됨' if self.is_initialized else '초기화되지 않음'}"
1148
+ )
1149
+
1150
+ # 처리된 파일 정보
1151
+ with gr.Accordion("캐시 세부 정보", open=False):
1152
+ cache_info = gr.Textbox(
1153
+ label="캐시된 파일 정보",
1154
+ value=self._get_cache_info(),
1155
+ lines=5,
1156
+ interactive=False
1157
+ )
1158
+
1159
+ with gr.Column(scale=2):
1160
+ # 채팅 인터페이스
1161
+ chatbot = gr.Chatbot(
1162
+ label="대화 내용",
1163
+ bubble_full_width=False,
1164
+ height=500,
1165
+ show_copy_button=True
1166
+ )
1167
+
1168
+ # 음성 녹음 UI 추가
1169
+ with gr.Row():
1170
+ with gr.Column(scale=4):
1171
+ # 질문 입력과 전송 버튼
1172
+ query_box = gr.Textbox(
1173
+ label="질문",
1174
+ placeholder="처리된 문서 내용에 대해 질문하세요...",
1175
+ lines=2
1176
+ )
1177
+ with gr.Column(scale=1):
1178
+ # 음성 녹음 컴포넌트
1179
+ audio_input = gr.Audio(
1180
+ sources=["microphone"],
1181
+ type="numpy",
1182
+ label="음성으로 질문하기"
1183
+ )
1184
+
1185
+ with gr.Row():
1186
+ submit_btn = gr.Button("전송", variant="primary")
1187
+ clear_chat_button = gr.Button("대화 초기화")
1188
+
1189
+ # 음성 인식 처리 함수
1190
+ # app.py 내 process_audio 함수 보강
1191
+ # Gradio 앱 내에 있는 음성 인식 처리 함수 (원본)
1192
+ def process_audio(audio):
1193
+ logger.info("음성 인식 처리 시작...")
1194
+ try:
1195
+ from clova_stt import ClovaSTT
1196
+ import numpy as np
1197
+ import soundfile as sf
1198
+ import tempfile
1199
+ import os
1200
+
1201
+ if audio is None:
1202
+ return "음성이 녹음되지 않았습니다."
1203
+
1204
+ # 오디오 데이터를 임시 파일로 저장
1205
+ sr, y = audio
1206
+ logger.info(f"오디오 녹음 데이터 수신: 샘플레이트={sr}Hz, 길이={len(y)}샘플")
1207
+ if len(y) / sr < 1.0:
1208
+ return "녹음된 음성이 너무 짧습니다. 다시 시도해주세요."
1209
+
1210
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
1211
+ temp_path = temp_file.name
1212
+ sf.write(temp_path, y, sr, format="WAV")
1213
+ logger.info(f"임시 WAV 파일 저장됨: {temp_path}")
1214
+
1215
+ # 음성 인식 실행
1216
+ stt_client = ClovaSTT()
1217
+ with open(temp_path, "rb") as f:
1218
+ audio_bytes = f.read()
1219
+ result = stt_client.recognize(audio_bytes)
1220
+
1221
+ # 임시 파일 삭제
1222
+ try:
1223
+ os.unlink(temp_path)
1224
+ logger.info("임시 오디오 파일 삭제됨")
1225
+ except Exception as e:
1226
+ logger.warning(f"임시 파일 삭제 실패: {e}")
1227
+
1228
+ if result["success"]:
1229
+ recognized_text = result["text"]
1230
+ logger.info(f"음성인식 성공: {recognized_text}")
1231
+ return recognized_text
1232
+ else:
1233
+ error_msg = f"음성 인식 실패: {result.get('error', '알 수 없는 오류')}"
1234
+ logger.error(error_msg)
1235
+ return error_msg
1236
+
1237
+ except ImportError as e:
1238
+ logger.error(f"필요한 라이브러리 누락: {e}")
1239
+ return "음성인식에 필요한 라이브러리가 설치되지 않았습니다. pip install soundfile numpy requests를 실행해주세요."
1240
+ except Exception as e:
1241
+ logger.error(f"음성 처리 중 오류 발생: {e}", exc_info=True)
1242
+ return f"음성 처리 중 오류 발생: {str(e)}"
1243
+
1244
+ # 새로 추가할 process_audio_and_submit 함수
1245
+ def process_audio_and_submit(audio, chat_history):
1246
+ """
1247
+ 녹음 정지 시 음성 인식 후 자동으로 질문을 처리하는 함수.
1248
+ 입력:
1249
+ - audio: 녹음 데이터 (gr.Audio의 값)
1250
+ - chat_history: 현재 대화 기록 (gr.Chatbot의 값)
1251
+ 출력:
1252
+ - query_box: 빈 문자열 (질문 입력란 초기화)
1253
+ - chatbot: 업데이트된 대화 기록
1254
+ """
1255
+ recognized_text = process_audio(audio)
1256
+
1257
+ # 음성 인식 결과가 오류 메시지인 경우 그대로 반환
1258
+ if not recognized_text or recognized_text.startswith("음성 인식 실패") or recognized_text.startswith(
1259
+ "음성 처리 중 오류"):
1260
+ return recognized_text, chat_history
1261
+
1262
+ # 인식된 텍스트를 사용하여 질문 처리
1263
+ return app_instance.process_query(recognized_text, chat_history)
1264
+
1265
+ # 기존 update_ui_after_refresh 함수 수정 (self 대신 app_instance 사용)
1266
+ def update_ui_after_refresh(result):
1267
+ return (
1268
+ result, # 상태 메시지
1269
+ app_instance._get_status_message(), # 상태 박스 업데이트
1270
+ f"시스템 상태: {'초기화됨' if app_instance.is_initialized else '초기화되지 않음'}", # 상태 정보 업데이트
1271
+ app_instance._get_cache_info() # 캐시 정보 업데이트
1272
+ )
1273
+
1274
+ # --- Gradio 이벤트 핸들러 설정 ---
1275
+ # 예: audio_input 컴포넌트의 stop_recording 이벤트를 아래와 같이 수정
1276
+ audio_input.stop_recording(
1277
+ fn=process_audio_and_submit,
1278
+ inputs=[audio_input, chatbot],
1279
+ outputs=[query_box, chatbot]
1280
+ )
1281
+
1282
+ # 음성 인식 결과를 질문 상자에 업데이트
1283
+ audio_input.stop_recording(
1284
+ fn=process_audio,
1285
+ inputs=[audio_input],
1286
+ outputs=[query_box]
1287
+ )
1288
+
1289
+ # 문서 새로 읽기 버튼
1290
+ refresh_button.click(
1291
+ fn=lambda: update_ui_after_refresh(self.auto_process_documents()),
1292
+ inputs=[],
1293
+ outputs=[status_box, status_box, status_info, cache_info]
1294
+ )
1295
+
1296
+ # 캐시 초기화 버튼
1297
+ def reset_and_process():
1298
+ reset_result = self.reset_cache()
1299
+ process_result = self.auto_process_documents()
1300
+ return update_ui_after_refresh(f"{reset_result}\n\n{process_result}")
1301
+
1302
+ reset_button.click(
1303
+ fn=reset_and_process,
1304
+ inputs=[],
1305
+ outputs=[status_box, status_box, status_info, cache_info]
1306
+ )
1307
+
1308
+ # 전송 버튼 클릭 이벤트
1309
+ submit_btn.click(
1310
+ fn=self.process_query,
1311
+ inputs=[query_box, chatbot],
1312
+ outputs=[query_box, chatbot]
1313
+ )
1314
+
1315
+ # 엔터키 입력 이벤트
1316
+ query_box.submit(
1317
+ fn=self.process_query,
1318
+ inputs=[query_box, chatbot],
1319
+ outputs=[query_box, chatbot]
1320
+ )
1321
+
1322
+ # 대화 초기화 버튼
1323
+ clear_chat_button.click(
1324
+ fn=lambda: [],
1325
+ outputs=[chatbot]
1326
+ )
1327
+
1328
+ # 앱 실행
1329
+ app.launch(share=False)
1330
+ except Exception as e:
1331
+ logger.error(f"Gradio 앱 실행 중 오류 발생: {e}", exc_info=True)
1332
+ print(f"Gradio 앱 실행 중 오류 발생: {e}")
1333
+
1334
+
1335
+
1336
+ def _get_status_message(self) -> str:
1337
+ """
1338
+ 현재 처리 상태 메시지 생성
1339
+
1340
+ Returns:
1341
+ 상태 메시지
1342
+ """
1343
+ if not self.processed_files:
1344
+ return "처리된 문서가 없습니다. '문서 새로 읽기' 버튼을 클릭하세요."
1345
+
1346
+ # DeepSeek API 상태 확인
1347
+ from config import USE_DEEPSEEK, DEEPSEEK_API_KEY, DEEPSEEK_MODEL
1348
+
1349
+ model_info = ""
1350
+ if USE_DEEPSEEK and DEEPSEEK_API_KEY:
1351
+ # DeepSeek API 테스트 수행
1352
+ try:
1353
+ # 테스트 함수 가져오기 시도
1354
+ try:
1355
+ from deepseek_utils import test_deepseek_api
1356
+
1357
+ # DeepSeek 설정 가져오기
1358
+ from config import DEEPSEEK_ENDPOINT
1359
+
1360
+ # API 테스트
1361
+ test_result = test_deepseek_api(DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL)
1362
+
1363
+ if test_result["success"]:
1364
+ model_info = f"\nDeepSeek API 상태: 정상 ({DEEPSEEK_MODEL})"
1365
+ else:
1366
+ model_info = f"\nDeepSeek API 상태: 오류 - {test_result['message']}"
1367
+
1368
+ except ImportError:
1369
+ # 직접 테스트 실행
1370
+ import requests
1371
+ import json
1372
+
1373
+ # DeepSeek 설정 가져오기
1374
+ from config import DEEPSEEK_ENDPOINT
1375
+
1376
+ # 테스트용 간단한 프롬프트
1377
+ test_prompt = "Hello, please respond with a short greeting."
1378
+
1379
+ # API 요청 헤더 및 데이터
1380
+ headers = {
1381
+ "Content-Type": "application/json",
1382
+ "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
1383
+ }
1384
+
1385
+ payload = {
1386
+ "model": DEEPSEEK_MODEL,
1387
+ "messages": [{"role": "user", "content": test_prompt}],
1388
+ "temperature": 0.7,
1389
+ "max_tokens": 50
1390
+ }
1391
+
1392
+ # API 요청 전송
1393
+ try:
1394
+ response = requests.post(
1395
+ DEEPSEEK_ENDPOINT,
1396
+ headers=headers,
1397
+ data=json.dumps(payload),
1398
+ timeout=5 # 5초 타임아웃 (UI 반응성 유지)
1399
+ )
1400
+
1401
+ # 응답 확인
1402
+ if response.status_code == 200:
1403
+ model_info = f"\nDeepSeek API 상태: 정상 ({DEEPSEEK_MODEL})"
1404
+ else:
1405
+ error_message = response.text[:100]
1406
+ model_info = f"\nDeepSeek API 상태: 오류 (상태 코드: {response.status_code})"
1407
+ except Exception as e:
1408
+ model_info = f"\nDeepSeek API 상태: 연결 실패 ({str(e)[:100]})"
1409
+ except Exception as e:
1410
+ model_info = f"\nDeepSeek API 상태 확인 실패: {str(e)[:100]}"
1411
+
1412
+ return f"처리된 문서 ({len(self.processed_files)}개): {', '.join(self.processed_files)}{model_info}"
1413
+
1414
+ def _get_cache_info(self) -> str:
1415
+ """
1416
+ 캐시 세부 정보 메시지 생성
1417
+
1418
+ Returns:
1419
+ 캐시 정보 메시지
1420
+ """
1421
+ if not self.file_index:
1422
+ return "캐시된 파일이 없습니다."
1423
+
1424
+ file_info = ""
1425
+ for file_path, info in self.file_index.items():
1426
+ file_name = info.get('file_name', os.path.basename(file_path))
1427
+ chunks_count = info.get('chunks_count', 0)
1428
+ file_size = info.get('file_size', 0)
1429
+ last_processed = info.get('last_processed', 0)
1430
+
1431
+ # 파일 크기를 사람이 읽기 쉬운 형태로 변환
1432
+ if file_size < 1024:
1433
+ size_str = f"{file_size} bytes"
1434
+ elif file_size < 1024 * 1024:
1435
+ size_str = f"{file_size / 1024:.1f} KB"
1436
+ else:
1437
+ size_str = f"{file_size / (1024 * 1024):.1f} MB"
1438
+
1439
+ # 마지막 처리 시간을 날짜/시간 형식으로 변환
1440
+ if last_processed:
1441
+ from datetime import datetime
1442
+ last_time = datetime.fromtimestamp(last_processed).strftime('%Y-%m-%d %H:%M:%S')
1443
+ else:
1444
+ last_time = "알 수 없음"
1445
+
1446
+ file_info += f"- {file_name}: {chunks_count}개 청크, {size_str}, 마지막 처리: {last_time}\n"
1447
+
1448
+ return file_info
1449
+
1450
+
1451
+
1452
+ if __name__ == "__main__":
1453
+ app = AutoRAGChatApp()
1454
+ app.launch_app()
autorag.log ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3
2
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3\documents
3
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3\documents
4
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3\documents
5
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
6
+ 2025-03-29 23:17:05,934 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
7
+ 2025-03-29 23:17:07,025 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
8
+ 2025-03-29 23:17:07,025 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3\documents
9
+ 2025-03-29 23:17:07,026 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
10
+ 2025-03-29 23:17:07,026 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3\documents'
11
+ 2025-03-29 23:17:07,026 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data'
12
+ 2025-03-29 23:17:07,849 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
13
+ 2025-03-29 23:17:07,851 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
14
+ 2025-03-29 23:17:12,304 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
15
+ 2025-03-29 23:17:12,304 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
16
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3\documents
17
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - ���丮 ����: ['RAG �Ʒÿ� Q.pdf']
18
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
19
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
20
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
21
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - ó�� ��: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
22
+ 2025-03-29 23:17:12,305 - AutoRAG - INFO - docling���� ó�� �õ�: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
23
+ 2025-03-29 23:17:12,305 - AutoRAG - WARNING - �ñ׳� ���� ���� (������ ȯ���� �� ����): module 'signal' has no attribute 'SIGALRM'
24
+ 2025-03-29 23:17:13,014 - docling.document_converter - INFO - Going to convert document batch...
25
+ 2025-03-29 23:17:13,025 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
26
+ 2025-03-29 23:17:13,026 - docling.models.factories - INFO - Registered ocr engines: ['easyocr', 'ocrmac', 'rapidocr', 'tesserocr', 'tesseract']
27
+ 2025-03-29 23:17:13,150 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
28
+ 2025-03-29 23:17:15,247 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
29
+ 2025-03-29 23:17:16,731 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
30
+ 2025-03-29 23:17:17,206 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
31
+ 2025-03-29 23:17:17,207 - docling.models.factories - INFO - Registered picture descriptions: ['vlm', 'api']
32
+ 2025-03-29 23:17:17,207 - docling.pipeline.base_pipeline - INFO - Processing document RAG �Ʒÿ� Q.pdf
33
+ 2025-03-29 23:17:18,062 - docling.document_converter - INFO - Finished converting document RAG �Ʒÿ� Q.pdf in 5.76 sec.
34
+ 2025-03-29 23:17:18,110 - AutoRAG - INFO - ûũ ���� �Ϸ�: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
35
+ 2025-03-29 23:17:18,111 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 5.81��
36
+ 2025-03-29 23:17:18,111 - AutoRAG - INFO - �� ���� ��� ���� ��...
37
+ 2025-03-29 23:17:18,111 - VectorStore - INFO - FAISS ��� ���� ��: 1�� ����
38
+ 2025-03-29 23:17:18,331 - faiss.loader - INFO - Loading faiss with AVX2 support.
39
+ 2025-03-29 23:17:18,517 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
40
+ 2025-03-29 23:17:18,523 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
41
+ 2025-03-29 23:17:18,523 - VectorStore - INFO - FAISS �ε��� ���� �Ϸ�
42
+ 2025-03-29 23:17:18,523 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
43
+ 2025-03-29 23:17:18,525 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
44
+ 2025-03-29 23:17:18,525 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
45
+ 2025-03-29 23:17:18,525 - RAGChain - INFO - RAGChain �ʱ�ȭ ����...
46
+ 2025-03-29 23:17:18,525 - RAGChain - INFO - ����Ŀ ��� ����: True
47
+ 2025-03-29 23:17:20,596 - sentence_transformers.cross_encoder.CrossEncoder - INFO - Use pytorch device: cuda
48
+ 2025-03-29 23:17:20,800 - RAGChain - INFO - ����Ŀ �ʱ�ȭ ����
49
+ 2025-03-29 23:17:20,800 - RAGChain - INFO - Ollama �� �ʱ�ȭ: gemma3:latest
50
+ 2025-03-29 23:17:20,810 - RAGChain - INFO - Ollama �� �ʱ�ȭ ����
51
+ 2025-03-29 23:17:20,810 - RAGChain - INFO - RAG ü�� ���� ����...
52
+ 2025-03-29 23:17:20,810 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
53
+ 2025-03-29 23:17:20,810 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
54
+ 2025-03-29 23:17:20,810 - AutoRAG - INFO - ���� ó�� �Ϸ�!
55
+ - �� ����: 1��
56
+ - ij�õ� ����: 0��
57
+ - �� ����: 0��
58
+ - ������Ʈ�� ����: 1��
59
+ - ������ ����: 0��
60
+ - �� ûũ ��: 1��
61
+ - ó�� �ð�: 8.51��
62
+ ���� ������ �غ� �Ǿ����ϴ�!
63
+ 2025-03-29 23:17:20,810 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
64
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3
65
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3\documents
66
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3\documents
67
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3\documents
68
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
69
+ 2025-03-29 23:20:20,300 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
70
+ 2025-03-29 23:20:21,048 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
71
+ 2025-03-29 23:20:21,048 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3\documents
72
+ 2025-03-29 23:20:21,048 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
73
+ 2025-03-29 23:20:21,048 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3\documents'
74
+ 2025-03-29 23:20:21,048 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data'
75
+ 2025-03-29 23:20:21,794 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
76
+ 2025-03-29 23:20:21,796 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
77
+ 2025-03-29 23:20:25,626 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
78
+ 2025-03-29 23:20:25,626 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
79
+ 2025-03-29 23:20:25,626 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3\documents
80
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - ���丮 ����: ['RAG �Ʒÿ� Q.pdf']
81
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
82
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
83
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
84
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
85
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.00��
86
+ 2025-03-29 23:20:25,627 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
87
+ 2025-03-29 23:20:25,627 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
88
+ 2025-03-29 23:20:25,629 - faiss.loader - INFO - Loading faiss with AVX2 support.
89
+ 2025-03-29 23:20:25,641 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
90
+ 2025-03-29 23:20:25,644 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
91
+ 2025-03-29 23:20:25,645 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
92
+ 2025-03-29 23:20:25,645 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
93
+ 2025-03-29 23:20:25,645 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
94
+ 2025-03-29 23:20:25,645 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
95
+ 2025-03-29 23:20:25,645 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
96
+ 2025-03-29 23:20:25,645 - RAGChain - INFO - RAGChain �ʱ�ȭ ����...
97
+ 2025-03-29 23:20:25,645 - RAGChain - INFO - ����Ŀ ��� ����: True
98
+ 2025-03-29 23:20:27,424 - sentence_transformers.cross_encoder.CrossEncoder - INFO - Use pytorch device: cuda
99
+ 2025-03-29 23:20:27,622 - RAGChain - INFO - ����Ŀ �ʱ�ȭ ����
100
+ 2025-03-29 23:20:27,622 - RAGChain - INFO - Ollama �� �ʱ�ȭ: gemma3:latest
101
+ 2025-03-29 23:20:27,623 - RAGChain - INFO - Ollama �� �ʱ�ȭ ����
102
+ 2025-03-29 23:20:27,623 - RAGChain - INFO - RAG ü�� ���� ����...
103
+ 2025-03-29 23:20:27,623 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
104
+ 2025-03-29 23:20:27,623 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
105
+ 2025-03-29 23:20:27,624 - AutoRAG - INFO - ���� ó�� �Ϸ�!
106
+ - �� ����: 1��
107
+ - ij�õ� ����: 1��
108
+ - �� ����: 0��
109
+ - ������Ʈ�� ����: 0��
110
+ - ������ ����: 0��
111
+ - �� ûũ ��: 1��
112
+ - ó�� �ð�: 2.00��
113
+ ���� ������ �غ� �Ǿ����ϴ�!
114
+ 2025-03-29 23:20:27,624 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
115
+ 2025-03-29 23:20:29,758 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
116
+ 2025-03-29 23:20:30,349 - AutoRAG - ERROR - Gradio �� ���� �� ���� �߻�: 'AutoRAGChatApp' object has no attribute '_get_status_message'
117
+ Traceback (most recent call last):
118
+ File "C:\Users\USER\PycharmProjects\RagPipeline\RAG3\app.py", line 855, in launch_app
119
+ value=self._get_status_message(),
120
+ ^^^^^^^^^^^^^^^^^^^^^^^^
121
+ AttributeError: 'AutoRAGChatApp' object has no attribute '_get_status_message'
122
+ 2025-03-29 23:20:31,087 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
123
+ 2025-03-29 23:22:28,187 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3
124
+ 2025-03-29 23:22:28,188 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3\documents
125
+ 2025-03-29 23:22:28,188 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3\documents
126
+ 2025-03-29 23:22:28,188 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3\documents
127
+ 2025-03-29 23:22:28,188 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
128
+ 2025-03-29 23:22:28,188 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
129
+ 2025-03-29 23:22:28,930 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
130
+ 2025-03-29 23:22:28,930 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3\documents
131
+ 2025-03-29 23:22:28,930 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
132
+ 2025-03-29 23:22:28,931 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3\documents'
133
+ 2025-03-29 23:22:28,931 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data'
134
+ 2025-03-29 23:22:29,680 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
135
+ 2025-03-29 23:22:29,681 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
136
+ 2025-03-29 23:22:33,510 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
137
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
138
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3\documents
139
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - ���丮 ����: ['RAG �Ʒÿ� Q.pdf']
140
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
141
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
142
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
143
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
144
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.00��
145
+ 2025-03-29 23:22:33,510 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
146
+ 2025-03-29 23:22:33,510 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
147
+ 2025-03-29 23:22:33,512 - faiss.loader - INFO - Loading faiss with AVX2 support.
148
+ 2025-03-29 23:22:33,523 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
149
+ 2025-03-29 23:22:33,526 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
150
+ 2025-03-29 23:22:33,532 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
151
+ 2025-03-29 23:22:33,532 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
152
+ 2025-03-29 23:22:33,532 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
153
+ 2025-03-29 23:22:33,533 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
154
+ 2025-03-29 23:22:33,533 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
155
+ 2025-03-29 23:22:33,533 - RAGChain - INFO - RAGChain �ʱ�ȭ ����...
156
+ 2025-03-29 23:22:33,533 - RAGChain - INFO - ����Ŀ ��� ����: True
157
+ 2025-03-29 23:22:35,580 - sentence_transformers.cross_encoder.CrossEncoder - INFO - Use pytorch device: cuda
158
+ 2025-03-29 23:22:35,782 - RAGChain - INFO - ����Ŀ �ʱ�ȭ ����
159
+ 2025-03-29 23:22:35,782 - RAGChain - INFO - Ollama �� �ʱ�ȭ: gemma3:latest
160
+ 2025-03-29 23:22:35,783 - RAGChain - INFO - Ollama �� �ʱ�ȭ ����
161
+ 2025-03-29 23:22:35,783 - RAGChain - INFO - RAG ü�� ���� ����...
162
+ 2025-03-29 23:22:35,783 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
163
+ 2025-03-29 23:22:35,783 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
164
+ 2025-03-29 23:22:35,783 - AutoRAG - INFO - ���� ó�� �Ϸ�!
165
+ - �� ����: 1��
166
+ - ij�õ� ����: 1��
167
+ - �� ����: 0��
168
+ - ������Ʈ�� ����: 0��
169
+ - ������ ����: 0��
170
+ - �� ûũ ��: 1��
171
+ - ó�� �ð�: 2.27��
172
+ ���� ������ �غ� �Ǿ����ϴ�!
173
+ 2025-03-29 23:22:35,783 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
174
+ 2025-03-29 23:22:37,712 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
175
+ 2025-03-29 23:22:38,171 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
176
+ 2025-03-29 23:22:38,259 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
177
+ 2025-03-29 23:22:38,510 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
178
+ 2025-03-29 23:23:06,606 - AutoRAG - INFO - ���� ó�� ����: �ȳ�?
179
+ 2025-03-29 23:23:06,606 - RAGChain - INFO - RAG ü�� ����: '�ȳ�?'
180
+ 2025-03-29 23:23:06,616 - RAGChain - INFO - ���� �˻� ����: '�ȳ�?'
181
+ 2025-03-29 23:23:06,616 - VectorStore - INFO - �˻� ���� ����: '�ȳ�?', ���� 5�� ��� ��û
182
+ 2025-03-29 23:23:06,674 - VectorStore - INFO - �˻� �Ϸ�: 1�� ��� ã��
183
+ 2025-03-29 23:23:06,674 - RAGChain - INFO - ����ŷ ����: 1�� ����
184
+ 2025-03-29 23:23:06,992 - RAGChain - INFO - ����ŷ �Ϸ�: 1�� ���� ���õ�
185
+ 2025-03-29 23:23:06,992 - RAGChain - INFO - ���ؽ�Ʈ ���� �Ϸ�: 1�� ����, 683 ����
186
+ 2025-03-29 23:23:13,887 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
187
+ 2025-03-29 23:23:13,887 - AutoRAG - INFO - ���� ó�� �Ϸ�: 7.28��
188
+ 2025-03-29 23:23:44,199 - AutoRAG - INFO - ���� ó�� ����: ���ѹα� ������?
189
+ 2025-03-29 23:23:44,199 - RAGChain - INFO - RAG ü�� ����: '���ѹα� ������?'
190
+ 2025-03-29 23:23:44,200 - RAGChain - INFO - ���� �˻� ����: '���ѹα� ������?'
191
+ 2025-03-29 23:23:44,201 - VectorStore - INFO - �˻� ���� ����: '���ѹα� ������?', ���� 5�� ��� ��û
192
+ 2025-03-29 23:23:44,237 - VectorStore - INFO - �˻� �Ϸ�: 1�� ��� ã��
193
+ 2025-03-29 23:23:44,238 - RAGChain - INFO - ����ŷ ����: 1�� ����
194
+ 2025-03-29 23:23:44,358 - RAGChain - INFO - ����ŷ �Ϸ�: 1�� ���� ���õ�
195
+ 2025-03-29 23:23:44,358 - RAGChain - INFO - ���ؽ�Ʈ ���� �Ϸ�: 1�� ����, 683 ����
196
+ 2025-03-29 23:23:47,486 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
197
+ 2025-03-29 23:23:47,486 - AutoRAG - INFO - ���� ó�� �Ϸ�: 3.29��
198
+ 2025-03-29 23:24:29,670 - AutoRAG - INFO - ���� ó�� ����: �� ������?
199
+ 2025-03-29 23:24:29,670 - RAGChain - INFO - RAG ü�� ����: '�� ������?'
200
+ 2025-03-29 23:24:29,673 - RAGChain - INFO - ���� �˻� ����: '�� ������?'
201
+ 2025-03-29 23:24:29,673 - VectorStore - INFO - �˻� ���� ����: '�� ������?', ���� 5�� ��� ��û
202
+ 2025-03-29 23:24:29,707 - VectorStore - INFO - �˻� �Ϸ�: 1�� ��� ã��
203
+ 2025-03-29 23:24:29,708 - RAGChain - INFO - ����ŷ ����: 1�� ����
204
+ 2025-03-29 23:24:29,827 - RAGChain - INFO - ����ŷ �Ϸ�: 1�� ���� ���õ�
205
+ 2025-03-29 23:24:29,827 - RAGChain - INFO - ���ؽ�Ʈ ���� �Ϸ�: 1�� ����, 683 ����
206
+ 2025-03-29 23:24:32,394 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
207
+ 2025-03-29 23:24:32,394 - AutoRAG - INFO - ���� ó�� �Ϸ�: 2.72��
208
+ 2025-03-30 00:09:22,627 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3
209
+ 2025-03-30 00:09:22,627 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3\documents
210
+ 2025-03-30 00:09:22,627 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3\documents
211
+ 2025-03-30 00:09:22,627 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3\documents
212
+ 2025-03-30 00:09:22,627 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
213
+ 2025-03-30 00:09:22,628 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
214
+ 2025-03-30 00:09:23,685 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
215
+ 2025-03-30 00:09:23,685 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3\documents
216
+ 2025-03-30 00:09:23,685 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
217
+ 2025-03-30 00:09:23,686 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3\documents'
218
+ 2025-03-30 00:09:23,686 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data'
219
+ 2025-03-30 00:09:24,553 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
220
+ 2025-03-30 00:09:24,560 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
221
+ 2025-03-30 00:09:28,468 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
222
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
223
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3\documents
224
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - ���丮 ����: ['.gitkeep', 'RAG �Ʒÿ� Q.pdf']
225
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf
226
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
227
+ 2025-03-30 00:09:28,473 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
228
+ 2025-03-30 00:09:28,479 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
229
+ 2025-03-30 00:09:28,479 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.01��
230
+ 2025-03-30 00:09:28,479 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
231
+ 2025-03-30 00:09:28,479 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
232
+ 2025-03-30 00:09:28,480 - faiss.loader - INFO - Loading faiss with AVX2 support.
233
+ 2025-03-30 00:09:28,625 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
234
+ 2025-03-30 00:09:28,630 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
235
+ 2025-03-30 00:09:28,635 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
236
+ 2025-03-30 00:09:28,636 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
237
+ 2025-03-30 00:09:28,636 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
238
+ 2025-03-30 00:09:28,636 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
239
+ 2025-03-30 00:09:28,636 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
240
+ 2025-03-30 00:09:28,636 - RAGChain - INFO - RAGChain �ʱ�ȭ ����...
241
+ 2025-03-30 00:09:28,636 - RAGChain - INFO - ����Ŀ ��� ����: True
242
+ 2025-03-30 00:09:30,453 - sentence_transformers.cross_encoder.CrossEncoder - INFO - Use pytorch device: cuda
243
+ 2025-03-30 00:09:30,651 - RAGChain - INFO - ����Ŀ �ʱ�ȭ ����
244
+ 2025-03-30 00:09:30,652 - RAGChain - INFO - Ollama �� �ʱ�ȭ: gemma3:latest
245
+ 2025-03-30 00:09:30,653 - RAGChain - INFO - Ollama �� �ʱ�ȭ ����
246
+ 2025-03-30 00:09:30,653 - RAGChain - INFO - RAG ü�� ���� ����...
247
+ 2025-03-30 00:09:30,653 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
248
+ 2025-03-30 00:09:30,653 - RAGChain - INFO - RAG ü�� ���� �Ϸ�
249
+ 2025-03-30 00:09:30,653 - AutoRAG - INFO - ���� ó�� �Ϸ�!
250
+ - �� ����: 1��
251
+ - ij�õ� ����: 1��
252
+ - �� ����: 0��
253
+ - ������Ʈ�� ����: 0��
254
+ - ������ ����: 0��
255
+ - �� ûũ ��: 1��
256
+ - ó�� �ð�: 2.18��
257
+ ���� ������ �غ� �Ǿ����ϴ�!
258
+ 2025-03-30 00:09:30,653 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
259
+ 2025-03-30 00:09:33,004 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
260
+ 2025-03-30 00:09:33,980 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
261
+ 2025-03-30 00:09:34,074 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
262
+ 2025-03-30 00:09:34,318 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
263
+ 2025-03-30 00:32:10,853 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3
264
+ 2025-03-30 00:32:10,854 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
265
+ 2025-03-30 00:32:10,854 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
266
+ 2025-03-30 00:32:10,854 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
267
+ 2025-03-30 00:32:10,854 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\PycharmProjects\\RagPipeline\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
268
+ 2025-03-30 00:32:10,854 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
269
+ 2025-03-30 00:32:11,619 - AutoRAG - WARNING - RAG ü�� ����� �ε��� �� �����ϴ�.
270
+ 2025-03-30 00:32:11,620 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
271
+ 2025-03-30 00:32:11,620 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
272
+ 2025-03-30 00:32:11,620 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
273
+ 2025-03-30 00:32:11,620 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents'
274
+ 2025-03-30 00:32:11,620 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data'
275
+ 2025-03-30 00:32:12,436 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
276
+ 2025-03-30 00:32:12,514 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
277
+ 2025-03-30 00:32:16,937 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
278
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
279
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
280
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - ���丮 ����: ['RAG �Ʒÿ� Q.pdf']
281
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf
282
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\PycharmProjects\\RagPipeline\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
283
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
284
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - ó�� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf
285
+ 2025-03-30 00:32:16,938 - AutoRAG - INFO - docling���� ó�� �õ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf
286
+ 2025-03-30 00:32:16,938 - AutoRAG - WARNING - �ñ׳� ���� ���� (������ ȯ���� �� ����): module 'signal' has no attribute 'SIGALRM'
287
+ 2025-03-30 00:32:17,580 - docling.document_converter - INFO - Going to convert document batch...
288
+ 2025-03-30 00:32:17,596 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
289
+ 2025-03-30 00:32:17,596 - docling.models.factories - INFO - Registered ocr engines: ['easyocr', 'ocrmac', 'rapidocr', 'tesserocr', 'tesseract']
290
+ 2025-03-30 00:32:17,693 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
291
+ 2025-03-30 00:32:19,673 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
292
+ 2025-03-30 00:32:20,999 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cuda:0'
293
+ 2025-03-30 00:32:21,412 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
294
+ 2025-03-30 00:32:21,412 - docling.models.factories - INFO - Registered picture descriptions: ['vlm', 'api']
295
+ 2025-03-30 00:32:21,412 - docling.pipeline.base_pipeline - INFO - Processing document RAG �Ʒÿ� Q.pdf
296
+ 2025-03-30 00:32:22,019 - docling.document_converter - INFO - Finished converting document RAG �Ʒÿ� Q.pdf in 5.08 sec.
297
+ 2025-03-30 00:32:22,079 - AutoRAG - INFO - ûũ ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
298
+ 2025-03-30 00:32:22,079 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 5.14��
299
+ 2025-03-30 00:32:22,079 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
300
+ 2025-03-30 00:32:22,079 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
301
+ 2025-03-30 00:32:22,082 - faiss.loader - INFO - Loading faiss with AVX2 support.
302
+ 2025-03-30 00:32:22,104 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
303
+ 2025-03-30 00:32:22,108 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
304
+ 2025-03-30 00:32:22,113 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
305
+ 2025-03-30 00:32:22,113 - AutoRAG - INFO - ���� �ε��� ������Ʈ ��...
306
+ 2025-03-30 00:32:22,113 - VectorStore - INFO - 1�� ������ ���� ���� ���� �߰��մϴ�
307
+ 2025-03-30 00:32:22,324 - VectorStore - INFO - 1�� ���� �߰� �Ϸ�
308
+ 2025-03-30 00:32:22,324 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
309
+ 2025-03-30 00:32:22,324 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
310
+ 2025-03-30 00:32:22,325 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
311
+ 2025-03-30 00:32:22,325 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
312
+ 2025-03-30 00:32:22,325 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
313
+ 2025-03-30 00:32:24,654 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
314
+ 2025-03-30 00:32:24,990 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
315
+ 2025-03-30 00:32:25,035 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
316
+ 2025-03-30 00:32:25,293 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
317
+ 2025-03-30 00:32:45,649 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents
318
+ 2025-03-30 00:32:45,649 - AutoRAG - INFO - ���丮 ����: ['RAG �Ʒÿ� Q.pdf']
319
+ 2025-03-30 00:32:45,650 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf
320
+ 2025-03-30 00:32:45,650 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\PycharmProjects\\RagPipeline\\RAG3\\documents\\RAG �Ʒÿ� Q.pdf']
321
+ 2025-03-30 00:32:45,650 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
322
+ 2025-03-30 00:32:45,658 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
323
+ 2025-03-30 00:32:45,658 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.01��
324
+ 2025-03-30 00:32:45,658 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
325
+ 2025-03-30 00:32:45,658 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
326
+ 2025-03-30 00:32:45,666 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
327
+ 2025-03-30 00:32:45,666 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
328
+ 2025-03-30 00:32:45,666 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
329
+ 2025-03-30 00:32:45,667 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
330
+ 2025-03-30 00:32:45,667 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\PycharmProjects\RagPipeline\RAG3\cached_data\vector_index
331
+ 2025-04-10 23:37:57,365 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\RAG3_voice
332
+ 2025-04-10 23:37:57,365 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3_voice\documents
333
+ 2025-04-10 23:37:57,365 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3_voice\documents
334
+ 2025-04-10 23:37:57,366 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3_voice\documents
335
+ 2025-04-10 23:37:57,366 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
336
+ 2025-04-10 23:37:57,366 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
337
+ 2025-04-10 23:37:57,366 - Config - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
338
+ 2025-04-10 23:38:04,670 - Config - INFO - DeepSeek API ���� ����
339
+ 2025-04-10 23:38:04,674 - AutoRAG - WARNING - RAG ü�� ����� �ε��� �� �����ϴ�: cannot import name 'RAGChain' from 'rag_chain' (C:\Users\USER\RAG3_voice\rag_chain.py)
340
+ 2025-04-10 23:38:04,678 - AutoRAG - WARNING - �������� ���� RAG ����� �ε��� �� �����ϴ�: No module named 'offline_fallback_rag'
341
+ 2025-04-10 23:38:04,678 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
342
+ 2025-04-10 23:38:04,679 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3_voice\documents
343
+ 2025-04-10 23:38:04,679 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
344
+ 2025-04-10 23:38:04,679 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3_voice\documents'
345
+ 2025-04-10 23:38:04,679 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\RAG3_voice\cached_data'
346
+ 2025-04-10 23:38:05,611 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
347
+ 2025-04-10 23:38:05,823 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
348
+ 2025-04-10 23:38:09,891 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
349
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
350
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3_voice\documents
351
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - ���丮 ����: ['.gitkeep', 'RAG �Ʒÿ� Q.pdf']
352
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf
353
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
354
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
355
+ 2025-04-10 23:38:09,891 - AutoRAG - INFO - ó�� ��: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf
356
+ 2025-04-10 23:38:09,892 - AutoRAG - INFO - docling���� ó�� �õ�: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf
357
+ 2025-04-10 23:38:09,892 - AutoRAG - WARNING - �ñ׳� ���� ���� (������ ȯ���� �� ����): module 'signal' has no attribute 'SIGALRM'
358
+ 2025-04-10 23:38:09,911 - docling.document_converter - INFO - Going to convert document batch...
359
+ 2025-04-10 23:38:09,911 - docling.document_converter - INFO - Initializing pipeline for StandardPdfPipeline with options hash 3d2abd0e021741887551c73bd132b421
360
+ 2025-04-10 23:38:09,920 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
361
+ 2025-04-10 23:38:09,921 - docling.models.factories - INFO - Registered ocr engines: ['easyocr', 'ocrmac', 'rapidocr', 'tesserocr', 'tesseract']
362
+ 2025-04-10 23:38:09,951 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cpu'
363
+ 2025-04-10 23:38:11,882 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cpu'
364
+ 2025-04-10 23:38:12,840 - docling.utils.accelerator_utils - INFO - Accelerator device: 'cpu'
365
+ 2025-04-10 23:38:13,366 - docling.models.factories.base_factory - INFO - Loading plugin 'docling_defaults'
366
+ 2025-04-10 23:38:13,367 - docling.models.factories - INFO - Registered picture descriptions: ['vlm', 'api']
367
+ 2025-04-10 23:38:13,367 - docling.pipeline.base_pipeline - INFO - Processing document RAG �Ʒÿ� Q.pdf
368
+ 2025-04-10 23:38:14,803 - docling.document_converter - INFO - Finished converting document RAG �Ʒÿ� Q.pdf in 4.92 sec.
369
+ 2025-04-10 23:38:14,825 - AutoRAG - INFO - ûũ ���� �Ϸ�: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
370
+ 2025-04-10 23:38:14,825 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 4.93��
371
+ 2025-04-10 23:38:14,825 - AutoRAG - INFO - �� ���� ��� ���� ��...
372
+ 2025-04-10 23:38:14,826 - VectorStore - INFO - FAISS ��� ���� ��: 1�� ����
373
+ 2025-04-10 23:38:15,340 - faiss.loader - INFO - Loading faiss with AVX2 support.
374
+ 2025-04-10 23:38:15,355 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
375
+ 2025-04-10 23:38:15,359 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
376
+ 2025-04-10 23:38:15,359 - VectorStore - INFO - FAISS �ε��� ���� �Ϸ�
377
+ 2025-04-10 23:38:15,359 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\RAG3_voice\cached_data\vector_index
378
+ 2025-04-10 23:38:15,360 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
379
+ 2025-04-10 23:38:15,360 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
380
+ 2025-04-10 23:38:15,360 - AutoRAG - INFO - �⺻ RAG Chain�� ����� �� ���� ��ü ������ �õ��մϴ�...
381
+ 2025-04-10 23:38:15,360 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ...
382
+ 2025-04-10 23:38:15,360 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ: deepseek-chat
383
+ 2025-04-10 23:38:15,360 - DirectDeepSeek - INFO - DirectDeepSeekClient �ʱ�ȭ: ��=deepseek-chat, ��������Ʈ=https://api.deepseek.com/v1/chat/completions
384
+ 2025-04-10 23:38:15,360 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ ����
385
+ 2025-04-10 23:38:15,361 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ �Ϸ�
386
+ 2025-04-10 23:38:15,361 - AutoRAG - INFO - ���� RAG ü�� �ʱ�ȭ ����
387
+ 2025-04-10 23:38:15,361 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
388
+ 2025-04-10 23:38:16,336 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
389
+ 2025-04-10 23:38:16,681 - DeepSeekUtils - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
390
+ 2025-04-10 23:38:17,260 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
391
+ 2025-04-10 23:38:20,420 - DeepSeekUtils - INFO - DeepSeek API ���� ����
392
+ 2025-04-10 23:38:20,534 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
393
+ 2025-04-10 23:38:20,545 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
394
+ 2025-04-10 23:44:10,873 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\RAG3_voice
395
+ 2025-04-10 23:44:10,873 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3_voice\documents
396
+ 2025-04-10 23:44:10,874 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3_voice\documents
397
+ 2025-04-10 23:44:10,874 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3_voice\documents
398
+ 2025-04-10 23:44:10,874 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
399
+ 2025-04-10 23:44:10,875 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
400
+ 2025-04-10 23:44:10,875 - Config - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
401
+ 2025-04-10 23:44:17,355 - Config - INFO - DeepSeek API ���� ����
402
+ 2025-04-10 23:44:17,358 - AutoRAG - WARNING - RAG ü�� ����� �ε��� �� �����ϴ�: cannot import name 'RAGChain' from 'rag_chain' (C:\Users\USER\RAG3_voice\rag_chain.py)
403
+ 2025-04-10 23:44:17,363 - AutoRAG - WARNING - �������� ���� RAG ����� �ε��� �� �����ϴ�: No module named 'offline_fallback_rag'
404
+ 2025-04-10 23:44:17,363 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
405
+ 2025-04-10 23:44:17,364 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3_voice\documents
406
+ 2025-04-10 23:44:17,364 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
407
+ 2025-04-10 23:44:17,365 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3_voice\documents'
408
+ 2025-04-10 23:44:17,366 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\RAG3_voice\cached_data'
409
+ 2025-04-10 23:44:18,213 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
410
+ 2025-04-10 23:44:18,460 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
411
+ 2025-04-10 23:44:23,069 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
412
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
413
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3_voice\documents
414
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - ���丮 ����: ['.gitkeep', 'RAG �Ʒÿ� Q.pdf']
415
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf
416
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
417
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
418
+ 2025-04-10 23:44:23,070 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
419
+ 2025-04-10 23:44:23,071 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.00��
420
+ 2025-04-10 23:44:23,071 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
421
+ 2025-04-10 23:44:23,071 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\RAG3_voice\cached_data\vector_index
422
+ 2025-04-10 23:44:23,072 - faiss.loader - INFO - Loading faiss with AVX2 support.
423
+ 2025-04-10 23:44:23,083 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
424
+ 2025-04-10 23:44:23,085 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
425
+ 2025-04-10 23:44:23,086 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
426
+ 2025-04-10 23:44:23,086 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
427
+ 2025-04-10 23:44:23,086 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\RAG3_voice\cached_data\vector_index
428
+ 2025-04-10 23:44:23,087 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
429
+ 2025-04-10 23:44:23,087 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
430
+ 2025-04-10 23:44:23,087 - AutoRAG - INFO - �⺻ RAG Chain�� ����� �� ���� ��ü ������ �õ��մϴ�...
431
+ 2025-04-10 23:44:23,087 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ...
432
+ 2025-04-10 23:44:23,087 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ: deepseek-chat
433
+ 2025-04-10 23:44:23,087 - DirectDeepSeek - INFO - DirectDeepSeekClient �ʱ�ȭ: ��=deepseek-chat, ��������Ʈ=https://api.deepseek.com/v1/chat/completions
434
+ 2025-04-10 23:44:23,087 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ ����
435
+ 2025-04-10 23:44:23,087 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ �Ϸ�
436
+ 2025-04-10 23:44:23,087 - AutoRAG - INFO - ���� RAG ü�� �ʱ�ȭ ����
437
+ 2025-04-10 23:44:23,087 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
438
+ 2025-04-10 23:44:23,988 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
439
+ 2025-04-10 23:44:24,635 - DeepSeekUtils - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
440
+ 2025-04-10 23:44:25,196 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
441
+ 2025-04-10 23:44:28,870 - DeepSeekUtils - INFO - DeepSeek API ���� ����
442
+ 2025-04-10 23:44:29,004 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
443
+ 2025-04-10 23:44:29,016 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
444
+ 2025-04-11 00:59:57,522 - AutoRAG - INFO - ���� �۾� ���丮: C:\Users\USER\RAG3_voice
445
+ 2025-04-11 00:59:57,523 - AutoRAG - INFO - ������ PDF ���丮: C:\Users\USER\RAG3_voice\documents
446
+ 2025-04-11 00:59:57,523 - AutoRAG - INFO - ���� ��η� ��ȯ�� PDF ���丮: C:\Users\USER\RAG3_voice\documents
447
+ 2025-04-11 00:59:57,523 - AutoRAG - INFO - PDF ���丮�� �����մϴ�: C:\Users\USER\RAG3_voice\documents
448
+ 2025-04-11 00:59:57,524 - AutoRAG - INFO - ���丮 �� PDF ���� ���: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
449
+ 2025-04-11 00:59:57,524 - AutoRAG - INFO - ���ø����̼� ���� ���� ��...
450
+ 2025-04-11 00:59:57,524 - Config - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
451
+ 2025-04-11 01:00:01,647 - Config - INFO - DeepSeek API ���� ����
452
+ 2025-04-11 01:00:01,661 - AutoRAG - WARNING - RAG ü�� ����� �ε��� �� �����ϴ�: cannot import name 'RAGChain' from 'rag_chain' (C:\Users\USER\RAG3_voice\rag_chain.py)
453
+ 2025-04-11 01:00:01,666 - AutoRAG - WARNING - �������� ���� RAG ����� �ε��� �� �����ϴ�: No module named 'offline_fallback_rag'
454
+ 2025-04-11 01:00:01,667 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ ����
455
+ 2025-04-11 01:00:01,668 - AutoRAG - INFO - ������ PDF ���丮 (���� ���): C:\Users\USER\RAG3_voice\documents
456
+ 2025-04-11 01:00:01,668 - AutoRAG - INFO - PDF ���丮���� 1���� PDF ������ ã�ҽ��ϴ�: ['RAG �Ʒÿ� Q.pdf']
457
+ 2025-04-11 01:00:01,669 - AutoRAG - INFO - PDF ���� ���丮: 'C:\Users\USER\RAG3_voice\documents'
458
+ 2025-04-11 01:00:01,669 - AutoRAG - INFO - ij�� ���丮: 'C:\Users\USER\RAG3_voice\cached_data'
459
+ 2025-04-11 01:00:02,471 - VectorStore - INFO - �Ӻ��� �� �ε� ��: Alibaba-NLP/gte-multilingual-base
460
+ 2025-04-11 01:00:02,744 - sentence_transformers.SentenceTransformer - INFO - Load pretrained SentenceTransformer: Alibaba-NLP/gte-multilingual-base
461
+ 2025-04-11 01:00:07,061 - VectorStore - INFO - �Ӻ��� �� �ʱ�ȭ �Ϸ�: Alibaba-NLP/gte-multilingual-base
462
+ 2025-04-11 01:00:07,061 - AutoRAG - INFO - ���� �ڵ� �ε� �� ó�� ����...
463
+ 2025-04-11 01:00:07,061 - AutoRAG - INFO - PDF ���� �˻� ���: C:\Users\USER\RAG3_voice\documents
464
+ 2025-04-11 01:00:07,061 - AutoRAG - INFO - ���丮 ����: ['.gitkeep', 'RAG �Ʒÿ� Q.pdf']
465
+ 2025-04-11 01:00:07,061 - AutoRAG - INFO - PDF ���� ã��: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf
466
+ 2025-04-11 01:00:07,061 - AutoRAG - INFO - �߰ߵ� ��� PDF ����: ['C:\\Users\\USER\\RAG3_voice\\documents\\RAG �Ʒÿ� Q.pdf']
467
+ 2025-04-11 01:00:07,062 - AutoRAG - INFO - �߰ߵ� PDF ����: 1��
468
+ 2025-04-11 01:00:07,062 - AutoRAG - INFO - ûũ �ε� �Ϸ�: C:\Users\USER\RAG3_voice\documents\RAG �Ʒÿ� Q.pdf (1�� ûũ)
469
+ 2025-04-11 01:00:07,062 - AutoRAG - INFO - ���� ó�� �Ϸ�: 1�� ûũ, 0.00��
470
+ 2025-04-11 01:00:07,062 - AutoRAG - INFO - ����� ���� �ε��� �ε� ��...
471
+ 2025-04-11 01:00:07,062 - VectorStore - INFO - FAISS �ε��� �ε� ��: C:\Users\USER\RAG3_voice\cached_data\vector_index
472
+ 2025-04-11 01:00:07,065 - faiss.loader - INFO - Loading faiss with AVX2 support.
473
+ 2025-04-11 01:00:07,173 - faiss.loader - INFO - Successfully loaded faiss with AVX2 support.
474
+ 2025-04-11 01:00:07,177 - faiss - INFO - Failed to load GPU Faiss: name 'GpuIndexIVFFlat' is not defined. Will not load constructor refs for GPU indexes.
475
+ 2025-04-11 01:00:07,185 - VectorStore - INFO - FAISS �ε��� �ε� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
476
+ 2025-04-11 01:00:07,185 - AutoRAG - INFO - ���� �ε��� �ε� �Ϸ�
477
+ 2025-04-11 01:00:07,186 - AutoRAG - INFO - ���� ��� ���� ��: C:\Users\USER\RAG3_voice\cached_data\vector_index
478
+ 2025-04-11 01:00:07,187 - VectorStore - INFO - FAISS �ε��� ���� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
479
+ 2025-04-11 01:00:07,187 - AutoRAG - INFO - ���� �ε��� ���� �Ϸ�: C:\Users\USER\RAG3_voice\cached_data\vector_index
480
+ 2025-04-11 01:00:07,187 - AutoRAG - INFO - �⺻ RAG Chain�� ����� �� ���� ��ü ������ �õ��մϴ�...
481
+ 2025-04-11 01:00:07,188 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ...
482
+ 2025-04-11 01:00:07,188 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ: deepseek-chat
483
+ 2025-04-11 01:00:07,188 - DirectDeepSeek - INFO - DirectDeepSeekClient �ʱ�ȭ: ��=deepseek-chat, ��������Ʈ=https://api.deepseek.com/v1/chat/completions
484
+ 2025-04-11 01:00:07,188 - FallbackRAGChain - INFO - DeepSeek �� ���� �ʱ�ȭ ����
485
+ 2025-04-11 01:00:07,189 - FallbackRAGChain - INFO - ���� RAG ü�� �ʱ�ȭ �Ϸ�
486
+ 2025-04-11 01:00:07,189 - AutoRAG - INFO - ���� RAG ü�� �ʱ�ȭ ����
487
+ 2025-04-11 01:00:07,189 - AutoRAG - INFO - AutoRAGChatApp �ʱ�ȭ �Ϸ�
488
+ 2025-04-11 01:00:08,133 - httpx - INFO - HTTP Request: GET https://api.gradio.app/gradio-messaging/en "HTTP/1.1 200 OK"
489
+ 2025-04-11 01:00:09,395 - DeepSeekUtils - INFO - DeepSeek API ���� �׽�Ʈ ����: https://api.deepseek.com/v1/chat/completions, ��: deepseek-chat
490
+ 2025-04-11 01:00:09,953 - httpx - INFO - HTTP Request: GET https://api.gradio.app/pkg-version "HTTP/1.1 200 OK"
491
+ 2025-04-11 01:00:13,592 - DeepSeekUtils - INFO - DeepSeek API ���� ����
492
+ 2025-04-11 01:00:13,714 - httpx - INFO - HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events "HTTP/1.1 200 OK"
493
+ 2025-04-11 01:00:13,789 - httpx - INFO - HTTP Request: HEAD http://127.0.0.1:7860/ "HTTP/1.1 200 OK"
clova_stt.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 네이버 클로바 음성인식(STT) API 연동 모듈
3
+ """
4
+ import os
5
+ import json
6
+ import requests
7
+ import logging
8
+ from typing import Dict, Any
9
+ from dotenv import load_dotenv
10
+
11
+ # .env 파일 로드
12
+ load_dotenv()
13
+
14
+ # 로깅 설정
15
+ logger = logging.getLogger("ClovaSTT")
16
+
17
+ class ClovaSTT:
18
+ """
19
+ 네이버 클로바 음성인식(STT) API 클래스
20
+ """
21
+
22
+ def __init__(self):
23
+ """
24
+ 클로바 STT 클라이언트 초기화
25
+ """
26
+ # .env 파일에서 설정 가져오기
27
+ self.client_id = os.getenv("NAVER_CLIENT_ID", "")
28
+ self.client_secret = os.getenv("NAVER_CLIENT_SECRET", "")
29
+
30
+ # 클라이언트 ID와 Secret 검증
31
+ if not self.client_id or not self.client_secret:
32
+ logger.warning("네이버 클로바 API 키가 설정되지 않았습니다.")
33
+ logger.warning(".env 파일에 NAVER_CLIENT_ID와 NAVER_CLIENT_SECRET를 설정해주세요.")
34
+ else:
35
+ logger.info("네이버 클로바 STT API 설정 완료")
36
+
37
+ def recognize(self, audio_bytes, language="Kor") -> Dict[str, Any]:
38
+ """
39
+ 오디오 데이터를 텍스트로 변환
40
+
41
+ Args:
42
+ audio_bytes: 오디오 파일 바이트 데이터
43
+ language: 언어 코드 (기본값: 'Kor')
44
+
45
+ Returns:
46
+ 인식된 텍스트 또는 오류 메시지
47
+ """
48
+ if not self.client_id or not self.client_secret:
49
+ logger.error("API 키가 설정되지 않았습니다.")
50
+ return {"success": False, "error": "API 키가 설정되지 않았습니다."}
51
+
52
+ try:
53
+ # API 엔드포인트 URL
54
+ url = f"https://naveropenapi.apigw.ntruss.com/recog/v1/stt?lang={language}"
55
+
56
+ # 요청 헤더 설정
57
+ headers = {
58
+ "X-NCP-APIGW-API-KEY-ID": self.client_id,
59
+ "X-NCP-APIGW-API-KEY": self.client_secret,
60
+ "Content-Type": "application/octet-stream"
61
+ }
62
+
63
+ logger.info("네이버 클로바 STT 요청 전송 중...")
64
+
65
+ # API 요청 전송
66
+ response = requests.post(url, headers=headers, data=audio_bytes, timeout=30)
67
+
68
+ # 응답 처리
69
+ if response.status_code == 200:
70
+ result = response.json()
71
+ recognized_text = result.get("text", "")
72
+ logger.info(f"인식 성공: {recognized_text[:50]}...")
73
+ return {
74
+ "success": True,
75
+ "text": recognized_text,
76
+ "result": result
77
+ }
78
+ else:
79
+ logger.error(f"API 오류 응답: {response.status_code}, {response.text}")
80
+ return {
81
+ "success": False,
82
+ "error": f"API 오류: {response.status_code}",
83
+ "details": response.text
84
+ }
85
+
86
+ except Exception as e:
87
+ logger.error(f"음성인식 처리 중 오류 발생: {str(e)}")
88
+ return {
89
+ "success": False,
90
+ "error": "음성인식 처리 실패",
91
+ "details": str(e)
92
+ }
config.py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 벡터 스토어, 임베딩 모델, LLM 등 구성 요소 설정
3
+ 환경 변수 및 .env 파일 활용 개선 버전 - HuggingFace 환경 지원 추가
4
+ """
5
+ import os
6
+ import logging
7
+ import sys
8
+ import re
9
+ import requests
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Dict, Any
13
+ from dotenv import load_dotenv
14
+
15
+ # 로깅 설정
16
+ logger = logging.getLogger("Config")
17
+
18
+ # 현재 실행 위치 확인 (디버깅용)
19
+ script_dir = os.path.dirname(os.path.abspath(__file__))
20
+ logger.info(f"스크립트 디렉토리: {script_dir}")
21
+ logger.info(f"현재 작업 디렉토리: {os.getcwd()}")
22
+ logger.info(f"운영 체제: {os.name}")
23
+
24
+ # 환경 감지 - HuggingFace Space 환경인지 확인
25
+ IS_HUGGINGFACE = False
26
+ if os.getenv('SPACE_ID') is not None or os.getenv('SYSTEM') == 'spaces':
27
+ IS_HUGGINGFACE = True
28
+ logger.info("HuggingFace Spaces 환경이 감지되었습니다.")
29
+ else:
30
+ # 로컬 환경인 경우 .env 파일 로드
31
+ # .env 파일 위치 후보들
32
+ env_paths = [
33
+ ".env", # 현재 디렉토리
34
+ os.path.join(script_dir, ".env"), # 스크립트 디렉토리
35
+ os.path.join(script_dir, "config", ".env"), # config 하위 디렉토리
36
+ os.path.join(os.path.dirname(script_dir), ".env"), # 상위 디렉토리
37
+ ]
38
+
39
+ # .env 파일 찾아서 로드
40
+ env_loaded = False
41
+ for env_path in env_paths:
42
+ if os.path.isfile(env_path):
43
+ logger.info(f".env 파일 발견: {env_path}")
44
+ env_loaded = load_dotenv(env_path, verbose=True)
45
+ if env_loaded:
46
+ logger.info(f".env 파일 로드 성공: {env_path}")
47
+ break
48
+
49
+ if not env_loaded:
50
+ logger.warning(".env 파일을 찾을 수 없습니다. 기본값 또는 시스템 환경 변수를 사용합니다.")
51
+
52
+ logger.info(f"로컬 환경에서 실행 중입니다. (OS: {'Windows' if os.name == 'nt' else 'Unix/Linux/MacOS'})")
53
+
54
+ # Windows 환경 감지
55
+ IS_WINDOWS = os.name == 'nt'
56
+
57
+ # 유틸리티 함수: 환경 변수 가져오기 (HuggingFace 환경과 로컬 환경 구분)
58
+ def get_env(key: str, default: Any = None, required: bool = False) -> Any:
59
+ """
60
+ 환경 변수를 가져오는 유틸리티 함수 (HuggingFace 환경 지원)
61
+
62
+ Args:
63
+ key: 환경 변수 키
64
+ default: 환경 변수가 없을 경우 기본값
65
+ required: 환경 변수가 필수적인지 여부
66
+
67
+ Returns:
68
+ 환경 변수 값 또는 기본값
69
+ """
70
+ # HuggingFace Spaces 환경에서는 내부 환경변수 활용
71
+ if IS_HUGGINGFACE:
72
+ # HuggingFace Spaces에서는 시크릿 값을 직접 사용
73
+ # HF_SECRET_<KEY> 형식으로 저장된 시크릿 확인
74
+ hf_secret_key = f"HF_SECRET_{key.upper()}"
75
+ value = os.getenv(hf_secret_key)
76
+
77
+ # 시크릿이 없으면 일반 환경변수 확인
78
+ if value is None:
79
+ value = os.getenv(key, default)
80
+ else:
81
+ # 로컬 환경에서는 일반적인 방식으로 환경변수 가져오기
82
+ value = os.getenv(key, default)
83
+
84
+ if required and value is None:
85
+ if IS_HUGGINGFACE:
86
+ error_msg = f"필수 환경 변수 {key}가 설정되지 않았습니다. HuggingFace Space에서 시크릿을 설정해주세요."
87
+ logger.error(error_msg)
88
+ raise ValueError(error_msg)
89
+ else:
90
+ error_msg = f"필수 환경 변수 {key}가 설정되지 않았습니다. .env 파일에 추가해주세요."
91
+ logger.error(error_msg)
92
+ raise ValueError(error_msg)
93
+
94
+ return value
95
+
96
+ # 경로 생성 유틸리티 함수
97
+ def ensure_absolute_path(path_str: str) -> str:
98
+ """
99
+ 상대 경로를 절대 경로로 변환 (Windows 경로 지원)
100
+
101
+ Args:
102
+ path_str: 변환할 경로 문자열
103
+
104
+ Returns:
105
+ 절대 경로
106
+ """
107
+ # Windows 드라이브 문자(C:\ 등)로 시작하는 경로 확인
108
+ if IS_WINDOWS and re.match(r'^[a-zA-Z]:\\', path_str):
109
+ logger.info(f"Windows 절대 경로 감지: {path_str}")
110
+ # Windows 절대 경로는 그대로 사용
111
+ return path_str
112
+
113
+ path = Path(path_str)
114
+ if path.is_absolute():
115
+ return str(path)
116
+
117
+ # 스크립트 디렉토리 기준 경로
118
+ script_based_path = Path(script_dir) / path
119
+
120
+ # 현재 작업 디렉토리 기준 경로
121
+ cwd_based_path = Path.cwd() / path
122
+
123
+ # 두 경로 중 존재하는 경로 우선 사용
124
+ if script_based_path.exists():
125
+ return str(script_based_path)
126
+ elif cwd_based_path.exists():
127
+ return str(cwd_based_path)
128
+ else:
129
+ # 기본적으로 현재 작업 디렉토리 기준 경로 반환
130
+ return str(cwd_based_path)
131
+
132
+ # Windows 경로 처리를 위한 유틸리티 함수
133
+ def normalize_path(path_str: str) -> str:
134
+ """
135
+ 경로 문자열을 정규화하여 OS에 맞게 변환
136
+
137
+ Args:
138
+ path_str: 변환할 경로 문자열
139
+
140
+ Returns:
141
+ 정규화된 경로
142
+ """
143
+ # Windows 경로 형식('\')��� OS에 맞게 변환
144
+ return os.path.normpath(path_str)
145
+
146
+ # 기본 디렉토리 설정 (절대 경로로 변환)
147
+ PDF_DIRECTORY_RAW = get_env("PDF_DIRECTORY", "documents")
148
+ # Windows 백슬래시 이중 처리를 위해 정규화
149
+ PDF_DIRECTORY_RAW = normalize_path(PDF_DIRECTORY_RAW)
150
+ PDF_DIRECTORY = ensure_absolute_path(PDF_DIRECTORY_RAW)
151
+
152
+ CACHE_DIRECTORY_RAW = get_env("CACHE_DIRECTORY", "cached_data")
153
+ CACHE_DIRECTORY_RAW = normalize_path(CACHE_DIRECTORY_RAW)
154
+ CACHE_DIRECTORY = ensure_absolute_path(CACHE_DIRECTORY_RAW)
155
+
156
+ logger.info(f"PDF 디렉토리 (원본): {PDF_DIRECTORY_RAW}")
157
+ logger.info(f"PDF 디렉토리 (절대): {PDF_DIRECTORY}")
158
+ logger.info(f"캐시 디렉토리 (원본): {CACHE_DIRECTORY_RAW}")
159
+ logger.info(f"캐시 디렉토리 (절대): {CACHE_DIRECTORY}")
160
+
161
+ # 청킹 설정
162
+ CHUNK_SIZE = int(get_env("CHUNK_SIZE", "1000"))
163
+ CHUNK_OVERLAP = int(get_env("CHUNK_OVERLAP", "200"))
164
+
165
+ # API 키 및 환경 설정
166
+ OPENAI_API_KEY = get_env("OPENAI_API_KEY", "")
167
+ LANGFUSE_PUBLIC_KEY = get_env("LANGFUSE_PUBLIC_KEY", "")
168
+ LANGFUSE_SECRET_KEY = get_env("LANGFUSE_SECRET_KEY", "")
169
+ LANGFUSE_HOST = get_env("LANGFUSE_HOST", "https://cloud.langfuse.com")
170
+
171
+ # DeepSeek 관련 설정 추가
172
+ DEEPSEEK_API_KEY = get_env("DEEPSEEK_API_KEY", "")
173
+ DEEPSEEK_ENDPOINT = get_env("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
174
+ DEEPSEEK_MODEL = get_env("DEEPSEEK_MODEL", "deepseek-chat")
175
+
176
+ # 허깅페이스 환경에서 API 키 확인 및 로그 출력
177
+ if IS_HUGGINGFACE:
178
+ logger.info(f"허깅페이스 환경에서 DeepSeek API 키 존재 여부: {bool(DEEPSEEK_API_KEY)}")
179
+ # 보안을 위해 API 키 첫 4자리와 마지막 4자리만 표시 (키가 존재하는 경우)
180
+ if DEEPSEEK_API_KEY:
181
+ masked_key = DEEPSEEK_API_KEY[:4] + "****" + DEEPSEEK_API_KEY[-4:] if len(DEEPSEEK_API_KEY) > 8 else "****"
182
+ logger.info(f"DeepSeek API 키: {masked_key}")
183
+
184
+ logger.info(f"DeepSeek 모델: {DEEPSEEK_MODEL}")
185
+ logger.info(f"DeepSeek 엔드포인트: {DEEPSEEK_ENDPOINT}")
186
+
187
+ # Milvus 벡터 DB 설정
188
+ MILVUS_HOST = get_env("MILVUS_HOST", "localhost")
189
+ MILVUS_PORT = get_env("MILVUS_PORT", "19530")
190
+ MILVUS_COLLECTION = get_env("MILVUS_COLLECTION", "pdf_documents")
191
+
192
+ # 임베딩 모델 설정
193
+ EMBEDDING_MODEL = get_env("EMBEDDING_MODEL", "Alibaba-NLP/gte-multilingual-base") # 다국어 지원 모델
194
+ RERANKER_MODEL = get_env("RERANKER_MODEL", "Alibaba-NLP/gte-multilingual-reranker-base") # 다국어 지원 리랭커
195
+
196
+ # LLM 모델 설정 (환경에 따라 자동 선택)
197
+ USE_OPENAI = get_env("USE_OPENAI", "False").lower() == "true"
198
+ USE_DEEPSEEK = get_env("USE_DEEPSEEK", "False").lower() == "true"
199
+
200
+ # 허깅페이스 환경에서는 DeepSeek 우선 사용
201
+ if IS_HUGGINGFACE:
202
+ # 허깅페이스 환경에서 DeepSeek API 키가 있는지 확인
203
+ if DEEPSEEK_API_KEY:
204
+ USE_DEEPSEEK = True
205
+ USE_OPENAI = False
206
+ LLM_MODEL = DEEPSEEK_MODEL
207
+ logger.info("HuggingFace Spaces 환경: DeepSeek 모델 사용")
208
+ else:
209
+ logger.warning("HuggingFace Spaces 환경에서 DeepSeek API 키가 설정되지 않았습니다.")
210
+ USE_DEEPSEEK = False
211
+ USE_OPENAI = False # 기본적으로 API 키가 없으면 비활성화
212
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest") # 대체 모델 설정
213
+ logger.info(f"HuggingFace Spaces 환경: DeepSeek API 키 없음, LLM 모델: {LLM_MODEL}")
214
+ else:
215
+ # 로컬 환경에서는 설정에 따라 LLM 선택
216
+ if USE_DEEPSEEK:
217
+ LLM_MODEL = DEEPSEEK_MODEL
218
+ logger.info(f"로컬 환경: DeepSeek 모델 사용 ({DEEPSEEK_MODEL})")
219
+ elif USE_OPENAI:
220
+ LLM_MODEL = get_env("LLM_MODEL", "gpt-3.5-turbo")
221
+ logger.info(f"로컬 환경: OpenAI 모델 사용 ({LLM_MODEL})")
222
+ else:
223
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
224
+ OLLAMA_HOST = get_env("OLLAMA_HOST", "http://localhost:11434")
225
+ logger.info(f"로컬 환경: Ollama 모델 사용 ({LLM_MODEL})")
226
+
227
+ # API 키 검증 (로컬 환경만)
228
+ if not IS_HUGGINGFACE:
229
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
230
+ logger.warning("DeepSeek 모델이 선택되었지만 API 키가 설정되지 않았습니다.")
231
+ USE_DEEPSEEK = False
232
+ USE_OPENAI = False
233
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
234
+ logger.info("DeepSeek API 키가 없어 Ollama로 폴백합니다.")
235
+ elif USE_OPENAI and not OPENAI_API_KEY:
236
+ logger.warning("OpenAI 모델이 선택되었지만 API 키가 설정되지 않았습니다.")
237
+ logger.warning("OpenAI API 키가 없어 Ollama로 폴백합니다.")
238
+ USE_OPENAI = False
239
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
240
+
241
+ # DeepSeek API 테스트 함수
242
+ def test_deepseek_connection():
243
+ """
244
+ DeepSeek API 연결 테스트
245
+
246
+ Returns:
247
+ 테스트 결과 딕셔너리 (성공 여부 및 메시지)
248
+ """
249
+ if not DEEPSEEK_API_KEY:
250
+ logger.warning("DeepSeek API 키가 설정되지 않아 테스트를 건너뜁니다.")
251
+ return {
252
+ "success": False,
253
+ "message": "API 키가 설정되지 않았습니다.",
254
+ "status_code": None
255
+ }
256
+
257
+ try:
258
+ logger.info(f"DeepSeek API 연결 테스트 시작: {DEEPSEEK_ENDPOINT}, 모델: {DEEPSEEK_MODEL}")
259
+
260
+ # 테스트용 간단한 프롬프트
261
+ test_prompt = "Hello, please respond with a short greeting."
262
+
263
+ # API 요청 헤더 및 데이터
264
+ headers = {
265
+ "Content-Type": "application/json",
266
+ "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
267
+ }
268
+
269
+ payload = {
270
+ "model": DEEPSEEK_MODEL,
271
+ "messages": [{"role": "user", "content": test_prompt}],
272
+ "temperature": 0.7,
273
+ "max_tokens": 50
274
+ }
275
+
276
+ # API 요청 전송
277
+ response = requests.post(
278
+ DEEPSEEK_ENDPOINT,
279
+ headers=headers,
280
+ json=payload,
281
+ timeout=10 # 10초 타임아웃
282
+ )
283
+
284
+ # 응답 확인
285
+ if response.status_code == 200:
286
+ logger.info("DeepSeek API 연결 성공")
287
+ return {
288
+ "success": True,
289
+ "message": "API 연결 성공",
290
+ "status_code": response.status_code
291
+ }
292
+ else:
293
+ logger.error(f"DeepSeek API 오류: 상태 코드 {response.status_code}")
294
+ error_message = ""
295
+ try:
296
+ error_data = response.json()
297
+ error_message = error_data.get("error", {}).get("message", str(error_data))
298
+ except:
299
+ error_message = response.text
300
+
301
+ return {
302
+ "success": False,
303
+ "message": f"API 오류: {error_message}",
304
+ "status_code": response.status_code
305
+ }
306
+
307
+ except requests.exceptions.Timeout:
308
+ logger.error("DeepSeek API 요청 시간 초과")
309
+ return {
310
+ "success": False,
311
+ "message": "API 요청 시간 초과",
312
+ "status_code": None
313
+ }
314
+ except requests.exceptions.ConnectionError:
315
+ logger.error("DeepSeek API 연결 실패")
316
+ return {
317
+ "success": False,
318
+ "message": "API 서버 연결 실패",
319
+ "status_code": None
320
+ }
321
+ except Exception as e:
322
+ logger.error(f"DeepSeek API 테스트 중 예상치 못한 오류: {e}", exc_info=True)
323
+ return {
324
+ "success": False,
325
+ "message": f"예상치 못한 오류: {str(e)}",
326
+ "status_code": None
327
+ }
328
+
329
+ # 벡터 검색 설정
330
+ TOP_K_RETRIEVAL = int(get_env("TOP_K_RETRIEVAL", "5")) # 벡터 검색 결과 수
331
+ TOP_K_RERANK = int(get_env("TOP_K_RERANK", "3")) # 리랭킹 후 선택할 결과 수
332
+
333
+ # 로깅 설정
334
+ LOG_LEVEL = get_env("LOG_LEVEL", "INFO")
335
+ LOG_FILE = get_env("LOG_FILE", "autorag.log")
336
+
337
+ # 설정 정보 출력 (디버깅용)
338
+ def print_config():
339
+ """현재 설정 정보를 로그에 출력"""
340
+ logger.info("===== 현재 설정 정보 =====")
341
+ logger.info(f"실행 환경: {'HuggingFace Spaces' if IS_HUGGINGFACE else '로컬'}")
342
+ logger.info(f"문서 디렉토리: {PDF_DIRECTORY}")
343
+ logger.info(f"캐시 디렉토리: {CACHE_DIRECTORY}")
344
+ logger.info(f"청크 크기: {CHUNK_SIZE}, 오버랩: {CHUNK_OVERLAP}")
345
+ logger.info(f"OpenAI 사용: {USE_OPENAI}")
346
+ logger.info(f"DeepSeek 사용: {USE_DEEPSEEK}")
347
+ logger.info(f"LLM 모델: {LLM_MODEL}")
348
+ if not USE_OPENAI and not USE_DEEPSEEK and not IS_HUGGINGFACE:
349
+ logger.info(f"Ollama 호스트: {OLLAMA_HOST}")
350
+ logger.info(f"임베딩 모델: {EMBEDDING_MODEL}")
351
+ logger.info(f"리랭커 모델: {RERANKER_MODEL}")
352
+ logger.info(f"TOP_K 검색: {TOP_K_RETRIEVAL}, 리랭킹: {TOP_K_RERANK}")
353
+ logger.info("=========================")
354
+
355
+ # 설정 유효성 검사
356
+ def validate_config() -> Dict[str, Any]:
357
+ """
358
+ 현재 설정의 유효성을 검사하고 경고나 오류를 로그에 기록
359
+
360
+ Returns:
361
+ 검증 결과 (status: 상태, warnings: 경고 목록)
362
+ """
363
+ warnings = []
364
+
365
+ # 디렉토리 확인
366
+ if not os.path.exists(PDF_DIRECTORY):
367
+ warnings.append(f"PDF 디렉토리({PDF_DIRECTORY})가 존재하지 않습니다.")
368
+
369
+ # API 키 확인 (허깅페이스와 로컬 환경 구분)
370
+ if IS_HUGGINGFACE:
371
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
372
+ warnings.append("허깅페이스 환경에서 DeepSeek 사용이 설정되었지만 API 키가 제공되지 않았습니다.")
373
+ else:
374
+ if USE_OPENAI and not OPENAI_API_KEY:
375
+ warnings.append("OpenAI 사용이 설정되었지만 API 키가 제공되지 않았습니다.")
376
+
377
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
378
+ warnings.append("DeepSeek 사용이 설정되었지만 API 키가 제공되지 않았습니다.")
379
+
380
+ # 모델 및 설정 값 확인
381
+ if CHUNK_SIZE <= CHUNK_OVERLAP:
382
+ warnings.append(f"청크 크기({CHUNK_SIZE})가 오버랩({CHUNK_OVERLAP})보다 작거나 같습니다.")
383
+
384
+ # DeepSeek API 연결 확인 (설정된 경우)
385
+ if USE_DEEPSEEK and DEEPSEEK_API_KEY:
386
+ deepseek_test_result = test_deepseek_connection()
387
+ if not deepseek_test_result["success"]:
388
+ warnings.append(f"DeepSeek API 연결 테스트 실패: {deepseek_test_result['message']}")
389
+
390
+ # 결과 기록
391
+ if warnings:
392
+ for warning in warnings:
393
+ logger.warning(warning)
394
+
395
+ return {
396
+ "status": "valid" if not warnings else "warnings",
397
+ "warnings": warnings
398
+ }
399
+
400
+ # 설정 로드 시 실행
401
+ print_config()
402
+ config_status = validate_config()
custom_rag_chain.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DeepSeek API를 활용한 커스텀 RAG 체인 구현
3
+ """
4
+ import os
5
+ import logging
6
+ import time
7
+ from typing import List, Dict, Any, Optional, Tuple
8
+
9
+ from langchain.schema import Document
10
+ from langchain.prompts import PromptTemplate
11
+ from langchain_core.output_parsers import StrOutputParser
12
+ from langchain_core.runnables import RunnablePassthrough
13
+
14
+ # DeepSeek 커스텀 LLM 임포트
15
+ from deepseek_llm import DeepSeekLLM, DeepSeekChat
16
+
17
+ # 설정 가져오기
18
+ try:
19
+ from config import (
20
+ DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT,
21
+ TOP_K_RETRIEVAL, TOP_K_RERANK
22
+ )
23
+ except ImportError:
24
+ # 설정 모듈을 가져올 수 없는 경우 기본값 설정
25
+ DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
26
+ DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
27
+ DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
28
+ TOP_K_RETRIEVAL = int(os.environ.get("TOP_K_RETRIEVAL", "5"))
29
+ TOP_K_RERANK = int(os.environ.get("TOP_K_RERANK", "3"))
30
+
31
+ # 로깅 설정
32
+ logger = logging.getLogger("CustomRAGChain")
33
+
34
+
35
+ class CustomRAGChain:
36
+ """
37
+ DeepSeek API를 활용한 커스텀 RAG 체인
38
+ """
39
+
40
+ def __init__(self, vector_store, use_reranker=False):
41
+ """
42
+ RAG 체인 초기화
43
+
44
+ Args:
45
+ vector_store: 벡터 스토어 인스턴스
46
+ use_reranker: 리랭커 사용 여부 (현재 미지원)
47
+ """
48
+ logger.info("커스텀 RAG 체인 초기화...")
49
+ self.vector_store = vector_store
50
+ self.use_reranker = use_reranker
51
+
52
+ # API 키 확인
53
+ if not DEEPSEEK_API_KEY:
54
+ logger.error("DeepSeek API 키가 설정되지 않았습니다.")
55
+ raise ValueError("DeepSeek API 키가 설정되지 않았습니다.")
56
+
57
+ # DeepSeek LLM 초기화
58
+ try:
59
+ self.llm = DeepSeekLLM(
60
+ api_key=DEEPSEEK_API_KEY,
61
+ model=DEEPSEEK_MODEL,
62
+ endpoint=DEEPSEEK_ENDPOINT,
63
+ temperature=0.3,
64
+ max_tokens=1000,
65
+ request_timeout=120,
66
+ max_retries=5
67
+ )
68
+ logger.info(f"DeepSeek LLM 초기화 성공: {DEEPSEEK_MODEL}")
69
+ except Exception as e:
70
+ logger.error(f"DeepSeek LLM 초기화 실패: {e}")
71
+ raise ValueError(f"DeepSeek LLM 초기화 실패: {str(e)}")
72
+
73
+ # 챗 인터페이스 초기화 (대체용)
74
+ self.chat = DeepSeekChat(
75
+ api_key=DEEPSEEK_API_KEY,
76
+ model=DEEPSEEK_MODEL,
77
+ endpoint=DEEPSEEK_ENDPOINT
78
+ )
79
+
80
+ # RAG 프롬프트 템플릿
81
+ self.prompt = PromptTemplate.from_template("""
82
+ 다음 정보를 기반으로 질문에 정확하게 답변해주세요.
83
+
84
+ 질문: {question}
85
+
86
+ 참고 정보:
87
+ {context}
88
+
89
+ 참고 정보에 답이 있으면 반드시 그 정보를 기반으로 답변하세요.
90
+ 참고 정보에 답이 없는 경우에는 일반적인 지식을 활용하여 답변할 수 있지만, "제공된 문서에는 이 정보가 없으나, 일반적으로는..." 식으로 시작하세요.
91
+ 답변은 정확하고 간결하게 제공하되, 가능한 참고 정보에서 근거를 찾아 설명해주세요.
92
+ 참고 정보의 출처도 함께 알려주세요.
93
+ """)
94
+
95
+ # RAG 체인 구성
96
+ self.chain = (
97
+ {"context": self._retrieve, "question": RunnablePassthrough()}
98
+ | self.prompt
99
+ | self.llm
100
+ | StrOutputParser()
101
+ )
102
+
103
+ logger.info("커스텀 RAG 체인 초기화 완료")
104
+
105
+ def _retrieve(self, query: str) -> str:
106
+ """
107
+ 쿼리에 대한 관련 문서 검색 및 컨텍스트 구성
108
+
109
+ Args:
110
+ query: 사용자 질문
111
+
112
+ Returns:
113
+ 검색 결과를 포함한 컨텍스트 문자열
114
+ """
115
+ if not query or not query.strip():
116
+ logger.warning("빈 쿼리로 검색 시도")
117
+ return "검색 쿼리가 비어있습니다."
118
+
119
+ try:
120
+ # 벡터 검색 수행
121
+ logger.info(f"벡터 검색 수행: '{query[:50]}{'...' if len(query) > 50 else ''}'")
122
+ docs = self.vector_store.similarity_search(query, k=TOP_K_RETRIEVAL)
123
+
124
+ if not docs:
125
+ logger.warning("검색 결과가 없습니다")
126
+ return "관련 문서를 찾을 수 없습니다."
127
+
128
+ # 검색 결과 컨텍스트 구성
129
+ context_parts = []
130
+ for i, doc in enumerate(docs, 1):
131
+ source = doc.metadata.get("source", "알 수 없는 출처")
132
+ page = doc.metadata.get("page", "")
133
+ source_info = f"{source}"
134
+ if page:
135
+ source_info += f" (페이지: {page})"
136
+
137
+ context_parts.append(f"[참고자료 {i}] - 출처: {source_info}\n{doc.page_content}\n")
138
+
139
+ context = "\n".join(context_parts)
140
+
141
+ # ��텍스트 길이 제한 (토큰 수 제한)
142
+ if len(context) > 6000:
143
+ logger.warning(f"컨텍스트가 너무 깁니다 ({len(context)} 문자). 제한합니다.")
144
+ context = context[:2500] + "\n...(중략)...\n" + context[-2500:]
145
+
146
+ logger.info(f"컨텍스트 생성 완료: {len(context_parts)}개 문서, {len(context)} 문자")
147
+ return context
148
+
149
+ except Exception as e:
150
+ logger.error(f"검색 중 오류: {e}")
151
+ return f"검색 중 오류 발생: {str(e)}"
152
+
153
+ def run(self, query: str) -> str:
154
+ """
155
+ 사용자 쿼리에 대한 RAG 파이프라인 실행
156
+
157
+ Args:
158
+ query: 사용자 질문
159
+
160
+ Returns:
161
+ 모델 응답 문자열
162
+ """
163
+ if not query or not query.strip():
164
+ logger.warning("빈 쿼리로 실행 시도")
165
+ return "질문이 비어있습니다. 질문을 입력해 주세요."
166
+
167
+ try:
168
+ logger.info(f"RAG 체인 실행: '{query[:50]}{'...' if len(query) > 50 else ''}'")
169
+ start_time = time.time()
170
+
171
+ # 벡터 검색 실행
172
+ context = self._retrieve(query)
173
+
174
+ # 직접 LLM 호출 (체인 사용)
175
+ try:
176
+ response = self.chain.invoke(query)
177
+ logger.info(f"LangChain 체인 호출 성공")
178
+ except Exception as chain_error:
179
+ logger.error(f"체인 호출 실패: {chain_error}, 대체 방식 시도")
180
+
181
+ # 대체 방식: 직접 채팅 API 호출
182
+ try:
183
+ prompt = self.prompt.format(question=query, context=context)
184
+ response = self.chat.generate([{"role": "user", "content": prompt}])
185
+ logger.info("대체 채팅 API 호출 성공")
186
+ except Exception as chat_error:
187
+ logger.error(f"대체 채팅 API 호출 실패: {chat_error}")
188
+
189
+ # 미리 정의된 응답으로 폴백
190
+ predefined_answers = {
191
+ "대한민국의 수도": "대한민국의 수도는 서울입니다.",
192
+ "수도": "대한민국의 수도는 서울입니다.",
193
+ "누구야": "저는 RAG 기반 질의응답 시스템입니다. 문서를 검색하고 관련 정보를 찾아드립니다.",
194
+ "안녕": "안녕하세요! 무엇을 도와드릴까요?",
195
+ "뭐해": "사용자의 질문에 답변하기 위해 문서를 검색하고 있습니다. 무엇을 알려드릴까요?"
196
+ }
197
+
198
+ # 질문에 맞는 미리 정의된 응답이 있는지 확인
199
+ for key, answer in predefined_answers.items():
200
+ if key in query.lower():
201
+ response = answer
202
+ logger.info(f"미리 정의된 응답 제공: {key}")
203
+ break
204
+ else:
205
+ # 검색 결과만 표시
206
+ response = f"""
207
+ API 연결 오류로 인해 검색 결과만 표시합니다.
208
+
209
+ 질문: {query}
210
+
211
+ 검색된 관련 문서:
212
+ {context}
213
+
214
+ [참고] API 연결 문제로 인해 자동 요약이 제공되지 않습니다. 다시 시도하거나 다른 질문을 해보세요.
215
+ """
216
+ logger.info("검색 결과만 표시")
217
+
218
+ end_time = time.time()
219
+ logger.info(f"RAG 체인 실행 완료: {end_time - start_time:.2f}초")
220
+ return response
221
+
222
+ except Exception as e:
223
+ logger.error(f"RAG 체인 실행 중 오류: {e}")
224
+ return f"질문 처리 중 오류가 발생했습니다: {str(e)}"
deepseek_utils.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DeepSeek API 테스트 및 유틸리티 기능
3
+ """
4
+ import os
5
+ import logging
6
+ import requests
7
+ import json
8
+ from typing import Dict, Any, Optional
9
+
10
+ # 로깅 설정
11
+ logger = logging.getLogger("DeepSeekUtils")
12
+
13
+
14
+ class DeepSeekError(Exception):
15
+ """DeepSeek API 관련 오류"""
16
+ pass
17
+
18
+
19
+ def test_deepseek_api(api_key: str, endpoint: str, model: str) -> Dict[str, Any]:
20
+ """
21
+ DeepSeek API 연결 테스트
22
+
23
+ Args:
24
+ api_key: DeepSeek API 키
25
+ endpoint: DeepSeek API 엔드포인트
26
+ model: 사용할 모델명
27
+
28
+ Returns:
29
+ 테스트 결과 딕셔너리 (성공 여부 및 메시지)
30
+ """
31
+ if not api_key:
32
+ logger.error("DeepSeek API 키가 제공되지 않았습니다.")
33
+ return {
34
+ "success": False,
35
+ "message": "API 키가 제공되지 않았습니다.",
36
+ "status_code": None,
37
+ "response": None
38
+ }
39
+
40
+ try:
41
+ logger.info(f"DeepSeek API 연결 테스트 시작: {endpoint}, 모델: {model}")
42
+
43
+ # 테스트용 간단한 프롬프트
44
+ test_prompt = "Hello, please respond with a short greeting."
45
+
46
+ # API 요청 헤더 및 데이터
47
+ headers = {
48
+ "Content-Type": "application/json",
49
+ "Authorization": f"Bearer {api_key}"
50
+ }
51
+
52
+ payload = {
53
+ "model": model,
54
+ "messages": [{"role": "user", "content": test_prompt}],
55
+ "temperature": 0.7,
56
+ "max_tokens": 50
57
+ }
58
+
59
+ # API 요청 전송
60
+ response = requests.post(
61
+ endpoint,
62
+ headers=headers,
63
+ data=json.dumps(payload),
64
+ timeout=10 # 10초 타임아웃
65
+ )
66
+
67
+ # 응답 확인
68
+ if response.status_code == 200:
69
+ logger.info("DeepSeek API 연결 성공")
70
+ response_data = response.json()
71
+
72
+ # 응답 내용 확인
73
+ if "choices" in response_data and len(response_data["choices"]) > 0:
74
+ message_content = response_data["choices"][0].get("message", {}).get("content", "")
75
+ return {
76
+ "success": True,
77
+ "message": "API 연결 성공",
78
+ "status_code": response.status_code,
79
+ "response": message_content[:100] + "..." if len(message_content) > 100 else message_content
80
+ }
81
+ else:
82
+ return {
83
+ "success": True,
84
+ "message": "API 연결 성공했으나 응답 형식이 예상과 다릅니다.",
85
+ "status_code": response.status_code,
86
+ "response": response_data
87
+ }
88
+ else:
89
+ logger.error(f"DeepSeek API 오류: 상태 코드 {response.status_code}")
90
+ error_message = ""
91
+ try:
92
+ error_data = response.json()
93
+ error_message = error_data.get("error", {}).get("message", str(error_data))
94
+ except:
95
+ error_message = response.text
96
+
97
+ return {
98
+ "success": False,
99
+ "message": f"API 오류: {error_message}",
100
+ "status_code": response.status_code,
101
+ "response": error_message
102
+ }
103
+
104
+ except requests.exceptions.Timeout:
105
+ logger.error("DeepSeek API 요청 시간 초과")
106
+ return {
107
+ "success": False,
108
+ "message": "API 요청 시간 초과",
109
+ "status_code": None,
110
+ "response": None
111
+ }
112
+ except requests.exceptions.ConnectionError:
113
+ logger.error("DeepSeek API 연결 실패")
114
+ return {
115
+ "success": False,
116
+ "message": "API 서버 연결 실패",
117
+ "status_code": None,
118
+ "response": None
119
+ }
120
+ except Exception as e:
121
+ logger.error(f"DeepSeek API 테스트 중 예상치 못한 오류: {e}", exc_info=True)
122
+ return {
123
+ "success": False,
124
+ "message": f"예상치 못한 오류: {str(e)}",
125
+ "status_code": None,
126
+ "response": None
127
+ }
128
+
129
+
130
+ def create_deepseek_client(api_key: str, endpoint: str, model: str):
131
+ """
132
+ DeepSeek 클라이언트 생성 (LangChain 호환)
133
+
134
+ Args:
135
+ api_key: DeepSeek API 키
136
+ endpoint: DeepSeek API 엔드포인트
137
+ model: 사용할 모델명
138
+
139
+ Returns:
140
+ DeepSeek 클라이언트 객체 또는 None
141
+ """
142
+ # LangChain과 DeepSeek 통합 시도
143
+ try:
144
+ from langchain_openai import ChatOpenAI
145
+
146
+ # API 연결 테스트 먼저 수행
147
+ test_result = test_deepseek_api(api_key, endpoint, model)
148
+
149
+ if not test_result["success"]:
150
+ logger.error(f"DeepSeek API 연결 테스트 실패: {test_result['message']}")
151
+ return None
152
+
153
+ # 정상 연결 시 클라이언트 생성
154
+ # DeepSeek는 OpenAI 호환 API를 제공하므로 ChatOpenAI를 사용
155
+ client = ChatOpenAI(
156
+ model=model,
157
+ temperature=0.2,
158
+ api_key=api_key,
159
+ base_url=endpoint.rstrip("/v1/chat/completions"), # OpenAI 호환 베이스 URL
160
+ )
161
+
162
+ logger.info(f"DeepSeek 클라이언트 생성 성공: {model}")
163
+ return client
164
+
165
+ except ImportError as e:
166
+ logger.error(f"필요한 라이브러리 임포트 실패: {e}")
167
+ return None
168
+ except Exception as e:
169
+ logger.error(f"DeepSeek 클라이언트 생성 중 오류: {e}", exc_info=True)
170
+ return None
dir ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 동의어 처리 모듈
3
+ """
4
+ import os
5
+ import sys
6
+ import re
7
+ from typing import Dict, List, Optional, Set
8
+
9
+ # 기본 동의어 사전 (MP_synonyms.py 파일이 없을 경우 사용)
10
+ DEFAULT_SYNONYMS = {
11
+ "엑츄레이터": "액츄에이터",
12
+ "액츄에이터": "액츄에이터",
13
+ "모터": "액츄에이터",
14
+ "컨박": "컨트롤박스"
15
+ }
16
+
17
+
18
+ class SynonymsHandler:
19
+ """
20
+ 부품명의 동의어를 처리하는 클래스
21
+ """
22
+
23
+ def __init__(self, synonyms_file: Optional[str] = None):
24
+ """
25
+ 동의어 핸들러 초기화
26
+
27
+ Args:
28
+ synonyms_file: 동의어 파일 경로 (선택적)
29
+ """
30
+ self.synonyms = {}
31
+ self.loaded = False
32
+
33
+ # 1. 기본 제공된 파일 경로 확인
34
+ if synonyms_file and os.path.exists(synonyms_file):
35
+ self._load_from_file(synonyms_file)
36
+
37
+ # 2. 일반적인 위치 확인 (.venv/SYNONYMS/MP_synonyms.py)
38
+ elif os.path.exists(".venv/SYNONYMS/MP_synonyms.py"):
39
+ self._load_from_file(".venv/SYNONYMS/MP_synonyms.py")
40
+
41
+ # 3. 현재 디렉토리 확인
42
+ elif os.path.exists("MP_synonyms.py"):
43
+ self._load_from_file("MP_synonyms.py")
44
+
45
+ # 4. 기본 동의어 사용
46
+ else:
47
+ print("동의어 파일을 찾을 수 없어 기본 동의어 사전을 사용합니다.")
48
+ self.synonyms = DEFAULT_SYNONYMS
49
+ self.loaded = True
50
+
51
+ def _load_from_file(self, file_path: str) -> None:
52
+ """
53
+ 파일에서 동의어 사전 로드
54
+
55
+ Args:
56
+ file_path: 동의어 파일 경로
57
+ """
58
+ try:
59
+ # 파일 내용 읽기
60
+ with open(file_path, 'r', encoding='utf-8') as f:
61
+ content = f.read()
62
+
63
+ # SYNONYMS 딕셔너리 추출
64
+ synonyms_match = re.search(r'SYNONYMS\s*=\s*\{(.*?)\}', content, re.DOTALL)
65
+ if synonyms_match:
66
+ # 실행하지 않고 변환하는 방법
67
+ synonyms_str = "{" + synonyms_match.group(1) + "}"
68
+
69
+ # 정규식을 사용하여 딕셔너리 형태로 파싱
70
+ pattern = r'"([^"]*)"\s*:\s*"([^"]*)"'
71
+ matches = re.findall(pattern, synonyms_str)
72
+
73
+ self.synonyms = {key: value for key, value in matches}
74
+ self.loaded = True
75
+ print(f"동의어 사전 로드 완료: {file_path}, {len(self.synonyms)}개 항목")
76
+ else:
77
+ print(f"파일에서 SYNONYMS 딕셔너리를 찾을 수 없습니다: {file_path}")
78
+ self.synonyms = DEFAULT_SYNONYMS
79
+ self.loaded = True
80
+
81
+ except Exception as e:
82
+ print(f"동의어 사전 로드 중 오류: {e}")
83
+ self.synonyms = DEFAULT_SYNONYMS
84
+ self.loaded = True
85
+
86
+ def find_in_text(self, text: str) -> List[str]:
87
+ """
88
+ 텍스트에서 동의어 찾기
89
+
90
+ Args:
91
+ text: 검색할 텍스트
92
+
93
+ Returns:
94
+ 찾은 표준화된 부품명 리스트
95
+ """
96
+ if not text or not self.loaded:
97
+ return []
98
+
99
+ # 공백 제거 및 소문자 변환
100
+ text = text.lower()
101
+
102
+ found_parts = set()
103
+
104
+ # 동의어 키워드가 텍스트에 포함되어 있는지 확인
105
+ for keyword, standard_name in self.synonyms.items():
106
+ if keyword.lower() in text:
107
+ found_parts.add(standard_name)
108
+
109
+ return list(found_parts)
110
+
111
+ def standardize(self, part_name: str) -> str:
112
+ """
113
+ 부품명을 표준화
114
+
115
+ Args:
116
+ part_name: 표준화할 부품명
117
+
118
+ Returns:
119
+ 표준화된 부품명
120
+ """
121
+ if not part_name or not self.loaded:
122
+ return part_name
123
+
124
+ # 소문자 변환하여 비교
125
+ part_lower = part_name.lower().strip()
126
+
127
+ # 동의어 사전에서 검색
128
+ for keyword, standard_name in self.synonyms.items():
129
+ if part_lower == keyword.lower():
130
+ return standard_name
131
+
132
+ # 매칭되지 않으면 원래 이름 반환
133
+ return part_name
134
+
135
+ def standardize_parts_list(self, parts: List[str]) -> List[str]:
136
+ """
137
+ 부품명 리스트를 표준화
138
+
139
+ Args:
140
+ parts: 표준화할 부품명 리스트
141
+
142
+ Returns:
143
+ 표준화된 부품명 리스트
144
+ """
145
+ if not parts or not self.loaded:
146
+ return parts
147
+
148
+ standardized = set()
149
+
150
+ for part in parts:
151
+ if part:
152
+ standardized.add(self.standardize(part))
153
+
154
+ return list(standardized)
direct_deepseek.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 직접 DeepSeek API 호출을 위한 클라이언트 구현 - 허깅페이스 환경 지원
3
+ """
4
+ import os
5
+ import time
6
+ import logging
7
+ import requests
8
+ import json
9
+ from typing import Dict, Any, Optional, List
10
+
11
+ # 로깅 설정
12
+ logger = logging.getLogger("DirectDeepSeek")
13
+
14
+ # 환경 감지
15
+ IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None or os.getenv('SYSTEM') == 'spaces'
16
+
17
+ class DirectDeepSeekClient:
18
+ """
19
+ DeepSeek API를 직접 호출하는 클라이언트
20
+ OpenAI 클라이언트를 우회하고 직접 HTTP 요청 사용
21
+ 허깅페이스 환경 지원
22
+ """
23
+ def __init__(self, api_key: Optional[str] = None, model_name: str = "deepseek-chat"):
24
+ """
25
+ 클라이언트 초기화
26
+
27
+ Args:
28
+ api_key: DeepSeek API 키 (None인 경우 환경변수에서 가져옴)
29
+ model_name: 사용할 모델 이름 (기본값: "deepseek-chat")
30
+ """
31
+ # API 키 설정 (허깅페이스 환경 확인)
32
+ if api_key is None:
33
+ if IS_HUGGINGFACE:
34
+ # 허깅페이스 환경에서는 시크릿에서 가져오기 시도
35
+ api_key = os.getenv('HF_SECRET_DEEPSEEK_API_KEY')
36
+ if not api_key:
37
+ # 시크릿이 없으면 일반 환경변수 확인
38
+ api_key = os.getenv("DEEPSEEK_API_KEY", "")
39
+ else:
40
+ # 로컬 환경에서는 환경변수 사용
41
+ api_key = os.getenv("DEEPSEEK_API_KEY", "")
42
+
43
+ self.api_key = api_key
44
+ self.model_name = model_name
45
+
46
+ # 엔드포인트 설정 (허깅페이스 환경 확인)
47
+ if IS_HUGGINGFACE:
48
+ # 허깅페이스 환경에서는 시크릿에서 가져오기 시도
49
+ self.endpoint = os.getenv('HF_SECRET_DEEPSEEK_ENDPOINT')
50
+ if not self.endpoint:
51
+ # 시크릿이 없으면 일반 환경변수 확인
52
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
53
+ else:
54
+ # 로컬 환경에서는 환경변수 사용
55
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
56
+
57
+ logger.info(f"DirectDeepSeekClient 초기화: 모델={model_name}, 엔드포인트={self.endpoint}")
58
+
59
+ # API 키 확인
60
+ if not self.api_key:
61
+ if IS_HUGGINGFACE:
62
+ logger.warning("허깅페이스 환경에서 DeepSeek API 키가 설정되지 않았습니다. Space 시크릿을 확인하세요.")
63
+ else:
64
+ logger.warning("DeepSeek API 키가 설정되지 않았습니다. .env 파일이나 환경변수를 확인하세요.")
65
+
66
+ def generate(self,
67
+ prompt: str,
68
+ temperature: float = 0.3,
69
+ max_tokens: int = 1000,
70
+ max_retries: int = 3,
71
+ timeout: int = 60) -> Dict[str, Any]:
72
+ """
73
+ 텍스트 생성 요청
74
+
75
+ Args:
76
+ prompt: 입력 프롬프트
77
+ temperature: 생성 온도 (0.0 ~ 1.0)
78
+ max_tokens: 최대 생성 토큰 수
79
+ max_retries: 재시도 횟수
80
+ timeout: 요청 타임아웃 (초)
81
+
82
+ Returns:
83
+ 생성 결과 딕셔너리 (success, response, message 등)
84
+ """
85
+ # 메시지 구성 (단일 사용자 메시지)
86
+ messages = [{"role": "user", "content": prompt}]
87
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
88
+
89
+ def chat(self,
90
+ messages: List[Dict[str, str]],
91
+ temperature: float = 0.3,
92
+ max_tokens: int = 1000,
93
+ max_retries: int = 3,
94
+ timeout: int = 60) -> Dict[str, Any]:
95
+ """
96
+ 채팅 API 호출
97
+
98
+ Args:
99
+ messages: 채팅 메시지 리스트 (role, content 키를 가진 딕셔너리 리스트)
100
+ temperature: 생성 온도 (0.0 ~ 1.0)
101
+ max_tokens: 최대 생성 토큰 수
102
+ max_retries: 재시도 횟수
103
+ timeout: 요청 타임아웃 (초)
104
+
105
+ Returns:
106
+ 생성 결과 딕셔너리 (success, response, message 등)
107
+ """
108
+ # API 키 확인
109
+ if not self.api_key:
110
+ error_msg = "DeepSeek API 키가 설정되지 않았습니다."
111
+ logger.error(error_msg)
112
+ return {
113
+ "success": False,
114
+ "message": error_msg,
115
+ "status_code": None
116
+ }
117
+
118
+ # API 요청 헤더 및 데이터
119
+ headers = {
120
+ "Content-Type": "application/json",
121
+ "Authorization": f"Bearer {self.api_key}"
122
+ }
123
+
124
+ payload = {
125
+ "model": self.model_name,
126
+ "messages": messages,
127
+ "temperature": temperature,
128
+ "max_tokens": max_tokens
129
+ }
130
+
131
+ # 재시도 로직
132
+ retry_delay = 1.0
133
+ attempt = 0
134
+
135
+ while attempt < max_retries:
136
+ attempt += 1
137
+ try:
138
+ logger.info(f"DeepSeek API 요청 시도 ({attempt}/{max_retries})...")
139
+
140
+ # API 요청 전송
141
+ response = requests.post(
142
+ self.endpoint,
143
+ headers=headers,
144
+ json=payload,
145
+ timeout=timeout
146
+ )
147
+
148
+ # 응답 확인
149
+ if response.status_code == 200:
150
+ result = response.json()
151
+
152
+ # 응답 내용 추출
153
+ if "choices" in result and len(result["choices"]) > 0:
154
+ message_content = result["choices"][0].get("message", {}).get("content", "")
155
+ logger.info(f"DeepSeek API 응답 성공 (길이: {len(message_content)})")
156
+
157
+ return {
158
+ "success": True,
159
+ "response": message_content,
160
+ "status_code": response.status_code,
161
+ "raw_response": result
162
+ }
163
+ else:
164
+ logger.warning(f"DeepSeek API 응답은 성공했으나 예상치 못한 응답 형식: {result}")
165
+ return {
166
+ "success": False,
167
+ "message": "응답에서 메시지를 찾을 수 없습니다",
168
+ "status_code": response.status_code,
169
+ "raw_response": result
170
+ }
171
+ else:
172
+ logger.error(f"DeepSeek API 오류: 상태 코드 {response.status_code}")
173
+
174
+ # 오류 메시지 추출
175
+ error_message = ""
176
+ try:
177
+ error_data = response.json()
178
+ error_message = error_data.get("error", {}).get("message", str(error_data))
179
+ except:
180
+ error_message = response.text
181
+
182
+ # 요청 한도 초과시 더 오래 대기
183
+ if response.status_code == 429:
184
+ retry_delay = min(retry_delay * 3, 15)
185
+ else:
186
+ retry_delay = min(retry_delay * 2, 10)
187
+
188
+ if attempt < max_retries:
189
+ logger.info(f"{retry_delay}초 후 재시도...")
190
+ time.sleep(retry_delay)
191
+ else:
192
+ # 모든 시도 실패
193
+ return {
194
+ "success": False,
195
+ "message": f"API 오류: {error_message}",
196
+ "status_code": response.status_code
197
+ }
198
+
199
+ except requests.exceptions.Timeout:
200
+ logger.error("DeepSeek API 요청 시간 초과")
201
+
202
+ if attempt < max_retries:
203
+ logger.info(f"{retry_delay}초 후 재시도...")
204
+ time.sleep(retry_delay)
205
+ retry_delay = min(retry_delay * 2, 10)
206
+ else:
207
+ return {
208
+ "success": False,
209
+ "message": "API 요청 시간 초과",
210
+ "status_code": None
211
+ }
212
+
213
+ except requests.exceptions.ConnectionError:
214
+ logger.error("DeepSeek API 연결 실패")
215
+
216
+ if attempt < max_retries:
217
+ logger.info(f"{retry_delay}초 후 재시도...")
218
+ time.sleep(retry_delay)
219
+ retry_delay = min(retry_delay * 2, 10)
220
+ else:
221
+ return {
222
+ "success": False,
223
+ "message": "API 서버 연결 실패",
224
+ "status_code": None
225
+ }
226
+
227
+ except Exception as e:
228
+ logger.error(f"DeepSeek API 요청 중 예상치 못한 오류: {e}")
229
+
230
+ if attempt < max_retries:
231
+ logger.info(f"{retry_delay}초 후 재시도...")
232
+ time.sleep(retry_delay)
233
+ retry_delay = min(retry_delay * 2, 10)
234
+ else:
235
+ return {
236
+ "success": False,
237
+ "message": f"예상치 못한 오류: {str(e)}",
238
+ "status_code": None
239
+ }
240
+
241
+ # 모든 시도 실패
242
+ return {
243
+ "success": False,
244
+ "message": "최대 재시도 횟수 초과",
245
+ "status_code": None
246
+ }
247
+
248
+ def system_prompt_chat(self,
249
+ system_prompt: str,
250
+ user_prompt: str,
251
+ temperature: float = 0.3,
252
+ max_tokens: int = 1000,
253
+ max_retries: int = 3,
254
+ timeout: int = 60) -> Dict[str, Any]:
255
+ """
256
+ 시스템 프롬프트와 사용자 프롬프트를 이용한 채팅 API 호출
257
+
258
+ Args:
259
+ system_prompt: 시스템 프롬프트
260
+ user_prompt: 사용자 프롬프트
261
+ temperature: 생성 온도 (0.0 ~ 1.0)
262
+ max_tokens: 최대 생성 토큰 수
263
+ max_retries: 재시도 횟수
264
+ timeout: 요청 타임아웃 (초)
265
+
266
+ Returns:
267
+ 생성 결과 딕셔너리
268
+ """
269
+ messages = [
270
+ {"role": "system", "content": system_prompt},
271
+ {"role": "user", "content": user_prompt}
272
+ ]
273
+
274
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
275
+
276
+
277
+ # 단독 실행을 위한 테스트 코드
278
+ if __name__ == "__main__":
279
+ # 로깅 설정
280
+ logging.basicConfig(level=logging.INFO)
281
+
282
+ # 허깅페이스 환경 확인
283
+ if IS_HUGGINGFACE:
284
+ print("허깅페이스 환경에서 실행 중입니다.")
285
+ print("HF_SECRET_DEEPSEEK_API_KEY 시크릿 설정이 필요합니다.")
286
+ else:
287
+ print("로컬 환경에서 실행 중입니다.")
288
+ print("DEEPSEEK_API_KEY 환경변수 설정이 필요합니다.")
289
+
290
+ # 클라이언트 생성
291
+ client = DirectDeepSeekClient()
292
+
293
+ # API 키 확인
294
+ if not client.api_key:
295
+ print("DeepSeek API 키가 설정되지 않았습니다.")
296
+ exit(1)
297
+
298
+ # 간단한 테스트
299
+ response = client.generate("Hello, what can you do?")
300
+
301
+ # 결과 출력
302
+ if response["success"]:
303
+ print("응답 성공!")
304
+ print(response["response"])
305
+ else:
306
+ print(f"응답 실패: {response['message']}")
fallback_rag_chain.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 폴백 RAG 체인 구현 (기본적인 기능만 포함) - 직접 DeepSeek API 호출 방식
3
+ """
4
+ import os
5
+ import logging
6
+ import time
7
+ from typing import List, Dict, Any, Optional, Tuple
8
+ from langchain.schema import Document
9
+
10
+ # 직접 DeepSeek 클라이언트 사용
11
+ from direct_deepseek import DirectDeepSeekClient
12
+
13
+ # 설정 가져오기
14
+ from config import (
15
+ LLM_MODEL, USE_OPENAI, USE_DEEPSEEK,
16
+ DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL,
17
+ TOP_K_RETRIEVAL
18
+ )
19
+
20
+ # 로깅 설정
21
+ logger = logging.getLogger("FallbackRAGChain")
22
+
23
+ class FallbackRAGChain:
24
+ """
25
+ 기본적인 RAG 체인 구현 (단순화된 버전, 문제 해결용)
26
+ 직접 DeepSeek API 호출 방식 사용
27
+ """
28
+
29
+ def __init__(self, vector_store):
30
+ """
31
+ RAG 체인 초기화
32
+
33
+ Args:
34
+ vector_store: 벡터 스토어 인스턴스
35
+ """
36
+ logger.info("폴백 RAG 체인 초기화...")
37
+ self.vector_store = vector_store
38
+
39
+ # DeepSeek 모델 직접 초기화
40
+ if USE_DEEPSEEK and DEEPSEEK_API_KEY:
41
+ logger.info(f"DeepSeek 모델 직접 초기화: {DEEPSEEK_MODEL}")
42
+ try:
43
+ self.client = DirectDeepSeekClient(
44
+ api_key=DEEPSEEK_API_KEY,
45
+ model_name=DEEPSEEK_MODEL
46
+ )
47
+ logger.info("DeepSeek 모델 직접 초기화 성공")
48
+ except Exception as e:
49
+ logger.error(f"DeepSeek 모델 초기화 실패: {e}")
50
+ # 오프라인 모드로 폴백
51
+ self.client = None
52
+ logger.warning("LLM이 초기화되지 않아 오프라인 모드로 동작합니다.")
53
+ else:
54
+ # LLM이 설정되지 않음
55
+ logger.warning("LLM이 설정되지 않아 오프라인 모드로 동작합니다.")
56
+ self.client = None
57
+
58
+ logger.info("폴백 RAG 체인 초기화 완료")
59
+
60
+ def _retrieve(self, query: str) -> str:
61
+ """
62
+ 쿼리에 대한 관련 문서 검색 및 컨텍스트 구성
63
+
64
+ Args:
65
+ query: 사용자 질문
66
+
67
+ Returns:
68
+ 검색 결과를 포함한 컨텍스트 문자열
69
+ """
70
+ if not query or not query.strip():
71
+ return "검색 쿼리가 비어있습니다."
72
+
73
+ try:
74
+ # 벡터 검색 수행
75
+ logger.info(f"벡터 검색: '{query[:30]}...'")
76
+ docs = self.vector_store.similarity_search(query, k=TOP_K_RETRIEVAL)
77
+
78
+ if not docs:
79
+ return "관련 문서를 찾을 수 없습니다."
80
+
81
+ # 검색 결과 컨텍스트 구성
82
+ context_parts = []
83
+ for i, doc in enumerate(docs, 1):
84
+ source = doc.metadata.get("source", "알 수 없는 출처")
85
+ page = doc.metadata.get("page", "")
86
+ source_info = f"{source}"
87
+ if page:
88
+ source_info += f" (페이지: {page})"
89
+
90
+ context_parts.append(f"[참고자료 {i}] - 출처: {source_info}\n{doc.page_content}\n")
91
+
92
+ context = "\n".join(context_parts)
93
+
94
+ # 컨텍스트 길이 제한 (토큰 수 제한)
95
+ if len(context) > 6000:
96
+ logger.warning(f"컨텍스트가 너무 깁니다 ({len(context)} 문자). 제한합니다.")
97
+ context = context[:2500] + "\n...(중략)...\n" + context[-2500:]
98
+
99
+ return context
100
+
101
+ except Exception as e:
102
+ logger.error(f"검색 중 오류: {e}")
103
+ return f"검색 중 오류 발생: {str(e)}"
104
+
105
+ def _generate_prompt(self, query: str, context: str) -> List[Dict[str, str]]:
106
+ """
107
+ 프롬프트 생성 (DeepSeek API 형식)
108
+
109
+ Args:
110
+ query: 사용자 질문
111
+ context: 검색 결과 컨텍스트
112
+
113
+ Returns:
114
+ DeepSeek API용 messages 형식
115
+ """
116
+ # 시스템 프롬프트
117
+ system_prompt = """다음 정보를 기반으로 질문에 정확하게 답변해주세요.
118
+ 참고 정보에 답이 있으면 반드시 그 정보를 기반으로 답변하세요.
119
+ 참고 정보에 답이 없는 경우에는 일반적인 지식을 활용하여 답변할 수 있지만, "제공된 문서에는 이 정보가 없으나, 일반적으로는..." 식으로 시작하세요.
120
+ 답변은 정확하고 간결하게 제공하되, 가능한 참고 정보에서 근거를 찾아 설명해주세요.
121
+ 참고 정보의 출처도 함께 알려주세요."""
122
+
123
+ # 사용자 프롬프트 (질문과 컨텍스트 포함)
124
+ user_prompt = f"""질문: {query}
125
+
126
+ 참고 정보:
127
+ {context}"""
128
+
129
+ # DeepSeek API에 맞는 메시지 포맷
130
+ messages = [
131
+ {"role": "system", "content": system_prompt},
132
+ {"role": "user", "content": user_prompt}
133
+ ]
134
+
135
+ return messages
136
+
137
+ def _generate_simple_response(self, query: str, context: str) -> str:
138
+ """
139
+ 간단한 오프라인 응답 생성 (LLM이 없을 때 사용)
140
+ """
141
+ # 기본 제공 응답 (일반적인 질문에 대한 정해진 응답)
142
+ predefined_answers = {
143
+ "대한민국의 수도": "대한민국의 수도는 서울입니다.",
144
+ "수도": "대한민국의 수도는 서울입니다.",
145
+ "누구야": "저는 RAG 기반 질의응답 시스템입니다. 문서를 검색하고 관련 정보를 찾아드립니다.",
146
+ "안녕": "안녕하세요! 무엇을 도와드릴까요?",
147
+ "뭐해": "사용자의 질문에 답변하기 위해 문서를 검색하고 있습니다. 무엇을 알려드릴까요?"
148
+ }
149
+
150
+ # 질문에 맞는 미리 정의된 응답이 있는지 확인
151
+ for key, answer in predefined_answers.items():
152
+ if key in query.lower():
153
+ return answer
154
+
155
+ # 미리 정의된 응답이 없으면 검색 결과만 표시
156
+ return f"""
157
+ 현재 LLM API 연결에 문제가 있어 검색 결과만 표시합니다.
158
+
159
+ 질문: {query}
160
+
161
+ 검색된 관련 문서:
162
+ {context}
163
+
164
+ [참고] 관련 정보를 찾으셨나요? API 연결 문제로 인해 자동 요약이 제공되지 않습니다. 다시 시도하거나 다른 질문을 해보세요.
165
+ """
166
+
167
+ def run(self, query: str) -> str:
168
+ """
169
+ 사용자 쿼리에 대한 RAG 파이프라인 실행
170
+
171
+ Args:
172
+ query: 사용자 질문
173
+
174
+ Returns:
175
+ 모델 응답 문자열
176
+ """
177
+ if not query or not query.strip():
178
+ return "질문이 비어있습니다. 질문을 입력해 주세요."
179
+
180
+ try:
181
+ logger.info(f"RAG 체인 실행: '{query[:30]}...'")
182
+
183
+ # 문서 검색
184
+ context = self._retrieve(query)
185
+
186
+ # LLM이 초기화되지 않은 경우 오프라인 응답
187
+ if self.client is None:
188
+ logger.warning("LLM이 초기화되지 않아 오프라인 응답 생성")
189
+ return self._generate_simple_response(query, context)
190
+
191
+ # 프롬프트 구성
192
+ messages = self._generate_prompt(query, context)
193
+
194
+ # 응답 생성 (최대 3회 시도)
195
+ max_retries = 3
196
+ retry_delay = 1.0
197
+
198
+ for attempt in range(max_retries):
199
+ try:
200
+ logger.info(f"응답 생성 시도 ({attempt+1}/{max_retries})")
201
+
202
+ # 직접 DeepSeek API 호출
203
+ response = self.client.chat(messages)
204
+
205
+ if response["success"]:
206
+ logger.info(f"응답 생성 성공 (길이: {len(response['response'])})")
207
+ return response["response"]
208
+ else:
209
+ logger.error(f"응답 생성 실패: {response['message']}")
210
+ if attempt < max_retries - 1:
211
+ logger.info(f"{retry_delay}초 후 재시도...")
212
+ time.sleep(retry_delay)
213
+ retry_delay *= 2
214
+ else:
215
+ # 모든 시도 실패 시 오프라인 응답
216
+ logger.warning("최대 재시도 횟수 초과, 오프라인 응답 생성")
217
+ return self._generate_simple_response(query, context)
218
+ except Exception as e:
219
+ logger.error(f"응답 생성 중 오류: {e}")
220
+ if attempt < max_retries - 1:
221
+ logger.info(f"{retry_delay}초 후 재시도...")
222
+ time.sleep(retry_delay)
223
+ retry_delay *= 2
224
+ else:
225
+ # 모든 시도 실패 시 오프라인 응답 생성
226
+ return self._generate_simple_response(query, context)
227
+
228
+ except Exception as e:
229
+ logger.error(f"RAG 체인 실행 중 오류: {e}")
230
+ return f"질문 처리 중 오류가 발생했습니다: {str(e)}"
optimized_document_processor.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CPU에 최적화된 문서 처리 모듈 - 병렬 처리 적용
3
+ """
4
+ import os
5
+ import time
6
+ from typing import List, Dict, Any, Optional
7
+ from langchain.schema import Document
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ # 멀티프로세싱 가져오기
11
+ import multiprocessing
12
+
13
+ try:
14
+ CPU_COUNT = multiprocessing.cpu_count()
15
+ except:
16
+ CPU_COUNT = 4
17
+
18
+ print(f"CPU 코어 수: {CPU_COUNT}")
19
+
20
+ # docling 라이브러리 존재 여부 확인
21
+ try:
22
+ from docling.datamodel.base_models import InputFormat
23
+ from docling.document_converter import DocumentConverter, PdfFormatOption
24
+ from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode
25
+ from docling.chunking import HybridChunker
26
+
27
+ DOCLING_AVAILABLE = True
28
+ print("docling 라이브러리 사용 가능")
29
+ except ImportError:
30
+ print("docling 라이브러리를 찾을 수 없습니다. PyPDFLoader만 사용합니다.")
31
+ DOCLING_AVAILABLE = False
32
+
33
+ # LangChain 문서 로더
34
+ from langchain_community.document_loaders import PyPDFLoader
35
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
36
+
37
+
38
+ class OptimizedDocumentProcessor:
39
+ """
40
+ CPU에 최적화된 병렬 처리 문서 처리 클래스
41
+ """
42
+
43
+ def __init__(self,
44
+ chunk_size: int = 1000,
45
+ chunk_overlap: int = 200,
46
+ tokenizer: str = "Alibaba-NLP/gte-multilingual-base", # 올바른 모델 경로로 수정
47
+ max_workers: int = CPU_COUNT):
48
+ """
49
+ 문서 처리기 초기화
50
+
51
+ Args:
52
+ chunk_size: 텍스트 청크 크기
53
+ chunk_overlap: 청크 간 겹침 크기
54
+ tokenizer: HybridChunker에서 사용할 토크나이저
55
+ max_workers: 병렬 처리시 최대 작업자 수
56
+ """
57
+ self.chunk_size = chunk_size
58
+ self.chunk_overlap = chunk_overlap
59
+ self.tokenizer = tokenizer
60
+ self.max_workers = max(1, min(max_workers, CPU_COUNT)) # CPU 코어 수 초과하지 않도록
61
+
62
+ print(f"병렬 처리 작업자 수: {self.max_workers}")
63
+
64
+ # LangChain 텍스트 스플리터
65
+ self.text_splitter = RecursiveCharacterTextSplitter(
66
+ chunk_size=chunk_size,
67
+ chunk_overlap=chunk_overlap,
68
+ separators=["\n\n", "\n", ". ", " ", ""],
69
+ )
70
+
71
+ # docling 관련 컴포넌트 초기화
72
+ if DOCLING_AVAILABLE:
73
+ # 파이프라인 옵션 설정
74
+ self.pipeline_options = PdfPipelineOptions(do_table_structure=True)
75
+ self.pipeline_options.table_structure_options.mode = TableFormerMode.ACCURATE
76
+
77
+ # 문서 변환기 초기화
78
+ self.doc_converter = DocumentConverter(
79
+ format_options={
80
+ InputFormat.PDF: PdfFormatOption(pipeline_options=self.pipeline_options)
81
+ }
82
+ )
83
+
84
+ # HybridChunker 초기화 (trust_remote_code=True 추가)
85
+ self.hybrid_chunker = HybridChunker(
86
+ tokenizer=tokenizer,
87
+ chunk_size=chunk_size,
88
+ overlap=chunk_overlap,
89
+ tokenizer_kwargs={"trust_remote_code": True} # 원격 코드 실행 허용
90
+ )
91
+
92
+ print(f"docling 초기화 완료: HybridChunker(청크 크기={chunk_size}, 오버랩={chunk_overlap})")
93
+
94
+ def process_with_docling(self, pdf_path: str) -> Dict[str, Any]:
95
+ """
96
+ docling을 사용하여 PDF 문서 처리
97
+
98
+ Args:
99
+ pdf_path: PDF 파일 경로
100
+
101
+ Returns:
102
+ 처리된 문서 데이터
103
+ """
104
+ if not DOCLING_AVAILABLE:
105
+ raise ImportError("docling 라이브러리가 설치되지 않았습니다.")
106
+
107
+ try:
108
+ start_time = time.time()
109
+
110
+ # 문서 변환
111
+ conv_res = self.doc_converter.convert(pdf_path)
112
+ doc = conv_res.document
113
+
114
+ # 성능 측정
115
+ conversion_time = time.time() - start_time
116
+ print(f"PDF 변환 시간: {conversion_time:.2f}초")
117
+
118
+ # 메타데이터 추출
119
+ metadata = {
120
+ "source": pdf_path,
121
+ "title": os.path.basename(pdf_path),
122
+ "processing_time": conversion_time
123
+ }
124
+
125
+ return {
126
+ "content": doc.export_to_markdown(),
127
+ "metadata": metadata,
128
+ "raw_document": doc,
129
+ }
130
+
131
+ except Exception as e:
132
+ print(f"docling으로 문서 처리 중 오류 발생: {e}")
133
+ raise
134
+
135
+ def chunk_with_hybrid_chunker(self, doc: Any) -> List[Dict[str, Any]]:
136
+ """
137
+ HybridChunker를 사용하여 문서를 청크로 분할
138
+
139
+ Args:
140
+ doc: docling 문서 객체
141
+
142
+ Returns:
143
+ 청크 리스트
144
+ """
145
+ start_time = time.time()
146
+
147
+ # 청킹 수행
148
+ chunk_iter = self.hybrid_chunker.chunk(doc)
149
+ chunks = list(chunk_iter)
150
+
151
+ chunking_time = time.time() - start_time
152
+ print(f"청킹 시간: {chunking_time:.2f}초 (청크 수: {len(chunks)})")
153
+
154
+ return chunks
155
+
156
+ def create_langchain_documents_from_chunks(self,
157
+ chunks: List[Dict[str, Any]],
158
+ metadata: Dict[str, Any]) -> List[Document]:
159
+ """
160
+ docling 청크를 LangChain Document 객체로 변환
161
+
162
+ Args:
163
+ chunks: docling HybridChunker로 생성한 청크 리스트
164
+ metadata: 문서 메타데이터
165
+
166
+ Returns:
167
+ LangChain Document 객체 리스트
168
+ """
169
+ documents = []
170
+
171
+ for i, chunk in enumerate(chunks):
172
+ # 각 청크에 대한 메타데이터
173
+ chunk_metadata = metadata.copy()
174
+ chunk_metadata["chunk_id"] = i
175
+
176
+ # 청크 내용 추출
177
+ if hasattr(chunk, "text"):
178
+ content = chunk.text
179
+ elif hasattr(chunk, "content"):
180
+ content = chunk.content
181
+ else:
182
+ content = str(chunk)
183
+
184
+ document = Document(
185
+ page_content=content,
186
+ metadata=chunk_metadata
187
+ )
188
+ documents.append(document)
189
+
190
+ return documents
191
+
192
+ def process_with_langchain(self, pdf_path: str) -> List[Document]:
193
+ """
194
+ LangChain의 PyPDFLoader를 사용하여 PDF 문서 로드
195
+
196
+ Args:
197
+ pdf_path: PDF 파일 경로
198
+
199
+ Returns:
200
+ LangChain Document 객체 리스트
201
+ """
202
+ start_time = time.time()
203
+
204
+ try:
205
+ loader = PyPDFLoader(pdf_path)
206
+ documents = loader.load()
207
+
208
+ processing_time = time.time() - start_time
209
+ print(f"PyPDFLoader 처리 시간: {processing_time:.2f}초")
210
+
211
+ return documents
212
+ except Exception as e:
213
+ print(f"PyPDFLoader로 문서 처리 중 오류 발생: {e}")
214
+ raise
215
+
216
+ def process_pdf(self, pdf_path: str, use_docling: bool = True) -> List[Document]:
217
+ """
218
+ PDF 파일 처리
219
+
220
+ Args:
221
+ pdf_path: PDF 파일 경로
222
+ use_docling: docling 사용 여부
223
+
224
+ Returns:
225
+ 처리된 문서의 청크 리스트
226
+ """
227
+ total_start_time = time.time()
228
+
229
+ # docling 사용 가능 여부 확인
230
+ can_use_docling = use_docling and DOCLING_AVAILABLE
231
+
232
+ if can_use_docling:
233
+ try:
234
+ # 1. docling으로 PDF 처리
235
+ docling_result = self.process_with_docling(pdf_path)
236
+ doc = docling_result["raw_document"]
237
+ metadata = docling_result["metadata"]
238
+
239
+ # 2. HybridChunker로 청크 생성
240
+ chunks = self.chunk_with_hybrid_chunker(doc)
241
+
242
+ # 3. 청크를 LangChain Document로 변환
243
+ documents = self.create_langchain_documents_from_chunks(chunks, metadata)
244
+
245
+ total_time = time.time() - total_start_time
246
+ print(f"docling 처리 완료: '{pdf_path}', {len(documents)} 청크, 총 {total_time:.2f}초")
247
+
248
+ return documents
249
+ except Exception as e:
250
+ print(f"docling 처리 실패, PyPDFLoader로 대체: {e}")
251
+ can_use_docling = False
252
+
253
+ if not can_use_docling:
254
+ # PyPDFLoader로 처리 (대체 방안)
255
+ documents = self.process_with_langchain(pdf_path)
256
+ chunks = self.text_splitter.split_documents(documents)
257
+
258
+ total_time = time.time() - total_start_time
259
+ print(f"PyPDFLoader 처리 완료: '{pdf_path}', {len(chunks)} 청크, 총 {total_time:.2f}초")
260
+
261
+ return chunks
262
+
263
+ def process_directory_parallel(self, directory: str, use_docling: bool = True) -> List[Document]:
264
+ """
265
+ 디렉토리 내 모든 PDF 파일 병렬 처리 (멀티스레딩)
266
+
267
+ Args:
268
+ directory: PDF 파일 디렉토리 경로
269
+ use_docling: docling 사용 여부
270
+
271
+ Returns:
272
+ 처리된 모든 문서의 청크 리스트
273
+ """
274
+ all_documents = []
275
+ pdf_files = []
276
+
277
+ # PDF 파일 목록 수집
278
+ for file in os.listdir(directory):
279
+ if file.endswith(".pdf"):
280
+ pdf_path = os.path.join(directory, file)
281
+ pdf_files.append(pdf_path)
282
+
283
+ if not pdf_files:
284
+ print(f"'{directory}' 디렉토리에 PDF 파일이 없습니다.")
285
+ return []
286
+
287
+ print(f"총 {len(pdf_files)}개 PDF 파일 병렬 처리 시작 (최대 {self.max_workers} 작업자)")
288
+ start_time = time.time()
289
+
290
+ # 병렬 처리 실행
291
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
292
+ # 각 PDF 파일에 대해 process_pdf 함수 병렬 실행
293
+ future_to_pdf = {executor.submit(self.process_pdf, pdf_path, use_docling): pdf_path
294
+ for pdf_path in pdf_files}
295
+
296
+ # 결과 수집
297
+ for future in future_to_pdf:
298
+ pdf_path = future_to_pdf[future]
299
+ try:
300
+ # 결과 가져오기
301
+ chunks = future.result()
302
+ all_documents.extend(chunks)
303
+ print(f"'{os.path.basename(pdf_path)}' 처리 완료: {len(chunks)} 청크")
304
+ except Exception as e:
305
+ print(f"'{pdf_path}' 처리 중 오류 발생: {e}")
306
+
307
+ total_time = time.time() - start_time
308
+ print(f"병렬 처리 완료: 총 {len(all_documents)} 청크, 처리 시간: {total_time:.2f}초")
309
+
310
+ return all_documents
311
+
312
+ def process_directory(self, directory: str, use_docling: bool = True, parallel: bool = True) -> List[Document]:
313
+ """
314
+ 디렉토리 내 모든 PDF 파일 처리
315
+
316
+ Args:
317
+ directory: PDF 파일 디렉토리 경로
318
+ use_docling: docling 사용 여부
319
+ parallel: 병렬 처리 사용 여부
320
+
321
+ Returns:
322
+ 처리된 모든 문서의 청크 리스트
323
+ """
324
+ # 병렬 처리 사용
325
+ if parallel:
326
+ return self.process_directory_parallel(directory, use_docling)
327
+
328
+ # 순차 처리
329
+ all_documents = []
330
+ start_time = time.time()
331
+
332
+ for file in os.listdir(directory):
333
+ if file.endswith(".pdf"):
334
+ pdf_path = os.path.join(directory, file)
335
+ print(f"처리 중: {pdf_path}")
336
+
337
+ try:
338
+ chunks = self.process_pdf(pdf_path, use_docling=use_docling)
339
+ all_documents.extend(chunks)
340
+ except Exception as e:
341
+ print(f"'{pdf_path}' 처리 중 오류 발생: {e}")
342
+
343
+ total_time = time.time() - start_time
344
+ print(f"순차 처리 완료: 총 {len(all_documents)} 청크, 처리 시간: {total_time:.2f}초")
345
+
346
+ return all_documents
rag_chain.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 직접 DeepSeek API 호출을 위한 클라이언트 구현
3
+ """
4
+ import os
5
+ import time
6
+ import logging
7
+ import requests
8
+ import json
9
+ from typing import Dict, Any, Optional, List
10
+
11
+ # 로깅 설정
12
+ logger = logging.getLogger("DirectDeepSeek")
13
+
14
+ class DirectDeepSeekClient:
15
+ """
16
+ DeepSeek API를 직접 호출하는 클라이언트
17
+ OpenAI 클라이언트를 우회하고 직접 HTTP 요청 사용
18
+ """
19
+ def __init__(self, api_key: str, model_name: str = "deepseek-chat"):
20
+ """
21
+ 클라이언트 초기화
22
+
23
+ Args:
24
+ api_key: DeepSeek API 키
25
+ model_name: 사용할 모델 이름 (기본값: "deepseek-chat")
26
+ """
27
+ self.api_key = api_key
28
+ self.model_name = model_name
29
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
30
+ logger.info(f"DirectDeepSeekClient 초기화: 모델={model_name}, 엔드포인트={self.endpoint}")
31
+
32
+ def generate(self,
33
+ prompt: str,
34
+ temperature: float = 0.3,
35
+ max_tokens: int = 1000,
36
+ max_retries: int = 3,
37
+ timeout: int = 60) -> Dict[str, Any]:
38
+ """
39
+ 텍스트 생성 요청
40
+
41
+ Args:
42
+ prompt: 입력 프롬프트
43
+ temperature: 생성 온도 (0.0 ~ 1.0)
44
+ max_tokens: 최대 생성 토큰 수
45
+ max_retries: 재시도 횟수
46
+ timeout: 요청 타임아웃 (초)
47
+
48
+ Returns:
49
+ 생성 결과 딕셔너리 (success, response, message 등)
50
+ """
51
+ # 메시지 구성 (단일 사용자 메시지)
52
+ messages = [{"role": "user", "content": prompt}]
53
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
54
+
55
+ def chat(self,
56
+ messages: List[Dict[str, str]],
57
+ temperature: float = 0.3,
58
+ max_tokens: int = 1000,
59
+ max_retries: int = 3,
60
+ timeout: int = 60) -> Dict[str, Any]:
61
+ """
62
+ 채팅 API 호출
63
+
64
+ Args:
65
+ messages: 채팅 메시지 리스트 (role, content 키를 가진 딕셔너리 리스트)
66
+ temperature: 생성 온도 (0.0 ~ 1.0)
67
+ max_tokens: 최대 생성 토큰 수
68
+ max_retries: 재시도 횟수
69
+ timeout: 요청 타임아웃 (초)
70
+
71
+ Returns:
72
+ 생성 결과 딕셔너리 (success, response, message 등)
73
+ """
74
+ # API 요청 헤더 및 데이터
75
+ headers = {
76
+ "Content-Type": "application/json",
77
+ "Authorization": f"Bearer {self.api_key}"
78
+ }
79
+
80
+ payload = {
81
+ "model": self.model_name,
82
+ "messages": messages,
83
+ "temperature": temperature,
84
+ "max_tokens": max_tokens
85
+ }
86
+
87
+ # 재시도 로직
88
+ retry_delay = 1.0
89
+ attempt = 0
90
+
91
+ while attempt < max_retries:
92
+ attempt += 1
93
+ try:
94
+ logger.info(f"DeepSeek API 요청 시도 ({attempt}/{max_retries})...")
95
+
96
+ # API 요청 전송
97
+ response = requests.post(
98
+ self.endpoint,
99
+ headers=headers,
100
+ json=payload,
101
+ timeout=timeout
102
+ )
103
+
104
+ # 응답 확인
105
+ if response.status_code == 200:
106
+ result = response.json()
107
+
108
+ # 응답 내용 추출
109
+ if "choices" in result and len(result["choices"]) > 0:
110
+ message_content = result["choices"][0].get("message", {}).get("content", "")
111
+ logger.info(f"DeepSeek API 응답 성공 (길이: {len(message_content)})")
112
+
113
+ return {
114
+ "success": True,
115
+ "response": message_content,
116
+ "status_code": response.status_code,
117
+ "raw_response": result
118
+ }
119
+ else:
120
+ logger.warning(f"DeepSeek API 응답은 성공했으나 예상치 못한 응답 형식: {result}")
121
+ return {
122
+ "success": False,
123
+ "message": "응답에서 메시지를 찾을 수 없습니다",
124
+ "status_code": response.status_code,
125
+ "raw_response": result
126
+ }
127
+ else:
128
+ logger.error(f"DeepSeek API 오류: 상태 코드 {response.status_code}")
129
+
130
+ # 오류 메시지 추출
131
+ error_message = ""
132
+ try:
133
+ error_data = response.json()
134
+ error_message = error_data.get("error", {}).get("message", str(error_data))
135
+ except:
136
+ error_message = response.text
137
+
138
+ # 요청 한도 ��과시 더 오래 대기
139
+ if response.status_code == 429:
140
+ retry_delay = min(retry_delay * 3, 15)
141
+ else:
142
+ retry_delay = min(retry_delay * 2, 10)
143
+
144
+ if attempt < max_retries:
145
+ logger.info(f"{retry_delay}초 후 재시도...")
146
+ time.sleep(retry_delay)
147
+ else:
148
+ # 모든 시도 실패
149
+ return {
150
+ "success": False,
151
+ "message": f"API 오류: {error_message}",
152
+ "status_code": response.status_code
153
+ }
154
+
155
+ except requests.exceptions.Timeout:
156
+ logger.error("DeepSeek API 요청 시간 초과")
157
+
158
+ if attempt < max_retries:
159
+ logger.info(f"{retry_delay}초 후 재시도...")
160
+ time.sleep(retry_delay)
161
+ retry_delay = min(retry_delay * 2, 10)
162
+ else:
163
+ return {
164
+ "success": False,
165
+ "message": "API 요청 시간 초과",
166
+ "status_code": None
167
+ }
168
+
169
+ except requests.exceptions.ConnectionError:
170
+ logger.error("DeepSeek API 연결 실패")
171
+
172
+ if attempt < max_retries:
173
+ logger.info(f"{retry_delay}초 후 재시도...")
174
+ time.sleep(retry_delay)
175
+ retry_delay = min(retry_delay * 2, 10)
176
+ else:
177
+ return {
178
+ "success": False,
179
+ "message": "API 서버 연결 실패",
180
+ "status_code": None
181
+ }
182
+
183
+ except Exception as e:
184
+ logger.error(f"DeepSeek API 요청 중 예상치 못한 오류: {e}")
185
+
186
+ if attempt < max_retries:
187
+ logger.info(f"{retry_delay}초 후 재시도...")
188
+ time.sleep(retry_delay)
189
+ retry_delay = min(retry_delay * 2, 10)
190
+ else:
191
+ return {
192
+ "success": False,
193
+ "message": f"예상치 못한 오류: {str(e)}",
194
+ "status_code": None
195
+ }
196
+
197
+ # 모든 시도 실패
198
+ return {
199
+ "success": False,
200
+ "message": "최대 재시도 횟수 초과",
201
+ "status_code": None
202
+ }
203
+
204
+ def system_prompt_chat(self,
205
+ system_prompt: str,
206
+ user_prompt: str,
207
+ temperature: float = 0.3,
208
+ max_tokens: int = 1000,
209
+ max_retries: int = 3,
210
+ timeout: int = 60) -> Dict[str, Any]:
211
+ """
212
+ 시스템 프롬프트와 사용자 프롬프트를 이용한 채팅 API 호출
213
+
214
+ Args:
215
+ system_prompt: 시스템 프롬프트
216
+ user_prompt: 사용자 프롬프트
217
+ temperature: 생성 온도 (0.0 ~ 1.0)
218
+ max_tokens: 최대 생성 토큰 수
219
+ max_retries: 재시도 횟수
220
+ timeout: 요청 타임아웃 (초)
221
+
222
+ Returns:
223
+ 생성 결과 딕셔너리
224
+ """
225
+ messages = [
226
+ {"role": "system", "content": system_prompt},
227
+ {"role": "user", "content": user_prompt}
228
+ ]
229
+
230
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
231
+
232
+
233
+ # 단독 실행을 위한 테스트 코드
234
+ if __name__ == "__main__":
235
+ # 로깅 설정
236
+ logging.basicConfig(level=logging.INFO)
237
+
238
+ # API 키 확인
239
+ api_key = os.environ.get("DEEPSEEK_API_KEY")
240
+ if not api_key:
241
+ print("환경 변수 DEEPSEEK_API_KEY가 설정되지 않았습니다.")
242
+ exit(1)
243
+
244
+ # 클라이언트 생성
245
+ client = DirectDeepSeekClient(api_key)
246
+
247
+ # 간단한 테스트
248
+ response = client.generate("Hello, what can you do?")
249
+
250
+ # 결과 출력
251
+ if response["success"]:
252
+ print("응답 성공!")
253
+ print(response["response"])
254
+ else:
255
+ print(f"응답 실패: {response['message']}")
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ langchain>=0.1.0
2
+ langchain-community>=0.0.10
3
+ langchain-huggingface>=0.0.1
4
+ sentence-transformers>=2.2.2
5
+ faiss-cpu>=1.7.4
6
+ pypdf>=3.15.1
7
+ gradio>=4.0.0
8
+ python-dotenv>=1.0.0
9
+ torch>=2.0.0
10
+ transformers>=4.34.0
11
+ langchain-openai>=0.0.2
12
+ openai>=1.0.0
13
+ docling>=0.1.3
14
+ soundfile>=0.12.1
15
+ numpy>=1.20.0
16
+ requests>=2.25.1
reranker.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 원격 코드 실행 옵션이 추가된 리랭커 모듈
3
+ """
4
+ from typing import List, Dict, Tuple
5
+ import numpy as np
6
+ from sentence_transformers import CrossEncoder
7
+ from langchain.schema import Document
8
+ from config import RERANKER_MODEL
9
+
10
+ class Reranker:
11
+ def __init__(self, model_name: str = RERANKER_MODEL):
12
+ """
13
+ Cross-Encoder 리랭커 초기화
14
+
15
+ Args:
16
+ model_name: 사용할 Cross-Encoder 모델 이름
17
+ """
18
+ print(f"리랭커 모델 로드 중: {model_name}")
19
+
20
+ # 원격 코드 실행 허용 옵션 추가
21
+ self.model = CrossEncoder(
22
+ model_name,
23
+ trust_remote_code=True # 원격 코드 실행 허용 (필수)
24
+ )
25
+
26
+ print(f"리랭커 모델 로드 완료: {model_name}")
27
+
28
+ def rerank(self, query: str, documents: List[Document], top_k: int = 3) -> List[Document]:
29
+ """
30
+ 검색 결과 재정렬
31
+
32
+ Args:
33
+ query: 검색 쿼리
34
+ documents: 벡터 검색 결과 문서 리스트
35
+ top_k: 반환할 상위 결과 수
36
+
37
+ Returns:
38
+ 재정렬된 상위 문서 리스트
39
+ """
40
+ if not documents:
41
+ return []
42
+
43
+ # Cross-Encoder 입력 쌍 생성
44
+ document_texts = [doc.page_content for doc in documents]
45
+ query_doc_pairs = [(query, doc) for doc in document_texts]
46
+
47
+ # 점수 계산
48
+ print(f"리랭킹 수행 중: {len(documents)}개 문서")
49
+ scores = self.model.predict(query_doc_pairs)
50
+
51
+ # 점수에 따라 문서 재정렬
52
+ doc_score_pairs = list(zip(documents, scores))
53
+ doc_score_pairs.sort(key=lambda x: x[1], reverse=True)
54
+
55
+ print(f"리랭킹 완료: 상위 {top_k}개 문서 선택")
56
+
57
+ # 상위 k개 결과 반환
58
+ return [doc for doc, score in doc_score_pairs[:top_k]]
simple_rag_chain.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 간단한 RAG 체인 구현 (디버깅용) - 직접 DeepSeek API 호출 방식
3
+ """
4
+ import os
5
+ import logging
6
+ import time
7
+ from typing import Dict, Any, List
8
+
9
+ # 직접 DeepSeek 클라이언트 사용
10
+ from direct_deepseek import DirectDeepSeekClient
11
+
12
+ # 로깅 설정
13
+ logger = logging.getLogger("SimpleRAGChain")
14
+
15
+ class SimpleRAGChain:
16
+ def __init__(self, vector_store, api_key=None, model="deepseek-chat", endpoint=None):
17
+ """간단한 RAG 체인 초기화"""
18
+ logger.info("간단한 RAG 체인 초기화 중...")
19
+ self.vector_store = vector_store
20
+
21
+ # DeepSeek API 키 확인
22
+ self.api_key = api_key or os.environ.get("DEEPSEEK_API_KEY", "")
23
+ self.model = model or os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
24
+ logger.info(f"API 키 설정됨: {bool(self.api_key)}")
25
+
26
+ # DeepSeek 클라이언트 초기화
27
+ if self.api_key:
28
+ try:
29
+ self.client = DirectDeepSeekClient(
30
+ api_key=self.api_key,
31
+ model_name=self.model
32
+ )
33
+ logger.info(f"DeepSeek 클라이언트 초기화 성공: {self.model}")
34
+ except Exception as e:
35
+ logger.error(f"DeepSeek 클라이언트 초기화 실패: {e}")
36
+ self.client = None
37
+ else:
38
+ logger.warning("API 키가 설정되지 않아 클라이언트를 초기화할 수 없습니다.")
39
+ self.client = None
40
+
41
+ logger.info("간단한 RAG 체인 초기화 완료")
42
+
43
+ def _retrieve(self, query: str) -> str:
44
+ """문서 검색 및 컨텍스트 구성"""
45
+ try:
46
+ docs = self.vector_store.similarity_search(query, k=3)
47
+ if not docs:
48
+ return "관련 문서를 찾을 수 없습니다."
49
+
50
+ # 검색 결과 컨텍스트 구성
51
+ context_parts = []
52
+ for i, doc in enumerate(docs, 1):
53
+ source = doc.metadata.get("source", "알 수 없는 출처")
54
+ page = doc.metadata.get("page", "")
55
+ source_info = f"{source}"
56
+ if page:
57
+ source_info += f" (페이지: {page})"
58
+
59
+ context_parts.append(f"[참고자료 {i}] - 출처: {source_info}\n{doc.page_content}\n")
60
+
61
+ context = "\n".join(context_parts)
62
+
63
+ # 길이 제한
64
+ if len(context) > 6000:
65
+ context = context[:2500] + "\n...(중략)...\n" + context[-2500:]
66
+
67
+ return context
68
+ except Exception as e:
69
+ logger.error(f"검색 중 오류: {e}")
70
+ return "문서 검색 중 오류가 발생했습니다."
71
+
72
+ def _generate_prompt(self, query: str, context: str) -> List[Dict[str, str]]:
73
+ """DeepSeek API용 프롬프트 생성"""
74
+ # 시스템 프롬프트
75
+ system_prompt = """다음 정보를 기반으로 질문에 정확하게 답변해주세요.
76
+ 참고 정보에서 답을 찾을 수 없는 경우 "제공된 문서에서 해당 정보를 찾을 수 없습니다."라고 답변하세요.
77
+ 정보 출처를 포함해서 대답하세요."""
78
+
79
+ # 사용자 프롬프트
80
+ user_prompt = f"""질문: {query}
81
+
82
+ 참고 정보:
83
+ {context}"""
84
+
85
+ # DeepSeek API 프롬프트 포맷
86
+ messages = [
87
+ {"role": "system", "content": system_prompt},
88
+ {"role": "user", "content": user_prompt}
89
+ ]
90
+
91
+ return messages
92
+
93
+ def run(self, query: str) -> str:
94
+ """쿼리 처리"""
95
+ try:
96
+ logger.info(f"SimpleRAGChain 실행: {query[:50]}...")
97
+
98
+ # 문서 검색
99
+ context = self._retrieve(query)
100
+
101
+ # 클라이언트가 초기화되지 않은 경우
102
+ if self.client is None:
103
+ logger.warning("DeepSeek 클라이언트가 초기화되지 않음. 검색 결과만 반환.")
104
+ return f"API 연결이 설정되지 않았습니다. 검색 결과:\n\n{context}"
105
+
106
+ # 프롬프트 생성
107
+ messages = self._generate_prompt(query, context)
108
+
109
+ # API 호출
110
+ start_time = time.time()
111
+ response = self.client.chat(messages)
112
+ logger.info(f"API 응답 시간: {time.time() - start_time:.2f}초")
113
+
114
+ if response["success"]:
115
+ logger.info("응답 생성 성공")
116
+ return response["response"]
117
+ else:
118
+ logger.error(f"응답 생성 실패: {response['message']}")
119
+ return f"응답 생성 실패: {response['message']}\n\n검색 결과:\n{context}"
120
+
121
+ except Exception as e:
122
+ logger.error(f"실행 중 오류: {e}")
123
+ return f"오류 발생: {str(e)}"
vector_store.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 개선된 벡터 스토어 모듈 - Milvus 설정 최적화 및 예외 처리 강화
3
+ """
4
+ import os
5
+ import logging
6
+ from typing import List, Dict, Any, Optional
7
+ import uuid
8
+ from langchain.schema import Document
9
+
10
+ # 로깅 설정
11
+ logger = logging.getLogger("VectorStore")
12
+
13
+ # 벡터 스토어 관련 예외 클래스
14
+ class VectorStoreInitError(Exception):
15
+ """벡터 스토어 초기화 중 발생한 오류"""
16
+ pass
17
+
18
+ class EmbeddingModelError(Exception):
19
+ """임베딩 모델 초기화 중 발생한 오류"""
20
+ pass
21
+
22
+ class DocumentIndexError(Exception):
23
+ """문서 인덱싱 중 발생한 오류"""
24
+ pass
25
+
26
+ class VectorSearchError(Exception):
27
+ """벡터 검색 중 발생한 오류"""
28
+ pass
29
+
30
+ class PersistenceError(Exception):
31
+ """인덱스 저장/로드 중 발생한 오류"""
32
+ pass
33
+
34
+ # 벡터 스토어 임포트
35
+ try:
36
+ # 최신 버전 임포트
37
+ from langchain_milvus import Milvus
38
+ from langchain_community.vectorstores import FAISS
39
+ from langchain_huggingface import HuggingFaceEmbeddings
40
+ MODERN_IMPORTS = True
41
+ logger.info("최신 langchain 패키지 임포트 성공")
42
+ except ImportError:
43
+ try:
44
+ # 이전 버전 임포트
45
+ from langchain_community.vectorstores import Milvus, FAISS
46
+ from langchain_community.embeddings import HuggingFaceEmbeddings
47
+ MODERN_IMPORTS = False
48
+ logger.info("레거시 langchain_community 패키지 사용")
49
+ except ImportError as e:
50
+ logger.error(f"필수 벡터 스토어 라이브러리를 임포트할 수 없습니다: {e}")
51
+ raise VectorStoreInitError(f"필수 벡터 스토어 라이브러리를 임포트할 수 없습니다: {str(e)}")
52
+
53
+ from config import MILVUS_HOST, MILVUS_PORT, MILVUS_COLLECTION, EMBEDDING_MODEL
54
+
55
+ class VectorStore:
56
+ def __init__(self, use_milvus: bool = True):
57
+ """
58
+ 벡터 스토어 초기화
59
+
60
+ Args:
61
+ use_milvus: Milvus 사용 여부 (False이면 FAISS 사용)
62
+ """
63
+ self.use_milvus = use_milvus
64
+ self.vector_store = None
65
+
66
+ # 임베딩 모델 설정
67
+ logger.info(f"임베딩 모델 로드 중: {EMBEDDING_MODEL}")
68
+ model_kwargs = {
69
+ "device": "cpu",
70
+ "trust_remote_code": True # 원격 코드 실행 허용 (필수)
71
+ }
72
+ encode_kwargs = {"normalize_embeddings": True}
73
+
74
+ try:
75
+ self.embeddings = HuggingFaceEmbeddings(
76
+ model_name=EMBEDDING_MODEL,
77
+ model_kwargs=model_kwargs,
78
+ encode_kwargs=encode_kwargs
79
+ )
80
+ logger.info(f"임베딩 모델 초기화 완료: {EMBEDDING_MODEL}")
81
+ except Exception as e:
82
+ logger.error(f"임베딩 모델 초기화 실패: {e}", exc_info=True)
83
+ raise EmbeddingModelError(f"임베딩 모델 '{EMBEDDING_MODEL}' 초기화 실패: {str(e)}")
84
+
85
+ def init_milvus(self) -> Milvus:
86
+ """
87
+ Milvus 벡터 스토어 초기화
88
+
89
+ Returns:
90
+ Milvus 벡터 스토어 인스턴스
91
+ """
92
+ try:
93
+ connection_args = {
94
+ "host": MILVUS_HOST,
95
+ "port": MILVUS_PORT,
96
+ }
97
+
98
+ # 벡터 검색 인덱스 파라미터 (FLAT 인덱스 및 코사인 유사도 메트릭)
99
+ index_params = {
100
+ "index_type": "FLAT", # 정확도 우선 FLAT 인덱스
101
+ "metric_type": "COSINE", # 코사인 유사도 (정규화된 벡터에 적합)
102
+ "params": {} # FLAT 인덱스에는 추가 파라미터 없음
103
+ }
104
+
105
+ logger.info(f"Milvus 연결 시도 중: {MILVUS_HOST}:{MILVUS_PORT}")
106
+ milvus_store = Milvus(
107
+ embedding_function=self.embeddings,
108
+ collection_name=MILVUS_COLLECTION,
109
+ connection_args=connection_args,
110
+ index_params=index_params
111
+ )
112
+ logger.info(f"Milvus 연결 성공: {MILVUS_COLLECTION}")
113
+ return milvus_store
114
+ except Exception as e:
115
+ logger.error(f"Milvus 초기화 실패: {e}", exc_info=True)
116
+ raise VectorStoreInitError(f"Milvus 벡터 스토어 초기화 실패: {str(e)}")
117
+
118
+ def init_faiss(self) -> FAISS:
119
+ """
120
+ FAISS 벡터 스토어 초기화 (로컬 대체용)
121
+
122
+ Returns:
123
+ FAISS 벡터 스토어 인스턴스
124
+ """
125
+ try:
126
+ logger.info("FAISS 벡터 스토어 초기화 중")
127
+ faiss_store = FAISS.from_documents([], self.embeddings)
128
+ logger.info("FAISS 벡터 스토어 초기화 완료")
129
+ return faiss_store
130
+ except Exception as e:
131
+ logger.error(f"FAISS 초기화 실패: {e}", exc_info=True)
132
+ raise VectorStoreInitError(f"FAISS 벡터 스토어 초기화 실패: {str(e)}")
133
+
134
+ def create_or_load(self, documents: Optional[List[Document]] = None) -> Any:
135
+ """
136
+ 벡터 스토어 생성 또는 로드
137
+
138
+ Args:
139
+ documents: 저장할 문서 리스트 (None이면 빈 스토어 생성)
140
+
141
+ Returns:
142
+ 벡터 스토어 인스턴스
143
+ """
144
+ if self.use_milvus:
145
+ if documents:
146
+ # 문서가 제공된 경우 새 컬렉션 생성
147
+ try:
148
+ # 연결 설정
149
+ connection_args = {
150
+ "host": MILVUS_HOST,
151
+ "port": MILVUS_PORT,
152
+ }
153
+
154
+ # 검색 인덱스 설정
155
+ index_params = {
156
+ "index_type": "FLAT", # 정확도 우선
157
+ "metric_type": "COSINE", # 코사인 유사도
158
+ "params": {}
159
+ }
160
+
161
+ logger.info(f"Milvus 컬렉션 생성 중: {MILVUS_COLLECTION} (기존 컬렉션 삭제)")
162
+
163
+ # 문서로부터 Milvus 컬렉션 생성
164
+ self.vector_store = Milvus.from_documents(
165
+ documents=documents,
166
+ embedding=self.embeddings,
167
+ collection_name=MILVUS_COLLECTION,
168
+ connection_args=connection_args,
169
+ index_params=index_params,
170
+ drop_old=True # 기존 컬렉션 삭제 (재구축)
171
+ )
172
+
173
+ logger.info(f"Milvus 컬렉션 생성 완료: {len(documents)}개 문서 인덱싱됨")
174
+
175
+ except Exception as e:
176
+ logger.error(f"Milvus 컬렉션 생성 실패: {e}", exc_info=True)
177
+ # 대체 방안으로 FAISS 사용
178
+ logger.warning("Milvus 실패로 FAISS로 대체합니다")
179
+ self.use_milvus = False
180
+ try:
181
+ self.vector_store = FAISS.from_documents(documents, self.embeddings)
182
+ logger.info(f"FAISS로 대체 성공: {len(documents)}개 문서 인덱싱됨")
183
+ except Exception as faiss_err:
184
+ logger.error(f"FAISS 대체 실패: {faiss_err}", exc_info=True)
185
+ raise DocumentIndexError(f"문서 인덱싱 실패 (Milvus 및 FAISS): {str(e)} / {str(faiss_err)}")
186
+ else:
187
+ # 기존 컬렉션 로드
188
+ try:
189
+ self.vector_store = self.init_milvus()
190
+ except VectorStoreInitError as e:
191
+ logger.error(f"Milvus 컬렉션 로드 실패: {e}")
192
+ # 대체 방안으로 FAISS 사용
193
+ logger.warning("Milvus 실패로 FAISS로 대체합니다")
194
+ self.use_milvus = False
195
+ try:
196
+ self.vector_store = self.init_faiss()
197
+ except VectorStoreInitError as faiss_err:
198
+ logger.error(f"FAISS 대체 실패: {faiss_err}", exc_info=True)
199
+ raise VectorStoreInitError(f"벡터 스토어 초기화 실패 (Milvus 및 FAISS): {str(e)} / {str(faiss_err)}")
200
+ else:
201
+ # FAISS 사용
202
+ if documents:
203
+ try:
204
+ logger.info(f"FAISS 인덱스 생성 중: {len(documents)}개 문서")
205
+ self.vector_store = FAISS.from_documents(documents, self.embeddings)
206
+ logger.info("FAISS 인덱스 생성 완료")
207
+ except Exception as e:
208
+ logger.error(f"FAISS 인덱스 생성 실패: {e}", exc_info=True)
209
+ raise DocumentIndexError(f"FAISS 문서 인덱싱 실패: {str(e)}")
210
+ else:
211
+ try:
212
+ self.vector_store = self.init_faiss()
213
+ except VectorStoreInitError as e:
214
+ # 이미 로깅됨
215
+ raise
216
+
217
+ return self.vector_store
218
+
219
+ def add_documents(self, documents: List[Document]) -> None:
220
+ """
221
+ 벡터 스토어에 문서 추가
222
+
223
+ Args:
224
+ documents: 추가할 문서 리스트
225
+ """
226
+ if not documents:
227
+ logger.warning("추가할 문서가 없습니다")
228
+ return
229
+
230
+ try:
231
+ if self.vector_store is None:
232
+ logger.info("벡터 스토어가 초기화되지 않았습니다. 새 벡터 스토어를 생성합니다.")
233
+ self.create_or_load(documents)
234
+ else:
235
+ logger.info(f"{len(documents)}개 문서를 기존 벡터 스토어에 추가합니다")
236
+ self.vector_store.add_documents(documents)
237
+ logger.info(f"{len(documents)}개 문서 추가 완료")
238
+ except Exception as e:
239
+ logger.error(f"문서 추가 실패: {e}", exc_info=True)
240
+ raise DocumentIndexError(f"벡터 스토어에 문서 추가 실패: {str(e)}")
241
+
242
+ def similarity_search(self, query: str, k: int = 5) -> List[Document]:
243
+ """
244
+ 벡터 유사도 검색 수행
245
+
246
+ Args:
247
+ query: 검색 쿼리
248
+ k: 반환할 결과 수
249
+
250
+ Returns:
251
+ 유사도가 높은 문서 리스트
252
+ """
253
+ if not query or not query.strip():
254
+ logger.warning("빈 쿼리로 검색 시도")
255
+ return []
256
+
257
+ if self.vector_store is None:
258
+ logger.error("벡터 스토어가 초기화되지 않았습니다")
259
+ raise VectorSearchError("벡터 스토어가 초기화되지 않았습니다")
260
+
261
+ try:
262
+ logger.info(f"검색 쿼리 실행: '{query[:50]}{'...' if len(query) > 50 else ''}', 상위 {k}개 결과 요청")
263
+ results = self.vector_store.similarity_search(query, k=k)
264
+ logger.info(f"검색 완료: {len(results)}개 결과 찾음")
265
+ return results
266
+ except Exception as e:
267
+ logger.error(f"검색 중 오류 발생: {e}", exc_info=True)
268
+ raise VectorSearchError(f"벡터 검색 실패: {str(e)}")
269
+
270
+ def save_local(self, path: str = "faiss_index") -> bool:
271
+ """
272
+ FAISS 인덱스 로컬 저장 (Milvus 사용 안 할 경우)
273
+
274
+ Args:
275
+ path: 저장 경로
276
+
277
+ Returns:
278
+ 저장 성공 여부
279
+ """
280
+ if self.vector_store is None:
281
+ logger.error("저장할 벡터 스토어가 초기화되지 않았습니다")
282
+ raise PersistenceError("저장할 벡터 스토어가 초기화되지 않았습니다")
283
+
284
+ # FAISS만 로컬 저장 가능
285
+ if not self.use_milvus:
286
+ try:
287
+ # 저장 디렉토리가 존재하는지 확인
288
+ os.makedirs(os.path.dirname(path) if os.path.dirname(path) else path, exist_ok=True)
289
+
290
+ self.vector_store.save_local(path)
291
+ logger.info(f"FAISS 인덱스 로컬 저장 완료: {path}")
292
+ return True
293
+ except Exception as e:
294
+ logger.error(f"FAISS 인덱스 저장 실패: {e}", exc_info=True)
295
+ raise PersistenceError(f"벡터 인덱스 저장 실패: {str(e)}")
296
+ else:
297
+ logger.info("Milvus는 로컬 저장이 필요하지 않습니다")
298
+ return True
299
+
300
+ def load_local(self, path: str = "faiss_index") -> bool:
301
+ """
302
+ FAISS 인덱스 로컬 로드 (Milvus 사용 안 할 경우)
303
+
304
+ Args:
305
+ path: 로드할 인덱스 경로
306
+
307
+ Returns:
308
+ 로드 성공 여부
309
+ """
310
+ if self.use_milvus:
311
+ logger.info("Milvus 사용 중이므로 로컬 로드를 건너뜁니다")
312
+ try:
313
+ # Milvus 연결 확인
314
+ self.vector_store = self.init_milvus()
315
+ return True
316
+ except Exception as e:
317
+ logger.error(f"Milvus 연결 실패, FAISS로 대체: {e}")
318
+ self.use_milvus = False
319
+ # FAISS로 계속 진행
320
+
321
+ if not os.path.exists(path):
322
+ logger.warning(f"인덱스 경로가 존재하지 않음: {path}")
323
+ raise FileNotFoundError(f"벡터 인덱스 경로가 존재하지 않음: {path}")
324
+
325
+ try:
326
+ logger.info(f"FAISS 인덱스 로드 중: {path}")
327
+
328
+ # 역직렬화 허용 옵션 추가 (보안 경고 확인 필요)
329
+ self.vector_store = FAISS.load_local(
330
+ path,
331
+ self.embeddings,
332
+ allow_dangerous_deserialization=True # 역직렬화 허용
333
+ )
334
+ logger.info(f"FAISS 인덱스 로드 완료: {path}")
335
+ return True
336
+ except FileNotFoundError as e:
337
+ logger.error(f"FAISS 인덱스 파일을 찾을 수 없음: {e}")
338
+ raise PersistenceError(f"벡터 인덱스 파일을 찾을 수 없음: {str(e)}")
339
+ except Exception as e:
340
+ logger.error(f"FAISS 인덱스 로드 실패: {e}", exc_info=True)
341
+
342
+ # 오류 세부 정보 출력
343
+ import traceback
344
+ logger.error(f"상세 오류: {traceback.format_exc()}")
345
+
346
+ # 새 인덱스 초기화
347
+ logger.warning("인덱스 로드 실패로 새 FAISS 인덱스 초기화")
348
+ self.vector_store = self.init_faiss()
349
+ return False