gbrabbit commited on
Commit
e3f9de3
·
1 Parent(s): 4056037

Auto commit at 08-2025-08 3:40:22

Browse files
Files changed (4) hide show
  1. app.py +60 -112
  2. test_input.py +0 -100
  3. test_text.py +0 -100
  4. 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 # PyMuPDF
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.float16, trust_remote_code=True, local_files_only=True,
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
- ).to(DEVICE)
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 = os.path.splitext(file_path)[1].lower()
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
- pixel_values = processed_data["pixel_values"]
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": 256, "temperature": 0.7, "do_sample": True,
142
- "pad_token_id": tokenizer.eos_token_id, "eos_token_id": tokenizer.eos_token_id
 
 
 
 
143
  }
144
-
145
- # 5. 모델 추론 (멀티모달 / 텍스트 전용 분기)
146
  with torch.no_grad():
147
  if all_pixel_values:
148
- print(f"🖼️ 이미지 {len(all_pixel_values)}개 포함, 멀티모달 모드로 생성")
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
- # --- 4. Gradio UI 및 실행 ---
 
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=500, label="대화창", elem_id="chatbot", type="messages")
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
- label="메시지 입력",
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
- return "", chat_history
210
-
211
- send_btn.click(respond, inputs=[msg, chatbot, file_input], outputs=[msg, chatbot])
212
- msg.submit(respond, inputs=[msg, chatbot, file_input], outputs=[msg, chatbot])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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://localhost:8006")
222
- demo.launch(server_name="localhost", server_port=8006, share=False)
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✅ 테스트 완료!")