seawolf2357 commited on
Commit
7a2b5d0
ยท
verified ยท
1 Parent(s): 1c72d37

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -87
app.py CHANGED
@@ -13,16 +13,13 @@ import torch
13
  from loguru import logger
14
  from PIL import Image
15
  from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
16
-
17
- # CSV/TXT ๋ถ„์„
18
  import pandas as pd
19
- # PDF ํ…์ŠคํŠธ ์ถ”์ถœ์šฉ
20
  import PyPDF2
21
 
22
  ##################################################
23
- # ์ƒ์ˆ˜ ๋ฐ ๋ชจ๋ธ ๋กœ๋”ฉ
24
  ##################################################
25
- MAX_CONTENT_CHARS = 8000 # ํ…์ŠคํŠธ๋กœ ์ „๋‹ฌ ์‹œ ์ตœ๋Œ€ 8000์ž๊นŒ์ง€๋งŒ
26
  model_id = os.getenv("MODEL_ID", "google/gemma-3-27b-it")
27
 
28
  processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
@@ -32,18 +29,17 @@ model = Gemma3ForConditionalGeneration.from_pretrained(
32
  torch_dtype=torch.bfloat16,
33
  attn_implementation="eager"
34
  )
35
-
36
  MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
37
 
38
-
39
  ##################################################
40
- # 1) CSV, TXT, PDF ๋ถ„์„ ํ•จ์ˆ˜
41
  ##################################################
42
  def analyze_csv_file(path: str) -> str:
43
- """CSV ํŒŒ์ผ -> ๋ฌธ์ž์—ด. ๊ธธ๋ฉด ์ž˜๋ผ๋ƒ„."""
44
  try:
45
  df = pd.read_csv(path)
46
- df_str = df.to_string()
 
 
47
  if len(df_str) > MAX_CONTENT_CHARS:
48
  df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
49
  return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
@@ -52,10 +48,11 @@ def analyze_csv_file(path: str) -> str:
52
 
53
 
54
  def analyze_txt_file(path: str) -> str:
55
- """TXT ํŒŒ์ผ -> ์ „์ฒด ๋ฌธ์ž์—ด. ๊ธธ๋ฉด ์ž˜๋ผ๋ƒ„."""
56
  try:
57
  with open(path, "r", encoding="utf-8") as f:
58
- text = f.read()
 
 
59
  if len(text) > MAX_CONTENT_CHARS:
60
  text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
61
  return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
@@ -64,28 +61,26 @@ def analyze_txt_file(path: str) -> str:
64
 
65
 
66
  def pdf_to_markdown(pdf_path: str) -> str:
67
- """PDF -> ํ…์ŠคํŠธ ์ถ”์ถœ -> Markdown. ๊ธธ๋ฉด ์ž˜๋ผ๋ƒ„."""
68
  try:
69
- text_chunks = []
70
  with open(pdf_path, "rb") as f:
71
  reader = PyPDF2.PdfReader(f)
 
72
  for page_num, page in enumerate(reader.pages, start=1):
73
- page_text = page.extract_text() or ""
74
- page_text = page_text.strip()
75
- if page_text:
76
- text_chunks.append(f"## Page {page_num}\n\n{page_text}\n")
 
 
 
 
 
77
  except Exception as e:
78
  return f"Failed to read PDF ({os.path.basename(pdf_path)}): {str(e)}"
79
 
80
- full_text = "\n".join(text_chunks)
81
- if len(full_text) > MAX_CONTENT_CHARS:
82
- full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
83
-
84
- return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
85
-
86
 
87
  ##################################################
88
- # 2) ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์ œํ•œ ๊ฒ€์‚ฌ (CSV, PDF, TXT ์ œ์™ธ)
89
  ##################################################
90
  def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
91
  image_count = 0
@@ -102,9 +97,9 @@ def count_files_in_history(history: list[dict]) -> tuple[int, int]:
102
  image_count = 0
103
  video_count = 0
104
  for item in history:
 
105
  if item["role"] != "user" or isinstance(item["content"], str):
106
  continue
107
- # item["content"]๊ฐ€ ["๊ฒฝ๋กœ"] ํ˜•ํƒœ์ผ ๋•Œ, ํ™•์žฅ์ž๋ฅผ ํ™•์ธ
108
  file_path = item["content"][0]
109
  if file_path.endswith(".mp4"):
110
  video_count += 1
@@ -115,11 +110,10 @@ def count_files_in_history(history: list[dict]) -> tuple[int, int]:
115
 
116
  def validate_media_constraints(message: dict, history: list[dict]) -> bool:
117
  """
118
- ์ด๋ฏธ์ง€ & ๋น„๋””์˜ค ์ œํ•œ
119
  """
120
  media_files = []
121
  for f in message["files"]:
122
- # ์ด๋ฏธ์ง€/๋น„๋””์˜ค๋งŒ
123
  if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4"):
124
  media_files.append(f)
125
 
@@ -132,7 +126,7 @@ def validate_media_constraints(message: dict, history: list[dict]) -> bool:
132
  if video_count > 1:
133
  gr.Warning("Only one video is supported.")
134
  return False
135
- # ๋น„๋””์˜ค + ์ด๋ฏธ์ง€ ํ˜ผํ•ฉ ๋ถˆ๊ฐ€
136
  if video_count == 1:
137
  if image_count > 0:
138
  gr.Warning("Mixing images and videos is not allowed.")
@@ -144,7 +138,7 @@ def validate_media_constraints(message: dict, history: list[dict]) -> bool:
144
  if video_count == 0 and image_count > MAX_NUM_IMAGES:
145
  gr.Warning(f"You can upload up to {MAX_NUM_IMAGES} images.")
146
  return False
147
- # <image> ํƒœ๊ทธ์™€ ์‹ค์ œ ์ด๋ฏธ์ง€ ์ˆ˜ ์ผ์น˜?
148
  if "<image>" in message["text"] and message["text"].count("<image>") != new_image_count:
149
  gr.Warning("The number of <image> tags in the text does not match the number of images.")
150
  return False
@@ -182,7 +176,6 @@ def process_video(video_path: str) -> list[dict]:
182
  pil_image.save(temp_file.name)
183
  content.append({"type": "text", "text": f"Frame {timestamp}:"})
184
  content.append({"type": "image", "url": temp_file.name})
185
- logger.debug(f"{content=}")
186
  return content
187
 
188
 
@@ -206,46 +199,51 @@ def process_interleaved_images(message: dict) -> list[dict]:
206
 
207
 
208
  ##################################################
209
- # 5) CSV/PDF/TXT๋Š” ํ…์ŠคํŠธ ๋ณ€ํ™˜๋งŒ, ์ด๋ฏธ์ง€/๋น„๋””์˜ค๋Š” ๊ฒฝ๋กœ๋กœ
210
  ##################################################
211
  def process_new_user_message(message: dict) -> list[dict]:
 
212
  if not message["files"]:
213
- return [{"type": "text", "text": message["text"]}]
214
 
215
- # ํ™•์žฅ์ž๋ณ„ ๋ถ„๋ฅ˜
216
  video_files = [f for f in message["files"] if f.endswith(".mp4")]
217
  image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
218
  csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
219
  txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
220
  pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
221
 
222
- # user ํ…์ŠคํŠธ ์ถ”๊ฐ€
223
- content_list = [{"type": "text", "text": message["text"]}]
224
 
225
  # CSV
226
  for csv_path in csv_files:
227
  csv_analysis = analyze_csv_file(csv_path)
 
 
228
  content_list.append({"type": "text", "text": csv_analysis})
229
 
230
  # TXT
231
  for txt_path in txt_files:
232
  txt_analysis = analyze_txt_file(txt_path)
 
 
233
  content_list.append({"type": "text", "text": txt_analysis})
234
 
235
  # PDF
236
  for pdf_path in pdf_files:
237
- pdf_markdown = pdf_to_markdown(pdf_path)
238
- content_list.append({"type": "text", "text": pdf_markdown})
 
 
239
 
240
- # ๋น„๋””์˜ค
241
  if video_files:
 
242
  content_list += process_video(video_files[0])
243
  return content_list
244
 
245
- # ์ด๋ฏธ์ง€
246
- if "<image>" in message["text"]:
247
  return process_interleaved_images(message)
248
  else:
 
249
  for img_path in image_files:
250
  content_list.append({"type": "image", "url": img_path})
251
 
@@ -253,13 +251,9 @@ def process_new_user_message(message: dict) -> list[dict]:
253
 
254
 
255
  ##################################################
256
- # 6) ํžˆ์Šคํ† ๋ฆฌ -> LLM ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜
257
  ##################################################
258
  def process_history(history: list[dict]) -> list[dict]:
259
- """
260
- ์—ฌ๊ธฐ์„œ, ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์™ธ์˜ ํŒŒ์ผ(.csv, .pdf, .txt) ๊ฒฝ๋กœ๋Š”
261
- ๋ชจ๋ธ๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š๋„๋ก ์ œ๊ฑฐ (or ๋ฌด์‹œ)
262
- """
263
  messages = []
264
  current_user_content = []
265
  for item in history:
@@ -267,71 +261,84 @@ def process_history(history: list[dict]) -> list[dict]:
267
  if current_user_content:
268
  messages.append({"role": "user", "content": current_user_content})
269
  current_user_content = []
270
- # assistant -> ๊ทธ๋ƒฅ ํ…์ŠคํŠธ๋กœ
271
  messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
272
  else:
273
  # user
274
  content = item["content"]
275
  if isinstance(content, str):
276
- # ๋‹จ์ˆœ ํ…์ŠคํŠธ
277
  current_user_content.append({"type": "text", "text": content})
278
  else:
279
- # ๋ณดํ†ต [ํŒŒ์ผ๊ฒฝ๋กœ] ํ˜•ํƒœ
280
- file_path = content[0]
281
- # ๋งŒ์•ฝ ์ด๋ฏธ์ง€๋‚˜ mp4๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด -> ๋ฌด์‹œ
282
- if re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE) or file_path.endswith(".mp4"):
283
- current_user_content.append({"type": "image", "url": file_path})
284
  else:
285
- # csv, pdf, txt ๋“ฑ์€ ์ œ๊ฑฐ
286
  pass
287
  return messages
288
 
289
 
290
  ##################################################
291
- # 7) ๋ฉ”์ธ ์ถ”๋ก 
292
  ##################################################
293
  @spaces.GPU(duration=120)
294
  def run(message: dict, history: list[dict], system_prompt: str = "", max_new_tokens: int = 512) -> Iterator[str]:
295
- # a) ๋ฏธ๋””์–ด ์ œํ•œ ๊ฒ€์‚ฌ
296
  if not validate_media_constraints(message, history):
297
  yield ""
298
  return
299
 
300
- # b) ๊ธฐ์กด ํžˆ์Šคํ† ๋ฆฌ -> LLM ๋ฉ”์‹œ์ง€
301
  messages = []
302
  if system_prompt:
303
  messages.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]})
304
  messages.extend(process_history(history))
305
- messages.append({"role": "user", "content": process_new_user_message(message)})
306
 
307
- # c) ๋ชจ๋ธ ํ˜ธ์ถœ
308
- inputs = processor.apply_chat_template(
 
 
 
309
  messages,
310
- add_generation_prompt=True,
311
- tokenize=True,
312
- return_dict=True,
 
 
 
 
 
 
 
 
 
313
  return_tensors="pt",
314
- ).to(device=model.device, dtype=torch.bfloat16)
 
 
315
 
316
- streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
 
317
  gen_kwargs = {
318
- "inputs": inputs,
 
319
  "streamer": streamer,
320
  "max_new_tokens": max_new_tokens,
 
 
 
321
  }
 
 
322
  t = Thread(target=model.generate, kwargs=gen_kwargs)
323
  t.start()
324
 
325
  output = ""
326
- for new_text in streamer:
327
- output += new_text
328
  yield output
329
 
330
 
331
-
332
-
333
  ##################################################
334
- # ์˜ˆ์‹œ๋“ค (ํ•œ๊ธ€ํ™” ๋ฒ„์ „)
335
  ##################################################
336
  examples = [
337
 
@@ -463,17 +470,10 @@ examples = [
463
  ]
464
 
465
 
466
-
467
-
468
-
469
- ##################################################
470
- # 9) Gradio ChatInterface
471
- ##################################################
472
  demo = gr.ChatInterface(
473
  fn=run,
474
  type="messages",
475
  chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
476
- # ์ด๋ฏธ์ง€(์—ฌ๋Ÿฌ ํ™•์žฅ์ž), mp4, csv, txt, pdf ํ—ˆ์šฉ
477
  textbox=gr.MultimodalTextbox(
478
  file_types=[
479
  ".png", ".jpg", ".jpeg", ".gif", ".webp",
@@ -488,13 +488,7 @@ demo = gr.ChatInterface(
488
  label="System Prompt",
489
  value="You are a deeply thoughtful AI. Consider problems thoroughly and derive correct solutions through systematic reasoning. Please answer in korean."
490
  ),
491
- gr.Slider(
492
- label="Max New Tokens",
493
- minimum=100,
494
- maximum=8000,
495
- step=50,
496
- value=2000
497
- ),
498
  ],
499
  stop_btn=False,
500
  title="Gemma 3 27B IT",
@@ -505,7 +499,5 @@ demo = gr.ChatInterface(
505
  delete_cache=(1800, 1800),
506
  )
507
 
508
-
509
  if __name__ == "__main__":
510
  demo.launch()
511
-
 
13
  from loguru import logger
14
  from PIL import Image
15
  from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
 
 
16
  import pandas as pd
 
17
  import PyPDF2
18
 
19
  ##################################################
20
+ # ๊ธฐ๋ณธ ์„ค์ •
21
  ##################################################
22
+ MAX_CONTENT_CHARS = 8000 # ํ…์ŠคํŠธ๋กœ ์ „๋‹ฌ ์‹œ ์ตœ๋Œ€ ๊ธ€์ž ์ˆ˜
23
  model_id = os.getenv("MODEL_ID", "google/gemma-3-27b-it")
24
 
25
  processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
 
29
  torch_dtype=torch.bfloat16,
30
  attn_implementation="eager"
31
  )
 
32
  MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
33
 
 
34
  ##################################################
35
+ # 1) CSV, TXT, PDF ๋ถ„์„ ํ•จ์ˆ˜ (๋นˆ ํŒŒ์ผ ๋Œ€๋น„)
36
  ##################################################
37
  def analyze_csv_file(path: str) -> str:
 
38
  try:
39
  df = pd.read_csv(path)
40
+ df_str = df.to_string().strip()
41
+ if not df_str:
42
+ df_str = "(CSV is empty)"
43
  if len(df_str) > MAX_CONTENT_CHARS:
44
  df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
45
  return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
 
48
 
49
 
50
  def analyze_txt_file(path: str) -> str:
 
51
  try:
52
  with open(path, "r", encoding="utf-8") as f:
53
+ text = f.read().strip()
54
+ if not text:
55
+ text = "(TXT is empty)"
56
  if len(text) > MAX_CONTENT_CHARS:
57
  text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
58
  return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
 
61
 
62
 
63
  def pdf_to_markdown(pdf_path: str) -> str:
 
64
  try:
 
65
  with open(pdf_path, "rb") as f:
66
  reader = PyPDF2.PdfReader(f)
67
+ chunks = []
68
  for page_num, page in enumerate(reader.pages, start=1):
69
+ ptext = (page.extract_text() or "").strip()
70
+ if ptext:
71
+ chunks.append(f"## Page {page_num}\n\n{ptext}\n")
72
+ full_text = "\n".join(chunks).strip()
73
+ if not full_text:
74
+ full_text = "(PDF is empty)"
75
+ if len(full_text) > MAX_CONTENT_CHARS:
76
+ full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
77
+ return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
78
  except Exception as e:
79
  return f"Failed to read PDF ({os.path.basename(pdf_path)}): {str(e)}"
80
 
 
 
 
 
 
 
81
 
82
  ##################################################
83
+ # 2) ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์—…๋กœ๋“œ ์ œํ•œ
84
  ##################################################
85
  def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
86
  image_count = 0
 
97
  image_count = 0
98
  video_count = 0
99
  for item in history:
100
+ # assistant ๋˜๋Š” content๊ฐ€ str์ด๋ฉด ์ œ์™ธ
101
  if item["role"] != "user" or isinstance(item["content"], str):
102
  continue
 
103
  file_path = item["content"][0]
104
  if file_path.endswith(".mp4"):
105
  video_count += 1
 
110
 
111
  def validate_media_constraints(message: dict, history: list[dict]) -> bool:
112
  """
113
+ ์ด๋ฏธ์ง€/๋น„๋””์˜ค ๊ฐœ์ˆ˜ ์ œํ•œ
114
  """
115
  media_files = []
116
  for f in message["files"]:
 
117
  if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4"):
118
  media_files.append(f)
119
 
 
126
  if video_count > 1:
127
  gr.Warning("Only one video is supported.")
128
  return False
129
+ # ๋น„๋””์˜ค+์ด๋ฏธ์ง€ ํ˜ผํ•ฉ ๋ถˆ๊ฐ€
130
  if video_count == 1:
131
  if image_count > 0:
132
  gr.Warning("Mixing images and videos is not allowed.")
 
138
  if video_count == 0 and image_count > MAX_NUM_IMAGES:
139
  gr.Warning(f"You can upload up to {MAX_NUM_IMAGES} images.")
140
  return False
141
+ # <image> ํƒœ๊ทธ ์ˆ˜์™€ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ˆ˜ ์ผ์น˜
142
  if "<image>" in message["text"] and message["text"].count("<image>") != new_image_count:
143
  gr.Warning("The number of <image> tags in the text does not match the number of images.")
144
  return False
 
176
  pil_image.save(temp_file.name)
177
  content.append({"type": "text", "text": f"Frame {timestamp}:"})
178
  content.append({"type": "image", "url": temp_file.name})
 
179
  return content
180
 
181
 
 
199
 
200
 
201
  ##################################################
202
+ # 5) CSV/PDF/TXT = ํ…์ŠคํŠธ / ์ด๋ฏธ์ง€,๋น„๋””์˜ค = ์‹ค์ œ ๊ฒฝ๋กœ
203
  ##################################################
204
  def process_new_user_message(message: dict) -> list[dict]:
205
+ user_text = (message["text"] or "").strip() or "(No text)"
206
  if not message["files"]:
207
+ return [{"type": "text", "text": user_text}]
208
 
 
209
  video_files = [f for f in message["files"] if f.endswith(".mp4")]
210
  image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
211
  csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
212
  txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
213
  pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
214
 
215
+ content_list = [{"type": "text", "text": user_text}]
 
216
 
217
  # CSV
218
  for csv_path in csv_files:
219
  csv_analysis = analyze_csv_file(csv_path)
220
+ if not csv_analysis.strip():
221
+ csv_analysis = "(No CSV content?)"
222
  content_list.append({"type": "text", "text": csv_analysis})
223
 
224
  # TXT
225
  for txt_path in txt_files:
226
  txt_analysis = analyze_txt_file(txt_path)
227
+ if not txt_analysis.strip():
228
+ txt_analysis = "(No TXT content?)"
229
  content_list.append({"type": "text", "text": txt_analysis})
230
 
231
  # PDF
232
  for pdf_path in pdf_files:
233
+ pdf_md = pdf_to_markdown(pdf_path)
234
+ if not pdf_md.strip():
235
+ pdf_md = "(No PDF content?)"
236
+ content_list.append({"type": "text", "text": pdf_md})
237
 
 
238
  if video_files:
239
+ # ํ•˜๋‚˜๋งŒ ์ฒ˜๋ฆฌ
240
  content_list += process_video(video_files[0])
241
  return content_list
242
 
243
+ if "<image>" in user_text:
 
244
  return process_interleaved_images(message)
245
  else:
246
+ # ์ผ๋ฐ˜ ์ด๋ฏธ์ง€
247
  for img_path in image_files:
248
  content_list.append({"type": "image", "url": img_path})
249
 
 
251
 
252
 
253
  ##################################################
254
+ # 6) ํžˆ์Šคํ† ๋ฆฌ -> LLM ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜ (๋น„์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋Š” ๋ฌด์‹œ)
255
  ##################################################
256
  def process_history(history: list[dict]) -> list[dict]:
 
 
 
 
257
  messages = []
258
  current_user_content = []
259
  for item in history:
 
261
  if current_user_content:
262
  messages.append({"role": "user", "content": current_user_content})
263
  current_user_content = []
 
264
  messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
265
  else:
266
  # user
267
  content = item["content"]
268
  if isinstance(content, str):
 
269
  current_user_content.append({"type": "text", "text": content})
270
  else:
271
+ # [ํŒŒ์ผ๊ฒฝ๋กœ]
272
+ fpath = content[0]
273
+ # ์ด๋ฏธ์ง€๋‚˜ mp4๋งŒ ์œ ์ง€, ๋‚˜๋จธ์ง€๋Š” ์ œ์™ธ
274
+ if re.search(r"\.(png|jpg|jpeg|gif|webp)$", fpath, re.IGNORECASE) or fpath.endswith(".mp4"):
275
+ current_user_content.append({"type": "image", "url": fpath})
276
  else:
 
277
  pass
278
  return messages
279
 
280
 
281
  ##################################################
282
+ # 7) ๋ฉ”์ธ ์ถ”๋ก  (๋นˆ ํ† ํฐ ๋ฐฉ์–ด)
283
  ##################################################
284
  @spaces.GPU(duration=120)
285
  def run(message: dict, history: list[dict], system_prompt: str = "", max_new_tokens: int = 512) -> Iterator[str]:
 
286
  if not validate_media_constraints(message, history):
287
  yield ""
288
  return
289
 
 
290
  messages = []
291
  if system_prompt:
292
  messages.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]})
293
  messages.extend(process_history(history))
 
294
 
295
+ user_content = process_new_user_message(message)
296
+ messages.append({"role": "user", "content": user_content})
297
+
298
+ # 1) tokenize=False ํ›„ ํ† ํฐ ๊ธธ์ด ์ฒดํฌ
299
+ raw_text = processor.tokenizer.apply_chat_template(
300
  messages,
301
+ tokenize=False,
302
+ add_generation_prompt=True
303
+ )
304
+ token_ids = processor.tokenizer.encode(raw_text, add_special_tokens=False)
305
+ if len(token_ids) == 0:
306
+ # ๋นˆ ์ž…๋ ฅ โ†’ ์ž„์˜ ๋ฌธ๊ตฌ ์ถ”๊ฐ€
307
+ raw_text += " (No content?)"
308
+ token_ids = processor.tokenizer.encode(raw_text, add_special_tokens=False)
309
+
310
+ # 2) ์‹ค์ œ tokenizer
311
+ inputs = processor.tokenizer(
312
+ raw_text,
313
  return_tensors="pt",
314
+ padding=True
315
+ )
316
+ inputs = {k: v.to(model.device, dtype=torch.bfloat16) for k, v in inputs.items()}
317
 
318
+ # 3) ์ŠคํŠธ๋ฆฌ๋ฐ ์ƒ์„ฑ
319
+ streamer = TextIteratorStreamer(processor.tokenizer, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
320
  gen_kwargs = {
321
+ "inputs": inputs["input_ids"],
322
+ "attention_mask": inputs.get("attention_mask"),
323
  "streamer": streamer,
324
  "max_new_tokens": max_new_tokens,
325
+ "do_sample": True,
326
+ "temperature": 0.3,
327
+ "top_p": 0.95,
328
  }
329
+ gen_kwargs = {k: v for k, v in gen_kwargs.items() if v is not None}
330
+
331
  t = Thread(target=model.generate, kwargs=gen_kwargs)
332
  t.start()
333
 
334
  output = ""
335
+ for chunk in streamer:
336
+ output += chunk
337
  yield output
338
 
339
 
 
 
340
  ##################################################
341
+ # 8) ์˜ˆ์‹œ
342
  ##################################################
343
  examples = [
344
 
 
470
  ]
471
 
472
 
 
 
 
 
 
 
473
  demo = gr.ChatInterface(
474
  fn=run,
475
  type="messages",
476
  chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
 
477
  textbox=gr.MultimodalTextbox(
478
  file_types=[
479
  ".png", ".jpg", ".jpeg", ".gif", ".webp",
 
488
  label="System Prompt",
489
  value="You are a deeply thoughtful AI. Consider problems thoroughly and derive correct solutions through systematic reasoning. Please answer in korean."
490
  ),
491
+ gr.Slider(label="Max New Tokens", minimum=100, maximum=8000, step=50, value=2000),
 
 
 
 
 
 
492
  ],
493
  stop_btn=False,
494
  title="Gemma 3 27B IT",
 
499
  delete_cache=(1800, 1800),
500
  )
501
 
 
502
  if __name__ == "__main__":
503
  demo.launch()