gardio_test / app.py
dev-yuje's picture
fix: quantization_config ์—ญ์ง๋ ฌํ™” ์˜ค๋ฅ˜๋ฅผ config.json ํŒจ์น˜๋กœ ์™„์ „ ํ•ด๊ฒฐ (๋กœ์ปฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ)
415ebef
Raw History Blame Contribute Delete
20.3 kB
import sys
# 1. ์ตœ์ƒ๋‹จ ๋ชฝํ‚ค ํŒจ์น˜ - ๋ชจ๋“  ์ž„ํฌํŠธ๋ณด๋‹ค ์ตœ์šฐ์„ 
import huggingface_hub
if not hasattr(huggingface_hub, "HfFolder"):
class MockHfFolder:
@staticmethod
def get_token(): return None
@staticmethod
def save_token(token): pass
@staticmethod
def delete_token(): pass
huggingface_hub.HfFolder = MockHfFolder
import gradio as gr
import os
import re
import zipfile
import tempfile
import numpy as np
import pandas as pd
import tensorflow as tf
import joblib
import traceback
from huggingface_hub import hf_hub_download
# TF ์ตœ์ ํ™” ๊ฒฝ๊ณ  ๋ฐฉ์ง€ ๋ฐ ์•ˆ์ •์„ฑ
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
# ๋ฌด๊ฑฐ์šด ๋ชจ๋ธ ์ง€์—ฐ ๋กœ๋”ฉ
_models = {"predictor": None, "consultant": None}
REPO_ID = "dev-yuje/gardio_test"
def load_keras_model_compat(model_path):
"""quantization_config ์—ญ์ง๋ ฌํ™” ์˜ค๋ฅ˜๋ฅผ config.json ํŒจ์น˜๋กœ ์šฐํšŒ"""
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(model_path, 'r') as z:
z.extractall(tmpdir)
config_path = os.path.join(tmpdir, 'config.json')
with open(config_path, 'r', encoding='utf-8') as f:
config_str = f.read()
config_str = re.sub(r',\s*"quantization_config":\s*null', '', config_str)
config_str = re.sub(r'"quantization_config":\s*null,?\s*', '', config_str)
with open(config_path, 'w', encoding='utf-8') as f:
f.write(config_str)
fixed_path = model_path + '.tmp_fixed.keras'
with zipfile.ZipFile(fixed_path, 'w', zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk(tmpdir):
for file in files:
fp = os.path.join(root, file)
arcname = os.path.relpath(fp, tmpdir)
z.write(fp, arcname)
model = tf.keras.models.load_model(fixed_path, compile=False)
os.remove(fixed_path)
return model
def load_all_models():
if _models["predictor"] is None:
try:
# ๋‚ด๋ถ€ ์˜ˆ์ธก ๋กœ์ง (Self-contained)
class RobustCreditPredictor:
def __init__(self):
self.preprocessor_path = "models/preprocessor.pkl"
self.model_path = "models/telecom_cb_model.keras"
self.preprocessor = None
self.model = None
self.load_error = "์ดˆ๊ธฐํ™”๋จ"
self.load_resources()
def load_resources(self):
try:
# [๊ฐœ์„ ] ๋กœ์ปฌ ํŒŒ์ผ ์šฐ์„  ๋กœ๋“œ โ†’ ์—†์œผ๋ฉด HuggingFace ๋‹ค์šด๋กœ๋“œ
# --- ์ „์ฒ˜๋ฆฌ๊ธฐ ๋กœ๋“œ ---
try:
if os.path.exists(self.preprocessor_path):
self.preprocessor = joblib.load(self.preprocessor_path)
print(f"โœ… ์ „์ฒ˜๋ฆฌ๊ธฐ ๋กœ์ปฌ ๋กœ๋“œ ์„ฑ๊ณต: {self.preprocessor_path}")
else:
cached_preprocessor = hf_hub_download(repo_id=REPO_ID, filename=self.preprocessor_path, repo_type="space")
self.preprocessor = joblib.load(cached_preprocessor)
print(f"โœ… ์ „์ฒ˜๋ฆฌ๊ธฐ HuggingFace ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต")
except Exception as prep_e:
self.load_error = f"์ „์ฒ˜๋ฆฌ๊ธฐ ๋กœ๋“œ ์‹คํŒจ: {prep_e}"
return
# --- ๋ชจ๋ธ ๋กœ๋“œ ---
try:
if os.path.exists(self.model_path):
target_path = self.model_path
print(f"โœ… ๋ชจ๋ธ ๋กœ์ปฌ ๊ฒฝ๋กœ ์‚ฌ์šฉ: {target_path}")
else:
target_path = hf_hub_download(repo_id=REPO_ID, filename=self.model_path, repo_type="space")
print(f"โœ… ๋ชจ๋ธ HuggingFace ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ")
fsize = os.path.getsize(target_path)
if fsize < 1000:
self.load_error = f"ํŒŒ์ผ์ด ๋„ˆ๋ฌด ์ž‘์Œ({fsize}B). LFS ํฌ์ธํ„ฐ์ผ ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ."
return
self.model = load_keras_model_compat(target_path)
self.load_error = "์„ฑ๊ณต"
print(f"โœ… ๋ชจ๋ธ ๋กœ๋“œ ์„ฑ๊ณต (ํŒŒ์ผ ํฌ๊ธฐ: {fsize:,}B)")
except Exception as model_e:
self.load_error = f"๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: {str(model_e)[:300]}"
except Exception as e:
self.load_error = f"๋ฆฌ์†Œ์Šค ๋กœ๋“œ ํ†ตํ•ฉ ์—๋Ÿฌ: {e}"
def predict(self, features_dict):
try:
if self.model is None or self.preprocessor is None:
err_short = self.load_error[:500] if self.load_error else "์›์ธ ๋ถˆ๋ช…"
return f"Error: ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ ์š”๋ง. ์›๋ณธ ์—๋Ÿฌ: {err_short}"
ALL_FEATURES = [
'C1Z001386', 'C1M210000', 'C18210000', 'C1L120001', 'C1L120004',
'L10210000', 'L90210100', 'L90210200', 'L10210B00', 'L10216000',
'L10217000', 'D10110000', 'D10133000', 'PERF1'
]
input_values = [float(features_dict.get(col, 0.0)) for col in ALL_FEATURES]
df = pd.DataFrame([input_values], columns=ALL_FEATURES)
log_cols = ['C1Z001386', 'C1L120004', 'D10110000', 'D10133000', 'L90210200',
'L10216000', 'L10210B00', 'L10217000', 'L90210100', 'L10210000']
df[log_cols] = np.log1p(df[log_cols].astype(float).clip(lower=0))
scaled_data = self.preprocessor.transform(df)
prediction = self.model.predict(scaled_data, verbose=0)
return float(prediction[0][0])
except Exception as e:
return f"Error: ์˜ˆ์ธก ์—ฐ์‚ฐ ์—๋Ÿฌ: {str(e)}"
_models["predictor"] = RobustCreditPredictor()
# ์ƒ๋‹ด์‚ฌ ๋กœ์ง
from langchain_google_genai import ChatGoogleGenerativeAI
class Consultant:
def __init__(self):
api_key = os.getenv("GOOGLE_API_KEY", "")
from config import LLM_MODEL
# [์ˆ˜์ •] ๋ชจ๋ธ ๋ช…์นญ ๋ฐ API ๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ๊ณ ๋ ค (config ์—ฐ๋™)
self.llm = ChatGoogleGenerativeAI(
model=LLM_MODEL,
google_api_key=api_key,
temperature=0.7,
convert_system_message_to_human=True
)
self.embedding_model = None
self.retriever = None
def lazy_load_search(self):
if self.embedding_model is None:
try:
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from config import EMBEDDING_MODEL, FAISS_PATH, RETRIEVER_K
self.embedding_model = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
if os.path.exists(FAISS_PATH):
self.vectorstore = FAISS.load_local(FAISS_PATH, self.embedding_model, allow_dangerous_deserialization=True)
self.retriever = self.vectorstore.as_retriever(search_kwargs={"k": RETRIEVER_K})
except: pass
_models["consultant"] = Consultant()
except Exception as e:
print(f"Grand Load Error: {e}")
FEATURES_DETAIL = {
'C1Z001386': ('1๋…„ ๋‚ด ์นด๋“œ ์ด ์ด์šฉ๊ธˆ์•ก', '๋งŒ์› ๋‹จ์œ„', '0'),
'C1M210000': ('๋ณด์œ  ์‹ ์šฉ์นด๋“œ ์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'C18210000': ('๋ณด์œ  ์ฒดํฌ์นด๋“œ ์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'C1L120001': ('์นด๋“œ ์ด ํ•œ๋„๊ธˆ์•ก', '๋งŒ์› ๋‹จ์œ„', '0'),
'C1L120004': ('์‹ ์šฉ์นด๋“œ ๊ฐœ์„ค ํ›„ ๊ฒฝ๊ณผ์ผ์ˆ˜', '์ผ ๋‹จ์œ„', '0'),
'L90210100': ('์€ํ–‰์—…์ข… ๋Œ€์ถœ ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'L90210200': ('์นด๋“œ์—…์ข… ๋Œ€์ถœ ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'L10210B00': ('๋ณดํ—˜์—…์ข… ๋Œ€์ถœ ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'L10216000': ('์‹ ์šฉ๋Œ€์ถœ ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'L10217000': ('๋‹ด๋ณด๋Œ€์ถœ ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'D10110000': ('๊ณผ๊ฑฐ ์—ฐ์ฒด ๊ฑด์ˆ˜', '๊ฐœ์ˆ˜', '0'),
'D10133000': ('์ด ์—ฐ์ฒด ์ƒํ™˜ ๊ธˆ์•ก', '๋งŒ์› ๋‹จ์œ„', '0'),
'PERF1': ('1๋…„ ๋‚ด 90์ผ ์ด์ƒ ์—ฐ์ฒด ๊ฒฝํ—˜', '์ฒดํฌ ์‹œ ์—ฐ์ฒด ๊ฒฝํ—˜ ์žˆ์Œ', None),
}
ALL_KEYS = list(FEATURES_DETAIL.keys())
def get_grade_info(score_str):
"""์ ์ˆ˜ โ†’ (๋“ฑ๊ธ‰, ๋“ฑ๊ธ‰ ๊ตฌ๋ถ„, ์˜๋ฏธ/ํŠน์ง•, ์ƒ‰์ƒ) ๋ฐ˜ํ™˜"""
try:
score = int(score_str)
except:
return None
if score >= 942: grade, label, desc, color = "1๋“ฑ๊ธ‰", "์ตœ์šฐ๋Ÿ‰๋“ฑ๊ธ‰", "์˜ค๋žœ ์‹ ์šฉ๊ฑฐ๋ž˜ ๊ฒฝ๋ ฅ๊ณผ ๋‹ค์–‘ํ•˜๊ณ  ์šฐ๋Ÿ‰ํ•œ ์‹ ์šฉ๊ฑฐ๋ž˜ ์‹ค์ ์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋‚™์Œ", "#A8D8EA"
elif score >= 891: grade, label, desc, color = "2๋“ฑ๊ธ‰", "์ตœ์šฐ๋Ÿ‰๋“ฑ๊ธ‰", "์˜ค๋žœ ์‹ ์šฉ๊ฑฐ๋ž˜ ๊ฒฝ๋ ฅ๊ณผ ๋‹ค์–‘ํ•˜๊ณ  ์šฐ๋Ÿ‰ํ•œ ์‹ ์šฉ๊ฑฐ๋ž˜ ์‹ค์ ์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋‚™์Œ", "#A8D8EA"
elif score >= 832: grade, label, desc, color = "3๋“ฑ๊ธ‰", "์šฐ๋Ÿ‰๋“ฑ๊ธ‰", "ํ™œ๋ฐœํ•œ ์‹ ์šฉ๊ฑฐ๋ž˜ ์‹ค์ ์€ ์—†์œผ๋‚˜, ๊ผธ์ค€ํžˆ ์šฐ๋Ÿ‰ ๊ฑฐ๋ž˜๋ฅผ ์ง€์†ํ•œ๋‹ค๋ฉด ์ƒ์œ„๋“ฑ๊ธ‰ ์ง„์ž… ๊ฐ€๋Šฅํ•˜๋ฉฐ ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์€ ๋‚™์€ ์ˆ˜์ค€์ž„", "#B8F0C8"
elif score >= 768: grade, label, desc, color = "4๋“ฑ๊ธ‰", "์šฐ๋Ÿ‰๋“ฑ๊ธ‰", "ํ™œ๋ฐœํ•œ ์‹ ์šฉ๊ฑฐ๋ž˜ ์‹ค์ ์€ ์—†์œผ๋‚˜, ๊ผธ์ค€ํžˆ ์šฐ๋Ÿ‰ ๊ฑฐ๋ž˜๋ฅผ ์ง€์†ํ•œ๋‹ค๋ฉด ์ƒ์œ„๋“ฑ๊ธ‰ ์ง„์ž… ๊ฐ€๋Šฅํ•˜๋ฉฐ ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์€ ๋‚™์€ ์ˆ˜์ค€์ž„", "#B8F0C8"
elif score >= 698: grade, label, desc, color = "5๋“ฑ๊ธ‰", "์ผ๋ฐ˜๋“ฑ๊ธ‰", "๋น„๊ต์  ๊ธˆ๋ฆฌ๊ฐ€ ๋†’์€ ๊ธˆ์œต์—…๊ถŒ๊ณผ์˜ ๊ฑฐ๋ž˜๊ฐ€ ์žˆ๋Š” ๊ณ ๊ฐ์œผ๋กœ, ๋‹จ๊ธฐ์—ฐ์ฒด ๊ฒฝํ—˜์ด ์žˆ์œผ๋ฉฐ ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์€ ์ผ๋ฐ˜์ ์ธ ์ˆ˜์ค€์ž„", "#FEE8A0"
elif score >= 620: grade, label, desc, color = "6๋“ฑ๊ธ‰", "์ผ๋ฐ˜๋“ฑ๊ธ‰", "๋น„๊ต์  ๊ธˆ๋ฆฌ๊ฐ€ ๋†’์€ ๊ธˆ์œต์—…๊ถŒ๊ณผ์˜ ๊ฑฐ๋ž˜๊ฐ€ ์žˆ๋Š” ๊ณ ๊ฐ์œผ๋กœ, ๋‹จ๊ธฐ์—ฐ์ฒด ๊ฒฝํ—˜์ด ์žˆ์œผ๋ฉฐ ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์€ ์ผ๋ฐ˜์ ์ธ ์ˆ˜์ค€์ž„", "#FEE8A0"
elif score >= 530: grade, label, desc, color = "7๋“ฑ๊ธ‰", "์ฃผ์˜๋“ฑ๊ธ‰", "๋น„๊ต์  ๊ธˆ๋ฆฌ๊ฐ€ ๋†’์€ ๊ธˆ์œต์—…๊ถŒ๊ณผ์˜ ๊ฑฐ๋ž˜๊ฐ€ ๋งŽ์€ ๊ณ ๊ฐ์œผ๋กœ, ๋‹จ๊ธฐ์—ฐ์ฒด ๊ฒฝํ—˜์„ ๋น„๊ต์  ๋งŽ์ด ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Œ", "#FFCB9A"
elif score >= 454: grade, label, desc, color = "8๋“ฑ๊ธ‰", "์ฃผ์˜๋“ฑ๊ธ‰", "๋น„๊ต์  ๊ธˆ๋ฆฌ๊ฐ€ ๋†’์€ ๊ธˆ์œต์—…๊ถŒ๊ณผ์˜ ๊ฑฐ๋ž˜๊ฐ€ ๋งŽ์€ ๊ณ ๊ฐ์œผ๋กœ, ๋‹จ๊ธฐ์—ฐ์ฒด ๊ฒฝํ—˜์„ ๋น„๊ต์  ๋งŽ์ด ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Œ", "#FFCB9A"
elif score >= 335: grade, label, desc, color = "9๋“ฑ๊ธ‰", "์œ„ํ—˜๋“ฑ๊ธ‰", "ํ˜„์žฌ ์—ฐ์ฒด ์ค‘์ด๊ฑฐ๋‚˜ ๋งค์šฐ ์‹ฌ๊ฐํ•œ ์—ฐ์ฒด ๊ฒฝํ—˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋†’์Œ", "#FFB3B3"
else: grade, label, desc, color = "10๋“ฑ๊ธ‰", "์œ„ํ—˜๋“ฑ๊ธ‰", "ํ˜„์žฌ ์—ฐ์ฒด ์ค‘์ด๊ฑฐ๋‚˜ ๋งค์šฐ ์‹ฌ๊ฐํ•œ ์—ฐ์ฒด ๊ฒฝํ—˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์–ด ๋ถ€์‹คํ™” ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋†’์Œ", "#FFB3B3"
return grade, label, desc, color
SCORE_PLACEHOLDER = '''
<div style="border:2px dashed rgba(128,128,128,0.3); border-radius:16px; padding:24px; text-align:center; min-height:130px; display:flex; flex-direction:column; align-items:center; justify-content:center;">
<div style="font-size:36px; opacity:0.25;">๐Ÿ“‹</div>
<div style="font-size:14px; opacity:0.45; margin-top:10px;">๐Ÿ“Œ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ ์ˆ˜ ๋ถ„์„ํ•˜๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์ฃผ์„ธ์š”</div>
</div>
'''
def make_score_html(score_str):
if not score_str or score_str.startswith("โŒ") or score_str.startswith("โš ๏ธ"):
return f'''
<div style="border:2px solid #e74c3c33; border-radius:16px; padding:24px; text-align:center; min-height:120px; display:flex; align-items:center; justify-content:center;">
<div style="font-size:15px; color:#e74c3c;">{score_str}</div>
</div>'''
info = get_grade_info(score_str)
if info is None:
return f'<div style="padding:20px; text-align:center;">{score_str}</div>'
grade, label, desc, color = info
# ๋‹คํฌ๋ชจ๋“œ ํ˜ธํ™˜: ๋ฐฐ๊ฒฝ ๋ฐ˜ํˆฌ๋ช…, ํ…์ŠคํŠธ๋Š” CSS inherit ๋Œ€์‹  ๋ช…์‹œ์  ๋‹คํฌ๋ชจ๋“œ ๊ณต์œ  ์ƒˆ๋ฌธ (prefers-color-scheme)
return f'''
<style>
.score-card-{grade[0]} {{ background:{color}22; border:2px solid {color}; border-radius:16px; padding:24px; text-align:center; font-family:sans-serif; }}
.score-num-{grade[0]} {{ font-size:64px; font-weight:900; color:{color}; letter-spacing:-2px; line-height:1; }}
.score-grade-{grade[0]} {{ font-size:22px; font-weight:700; margin-top:8px; color:{color}; }}
.score-desc-{grade[0]} {{ font-size:13px; line-height:1.6; opacity:0.75; }}
@media (prefers-color-scheme: dark) {{
.score-desc-{grade[0]} {{ color: #ccc; }}
}}
@media (prefers-color-scheme: light) {{
.score-desc-{grade[0]} {{ color: #555; }}
}}
</style>
<div class="score-card-{grade[0]}">
<div class="score-num-{grade[0]}">{score_str}์ </div>
<div class="score-grade-{grade[0]}">{grade} &nbsp;|&nbsp; {label}</div>
<hr style="border:none; border-top:1px solid {color}; margin:14px 0;">
<div class="score-desc-{grade[0]}">{desc}</div>
</div>
'''
def handle_predict(*args):
try:
load_all_models()
features_dict = {}
for i, key in enumerate(ALL_KEYS):
val_raw = str(args[i]).strip().replace(",", "")
if key == 'PERF1':
features_dict[key] = 1.0 if (val_raw.lower() == 'true' or val_raw == '1' or args[i] is True) else 0.0
else:
try:
features_dict[key] = float(val_raw or 0)
except:
return f"โŒ ์˜ค๋ฅ˜: '{FEATURES_DETAIL[key][0]}' ์ˆซ์ž ์•„๋‹˜", "โŒ"
features_dict['L10210000'] = features_dict['L10216000'] + features_dict['L10217000']
res = _models["predictor"].predict(features_dict)
if isinstance(res, str) and "Error" in res:
return f"โŒ ๋ถ„์„ ์‹คํŒจ: {res}", make_score_html(f"โŒ ๋กœ๋“œ ์‹คํŒจ: {res[:60]}")
score_val = str(int(round(float(res))))
return {"features": features_dict, "score": score_val}, make_score_html(score_val)
except Exception as e:
return f"โŒ ์‹œ์Šคํ…œ ์˜ค๋ฅ˜: {str(e)}", make_score_html(f"โŒ ์‹œ์Šคํ…œ ์—๋Ÿฌ")
def generate_response(chatbot, user_message, analysis_report):
if not user_message: yield chatbot, ""; return
# [์ตœ์ข… ์ˆ˜์ •] Gradio 5์˜ 'Data incompatible' ์—๋Ÿฌ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ๋ช…์‹œ์ ์ธ ๋”•์…”๋„ˆ๋ฆฌ ํฌ๋งท ์‚ฌ์šฉ
chatbot.append({"role": "user", "content": user_message})
chatbot.append({"role": "assistant", "content": "๐Ÿ” [Retrieval] ๊ด€๋ จ ๊ทœ์ • ๋ฐ ์ง€์นจ์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."})
yield chatbot, ""
try:
load_all_models()
cons = _models["consultant"]
cons.lazy_load_search()
context = ""
if cons.retriever:
try:
docs = cons.retriever.invoke(user_message)
context = "\n\n".join([d.page_content for d in docs])
except: pass
chatbot[-1] = {"role": "assistant", "content": "๐Ÿง  [Augmentation] ๊ฒ€์ƒ‰๋œ ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."}
yield chatbot, ""
from llm.prompt import QA_PROMPT
if isinstance(analysis_report, dict) and "features" in analysis_report:
score_val = analysis_report.get("score", "๋ฏธ์ธก์ •")
# L10210000์€ ํŒŒ์ƒ ์ปจ๋Ÿผ์œผ๋กœ FEATURES_DETAIL์— ์—†์Œ - ํ‚ค๊ฐ€ ์žˆ๋Š” ๊ฒƒ๋งŒ ํ‘œ์‹œ
features_text = "\n".join([f"- {FEATURES_DETAIL[k][0]}: {v}" for k, v in analysis_report["features"].items() if k in FEATURES_DETAIL])
query_text = f"โ–  ๊ณ ๊ฐ ์‹ ์šฉ ์ ์ˆ˜: {score_val}์ \nโ–  ๊ณ ๊ฐ์˜ ํ˜„์žฌ ์ƒํƒœ(์ž…๋ ฅ๋œ ์ •๋ณด):\n{features_text}\n\nโ–  ๊ณ ๊ฐ ์งˆ๋ฌธ: {user_message}"
else:
query_text = f"โ–  ๊ณ ๊ฐ ์‹ ์šฉ ์ ์ˆ˜: ์ •๋ณด ์—†์Œ(์ผ๋ฐ˜ ์งˆ๋ฌธ ์ƒํƒœ)\nโ–  ์ƒํƒœ: ๋ถ„์„์„ ์ง„ํ–‰ํ•˜์ง€ ์•Š์Œ\nโ–  ์งˆ๋ฌธ: {user_message}"
full_prompt = QA_PROMPT.format(context=context, query=query_text)
# [์ค‘์š”] ๋”•์…”๋„ˆ๋ฆฌ ๋ฆฌ์ŠคํŠธ๋กœ ์ „๋‹ฌ (Gradio 5 UI ๋Œ€์‘)
from langchain_core.messages import HumanMessage
messages = [HumanMessage(content=full_prompt)]
chatbot[-1] = {"role": "assistant", "content": "๐Ÿ’ก [Generation] ๋‹ต๋ณ€์„ ์ƒ์„ฑ ์ค‘์ž…๋‹ˆ๋‹ค...\n\n"}
yield chatbot, ""
answer_buffer = ""
for chunk in cons.llm.stream(messages):
answer_buffer += chunk.content
# ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋”•์…”๋„ˆ๋ฆฌ ํฌ๋งท์œผ๋กœ ์—…๋ฐ์ดํŠธ
chatbot[-1] = {"role": "assistant", "content": answer_buffer}
yield chatbot, ""
except Exception as e:
chatbot[-1] = {"role": "assistant", "content": f"โš ๏ธ ์ƒ๋‹ด ์—๋Ÿฌ: {str(e)}"}
yield chatbot, ""
with gr.Blocks(title="KCB AI Consultant") as demo:
analysis_report = gr.State(None)
gr.Markdown("# ๐Ÿ›ก๏ธ KCB AI ์‹ ์šฉ ์ƒ๋‹ด ์‹œ์Šคํ…œ")
with gr.Row(equal_height=False):
# ==== ์™ผ์ชฝ: ์ž…๋ ฅ ํŒจ๋„ (2์—ด ๊ทธ๋ฆฌ๋“œ) ====
with gr.Column(scale=1, min_width=340):
gr.HTML('<h2 style="margin:4px 0 12px 0; font-size:20px; font-weight:700;">&#128202; ์‹ ์šฉ ์ง€ํ‘œ ์ž…๋ ฅ</h2>')
input_list = []
keys_no_perf = [k for k in ALL_KEYS if k != 'PERF1']
# 2์—ด๋กœ ์ž…๋ ฅ ํ•„๋“œ ๋ฐฐ์น˜
for i in range(0, len(keys_no_perf), 2):
with gr.Row():
for key in keys_no_perf[i:i+2]:
field_label, unit, _ = FEATURES_DETAIL[key]
unit_short = unit.replace(" ๋‹จ์œ„", "").replace("๊ฐœ์ˆ˜", "๊ฐœ")
# label์— ๋‹จ์œ„๋ฅผ ๊ดดํ˜ธ๋กœ ๋ถ™์ด๋˜, info ์—†์•  ๋ ˆ์ด์•„์›ƒ ์ •๋ฆฌ
tb = gr.Textbox(
label=f"{field_label} ({unit_short})",
placeholder="0",
min_width=120
)
input_list.append(tb)
# PERF1 ์ผฌ ์ฝ”๋„ˆ๋กœ ๋ฐฐ์น˜
with gr.Row():
perf_cb = gr.Checkbox(label=FEATURES_DETAIL['PERF1'][0], info=FEATURES_DETAIL['PERF1'][1], value=False)
input_list.append(perf_cb)
predict_btn = gr.Button("๐Ÿ“ˆ ์ ์ˆ˜ ๋ถ„์„ํ•˜๊ธฐ", variant="primary", size="lg")
# ==== ์˜ค๋ฅธ์ชฝ: ๊ฒฐ๊ณผ ํŒจ๋„ ====
with gr.Column(scale=2):
gr.HTML('<h2 style="margin:0 0 8px 0; font-size:22px; font-weight:700;">๐ŸŽฏ AI ์˜ˆ์ธก ์‹ ์šฉ ์ ์ˆ˜</h2>')
result_display = gr.HTML(value=SCORE_PLACEHOLDER)
chatbot = gr.Chatbot(label="AI ์ƒ๋‹ด์‚ฌ", height=430)
with gr.Row():
msg = gr.Textbox(placeholder="์งˆ๋ฌธ์„ ์ž…๋ ฅํ•˜์„ธ์š” (์‹ ์šฉ์ ์ˆ˜ ๋ถ„์„ ํ›„์—๋Š” ๊ฐœ์ธํ™” ์ƒ๋‹ด์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค)", show_label=False, scale=8)
submit_btn = gr.Button("์ „์†ก", variant="primary", scale=1)
predict_btn.click(handle_predict, inputs=input_list, outputs=[analysis_report, result_display])
msg.submit(generate_response, inputs=[chatbot, msg, analysis_report], outputs=[chatbot, msg])
submit_btn.click(generate_response, inputs=[chatbot, msg, analysis_report], outputs=[chatbot, msg])
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)