Spaces:
Sleeping
Sleeping
Auto commit at 08-2025-08 3:40:22
Browse files- app.py +60 -112
- test_input.py +0 -100
- test_text.py +0 -100
- test_tokenizer.py +0 -159
app.py
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import os
|
| 3 |
import traceback
|
| 4 |
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoImageProcessor
|
| 5 |
import torch
|
| 6 |
-
import fitz
|
| 7 |
from PIL import Image
|
| 8 |
from typing import Optional, List
|
| 9 |
|
| 10 |
-
# --- 1. 전역 변수
|
|
|
|
|
|
|
|
|
|
| 11 |
tokenizer = None
|
| 12 |
model = None
|
| 13 |
-
image_processor = None
|
| 14 |
MODEL_LOADED = False
|
| 15 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
-
|
| 17 |
IS_LOCAL = os.path.exists('.env') or os.path.exists('../.env') or os.getenv('IS_LOCAL') == 'true'
|
| 18 |
-
|
| 19 |
try:
|
| 20 |
from dotenv import load_dotenv
|
| 21 |
if IS_LOCAL:
|
|
@@ -23,58 +26,43 @@ try:
|
|
| 23 |
print("✅ .env 파일 로드됨")
|
| 24 |
except ImportError:
|
| 25 |
print("⚠️ python-dotenv가 설치되지 않음")
|
| 26 |
-
|
| 27 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 28 |
MODEL_NAME_SERVER = os.getenv("MODEL_NAME", "gbrabbit/lily-math-model")
|
| 29 |
MODEL_PATH_LOCAL = "../lily_llm_core/models/kanana_1_5_v_3b_instruct"
|
| 30 |
MODEL_PATH = MODEL_PATH_LOCAL if IS_LOCAL else MODEL_NAME_SERVER
|
| 31 |
-
|
| 32 |
print(f"============== 시스템 환경 정보 ==============")
|
| 33 |
print(f"🔍 실행 환경: {'로컬' if IS_LOCAL else '서버'}")
|
| 34 |
print(f"🔍 모델 경로: {MODEL_PATH}")
|
| 35 |
print(f"🔍 사용 디바이스: {DEVICE.upper()}")
|
| 36 |
print("==========================================")
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
# --- 2. 핵심 로직: 모델 및 프로세서 로딩 ---
|
| 40 |
try:
|
| 41 |
print("🔧 모델 로딩 시작...")
|
| 42 |
from modeling import KananaVForConditionalGeneration
|
| 43 |
-
|
| 44 |
if IS_LOCAL:
|
| 45 |
if not os.path.exists(MODEL_PATH):
|
| 46 |
raise FileNotFoundError(f"로컬 모델 경로를 찾을 수 없습니다: {MODEL_PATH}")
|
| 47 |
-
|
| 48 |
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True, local_files_only=True)
|
| 49 |
model = KananaVForConditionalGeneration.from_pretrained(
|
| 50 |
-
MODEL_PATH, torch_dtype=torch.
|
| 51 |
).to(DEVICE)
|
| 52 |
-
# 이미지 프로세서 로드 (로컬)
|
| 53 |
image_processor = AutoImageProcessor.from_pretrained(MODEL_PATH, trust_remote_code=True, local_files_only=True)
|
| 54 |
print("✅ 로컬 모델 및 이미지 프로세서 로딩 완료!")
|
| 55 |
-
|
| 56 |
-
else: # 서버 환경
|
| 57 |
if not HF_TOKEN:
|
| 58 |
raise ValueError("서버 환경에서는 Hugging Face 토큰(HF_TOKEN)이 반드시 필요합니다.")
|
| 59 |
-
|
| 60 |
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, token=HF_TOKEN, trust_remote_code=True)
|
| 61 |
model = KananaVForConditionalGeneration.from_pretrained(
|
| 62 |
-
MODEL_PATH, token=HF_TOKEN, torch_dtype=torch.float16, trust_remote_code=True,
|
| 63 |
-
)
|
| 64 |
-
# 이미지 프로세서 로드 (서버)
|
| 65 |
image_processor = AutoImageProcessor.from_pretrained(MODEL_PATH, token=HF_TOKEN, trust_remote_code=True)
|
| 66 |
print("✅ 서버 모델 및 이미지 프로세서 로딩 완료!")
|
| 67 |
-
|
| 68 |
MODEL_LOADED = True
|
| 69 |
-
|
| 70 |
except Exception as e:
|
| 71 |
print(f"❌ 모델 로딩 실패: {e}")
|
| 72 |
traceback.print_exc()
|
| 73 |
MODEL_LOADED = False
|
| 74 |
|
| 75 |
-
|
| 76 |
-
# --- 3. 파일 처리 및 응답 생성 로직 ---
|
| 77 |
-
|
| 78 |
def extract_text_from_pdf(pdf_file_path):
|
| 79 |
try:
|
| 80 |
doc = fitz.open(pdf_file_path)
|
|
@@ -86,140 +74,100 @@ def extract_text_from_pdf(pdf_file_path):
|
|
| 86 |
return f"PDF 파일을 읽는 중 오류가 발생했습니다: {e}"
|
| 87 |
|
| 88 |
def generate_response(prompt_template: str, message: str, files: Optional[List] = None):
|
| 89 |
-
if not MODEL_LOADED:
|
| 90 |
-
return "❌ 모델이 로드되지 않았습니다."
|
| 91 |
-
|
| 92 |
try:
|
| 93 |
-
all_pixel_values = []
|
| 94 |
-
all_image_metas = []
|
| 95 |
-
file_texts = []
|
| 96 |
-
|
| 97 |
-
# 1. 업로드된 파일들 처리 (이미지/PDF 분리)
|
| 98 |
if files:
|
| 99 |
for file in files:
|
| 100 |
-
file_path = file.name
|
| 101 |
-
file_extension =
|
| 102 |
-
|
| 103 |
-
if file_extension == '.pdf':
|
| 104 |
-
file_texts.append(extract_text_from_pdf(file_path))
|
| 105 |
elif file_extension in ['.png', '.jpg', '.jpeg']:
|
| 106 |
pil_image = Image.open(file_path).convert('RGB')
|
| 107 |
processed_data = image_processor(pil_image)
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
image_metas = processed_data["image_meta"]
|
| 111 |
-
|
| 112 |
-
all_pixel_values.append(pixel_values)
|
| 113 |
-
all_image_metas.append(image_metas)
|
| 114 |
-
|
| 115 |
-
# 2. 프롬프트 구성
|
| 116 |
image_tokens = "<image>" * len(all_pixel_values)
|
| 117 |
pdf_content = "\n\n".join(file_texts)
|
| 118 |
full_message = message + (f"\n{image_tokens}" if image_tokens else "") + (f"\n\n[첨부된 PDF 내용]:\n{pdf_content}" if pdf_content else "")
|
| 119 |
full_prompt = prompt_template.format(message=full_message)
|
| 120 |
-
|
| 121 |
-
# 3. 토크나이징 및 `image_metas` 결합
|
| 122 |
if all_image_metas:
|
| 123 |
-
|
| 124 |
-
combined_metas = {}
|
| 125 |
-
for key in all_image_metas[0].keys():
|
| 126 |
-
combined_metas[key] = [meta[key] for meta in all_image_metas]
|
| 127 |
-
|
| 128 |
-
# `encode_prompt`는 Kanana 모델의 토크나이저에 내장된 커스텀 함수로 가정
|
| 129 |
inputs = tokenizer.encode_prompt(prompt=full_prompt, image_meta=combined_metas)
|
| 130 |
-
|
| 131 |
-
# 값이 텐서인 경우에만 배치 차원을 추가하고 디바이스로 보냅니다.
|
| 132 |
-
inputs = {
|
| 133 |
-
k: (v.unsqueeze(0).to(model.device) if torch.is_tensor(v) else v)
|
| 134 |
-
for k, v in inputs.items()
|
| 135 |
-
}
|
| 136 |
else:
|
| 137 |
inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
|
| 138 |
-
|
| 139 |
-
# 4. 생성 파라미터 준비
|
| 140 |
generation_args = {
|
| 141 |
-
"max_new_tokens":
|
| 142 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
}
|
| 144 |
-
|
| 145 |
-
# 5. 모델 추론 (멀티모달 / 텍스트 전용 분기)
|
| 146 |
with torch.no_grad():
|
| 147 |
if all_pixel_values:
|
| 148 |
-
|
| 149 |
-
# pixel_values와 image_metas를 `generate` 함수에 직접 전달
|
| 150 |
-
outputs = model.generate(
|
| 151 |
-
**inputs,
|
| 152 |
-
pixel_values=all_pixel_values,
|
| 153 |
-
image_metas=combined_metas,
|
| 154 |
-
**generation_args
|
| 155 |
-
)
|
| 156 |
else:
|
| 157 |
-
print("📄 텍스트만으로 생성")
|
| 158 |
outputs = model.generate(**inputs, **generation_args)
|
| 159 |
-
|
| 160 |
-
# 6. 결과 디코딩
|
| 161 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 162 |
-
|
| 163 |
-
assistant_response = response.split("<|im_start|>assistant\n")[-1].strip()
|
| 164 |
-
|
| 165 |
-
return assistant_response
|
| 166 |
-
|
| 167 |
except Exception as e:
|
| 168 |
-
print(f"❌ 응답 생성 중 오류 발생: {e}")
|
| 169 |
-
traceback.print_exc()
|
| 170 |
-
return f"오류가 발생했습니다: {e}"
|
| 171 |
|
| 172 |
-
|
|
|
|
| 173 |
with gr.Blocks(title="Lily LLM System", theme=gr.themes.Soft()) as demo:
|
| 174 |
gr.Markdown("# 🧮 Lily LLM System")
|
| 175 |
gr.Markdown("이미지, PDF, 텍스트를 이해하고 답변하는 멀티모달 AI 시스템입니다.")
|
| 176 |
|
| 177 |
-
with gr.Tabs():
|
| 178 |
with gr.Tab("💬 채팅"):
|
| 179 |
chat_prompt = "<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
|
| 180 |
-
chatbot = gr.Chatbot(height=
|
| 181 |
-
|
| 182 |
-
with gr.Row():
|
| 183 |
-
file_input = gr.File(
|
| 184 |
-
label="파일 업로드 (다중 선택 가능)",
|
| 185 |
-
file_count="multiple", # 다중 파일 업로드 활성화
|
| 186 |
-
file_types=[".pdf", ".png", ".jpg", ".jpeg"]
|
| 187 |
-
)
|
| 188 |
with gr.Row():
|
| 189 |
-
msg = gr.Textbox(
|
| 190 |
-
|
| 191 |
-
placeholder="파일을 업로드하고 질문하거나, 텍스트로만 대화할 수 있습니다.",
|
| 192 |
-
lines=3,
|
| 193 |
-
show_label=False,
|
| 194 |
-
scale=7
|
| 195 |
-
)
|
| 196 |
send_btn = gr.Button("전송", variant="primary", scale=1)
|
| 197 |
|
|
|
|
| 198 |
def respond(message, chat_history, files):
|
| 199 |
if not message.strip() and not files:
|
| 200 |
-
|
| 201 |
-
return "", chat_history
|
| 202 |
|
| 203 |
bot_message = generate_response(chat_prompt, message, files)
|
| 204 |
|
| 205 |
-
# 'messages' 타입에 맞는 딕셔너리 형태로 대화 기록 추가
|
| 206 |
chat_history.append({"role": "user", "content": message})
|
| 207 |
chat_history.append({"role": "assistant", "content": bot_message})
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
with gr.Tab("⚙️ 시스템 정보"):
|
| 215 |
gr.Markdown(f"**실행 환경**: `{'로컬' if IS_LOCAL else '서버'}`")
|
| 216 |
gr.Markdown(f"**모델 경로**: `{MODEL_PATH}`")
|
| 217 |
gr.Markdown(f"**모델 상태**: `{'✅ 로드됨' if MODEL_LOADED else '❌ 로드 실패'}`")
|
| 218 |
|
| 219 |
-
if __name__ == "__main__":
|
| 220 |
if IS_LOCAL:
|
| 221 |
-
print("\n🚀 로컬 서버를 시작합니다. http://
|
| 222 |
-
demo.launch(server_name="
|
| 223 |
else:
|
| 224 |
print("\n🚀 서버를 시작합니다...")
|
| 225 |
demo.launch()
|
|
|
|
| 1 |
+
# 파일: app.py (최종 수정본)
|
| 2 |
+
|
| 3 |
import gradio as gr
|
| 4 |
import os
|
| 5 |
import traceback
|
| 6 |
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoImageProcessor
|
| 7 |
import torch
|
| 8 |
+
import fitz
|
| 9 |
from PIL import Image
|
| 10 |
from typing import Optional, List
|
| 11 |
|
| 12 |
+
# --- 1 & 2. 전역 변수, 환경 설정, 모델 로딩 (기존 코드와 동일) ---
|
| 13 |
+
# (이 부분은 수정할 필요 없이 그대로 두시면 됩니다)
|
| 14 |
+
# ... (생략) ...
|
| 15 |
+
# --- 1 & 2. 전역 변수, 환경 설정, 모델 로딩 (기존 코드와 동일) ---
|
| 16 |
tokenizer = None
|
| 17 |
model = None
|
| 18 |
+
image_processor = None
|
| 19 |
MODEL_LOADED = False
|
| 20 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
|
|
|
| 21 |
IS_LOCAL = os.path.exists('.env') or os.path.exists('../.env') or os.getenv('IS_LOCAL') == 'true'
|
|
|
|
| 22 |
try:
|
| 23 |
from dotenv import load_dotenv
|
| 24 |
if IS_LOCAL:
|
|
|
|
| 26 |
print("✅ .env 파일 로드됨")
|
| 27 |
except ImportError:
|
| 28 |
print("⚠️ python-dotenv가 설치되지 않음")
|
|
|
|
| 29 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 30 |
MODEL_NAME_SERVER = os.getenv("MODEL_NAME", "gbrabbit/lily-math-model")
|
| 31 |
MODEL_PATH_LOCAL = "../lily_llm_core/models/kanana_1_5_v_3b_instruct"
|
| 32 |
MODEL_PATH = MODEL_PATH_LOCAL if IS_LOCAL else MODEL_NAME_SERVER
|
|
|
|
| 33 |
print(f"============== 시스템 환경 정보 ==============")
|
| 34 |
print(f"🔍 실행 환경: {'로컬' if IS_LOCAL else '서버'}")
|
| 35 |
print(f"🔍 모델 경로: {MODEL_PATH}")
|
| 36 |
print(f"🔍 사용 디바이스: {DEVICE.upper()}")
|
| 37 |
print("==========================================")
|
|
|
|
|
|
|
|
|
|
| 38 |
try:
|
| 39 |
print("🔧 모델 로딩 시작...")
|
| 40 |
from modeling import KananaVForConditionalGeneration
|
|
|
|
| 41 |
if IS_LOCAL:
|
| 42 |
if not os.path.exists(MODEL_PATH):
|
| 43 |
raise FileNotFoundError(f"로컬 모델 경로를 찾을 수 없습니다: {MODEL_PATH}")
|
|
|
|
| 44 |
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True, local_files_only=True)
|
| 45 |
model = KananaVForConditionalGeneration.from_pretrained(
|
| 46 |
+
MODEL_PATH, torch_dtype=torch.bfloat16, trust_remote_code=True, local_files_only=True,
|
| 47 |
).to(DEVICE)
|
|
|
|
| 48 |
image_processor = AutoImageProcessor.from_pretrained(MODEL_PATH, trust_remote_code=True, local_files_only=True)
|
| 49 |
print("✅ 로컬 모델 및 이미지 프로세서 로딩 완료!")
|
| 50 |
+
else:
|
|
|
|
| 51 |
if not HF_TOKEN:
|
| 52 |
raise ValueError("서버 환경에서는 Hugging Face 토큰(HF_TOKEN)이 반드시 필요합니다.")
|
|
|
|
| 53 |
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, token=HF_TOKEN, trust_remote_code=True)
|
| 54 |
model = KananaVForConditionalGeneration.from_pretrained(
|
| 55 |
+
MODEL_PATH, token=HF_TOKEN, torch_dtype=torch.float16, trust_remote_code=True, device_map="auto"
|
| 56 |
+
)
|
|
|
|
| 57 |
image_processor = AutoImageProcessor.from_pretrained(MODEL_PATH, token=HF_TOKEN, trust_remote_code=True)
|
| 58 |
print("✅ 서버 모델 및 이미지 프로세서 로딩 완료!")
|
|
|
|
| 59 |
MODEL_LOADED = True
|
|
|
|
| 60 |
except Exception as e:
|
| 61 |
print(f"❌ 모델 로딩 실패: {e}")
|
| 62 |
traceback.print_exc()
|
| 63 |
MODEL_LOADED = False
|
| 64 |
|
| 65 |
+
# --- 3. 응답 생성 로직 (기존 코드와 동일) ---
|
|
|
|
|
|
|
| 66 |
def extract_text_from_pdf(pdf_file_path):
|
| 67 |
try:
|
| 68 |
doc = fitz.open(pdf_file_path)
|
|
|
|
| 74 |
return f"PDF 파일을 읽는 중 오류가 발생했습니다: {e}"
|
| 75 |
|
| 76 |
def generate_response(prompt_template: str, message: str, files: Optional[List] = None):
|
| 77 |
+
if not MODEL_LOADED: return "❌ 모델이 로드되지 않았습니다."
|
|
|
|
|
|
|
| 78 |
try:
|
| 79 |
+
all_pixel_values, all_image_metas, file_texts = [], [], []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
if files:
|
| 81 |
for file in files:
|
| 82 |
+
file_path, file_extension = file.name, os.path.splitext(file.name)[1].lower()
|
| 83 |
+
if file_extension == '.pdf': file_texts.append(extract_text_from_pdf(file_path))
|
|
|
|
|
|
|
|
|
|
| 84 |
elif file_extension in ['.png', '.jpg', '.jpeg']:
|
| 85 |
pil_image = Image.open(file_path).convert('RGB')
|
| 86 |
processed_data = image_processor(pil_image)
|
| 87 |
+
all_pixel_values.append(processed_data["pixel_values"])
|
| 88 |
+
all_image_metas.append(processed_data["image_meta"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
image_tokens = "<image>" * len(all_pixel_values)
|
| 90 |
pdf_content = "\n\n".join(file_texts)
|
| 91 |
full_message = message + (f"\n{image_tokens}" if image_tokens else "") + (f"\n\n[첨부된 PDF 내용]:\n{pdf_content}" if pdf_content else "")
|
| 92 |
full_prompt = prompt_template.format(message=full_message)
|
|
|
|
|
|
|
| 93 |
if all_image_metas:
|
| 94 |
+
combined_metas = {key: [meta[key] for meta in all_image_metas] for key in all_image_metas[0]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
inputs = tokenizer.encode_prompt(prompt=full_prompt, image_meta=combined_metas)
|
| 96 |
+
inputs = {k: (v.unsqueeze(0).to(model.device) if torch.is_tensor(v) else v) for k, v in inputs.items()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
else:
|
| 98 |
inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
|
|
|
|
|
|
|
| 99 |
generation_args = {
|
| 100 |
+
"max_new_tokens": 32,
|
| 101 |
+
"temperature": 0.8,
|
| 102 |
+
"do_sample": True,
|
| 103 |
+
"pad_token_id": tokenizer.eos_token_id,
|
| 104 |
+
"eos_token_id": tokenizer.eos_token_id,
|
| 105 |
+
"top_p": 0.95,
|
| 106 |
}
|
|
|
|
|
|
|
| 107 |
with torch.no_grad():
|
| 108 |
if all_pixel_values:
|
| 109 |
+
outputs = model.generate(**inputs, pixel_values=all_pixel_values, image_metas=combined_metas, **generation_args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
else:
|
|
|
|
| 111 |
outputs = model.generate(**inputs, **generation_args)
|
|
|
|
|
|
|
| 112 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 113 |
+
return response.split("<|im_start|>assistant\n")[-1].strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
except Exception as e:
|
| 115 |
+
print(f"❌ 응답 생성 중 오류 발생: {e}"); traceback.print_exc(); return f"오류가 발생했습니다: {e}"
|
|
|
|
|
|
|
| 116 |
|
| 117 |
+
|
| 118 |
+
# --- 4. Gradio UI 및 실행 (최종 수정) ---
|
| 119 |
with gr.Blocks(title="Lily LLM System", theme=gr.themes.Soft()) as demo:
|
| 120 |
gr.Markdown("# 🧮 Lily LLM System")
|
| 121 |
gr.Markdown("이미지, PDF, 텍스트를 이해하고 답변하는 멀티모달 AI 시스템입니다.")
|
| 122 |
|
| 123 |
+
with gr.Tabs():
|
| 124 |
with gr.Tab("💬 채팅"):
|
| 125 |
chat_prompt = "<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
|
| 126 |
+
chatbot = gr.Chatbot(height=320, label="대화창", elem_id="chatbot", type="messages")
|
| 127 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
with gr.Row():
|
| 129 |
+
msg = gr.Textbox(label="메시지 입력", placeholder="메시지를 입력하세요", lines=3, show_label=False, scale=4)
|
| 130 |
+
file_input = gr.File(label="파일 업로드", file_count="multiple", file_types=[".pdf", ".png", ".jpg", ".jpeg"], scale=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
send_btn = gr.Button("전송", variant="primary", scale=1)
|
| 132 |
|
| 133 |
+
# ✅ 1. respond 함수가 'files'를 세 번째 인자로 받도록 수정
|
| 134 |
def respond(message, chat_history, files):
|
| 135 |
if not message.strip() and not files:
|
| 136 |
+
return "", chat_history, None # files 출력도 비워줌
|
|
|
|
| 137 |
|
| 138 |
bot_message = generate_response(chat_prompt, message, files)
|
| 139 |
|
|
|
|
| 140 |
chat_history.append({"role": "user", "content": message})
|
| 141 |
chat_history.append({"role": "assistant", "content": bot_message})
|
| 142 |
|
| 143 |
+
# ✅ 2. 출력의 개수를 inputs와 맞추기 위해 file_input도 반환값에 추가
|
| 144 |
+
return "", chat_history, None
|
| 145 |
+
|
| 146 |
+
# ✅ 3. click과 submit의 inputs 리스트에 'file_input' 추가
|
| 147 |
+
send_btn.click(
|
| 148 |
+
respond,
|
| 149 |
+
inputs=[msg, chatbot, file_input],
|
| 150 |
+
outputs=[msg, chatbot, file_input], # 출력에도 file_input 추가
|
| 151 |
+
api_name="chat", # api_name은 슬래시 없이 사용
|
| 152 |
+
# queue=False
|
| 153 |
+
)
|
| 154 |
+
msg.submit(
|
| 155 |
+
respond,
|
| 156 |
+
inputs=[msg, chatbot, file_input],
|
| 157 |
+
outputs=[msg, chatbot, file_input], # 출력에도 file_input 추가
|
| 158 |
+
api_name="chat",
|
| 159 |
+
# queue=False
|
| 160 |
+
)
|
| 161 |
|
| 162 |
with gr.Tab("⚙️ 시스템 정보"):
|
| 163 |
gr.Markdown(f"**실행 환경**: `{'로컬' if IS_LOCAL else '서버'}`")
|
| 164 |
gr.Markdown(f"**모델 경로**: `{MODEL_PATH}`")
|
| 165 |
gr.Markdown(f"**모델 상태**: `{'✅ 로드됨' if MODEL_LOADED else '❌ 로드 실패'}`")
|
| 166 |
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
if IS_LOCAL:
|
| 169 |
+
print("\n🚀 로컬 서버를 시작합니다. http://127.0.0.1:8006")
|
| 170 |
+
demo.launch(server_name="127.0.0.1", server_port=8006, share=False)
|
| 171 |
else:
|
| 172 |
print("\n🚀 서버를 시작합니다...")
|
| 173 |
demo.launch()
|
test_input.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from gradio_client import Client, file
|
| 3 |
-
|
| 4 |
-
# --- 설정 ---
|
| 5 |
-
# 로컬 Gradio 서버 주소 (app.py 실행 시 터미널에 표시되는 주소)
|
| 6 |
-
SERVER_URL = "http://localhost:8006/"
|
| 7 |
-
|
| 8 |
-
def run_chat_test(client):
|
| 9 |
-
"""일반 채팅 탭의 기능을 테스트합니다."""
|
| 10 |
-
print("\n--- 💬 일반 채팅 테스트 시작 ---")
|
| 11 |
-
|
| 12 |
-
test_message = "안녕하세요! 오늘 날씨는 어떤가요?"
|
| 13 |
-
chat_history = [] # 초기 대화 내역은 비어있음
|
| 14 |
-
|
| 15 |
-
print(f"보내는 메시지: '{test_message}'")
|
| 16 |
-
|
| 17 |
-
# `respond` 함수 호출 (API 엔드포인트 인덱스: 0)
|
| 18 |
-
# 입력: (메시지, 채팅 내역, 파일)
|
| 19 |
-
# 출력: (비워진 텍스트 박스, 갱신된 채팅 내역)
|
| 20 |
-
result = client.predict(
|
| 21 |
-
test_message,
|
| 22 |
-
chat_history,
|
| 23 |
-
None, # 파일 없음
|
| 24 |
-
fn_index=0
|
| 25 |
-
)
|
| 26 |
-
|
| 27 |
-
# 갱신된 채팅 내역에서 마지막 응답(봇 메시지)을 추출
|
| 28 |
-
updated_history = result[1]
|
| 29 |
-
bot_response = updated_history[-1]['content']
|
| 30 |
-
|
| 31 |
-
print("✅ 테스트 성공!")
|
| 32 |
-
print(f"🤖 받은 응답: '{bot_response}'")
|
| 33 |
-
|
| 34 |
-
def run_math_test(client):
|
| 35 |
-
"""수학 문제 해결 탭의 기능을 테스트합니다."""
|
| 36 |
-
print("\n--- 🧮 수학 문제 해결 테스트 시작 ---")
|
| 37 |
-
|
| 38 |
-
test_problem = "두 개의 연속된 짝수의 합이 34일 때, 두 짝수는 무엇인가요?"
|
| 39 |
-
|
| 40 |
-
print(f"보내는 문제: '{test_problem}'")
|
| 41 |
-
|
| 42 |
-
# 수학 문제 해결 함수 호출 (API 엔드포인트 인덱스: 1)
|
| 43 |
-
# 입력: (수학 문제, 파일)
|
| 44 |
-
# 출력: (결과 텍스트)
|
| 45 |
-
result = client.predict(
|
| 46 |
-
test_problem,
|
| 47 |
-
None, # 파일 없음
|
| 48 |
-
fn_index=1
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
print("✅ 테스트 성공!")
|
| 52 |
-
print(f"🤖 받은 응답 (일부): '{result[:200]}...'")
|
| 53 |
-
|
| 54 |
-
def run_file_test(client):
|
| 55 |
-
"""파일 업로드 기능을 테스트합니다."""
|
| 56 |
-
print("\n--- 📁 파일 업로드 채팅 테스트 시작 ---")
|
| 57 |
-
|
| 58 |
-
# 테스트용 임시 텍스트 파일 생성
|
| 59 |
-
temp_file_path = "test_document.txt"
|
| 60 |
-
with open(temp_file_path, "w", encoding="utf-8") as f:
|
| 61 |
-
f.write("이 파일은 테스트를 위해 생성되었습니다.\n")
|
| 62 |
-
f.write("파일의 핵심 내용은 '대한민국의 수도는 서울이다' 입니다.")
|
| 63 |
-
|
| 64 |
-
print(f"업로드할 파일: '{temp_file_path}'")
|
| 65 |
-
test_message = "업로드한 파일의 핵심 내용이 뭐야?"
|
| 66 |
-
print(f"보내는 메시지: '{test_message}'")
|
| 67 |
-
|
| 68 |
-
# `file()` 함수를 사용하여 파일을 서버에 업로드 가능한 형태로 변환
|
| 69 |
-
result = client.predict(
|
| 70 |
-
test_message,
|
| 71 |
-
[], # 채팅 내역 없음
|
| 72 |
-
file(temp_file_path),
|
| 73 |
-
fn_index=0
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
# 임시 파일 삭제
|
| 77 |
-
os.remove(temp_file_path)
|
| 78 |
-
|
| 79 |
-
bot_response = result[1][-1]['content']
|
| 80 |
-
print("✅ 테스트 성공!")
|
| 81 |
-
print(f"🤖 받은 응답: '{bot_response}'")
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
if __name__ == "__main__":
|
| 85 |
-
print(f"Gradio 서버({SERVER_URL})에 연결을 시도합니다...")
|
| 86 |
-
|
| 87 |
-
try:
|
| 88 |
-
# 서버에 클라이언트로 연결
|
| 89 |
-
client = Client(SERVER_URL, verbose=False)
|
| 90 |
-
print("✅ 서버 연결 성공!")
|
| 91 |
-
|
| 92 |
-
# 테스트 실행
|
| 93 |
-
run_chat_test(client)
|
| 94 |
-
run_math_test(client)
|
| 95 |
-
# run_file_test(client) # 파일 테스트는 필요시 주석 해제하여 사용
|
| 96 |
-
|
| 97 |
-
except Exception as e:
|
| 98 |
-
print(f"\n❌ 테스트 실패: 서버에 연결할 수 없거나 오류가 발생했습니다.")
|
| 99 |
-
print("먼저 다른 터미널에서 'python app.py'를 실행했는지 확인해주세요.")
|
| 100 |
-
print(f"오류 상세 정보: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_text.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from gradio_client import Client, file
|
| 3 |
-
|
| 4 |
-
# --- 설정 ---
|
| 5 |
-
# 로컬 Gradio 서버 주소 (app.py 실행 시 터미널에 표시되는 주소)
|
| 6 |
-
SERVER_URL = "http://localhost:8006/"
|
| 7 |
-
|
| 8 |
-
def run_chat_test(client):
|
| 9 |
-
"""일반 채팅 탭의 기능을 테스트합니다."""
|
| 10 |
-
print("\n--- 💬 일반 채팅 테스트 시작 ---")
|
| 11 |
-
|
| 12 |
-
test_message = "안녕하세요! 오늘 날씨는 어떤가요?"
|
| 13 |
-
chat_history = [] # 초기 대화 내역은 비어있음
|
| 14 |
-
|
| 15 |
-
print(f"보내는 메시지: '{test_message}'")
|
| 16 |
-
|
| 17 |
-
# `respond` 함수 호출 (API 엔드포인트 인덱스: 0)
|
| 18 |
-
# 입력: (메시지, 채팅 내역, 파일)
|
| 19 |
-
# 출력: (비워진 텍스트 박스, 갱신된 채팅 내역)
|
| 20 |
-
result = client.predict(
|
| 21 |
-
test_message,
|
| 22 |
-
chat_history,
|
| 23 |
-
None, # 파일 없음
|
| 24 |
-
fn_index=0
|
| 25 |
-
)
|
| 26 |
-
|
| 27 |
-
# 갱신된 채팅 내역에서 마지막 응답(봇 메시지)을 추출
|
| 28 |
-
updated_history = result[1]
|
| 29 |
-
bot_response = updated_history[-1]['content']
|
| 30 |
-
|
| 31 |
-
print("✅ 테스트 성공!")
|
| 32 |
-
print(f"🤖 받은 응답: '{bot_response}'")
|
| 33 |
-
|
| 34 |
-
def run_math_test(client):
|
| 35 |
-
"""수학 문제 해결 탭의 기능을 테스트합니다."""
|
| 36 |
-
print("\n--- 🧮 수학 문제 해결 테스트 시작 ---")
|
| 37 |
-
|
| 38 |
-
test_problem = "두 개의 연속된 짝수의 합이 34일 때, 두 짝수는 무엇인가요?"
|
| 39 |
-
|
| 40 |
-
print(f"보내는 문제: '{test_problem}'")
|
| 41 |
-
|
| 42 |
-
# 수학 문제 해결 함수 호출 (API 엔드포인트 인덱스: 1)
|
| 43 |
-
# 입력: (수학 문제, 파일)
|
| 44 |
-
# 출력: (결과 텍스트)
|
| 45 |
-
result = client.predict(
|
| 46 |
-
test_problem,
|
| 47 |
-
None, # 파일 없음
|
| 48 |
-
fn_index=1
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
print("✅ 테스트 성공!")
|
| 52 |
-
print(f"🤖 받은 응답 (일부): '{result[:200]}...'")
|
| 53 |
-
|
| 54 |
-
def run_file_test(client):
|
| 55 |
-
"""파일 업로드 기능을 테스트합니다."""
|
| 56 |
-
print("\n--- 📁 파일 업로드 채팅 테스트 시작 ---")
|
| 57 |
-
|
| 58 |
-
# 테스트용 임시 텍스트 파일 생성
|
| 59 |
-
temp_file_path = "test_document.txt"
|
| 60 |
-
with open(temp_file_path, "w", encoding="utf-8") as f:
|
| 61 |
-
f.write("이 파일은 테스트를 위해 생성되었습니다.\n")
|
| 62 |
-
f.write("파일의 핵심 내용은 '대한민국의 수도는 서울이다' 입니다.")
|
| 63 |
-
|
| 64 |
-
print(f"업로드할 파일: '{temp_file_path}'")
|
| 65 |
-
test_message = "업로드한 파일의 핵심 내용이 뭐야?"
|
| 66 |
-
print(f"보내는 메시지: '{test_message}'")
|
| 67 |
-
|
| 68 |
-
# `file()` 함수를 사용하여 파일을 서버에 업로드 가능한 형태로 변환
|
| 69 |
-
result = client.predict(
|
| 70 |
-
test_message,
|
| 71 |
-
[], # 채팅 내역 없음
|
| 72 |
-
file(temp_file_path),
|
| 73 |
-
fn_index=0
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
# 임시 파일 삭제
|
| 77 |
-
os.remove(temp_file_path)
|
| 78 |
-
|
| 79 |
-
bot_response = result[1][-1]['content']
|
| 80 |
-
print("✅ 테스트 성공!")
|
| 81 |
-
print(f"🤖 받은 응답: '{bot_response}'")
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
if __name__ == "__main__":
|
| 85 |
-
print(f"Gradio 서버({SERVER_URL})에 연결을 시도합니다...")
|
| 86 |
-
|
| 87 |
-
try:
|
| 88 |
-
# 서버에 클라이언트로 연결
|
| 89 |
-
client = Client(SERVER_URL, verbose=False)
|
| 90 |
-
print("✅ 서버 연결 성공!")
|
| 91 |
-
|
| 92 |
-
# 테스트 실행
|
| 93 |
-
run_chat_test(client)
|
| 94 |
-
run_math_test(client)
|
| 95 |
-
# run_file_test(client) # 파일 테스트는 필요시 주석 해제하여 사용
|
| 96 |
-
|
| 97 |
-
except Exception as e:
|
| 98 |
-
print(f"\n❌ 테스트 실패: 서버에 연결할 수 없거나 오류가 발생했습니다.")
|
| 99 |
-
print("먼저 다른 터미널에서 'python app.py'를 실행했는지 확인해주세요.")
|
| 100 |
-
print(f"오류 상세 정보: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_tokenizer.py
DELETED
|
@@ -1,159 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import traceback
|
| 3 |
-
from typing import Optional
|
| 4 |
-
from transformers import AutoTokenizer
|
| 5 |
-
import torch
|
| 6 |
-
|
| 7 |
-
# 환경 변수 로드
|
| 8 |
-
try:
|
| 9 |
-
from dotenv import load_dotenv
|
| 10 |
-
load_dotenv()
|
| 11 |
-
print("✅ .env 파일 로드됨")
|
| 12 |
-
except ImportError:
|
| 13 |
-
print("⚠️ python-dotenv가 설치되지 않음")
|
| 14 |
-
|
| 15 |
-
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 16 |
-
|
| 17 |
-
# 환경 감지
|
| 18 |
-
IS_LOCAL = os.path.exists('../.env') or 'LOCAL_TEST' in os.environ
|
| 19 |
-
print(f"🔍 환경: {'로컬' if IS_LOCAL else '서버'}")
|
| 20 |
-
|
| 21 |
-
# 환경에 따른 모델 경로 설정
|
| 22 |
-
if IS_LOCAL:
|
| 23 |
-
# 로컬 모델 경로 (hearth_llm_model 폴더 사용)
|
| 24 |
-
MODEL_PATH = "../lily_llm_core/models/kanana_1_5_v_3b_instruct"
|
| 25 |
-
print(f"🔍 로컬 모델 경로: {MODEL_PATH}")
|
| 26 |
-
print(f"🔍 경로 존재: {os.path.exists(MODEL_PATH)}")
|
| 27 |
-
else:
|
| 28 |
-
# 서버에서는 Hugging Face 모델 사용
|
| 29 |
-
MODEL_PATH = os.getenv("MODEL_NAME", "gbrabbit/lily-math-model")
|
| 30 |
-
print(f"🔍 서버 모델: {MODEL_PATH}")
|
| 31 |
-
|
| 32 |
-
print(f"🔍 토큰: {'✅ 설정됨' if HF_TOKEN else '❌ 설정되지 않음'}")
|
| 33 |
-
|
| 34 |
-
# 토크나이저 테스트
|
| 35 |
-
print("\n🔧 토크나이저 테스트 시작...")
|
| 36 |
-
|
| 37 |
-
try:
|
| 38 |
-
print("📤 토크나이저 로딩 중...")
|
| 39 |
-
print(f" MODEL_PATH: {MODEL_PATH}")
|
| 40 |
-
print(f" IS_LOCAL: {IS_LOCAL}")
|
| 41 |
-
print(f" trust_remote_code: True")
|
| 42 |
-
print(f" use_fast: False")
|
| 43 |
-
|
| 44 |
-
if IS_LOCAL:
|
| 45 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
| 46 |
-
MODEL_PATH,
|
| 47 |
-
trust_remote_code=True,
|
| 48 |
-
)
|
| 49 |
-
else:
|
| 50 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
| 51 |
-
MODEL_PATH,
|
| 52 |
-
token=HF_TOKEN,
|
| 53 |
-
trust_remote_code=True,
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
print(f"✅ 토크나이저 로딩 완료")
|
| 57 |
-
print(f" 타입: {type(tokenizer)}")
|
| 58 |
-
print(f" 값: {tokenizer}")
|
| 59 |
-
print(f" hasattr('encode'): {hasattr(tokenizer, 'encode')}")
|
| 60 |
-
print(f" hasattr('__call__'): {hasattr(tokenizer, '__call__')}")
|
| 61 |
-
|
| 62 |
-
# 토크나이저 테스트
|
| 63 |
-
test_input = "안녕하세요"
|
| 64 |
-
print(f"\n🔤 토크나이저 테스트: '{test_input}'")
|
| 65 |
-
|
| 66 |
-
test_tokens = tokenizer(test_input, return_tensors="pt")
|
| 67 |
-
print(f" ✅ 토크나이저 호출 성공")
|
| 68 |
-
print(f" input_ids shape: {test_tokens['input_ids'].shape}")
|
| 69 |
-
print(f" attention_mask shape: {test_tokens['attention_mask'].shape}")
|
| 70 |
-
|
| 71 |
-
# 디코딩 테스트
|
| 72 |
-
decoded = tokenizer.decode(test_tokens['input_ids'][0], skip_special_tokens=True)
|
| 73 |
-
print(f" 디코딩 결과: '{decoded}'")
|
| 74 |
-
|
| 75 |
-
except Exception as e:
|
| 76 |
-
print(f"❌ 토크나이저 테스트 실패: {e}")
|
| 77 |
-
print(f" 오류 타입: {type(e).__name__}")
|
| 78 |
-
traceback.print_exc()
|
| 79 |
-
|
| 80 |
-
# 모델 테스트
|
| 81 |
-
print("\n🔧 모델 테스트 시작...")
|
| 82 |
-
|
| 83 |
-
try:
|
| 84 |
-
print("📤 모델 로딩 중...")
|
| 85 |
-
from modeling import KananaVForConditionalGeneration
|
| 86 |
-
|
| 87 |
-
if IS_LOCAL:
|
| 88 |
-
model = KananaVForConditionalGeneration.from_pretrained(
|
| 89 |
-
MODEL_PATH,
|
| 90 |
-
torch_dtype=torch.float16,
|
| 91 |
-
trust_remote_code=True,
|
| 92 |
-
device_map=None,
|
| 93 |
-
low_cpu_mem_usage=True
|
| 94 |
-
)
|
| 95 |
-
else:
|
| 96 |
-
model = KananaVForConditionalGeneration.from_pretrained(
|
| 97 |
-
MODEL_PATH,
|
| 98 |
-
token=HF_TOKEN,
|
| 99 |
-
torch_dtype=torch.float16,
|
| 100 |
-
trust_remote_code=True,
|
| 101 |
-
device_map=None,
|
| 102 |
-
low_cpu_mem_usage=True
|
| 103 |
-
)
|
| 104 |
-
|
| 105 |
-
print(f"✅ 모델 로딩 완료")
|
| 106 |
-
# print(f" 타입: {type(model)}")
|
| 107 |
-
# print(f" 디바이스: {next(model.parameters()).device}")
|
| 108 |
-
|
| 109 |
-
# 모델 테스트
|
| 110 |
-
test_input = "안녕하세요"
|
| 111 |
-
formatted_prompt = f"<|im_start|>user\n{test_input}<|im_end|>\n<|im_start|>assistant\n"
|
| 112 |
-
max_length: Optional[int] = None
|
| 113 |
-
|
| 114 |
-
inputs = tokenizer(
|
| 115 |
-
formatted_prompt,
|
| 116 |
-
return_tensors="pt",
|
| 117 |
-
padding=True,
|
| 118 |
-
truncation=True,
|
| 119 |
-
max_length=512
|
| 120 |
-
)
|
| 121 |
-
|
| 122 |
-
print(f"\n🤖 모델 추론 테스트: '{test_input}'")
|
| 123 |
-
|
| 124 |
-
# Kanana용 생성 설정
|
| 125 |
-
max_new_tokens = max_length or 100
|
| 126 |
-
|
| 127 |
-
with torch.no_grad():
|
| 128 |
-
outputs = model.generate(
|
| 129 |
-
input_ids=inputs["input_ids"],
|
| 130 |
-
attention_mask=inputs["attention_mask"],
|
| 131 |
-
max_new_tokens=max_new_tokens,
|
| 132 |
-
repetition_penalty=1.1,
|
| 133 |
-
no_repeat_ngram_size=2,
|
| 134 |
-
pad_token_id=tokenizer.eos_token_id,
|
| 135 |
-
eos_token_id=tokenizer.eos_token_id,
|
| 136 |
-
use_cache=True
|
| 137 |
-
)
|
| 138 |
-
|
| 139 |
-
print(f" ✅ 모델 호출 성공")
|
| 140 |
-
print(f" outputs 타입: {type(outputs)}")
|
| 141 |
-
print(f" outputs shape: {outputs.shape}")
|
| 142 |
-
|
| 143 |
-
# 디코딩 테스트
|
| 144 |
-
# model.generate()의 출력은 전체 시퀀스이므로 바로 디코딩합니다.
|
| 145 |
-
# outputs[0]은 배치 중 첫 번째 결과를 의미합니다.
|
| 146 |
-
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 147 |
-
|
| 148 |
-
# 입력 프롬프트를 응답에서 제거 (선택사항)
|
| 149 |
-
assistant_response = response.split("<|im_start|>assistant\n")[-1]
|
| 150 |
-
|
| 151 |
-
print(f" 생성된 전체 텍스트: '{response}'")
|
| 152 |
-
print(f" 어시스턴트 응답: '{assistant_response.strip()}'")
|
| 153 |
-
|
| 154 |
-
except Exception as e:
|
| 155 |
-
print(f"❌ 모델 테스트 실패: {e}")
|
| 156 |
-
print(f" 오류 타입: {type(e).__name__}")
|
| 157 |
-
traceback.print_exc()
|
| 158 |
-
|
| 159 |
-
print("\n✅ 테스트 완료!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|