Spaces:
Sleeping
Sleeping
File size: 20,271 Bytes
6d40f77 7ca482a eb55100 415ebef 6d40f77 53c9adb 0112759 737169d eb55100 5393c73 14f70ce 6d40f77 737169d eb55100 415ebef dfd1ae6 5393c73 0112759 5393c73 53c9adb 5393c73 3155350 737169d 3155350 737169d 3155350 737169d 3155350 737169d 3155350 b1040cf 3155350 415ebef 737169d 415ebef 737169d 415ebef 5393c73 3155350 53c9adb 5393c73 d42dc1b 5393c73 0112759 5393c73 737169d 53c9adb 5393c73 09dbaf7 b1040cf 09dbaf7 b1040cf 5393c73 c353fe2 5393c73 0112759 eb55100 5994e00 270cbe3 1f20aa8 270cbe3 1f20aa8 270cbe3 5994e00 eb55100 dc01a88 34a4c19 6e12111 34a4c19 dc01a88 34a4c19 dc01a88 34a4c19 dc01a88 34a4c19 3155350 34a4c19 3155350 34a4c19 3155350 34a4c19 3155350 dc01a88 3155350 dc01a88 dfd1ae6 5994e00 14f70ce 0112759 5994e00 0112759 5994e00 53c9adb 0112759 5393c73 737169d 270cbe3 5393c73 14f70ce 53c9adb dc01a88 53c9adb 14f70ce dc01a88 5994e00 dc01a88 dfd1ae6 5393c73 14f70ce 737169d c5ed44a 5393c73 1d4b2ed dfd1ae6 c353fe2 5393c73 0112759 5393c73 c5ed44a 1d4b2ed 2c9909d dc01a88 2c9909d 5393c73 737169d 1f20aa8 5393c73 1f20aa8 c5ed44a 3f9602c 1f20aa8 1d4b2ed 737169d 5393c73 1d4b2ed 737169d 5393c73 eb55100 2be8232 eb55100 34a4c19 eb55100 34a4c19 82f601c 5994e00 34a4c19 a973827 8cb66e5 34a4c19 5994e00 34a4c19 eb55100 6e12111 34a4c19 dc01a88 5393c73 34a4c19 5393c73 eb55100 5393c73 0112759 eb55100 e3388ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | 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} | {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;">📊 ์ ์ฉ ์งํ ์
๋ ฅ</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)
|