clovax-tax-chatbot / rag_system.py
bissal's picture
Complete RAGโ†’LLMโ†’Web pipeline integration ๐Ÿค– Generated with Claude Code
4aa05c6
# rag_system.py - RAG ์‹œ์Šคํ…œ ๋ชจ๋“ˆ
import os
import json
import torch
import pickle
import numpy as np
# FAISS GPU/CPU ์ž๋™ ์„ ํƒ
try:
# GPU๊ฐ€ ์žˆ๊ณ  CUDA๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉด faiss-gpu ์‹œ๋„
if torch.cuda.is_available():
try:
import subprocess
subprocess.run(['pip', 'install', 'faiss-gpu'], check=True, capture_output=True)
import faiss
print("โœ… faiss-gpu ์„ค์น˜ ๋ฐ ๋กœ๋“œ ์„ฑ๊ณต")
except:
import faiss
print("โš ๏ธ faiss-gpu ์„ค์น˜ ์‹คํŒจ, faiss-cpu ์‚ฌ์šฉ")
else:
import faiss
print("๐Ÿ’ป CPU ํ™˜๊ฒฝ: faiss-cpu ์‚ฌ์šฉ")
except ImportError:
print("โŒ FAISS ์„ค์น˜ ํ•„์š”: pip install faiss-cpu")
import sys
sys.exit(1)
from sentence_transformers import SentenceTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from law_fetcher import HFLawAPIFetcher
from config import RAG_CONFIG, TAX_KEYWORDS, RELEVANCE_KEYWORDS
class HFSpacesTaxRAG:
"""HF Spaces ์ตœ์ ํ™” ์ทจ๋“์„ธ RAG ์‹œ์Šคํ…œ"""
def __init__(self, custom_config=None):
print("๐Ÿš€ HF Spaces ์ทจ๋“์„ธ RAG ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” ์ค‘...")
# ์„ค์ • ๋กœ๋“œ (์ปค์Šคํ…€ ์„ค์ • ์ง€์›)
if custom_config:
self.config = custom_config
print("๐Ÿ”ง ์ปค์Šคํ…€ ์„ค์ • ์ ์šฉ")
else:
self.config = RAG_CONFIG
self.tax_keywords = TAX_KEYWORDS
# ๋””๋ฐ”์ด์Šค ์„ค์ • (ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ ๊ณ ๋ ค)
if torch.cuda.is_available():
self.device = 'cuda'
else:
self.device = 'cpu'
# ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ ๊ฐ์ง€
self.is_huggingface_space = os.getenv('SPACE_ID') is not None
if self.is_huggingface_space:
print(f"๐Ÿš€ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ ๊ฐ์ง€ - ๋””๋ฐ”์ด์Šค: {self.device}")
else:
print(f"๐Ÿ’ป ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ - ๋””๋ฐ”์ด์Šค: {self.device}")
# ์ปดํฌ๋„ŒํŠธ ์ดˆ๊ธฐํ™”
self.load_embedding_model()
self.setup_vectorizers()
self.setup_law_fetcher()
# ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
self.initialize_system()
print("โœ… RAG ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
def load_embedding_model(self):
"""์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ (ํ™˜๊ฒฝ๋ณ„ ์ตœ์ ํ™”)"""
print("์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
# ํ™˜๊ฒฝ์— ๋”ฐ๋ฅธ ๋ชจ๋ธ ์„ ํƒ ์ „๋žต
if self.is_huggingface_space and self.device == 'cuda':
# ํ—ˆ๊น…ํŽ˜์ด์Šค GPU ํ™˜๊ฒฝ: ์„ฑ๋Šฅ ์šฐ์„ 
model_priority = self.config['embedding_models']
else:
# ๋กœ์ปฌ CPU ํ™˜๊ฒฝ: ๊ฐ€๋ฒผ์šด ๋ชจ๋ธ ์šฐ์„ 
model_priority = [
'paraphrase-multilingual-MiniLM-L12-v2', # ๊ฐ€์žฅ ๊ฐ€๋ฒผ์šด ๋ชจ๋ธ
'sentence-transformers/paraphrase-multilingual-mpnet-base-v2',
'jhgan/ko-sroberta-multitask'
]
for model_name in model_priority:
try:
print(f"์‹œ๋„ ์ค‘: {model_name}")
# ๋กœ์ปฌ CPU ํ™˜๊ฒฝ์—์„œ๋Š” ๋” ๋ณด์ˆ˜์ ์ธ ์„ค์ •
if not self.is_huggingface_space and self.device == 'cpu':
self.embedding_model = SentenceTransformer(
model_name,
device=self.device,
cache_folder='./model_cache' # ๋กœ์ปฌ ์บ์‹œ ์‚ฌ์šฉ
)
else:
self.embedding_model = SentenceTransformer(
model_name,
device=self.device
)
print(f"์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ: {model_name}")
return
except Exception as e:
print(f"{model_name} ๋กœ๋“œ ์‹คํŒจ: {e}")
continue
raise Exception("๋ชจ๋“  ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ")
def setup_vectorizers(self):
"""๋ฒกํ„ฐ๋ผ์ด์ € ์„ค์ •"""
self.tfidf_vectorizer = TfidfVectorizer(
max_features=1000,
ngram_range=(1, 3),
stop_words=None
)
# ์ดˆ๊ธฐํ™”
self.vector_db = None
self.tfidf_matrix = None
self.documents = []
self.metadata = []
def setup_law_fetcher(self):
"""๋ฒ•๋ น ํŽ˜์ฒ˜ ์„ค์ •"""
self.law_fetcher = HFLawAPIFetcher()
def initialize_system(self):
"""์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” - ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋˜๋Š” ๊ตฌ์ถ•"""
if self.load_prebuilt_data():
print("โœ… ๊ธฐ์กด ๋ฒกํ„ฐ DB ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ")
else:
print("๐Ÿ”„ ์ƒˆ๋กœ์šด RAG ์‹œ์Šคํ…œ ๊ตฌ์ถ• ์‹œ์ž‘...")
self.build_system()
def load_prebuilt_data(self):
"""๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ ๋ฒกํ„ฐ DB ๋ฐ์ดํ„ฐ ๋กœ๋“œ"""
try:
# ๋ฒกํ„ฐ DB ๋กœ๋“œ
if os.path.exists('vector_db.faiss'):
self.vector_db = faiss.read_index('vector_db.faiss')
print(f"โœ… ๋ฒกํ„ฐ DB ๋กœ๋“œ: {self.vector_db.ntotal}๊ฐœ ๋ฒกํ„ฐ")
else:
return False
# ๋ฌธ์„œ ๋กœ๋“œ
if os.path.exists('documents.pkl'):
with open('documents.pkl', 'rb') as f:
self.documents = pickle.load(f)
print(f"โœ… ๋ฌธ์„œ ๋กœ๋“œ: {len(self.documents)}๊ฐœ")
else:
return False
# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋กœ๋“œ
if os.path.exists('metadata.json'):
with open('metadata.json', 'r', encoding='utf-8') as f:
self.metadata = json.load(f)
print(f"โœ… ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋กœ๋“œ: {len(self.metadata)}๊ฐœ")
else:
return False
# TF-IDF ํ–‰๋ ฌ ๊ตฌ์ถ•
if self.documents:
self.tfidf_matrix = self.tfidf_vectorizer.fit_transform(self.documents)
print("โœ… TF-IDF ํ–‰๋ ฌ ๊ตฌ์ถ• ์™„๋ฃŒ")
return True
except Exception as e:
print(f"โŒ ๋ฒกํ„ฐ DB ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ: {e}")
return False
def build_system(self):
"""์ „์ฒด ์‹œ์Šคํ…œ ๊ตฌ์ถ• (์บ์‹œ ํ™œ์šฉ)"""
print("๐Ÿ— ๏ธ RAG ์‹œ์Šคํ…œ ๊ตฌ์ถ• ์‹œ์ž‘...")
# 1. ๋ฒ•๋ น ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ (์บ์‹œ ํ™œ์šฉ)
raw_law_data = self.law_fetcher.fetch_laws()
if not raw_law_data:
print("โŒ ๋ฒ•๋ น ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์‹คํŒจ")
return False
# 2. ๋ฌธ์„œ ์ „์ฒ˜๋ฆฌ
documents = []
metadata = []
parsed_law_data = {}
for law_name, raw_data in raw_law_data.items():
parsed_data = self.law_fetcher._parse_json_response(raw_data)
if parsed_data:
parsed_law_data[law_name] = parsed_data
if not parsed_law_data:
print("โŒ ๋ฒ•๋ น ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์‹คํŒจ")
return False
for law_name, law_info in parsed_law_data.items():
for article_key, article_content in law_info.get('์กฐ๋ฌธ๋ชฉ๋ก', {}).items():
# ์ทจ๋“์„ธ ๊ด€๋ จ๋„ ๊ณ„์‚ฐ
relevance = self._calculate_relevance(article_content)
if relevance > 0.1:
documents.append(article_content)
metadata.append({
'law_name': law_name,
'article_key': article_key,
'relevance': relevance,
'length': len(article_content)
})
if not documents:
print("โŒ ์ทจ๋“์„ธ ๊ด€๋ จ ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
return False
print(f"๐Ÿ“„ ์ „์ฒ˜๋ฆฌ ์™„๋ฃŒ: {len(documents)}๊ฐœ ๋ฌธ์„œ")
# 3. ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
try:
embeddings = self.embedding_model.encode(
documents,
show_progress_bar=True,
convert_to_numpy=True,
batch_size=self.config['batch_size']
)
print(f"โœ… ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ์™„๋ฃŒ: {embeddings.shape}")
except Exception as e:
print(f"โŒ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ์‹คํŒจ: {e}")
return False
# 4. ๋ฒกํ„ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์ถ•
try:
dimension = embeddings.shape[1]
self.vector_db = faiss.IndexFlatIP(dimension)
faiss.normalize_L2(embeddings)
self.vector_db.add(embeddings.astype('float32'))
self.documents = documents
self.metadata = metadata
print(f"โœ… ๋ฒกํ„ฐ DB ๊ตฌ์ถ• ์™„๋ฃŒ: {self.vector_db.ntotal}๊ฐœ ๋ฒกํ„ฐ")
except Exception as e:
print(f"โŒ ๋ฒกํ„ฐ DB ๊ตฌ์ถ• ์‹คํŒจ: {e}")
return False
# 5. TF-IDF ๊ตฌ์ถ•
try:
self.tfidf_matrix = self.tfidf_vectorizer.fit_transform(documents)
print("โœ… TF-IDF ๊ตฌ์ถ• ์™„๋ฃŒ")
except Exception as e:
print(f"โŒ TF-IDF ๊ตฌ์ถ• ์‹คํŒจ: {e}")
# 6. ๊ตฌ์ถ•๋œ ๋ฐ์ดํ„ฐ ์ €์žฅ
self._save_built_system()
print("๐ŸŽ‰ RAG ์‹œ์Šคํ…œ ๊ตฌ์ถ• ์™„๋ฃŒ!")
return True
def _save_built_system(self):
"""๊ตฌ์ถ•๋œ ์‹œ์Šคํ…œ ์ €์žฅ"""
try:
print("๐Ÿ’พ ๊ตฌ์ถ•๋œ RAG ์‹œ์Šคํ…œ ์ €์žฅ ์ค‘...")
if self.vector_db:
faiss.write_index(self.vector_db, 'vector_db.faiss')
print("โœ… ๋ฒกํ„ฐ DB ์ €์žฅ ์™„๋ฃŒ")
if self.documents:
with open('documents.pkl', 'wb') as f:
pickle.dump(self.documents, f)
print("โœ… ๋ฌธ์„œ ์ €์žฅ ์™„๋ฃŒ")
if self.metadata:
with open('metadata.json', 'w', encoding='utf-8') as f:
json.dump(self.metadata, f, ensure_ascii=False, indent=2)
print("โœ… ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ ์™„๋ฃŒ")
except Exception as e:
print(f"โŒ ์‹œ์Šคํ…œ ์ €์žฅ ์‹คํŒจ: {e}")
def _calculate_relevance(self, content):
"""์ทจ๋“์„ธ ๊ด€๋ จ๋„ ๊ณ„์‚ฐ"""
content_lower = content.lower()
score = 0.0
total_possible_score = sum(RELEVANCE_KEYWORDS.values())
for keyword, weight in RELEVANCE_KEYWORDS.items():
if keyword in content_lower:
frequency = content_lower.count(keyword)
frequency_multiplier = min(1 + (frequency - 1) * 0.15, 2.0)
score += weight * frequency_multiplier
max_realistic_score = total_possible_score * 0.3
return min(score / max_realistic_score, 1.0)
def search(self, query, top_k=None):
"""ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰"""
if top_k is None:
top_k = self.config['top_k']
if not self.vector_db or not self.documents:
return []
print(f"๐Ÿ” ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ: '{query}'")
try:
# ๋ฒกํ„ฐ ๊ฒ€์ƒ‰
query_embedding = self.embedding_model.encode([query], convert_to_numpy=True)
faiss.normalize_L2(query_embedding)
vector_scores, vector_indices = self.vector_db.search(
query_embedding.astype('float32'),
min(top_k * 2, len(self.documents))
)
# TF-IDF ๊ฒ€์ƒ‰
tfidf_scores = []
if self.tfidf_matrix is not None:
query_tfidf = self.tfidf_vectorizer.transform([query])
tfidf_similarities = cosine_similarity(query_tfidf, self.tfidf_matrix).flatten()
tfidf_scores = tfidf_similarities
# ๊ฒฐ๊ณผ ์กฐํ•ฉ
results = []
for i, (v_score, v_idx) in enumerate(zip(vector_scores[0], vector_indices[0])):
if v_score > self.config['similarity_threshold']:
tfidf_score = tfidf_scores[v_idx] if len(tfidf_scores) > v_idx else 0.0
# ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ์ˆ˜
hybrid_score = (
self.config['hybrid_weights']['vector'] * float(v_score) +
self.config['hybrid_weights']['tfidf'] * float(tfidf_score)
)
results.append({
'content': self.documents[v_idx], # LLM ํ”„๋กœ์„ธ์„œ๊ฐ€ ๊ธฐ๋Œ€ํ•˜๋Š” ํ‚ค ์ด๋ฆ„
'document': self.documents[v_idx], # ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€
'metadata': self.metadata[v_idx],
'vector_score': float(v_score),
'tfidf_score': float(tfidf_score),
'hybrid_score': hybrid_score
})
# ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ์ˆ˜๋กœ ์ •๋ ฌ
results.sort(key=lambda x: x['hybrid_score'], reverse=True)
print(f"๐Ÿ“Š ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: {len(results[:top_k])}๊ฐœ ๋ฌธ์„œ ๋ฐœ๊ฒฌ")
return results[:top_k]
except Exception as e:
print(f"โŒ ๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {e}")
return []
def generate_answer(self, query, search_results):
"""๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€ ์ƒ์„ฑ"""
if not search_results:
return "๊ด€๋ จ๋œ ๋ฒ•๋ น ์กฐ๋ฌธ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
answer_parts = [f"**๐Ÿ’ฌ '{query}'์— ๋Œ€ํ•œ ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค.**\n"]
answer_parts.append("๐Ÿ“‹ **๊ด€๋ จ ๋ฒ•๋ น:**\n")
for i, result in enumerate(search_results, 1):
metadata = result['metadata']
hybrid_score = result['hybrid_score']
answer_parts.append(
f"**{i}. {metadata['law_name']} {metadata['article_key']}** "
f"(๊ด€๋ จ๋„: {hybrid_score:.3f})\n"
)
answer_parts.append(f"```\n{result['document'][:300]}...\n```\n")
# ๊ทœ์น™ ๊ธฐ๋ฐ˜ ์ถ”๊ฐ€ ์ •๋ณด
self._add_rule_based_info(query, answer_parts)
return '\n'.join(answer_parts)
def _add_rule_based_info(self, query, answer_parts):
"""๊ทœ์น™ ๊ธฐ๋ฐ˜ ์ถ”๊ฐ€ ์ •๋ณด"""
if any(keyword in query for keyword in ['์„ธ์œจ', '์„ธ์•ก', '์–ผ๋งˆ']):
answer_parts.append("\n๐Ÿ’ก **์ทจ๋“์„ธ ์„ธ์œจ ์ •๋ณด:**")
answer_parts.append("- **์ฃผํƒ**: 1~3% (๋ณด์œ  ์ฃผํƒ ์ˆ˜์— ๋”ฐ๋ผ)")
answer_parts.append("- **์ผ๋ฐ˜ ๋ถ€๋™์‚ฐ**: 4%")
answer_parts.append("- **๋†์ง€**: 3%\n")
if any(keyword in query for keyword in ['์‹ ๊ณ ', '๊ธฐํ•œ', '์–ธ์ œ']):
answer_parts.append("\nโฐ **์‹ ๊ณ  ๊ธฐํ•œ:**")
answer_parts.append("- **์ผ๋ฐ˜ ์ทจ๋“**: ์ทจ๋“์ผ๋ถ€ํ„ฐ 60์ผ ์ด๋‚ด")
answer_parts.append("- **์ƒ์†**: ์ทจ๋“์ผ์ด ์†ํ•˜๋Š” ๋‹ฌ์˜ ๋ง์ผ๋ถ€ํ„ฐ 6๊ฐœ์›” ์ด๋‚ด\n")
if any(keyword in query for keyword in ['๊ฐ๋ฉด', 'ํŠน๋ก€', 'ํ˜œํƒ']):
answer_parts.append("\n๐ŸŽ **์ฃผ์š” ๊ฐ๋ฉด ํ˜œํƒ:**")
answer_parts.append("- **1์„ธ๋Œ€ 1์ฃผํƒ**: 6์–ต์› ์ดํ•˜ 50% ๊ฐ๋ฉด")
answer_parts.append("- **์‹ ํ˜ผ๋ถ€๋ถ€**: ์ถ”๊ฐ€ ๊ฐ๋ฉด ํ˜œํƒ")
answer_parts.append("- **๋†์ง€**: ์ง์ ‘ ์˜๋†์‹œ ๊ฐ๋ฉด\n")
def query(self, user_question):
"""์ „์ฒด ์งˆ์˜์‘๋‹ต ์ฒ˜๋ฆฌ"""
print(f"\nโ“ ์งˆ๋ฌธ: {user_question}")
print("="*60)
# 1. ๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰
search_results = self.search(user_question, top_k=3)
# 2. ๋‹ต๋ณ€ ์ƒ์„ฑ
answer = self.generate_answer(user_question, search_results)
return {
'question': user_question,
'answer': answer,
'sources': search_results,
'source_count': len(search_results)
}
def get_cache_info(self):
"""์บ์‹œ ์ •๋ณด ๋ฐ˜ํ™˜"""
return {
'cache_dir': self.law_fetcher.cache_dir,
'cache_count': len(self.law_fetcher.cache_info),
'cache_info': self.law_fetcher.cache_info
}
# ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค (Flask ์•ฑ์—์„œ ์‚ฌ์šฉ)
_rag_instance = None
def get_rag_system(custom_config=None):
"""RAG ์‹œ์Šคํ…œ ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ๋ฐ˜ํ™˜"""
global _rag_instance
if _rag_instance is None or custom_config is not None:
_rag_instance = HFSpacesTaxRAG(custom_config)
return _rag_instance
# Flask ์•ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ํ•จ์ˆ˜๋“ค
def search_tax_law(question):
"""์ทจ๋“์„ธ ๋ฒ•๋ น ๊ฒ€์ƒ‰ + LLM ์ฒ˜๋ฆฌ (Flask ์•ฑ์šฉ)"""
try:
# LLM ์ฒ˜๋ฆฌ๊ธฐ import
from llm_processor import get_llm_processor, is_llm_available
print(f"๐Ÿ” ์ฒ˜๋ฆฌ ์‹œ์ž‘: {question}")
# 1. RAG ๊ฒ€์ƒ‰
rag = get_rag_system()
rag_result = rag.query(question)
print(f"๐Ÿ“š RAG ๊ฒ€์ƒ‰ ์™„๋ฃŒ: {rag_result['source_count']}๊ฐœ ๋ฌธ์„œ")
# 2. LLM ์ฒ˜๋ฆฌ ์‹œ๋„
if is_llm_available():
print("๐Ÿค– LLM ์ฒ˜๋ฆฌ ์‹œ๋„ ์ค‘...")
llm = get_llm_processor()
# RAG ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ LLM์— ์ „๋‹ฌ
search_results = rag_result.get('sources', [])
final_response = llm.process_with_rag(question, search_results)
print("โœ… LLM ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
return final_response
else:
print("โš ๏ธ LLM ์‚ฌ์šฉ ๋ถˆ๊ฐ€, RAG ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜")
return rag_result['answer']
except Exception as e:
print(f"โŒ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")
import traceback
traceback.print_exc()
return f"์งˆ๋ฌธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}"
def search_tax_law_rag_only(question):
"""RAG๋งŒ ์‚ฌ์šฉํ•œ ๊ฒ€์ƒ‰ (LLM ์—†์ด)"""
try:
rag = get_rag_system()
result = rag.query(question)
return result['answer']
except Exception as e:
print(f"โŒ RAG ๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {e}")
return f"๋ฒ•๋ น ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}"
def is_rag_available():
"""RAG ์‹œ์Šคํ…œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ"""
try:
rag = get_rag_system()
return rag.vector_db is not None and len(rag.documents) > 0
except Exception:
return False
def get_system_status():
"""์ „์ฒด ์‹œ์Šคํ…œ ์ƒํƒœ ํ™•์ธ"""
try:
from llm_processor import is_llm_available
status = {
'rag_available': is_rag_available(),
'llm_available': is_llm_available(),
'pipeline_complete': is_rag_available() and is_llm_available()
}
if status['rag_available']:
rag = get_rag_system()
status['rag_details'] = {
'documents': len(rag.documents),
'device': rag.device,
'is_hf_space': rag.is_huggingface_space
}
return status
except Exception as e:
return {'error': str(e)}