openfree commited on
Commit
0aa641f
ยท
verified ยท
1 Parent(s): 9a70f56

Delete app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +0 -768
app-backup.py DELETED
@@ -1,768 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- import os
4
- import re
5
- import tempfile
6
- from collections.abc import Iterator
7
- from threading import Thread
8
- import json
9
- import requests
10
- import cv2
11
- import gradio as gr
12
- import spaces
13
- import torch
14
- from loguru import logger
15
- from PIL import Image
16
- from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
17
-
18
- # CSV/TXT ๋ถ„์„
19
- import pandas as pd
20
- # PDF ํ…์ŠคํŠธ ์ถ”์ถœ
21
- import PyPDF2
22
-
23
- ##############################################################################
24
- # SERPHouse API key from environment variable
25
- ##############################################################################
26
- SERPHOUSE_API_KEY = os.getenv("SERPHOUSE_API_KEY", "")
27
-
28
- ##############################################################################
29
- # ๊ฐ„๋‹จํ•œ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ํ•จ์ˆ˜ (ํ•œ๊ธ€ + ์•ŒํŒŒ๋ฒณ + ์ˆซ์ž + ๊ณต๋ฐฑ ๋ณด์กด)
30
- ##############################################################################
31
- def extract_keywords(text: str, top_k: int = 5) -> str:
32
- """
33
- 1) ํ•œ๊ธ€(๊ฐ€-ํžฃ), ์˜์–ด(a-zA-Z), ์ˆซ์ž(0-9), ๊ณต๋ฐฑ๋งŒ ๋‚จ๊น€
34
- 2) ๊ณต๋ฐฑ ๊ธฐ์ค€ ํ† ํฐ ๋ถ„๋ฆฌ
35
- 3) ์ตœ๋Œ€ top_k๊ฐœ๋งŒ
36
- """
37
- text = re.sub(r"[^a-zA-Z0-9๊ฐ€-ํžฃ\s]", "", text)
38
- tokens = text.split()
39
- key_tokens = tokens[:top_k]
40
- return " ".join(key_tokens)
41
-
42
- ##############################################################################
43
- # SerpHouse Live endpoint ํ˜ธ์ถœ
44
- # - ์ƒ์œ„ 20๊ฐœ ๊ฒฐ๊ณผ JSON์„ LLM์— ๋„˜๊ธธ ๋•Œ link, snippet ๋“ฑ ๋ชจ๋‘ ํฌํ•จ
45
- ##############################################################################
46
- def do_web_search(query: str) -> str:
47
- """
48
- ์ƒ์œ„ 20๊ฐœ 'organic' ๊ฒฐ๊ณผ item ์ „์ฒด(์ œ๋ชฉ, link, snippet ๋“ฑ)๋ฅผ
49
- JSON ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
50
- """
51
- try:
52
- url = "https://api.serphouse.com/serp/live"
53
-
54
- # ๊ธฐ๋ณธ GET ๋ฐฉ์‹์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ„์†Œํ™”ํ•˜๊ณ  ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ 20๊ฐœ๋กœ ์ œํ•œ
55
- params = {
56
- "q": query,
57
- "domain": "google.com",
58
- "serp_type": "web", # ๊ธฐ๋ณธ ์›น ๊ฒ€์ƒ‰
59
- "device": "desktop",
60
- "lang": "en",
61
- "num": "20" # ์ตœ๋Œ€ 20๊ฐœ ๊ฒฐ๊ณผ๋งŒ ์š”์ฒญ
62
- }
63
-
64
- headers = {
65
- "Authorization": f"Bearer {SERPHOUSE_API_KEY}"
66
- }
67
-
68
- logger.info(f"SerpHouse API ํ˜ธ์ถœ ์ค‘... ๊ฒ€์ƒ‰์–ด: {query}")
69
- logger.info(f"์š”์ฒญ URL: {url} - ํŒŒ๋ผ๋ฏธํ„ฐ: {params}")
70
-
71
- # GET ์š”์ฒญ ์ˆ˜ํ–‰
72
- response = requests.get(url, headers=headers, params=params, timeout=30)
73
- response.raise_for_status()
74
-
75
- logger.info(f"SerpHouse API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
76
- data = response.json()
77
-
78
- # ๋‹ค์–‘ํ•œ ์‘๋‹ต ๊ตฌ์กฐ ์ฒ˜๋ฆฌ
79
- results = data.get("results", {})
80
- organic = None
81
-
82
- # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 1
83
- if isinstance(results, dict) and "organic" in results:
84
- organic = results["organic"]
85
-
86
- # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 2 (์ค‘์ฒฉ๋œ results)
87
- elif isinstance(results, dict) and "results" in results:
88
- if isinstance(results["results"], dict) and "organic" in results["results"]:
89
- organic = results["results"]["organic"]
90
-
91
- # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 3 (์ตœ์ƒ์œ„ organic)
92
- elif "organic" in data:
93
- organic = data["organic"]
94
-
95
- if not organic:
96
- logger.warning("์‘๋‹ต์—์„œ organic ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
97
- logger.debug(f"์‘๋‹ต ๊ตฌ์กฐ: {list(data.keys())}")
98
- if isinstance(results, dict):
99
- logger.debug(f"results ๊ตฌ์กฐ: {list(results.keys())}")
100
- return "No web search results found or unexpected API response structure."
101
-
102
- # ๊ฒฐ๊ณผ ์ˆ˜ ์ œํ•œ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ตœ์ ํ™”
103
- max_results = min(20, len(organic))
104
- limited_organic = organic[:max_results]
105
-
106
- # ๊ฒฐ๊ณผ ํ˜•์‹ ๊ฐ„์†Œํ™” - ์ „์ฒด JSON ๋Œ€์‹  ์ค‘์š” ํ•„๋“œ๋งŒ ํฌํ•จ
107
- summary_lines = []
108
- for idx, item in enumerate(limited_organic, start=1):
109
- title = item.get("title", "No title")
110
- link = item.get("link", "#")
111
- snippet = item.get("snippet", "No description")
112
-
113
- # ๊ฐ„์†Œํ™”๋œ ํ˜•์‹
114
- summary_lines.append(
115
- f"Result {idx}:\n"
116
- f"- Title: {title}\n"
117
- f"- Link: {link}\n"
118
- f"- Snippet: {snippet}\n"
119
- )
120
-
121
- logger.info(f"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ {len(limited_organic)}๊ฐœ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
122
- return "\n".join(summary_lines)
123
-
124
- except Exception as e:
125
- logger.error(f"Web search failed: {e}")
126
- return f"Web search failed: {str(e)}"
127
-
128
-
129
- ##############################################################################
130
- # ๋ชจ๋ธ/ํ”„๋กœ์„ธ์„œ ๋กœ๋”ฉ
131
- ##############################################################################
132
- MAX_CONTENT_CHARS = 4000
133
- model_id = os.getenv("MODEL_ID", "VIDraft/Gemma3-R1945-27B")
134
-
135
- processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
136
- model = Gemma3ForConditionalGeneration.from_pretrained(
137
- model_id,
138
- device_map="auto",
139
- torch_dtype=torch.bfloat16,
140
- attn_implementation="eager"
141
- )
142
- MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
143
-
144
-
145
- ##############################################################################
146
- # CSV, TXT, PDF ๋ถ„์„ ํ•จ์ˆ˜
147
- ##############################################################################
148
- def analyze_csv_file(path: str) -> str:
149
- """
150
- CSV ํŒŒ์ผ์„ ์ „์ฒด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜. ๋„ˆ๋ฌด ๊ธธ ๊ฒฝ์šฐ ์ผ๋ถ€๋งŒ ํ‘œ์‹œ.
151
- """
152
- try:
153
- df = pd.read_csv(path)
154
- if df.shape[0] > 50 or df.shape[1] > 10:
155
- df = df.iloc[:50, :10]
156
- df_str = df.to_string()
157
- if len(df_str) > MAX_CONTENT_CHARS:
158
- df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
159
- return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
160
- except Exception as e:
161
- return f"Failed to read CSV ({os.path.basename(path)}): {str(e)}"
162
-
163
-
164
- def analyze_txt_file(path: str) -> str:
165
- """
166
- TXT ํŒŒ์ผ ์ „๋ฌธ ์ฝ๊ธฐ. ๋„ˆ๋ฌด ๊ธธ๋ฉด ์ผ๋ถ€๋งŒ ํ‘œ์‹œ.
167
- """
168
- try:
169
- with open(path, "r", encoding="utf-8") as f:
170
- text = f.read()
171
- if len(text) > MAX_CONTENT_CHARS:
172
- text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
173
- return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
174
- except Exception as e:
175
- return f"Failed to read TXT ({os.path.basename(path)}): {str(e)}"
176
-
177
-
178
- def pdf_to_markdown(pdf_path: str) -> str:
179
- """
180
- PDF โ†’ Markdown. ํŽ˜์ด์ง€๋ณ„๋กœ ๊ฐ„๋‹จํžˆ ํ…์ŠคํŠธ ์ถ”์ถœ.
181
- """
182
- text_chunks = []
183
- try:
184
- with open(pdf_path, "rb") as f:
185
- reader = PyPDF2.PdfReader(f)
186
- max_pages = min(5, len(reader.pages))
187
- for page_num in range(max_pages):
188
- page = reader.pages[page_num]
189
- page_text = page.extract_text() or ""
190
- page_text = page_text.strip()
191
- if page_text:
192
- if len(page_text) > MAX_CONTENT_CHARS // max_pages:
193
- page_text = page_text[:MAX_CONTENT_CHARS // max_pages] + "...(truncated)"
194
- text_chunks.append(f"## Page {page_num+1}\n\n{page_text}\n")
195
- if len(reader.pages) > max_pages:
196
- text_chunks.append(f"\n...(Showing {max_pages} of {len(reader.pages)} pages)...")
197
- except Exception as e:
198
- return f"Failed to read PDF ({os.path.basename(pdf_path)}): {str(e)}"
199
-
200
- full_text = "\n".join(text_chunks)
201
- if len(full_text) > MAX_CONTENT_CHARS:
202
- full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
203
-
204
- return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
205
-
206
-
207
- ##############################################################################
208
- # ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์—…๋กœ๋“œ ์ œํ•œ ๊ฒ€์‚ฌ
209
- ##############################################################################
210
- def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
211
- image_count = 0
212
- video_count = 0
213
- for path in paths:
214
- if path.endswith(".mp4"):
215
- video_count += 1
216
- elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", path, re.IGNORECASE):
217
- image_count += 1
218
- return image_count, video_count
219
-
220
-
221
- def count_files_in_history(history: list[dict]) -> tuple[int, int]:
222
- image_count = 0
223
- video_count = 0
224
- for item in history:
225
- if item["role"] != "user" or isinstance(item["content"], str):
226
- continue
227
- if isinstance(item["content"], list) and len(item["content"]) > 0:
228
- file_path = item["content"][0]
229
- if isinstance(file_path, str):
230
- if file_path.endswith(".mp4"):
231
- video_count += 1
232
- elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE):
233
- image_count += 1
234
- return image_count, video_count
235
-
236
-
237
- def validate_media_constraints(message: dict, history: list[dict]) -> bool:
238
- media_files = []
239
- for f in message["files"]:
240
- if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4"):
241
- media_files.append(f)
242
-
243
- new_image_count, new_video_count = count_files_in_new_message(media_files)
244
- history_image_count, history_video_count = count_files_in_history(history)
245
- image_count = history_image_count + new_image_count
246
- video_count = history_video_count + new_video_count
247
-
248
- if video_count > 1:
249
- gr.Warning("Only one video is supported.")
250
- return False
251
- if video_count == 1:
252
- if image_count > 0:
253
- gr.Warning("Mixing images and videos is not allowed.")
254
- return False
255
- if "<image>" in message["text"]:
256
- gr.Warning("Using <image> tags with video files is not supported.")
257
- return False
258
- if video_count == 0 and image_count > MAX_NUM_IMAGES:
259
- gr.Warning(f"You can upload up to {MAX_NUM_IMAGES} images.")
260
- return False
261
-
262
- if "<image>" in message["text"]:
263
- image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
264
- image_tag_count = message["text"].count("<image>")
265
- if image_tag_count != len(image_files):
266
- gr.Warning("The number of <image> tags in the text does not match the number of image files.")
267
- return False
268
-
269
- return True
270
-
271
-
272
- ##############################################################################
273
- # ๋น„๋””์˜ค ์ฒ˜๋ฆฌ
274
- ##############################################################################
275
- def downsample_video(video_path: str) -> list[tuple[Image.Image, float]]:
276
- vidcap = cv2.VideoCapture(video_path)
277
- fps = vidcap.get(cv2.CAP_PROP_FPS)
278
- total_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
279
- frame_interval = max(int(fps), int(total_frames / 10))
280
- frames = []
281
-
282
- for i in range(0, total_frames, frame_interval):
283
- vidcap.set(cv2.CAP_PROP_POS_FRAMES, i)
284
- success, image = vidcap.read()
285
- if success:
286
- image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
287
- pil_image = Image.fromarray(image)
288
- timestamp = round(i / fps, 2)
289
- frames.append((pil_image, timestamp))
290
- if len(frames) >= 5:
291
- break
292
-
293
- vidcap.release()
294
- return frames
295
-
296
-
297
- def process_video(video_path: str) -> list[dict]:
298
- content = []
299
- frames = downsample_video(video_path)
300
- for frame in frames:
301
- pil_image, timestamp = frame
302
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
303
- pil_image.save(temp_file.name)
304
- content.append({"type": "text", "text": f"Frame {timestamp}:"})
305
- content.append({"type": "image", "url": temp_file.name})
306
- logger.debug(f"{content=}")
307
- return content
308
-
309
-
310
- ##############################################################################
311
- # interleaved <image> ์ฒ˜๋ฆฌ
312
- ##############################################################################
313
- def process_interleaved_images(message: dict) -> list[dict]:
314
- parts = re.split(r"(<image>)", message["text"])
315
- content = []
316
- image_index = 0
317
-
318
- image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
319
-
320
- for part in parts:
321
- if part == "<image>" and image_index < len(image_files):
322
- content.append({"type": "image", "url": image_files[image_index]})
323
- image_index += 1
324
- elif part.strip():
325
- content.append({"type": "text", "text": part.strip()})
326
- else:
327
- if isinstance(part, str) and part != "<image>":
328
- content.append({"type": "text", "text": part})
329
- return content
330
-
331
-
332
- ##############################################################################
333
- # PDF + CSV + TXT + ์ด๋ฏธ์ง€/๋น„๋””์˜ค
334
- ##############################################################################
335
- def is_image_file(file_path: str) -> bool:
336
- return bool(re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE))
337
-
338
- def is_video_file(file_path: str) -> bool:
339
- return file_path.endswith(".mp4")
340
-
341
- def is_document_file(file_path: str) -> bool:
342
- return (
343
- file_path.lower().endswith(".pdf")
344
- or file_path.lower().endswith(".csv")
345
- or file_path.lower().endswith(".txt")
346
- )
347
-
348
-
349
- def process_new_user_message(message: dict) -> list[dict]:
350
- if not message["files"]:
351
- return [{"type": "text", "text": message["text"]}]
352
-
353
- video_files = [f for f in message["files"] if is_video_file(f)]
354
- image_files = [f for f in message["files"] if is_image_file(f)]
355
- csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
356
- txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
357
- pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
358
-
359
- content_list = [{"type": "text", "text": message["text"]}]
360
-
361
- for csv_path in csv_files:
362
- csv_analysis = analyze_csv_file(csv_path)
363
- content_list.append({"type": "text", "text": csv_analysis})
364
-
365
- for txt_path in txt_files:
366
- txt_analysis = analyze_txt_file(txt_path)
367
- content_list.append({"type": "text", "text": txt_analysis})
368
-
369
- for pdf_path in pdf_files:
370
- pdf_markdown = pdf_to_markdown(pdf_path)
371
- content_list.append({"type": "text", "text": pdf_markdown})
372
-
373
- if video_files:
374
- content_list += process_video(video_files[0])
375
- return content_list
376
-
377
- if "<image>" in message["text"] and image_files:
378
- interleaved_content = process_interleaved_images({"text": message["text"], "files": image_files})
379
- if content_list and content_list[0]["type"] == "text":
380
- content_list = content_list[1:]
381
- return interleaved_content + content_list
382
- else:
383
- for img_path in image_files:
384
- content_list.append({"type": "image", "url": img_path})
385
-
386
- return content_list
387
-
388
-
389
- ##############################################################################
390
- # history -> LLM ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜
391
- ##############################################################################
392
- def process_history(history: list[dict]) -> list[dict]:
393
- messages = []
394
- current_user_content: list[dict] = []
395
- for item in history:
396
- if item["role"] == "assistant":
397
- if current_user_content:
398
- messages.append({"role": "user", "content": current_user_content})
399
- current_user_content = []
400
- messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
401
- else:
402
- content = item["content"]
403
- if isinstance(content, str):
404
- current_user_content.append({"type": "text", "text": content})
405
- elif isinstance(content, list) and len(content) > 0:
406
- file_path = content[0]
407
- if is_image_file(file_path):
408
- current_user_content.append({"type": "image", "url": file_path})
409
- else:
410
- current_user_content.append({"type": "text", "text": f"[File: {os.path.basename(file_path)}]"})
411
-
412
- if current_user_content:
413
- messages.append({"role": "user", "content": current_user_content})
414
-
415
- return messages
416
-
417
-
418
- ##############################################################################
419
- # ๋ฉ”์ธ ์ถ”๋ก  ํ•จ์ˆ˜ (web search ์ฒดํฌ ์‹œ ์ž๋™ ํ‚ค์›Œ๋“œ์ถ”์ถœ->๊ฒ€์ƒ‰->๊ฒฐ๊ณผ system msg)
420
- ##############################################################################
421
- @spaces.GPU(duration=120)
422
- def run(
423
- message: dict,
424
- history: list[dict],
425
- system_prompt: str = "",
426
- max_new_tokens: int = 512,
427
- use_web_search: bool = False,
428
- web_search_query: str = "",
429
- ) -> Iterator[str]:
430
-
431
- if not validate_media_constraints(message, history):
432
- yield ""
433
- return
434
-
435
- try:
436
- combined_system_msg = ""
437
-
438
- # ๋‚ด๋ถ€์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ (UI์—์„œ๋Š” ๋ณด์ด์ง€ ์•Š์Œ)
439
- if system_prompt.strip():
440
- combined_system_msg += f"[System Prompt]\n{system_prompt.strip()}\n\n"
441
-
442
- if use_web_search:
443
- user_text = message["text"]
444
- ws_query = extract_keywords(user_text, top_k=5)
445
- if ws_query.strip():
446
- logger.info(f"[Auto WebSearch Keyword] {ws_query!r}")
447
- ws_result = do_web_search(ws_query)
448
- combined_system_msg += f"[Search top-20 Full Items Based on user prompt]\n{ws_result}\n\n"
449
- # >>> ์ถ”๊ฐ€๋œ ์•ˆ๋‚ด ๋ฌธ๊ตฌ (๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์˜ link ๋“ฑ ์ถœ์ฒ˜๋ฅผ ํ™œ์šฉ)
450
- combined_system_msg += "[์ฐธ๊ณ : ์œ„ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ ๋‚ด์šฉ๊ณผ link๋ฅผ ์ถœ์ฒ˜๋กœ ์ธ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•ด ์ฃผ์„ธ์š”.]\n\n"
451
- else:
452
- combined_system_msg += "[No valid keywords found, skipping WebSearch]\n\n"
453
-
454
- messages = []
455
- if combined_system_msg.strip():
456
- messages.append({
457
- "role": "system",
458
- "content": [{"type": "text", "text": combined_system_msg.strip()}],
459
- })
460
-
461
- messages.extend(process_history(history))
462
-
463
- user_content = process_new_user_message(message)
464
- for item in user_content:
465
- if item["type"] == "text" and len(item["text"]) > MAX_CONTENT_CHARS:
466
- item["text"] = item["text"][:MAX_CONTENT_CHARS] + "\n...(truncated)..."
467
- messages.append({"role": "user", "content": user_content})
468
-
469
- inputs = processor.apply_chat_template(
470
- messages,
471
- add_generation_prompt=True,
472
- tokenize=True,
473
- return_dict=True,
474
- return_tensors="pt",
475
- ).to(device=model.device, dtype=torch.bfloat16)
476
-
477
- streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
478
- gen_kwargs = dict(
479
- inputs,
480
- streamer=streamer,
481
- max_new_tokens=max_new_tokens,
482
- )
483
-
484
- t = Thread(target=_model_gen_with_oom_catch, kwargs=gen_kwargs)
485
- t.start()
486
-
487
- output = ""
488
- for new_text in streamer:
489
- output += new_text
490
- yield output
491
-
492
- except Exception as e:
493
- logger.error(f"Error in run: {str(e)}")
494
- yield f"์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
495
-
496
-
497
- ##############################################################################
498
- # [์ถ”๊ฐ€] ๋ณ„๋„ ํ•จ์ˆ˜์—์„œ model.generate(...)๋ฅผ ํ˜ธ์ถœ, OOM ์บ์น˜
499
- ##############################################################################
500
- def _model_gen_with_oom_catch(**kwargs):
501
- """
502
- ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ OutOfMemoryError๋ฅผ ์žก์•„์ฃผ๊ธฐ ์œ„ํ•ด
503
- """
504
- try:
505
- model.generate(**kwargs)
506
- except torch.cuda.OutOfMemoryError:
507
- raise RuntimeError(
508
- "[OutOfMemoryError] GPU ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. "
509
- "Max New Tokens์„ ์ค„์ด๊ฑฐ๋‚˜, ํ”„๋กฌํ”„ํŠธ ๊ธธ์ด๋ฅผ ์ค„์—ฌ์ฃผ์„ธ์š”."
510
- )
511
-
512
-
513
- ##############################################################################
514
- # ์˜ˆ์‹œ๋“ค (ํ•œ๊ธ€ํ™”)
515
- ##############################################################################
516
- examples = [
517
- [
518
- {
519
- "text": "๋‘ PDF ํŒŒ์ผ ๋‚ด์šฉ์„ ๋น„๊ตํ•˜๋ผ.",
520
- "files": [
521
- "assets/additional-examples/before.pdf",
522
- "assets/additional-examples/after.pdf",
523
- ],
524
- }
525
- ],
526
- [
527
- {
528
- "text": "CSV ํŒŒ์ผ ๋‚ด์šฉ์„ ์š”์•ฝ, ๋ถ„์„ํ•˜๋ผ",
529
- "files": ["assets/additional-examples/sample-csv.csv"],
530
- }
531
- ],
532
- [
533
- {
534
- "text": "์ด ์˜์ƒ์˜ ๋‚ด์šฉ์„ ์„ค๋ช…ํ•˜๋ผ",
535
- "files": ["assets/additional-examples/tmp.mp4"],
536
- }
537
- ],
538
- [
539
- {
540
- "text": "ํ‘œ์ง€ ๋‚ด์šฉ์„ ์„ค๋ช…ํ•˜๊ณ  ๊ธ€์ž๋ฅผ ์ฝ์–ด์ฃผ์„ธ์š”.",
541
- "files": ["assets/additional-examples/maz.jpg"],
542
- }
543
- ],
544
- [
545
- {
546
- "text": "์ด๋ฏธ ์ด ์˜์–‘์ œ๋ฅผ <image> ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ด ์ œํ’ˆ <image>์„ ์ƒˆ๋กœ ์‚ฌ๋ ค ํ•ฉ๋‹ˆ๋‹ค. ํ•จ๊ป˜ ์„ญ์ทจํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์ด ์žˆ์„๊นŒ์š”?",
547
- "files": ["assets/additional-examples/pill1.png", "assets/additional-examples/pill2.png"],
548
- }
549
- ],
550
- [
551
- {
552
- "text": "์ด ์ ๋ถ„์„ ํ’€์–ด์ฃผ์„ธ์š”.",
553
- "files": ["assets/additional-examples/4.png"],
554
- }
555
- ],
556
- [
557
- {
558
- "text": "์ด ํ‹ฐ์ผ“์€ ์–ธ์ œ ๋ฐœ๊ธ‰๋œ ๊ฒƒ์ด๊ณ , ๊ฐ€๊ฒฉ์€ ์–ผ๋งˆ์ธ๊ฐ€์š”?",
559
- "files": ["assets/additional-examples/2.png"],
560
- }
561
- ],
562
- [
563
- {
564
- "text": "์ด๋ฏธ์ง€๋“ค์˜ ์ˆœ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์งง์€ ์ด์•ผ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์„ธ์š”.",
565
- "files": [
566
- "assets/sample-images/09-1.png",
567
- "assets/sample-images/09-2.png",
568
- "assets/sample-images/09-3.png",
569
- "assets/sample-images/09-4.png",
570
- "assets/sample-images/09-5.png",
571
- ],
572
- }
573
- ],
574
-
575
- [
576
- {
577
- "text": "๋™์ผํ•œ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ฆฌ๋Š” matplotlib ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.",
578
- "files": ["assets/additional-examples/barchart.png"],
579
- }
580
- ],
581
- [
582
- {
583
- "text": "์ด๋ฏธ์ง€์— ์žˆ๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฝ์–ด์„œ ๋งˆํฌ๋‹ค์šด ํ˜•ํƒœ๋กœ ์ ์–ด์ฃผ์„ธ์š”.",
584
- "files": ["assets/additional-examples/3.png"],
585
- }
586
- ],
587
- [
588
- {
589
- "text": "์ด ํ‘œ์ง€ํŒ์—๋Š” ๋ฌด์Šจ ๋ฌธ๊ตฌ๊ฐ€ ์ ํ˜€ ์žˆ๋‚˜์š”?",
590
- "files": ["assets/sample-images/02.png"],
591
- }
592
- ],
593
- [
594
- {
595
- "text": "๋‘ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ตํ•ด์„œ ๊ณตํ†ต์ ๊ณผ ์ฐจ์ด์ ์„ ๋งํ•ด์ฃผ์„ธ์š”.",
596
- "files": ["assets/sample-images/03.png"],
597
- }
598
- ],
599
- [
600
- {
601
- "text": "๋„ˆ๋Š” ์นœ๊ทผํ•˜๊ณ  ๋‹ค์ •ํ•œ ์ดํ•ด์‹ฌ ๋งŽ์€ ์—ฌ์ž์นœ๊ตฌ ์—ญํ• ์ด๋‹ค.",
602
- }
603
- ],
604
- [
605
- {
606
- "text": """์ธ๋ฅ˜์˜ ๋งˆ์ง€๋ง‰ ์‹œํ—˜(Humanity's Last Exam) ๋ฌธ์ œ๋ฅผ ํ’€์ดํ•˜๋ผ('Deep Research' ๋ฒ„ํŠผ ํด๋ฆญํ• ๊ฒƒ) Which was the first statute in the modern State of Israel to explicitly introduce the concept of "good faith"? (Do not append "the" or the statute's year to the answer.)""",
607
- }
608
- ],
609
- [
610
- {
611
- "text": """์ธ๋ฅ˜์˜ ๋งˆ์ง€๋ง‰ ์‹œํ—˜(Humanity's Last Exam) ๋ฌธ์ œ๋ฅผ ํ’€์ดํ•˜๋ผ. How does Guarani's nominal tense/aspect system interact with effected objects in sentences?
612
-
613
- Answer Choices:
614
- A. Effected objects cannot take nominal tense/aspect markers
615
- B. Effected objects require the post-stative -kue
616
- C. Effected objects must be marked with the destinative -rรฃ
617
- D. Nominal tense/aspect is optional for effected objects
618
- E. Effected objects use a special set of tense/aspect markers""",
619
- }
620
- ],
621
- ]
622
-
623
-
624
- ##############################################################################
625
- # Gradio UI (Blocks) ๊ตฌ์„ฑ (์ขŒ์ธก ์‚ฌ์ด๋“œ ๋ฉ”๋‰ด ์—†์ด ์ „์ฒดํ™”๋ฉด ์ฑ„ํŒ…)
626
- ##############################################################################
627
- css = """
628
- /* 1) UI๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ฐ€์žฅ ๋„“๊ฒŒ (width 100%) ๊ณ ์ •ํ•˜์—ฌ ํ‘œ์‹œ */
629
- .gradio-container {
630
- background: rgba(255, 255, 255, 0.95);
631
- border-radius: 15px;
632
- padding: 30px 40px;
633
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
634
- margin: 20px auto; /* ์œ„์•„๋ž˜ ์—ฌ๋ฐฑ๋งŒ ์œ ์ง€ */
635
- width: 100% !important;
636
- max-width: none !important; /* 1200px ์ œํ•œ ์ œ๊ฑฐ */
637
- }
638
-
639
- .fillable {
640
- width: 100% !important;
641
- max-width: 100% !important;
642
- }
643
-
644
- /* 2) ๋ฐฐ๊ฒฝ์„ ์—ฐํ•˜๊ณ  ํˆฌ๋ช…ํ•œ ํŒŒ์Šคํ…” ํ†ค ๊ทธ๋ผ๋””์–ธํŠธ๋กœ ๋ณ€๊ฒฝ */
645
- body {
646
- background: linear-gradient(
647
- 135deg,
648
- rgba(255, 229, 210, 0.6),
649
- rgba(255, 240, 245, 0.6)
650
- );
651
- margin: 0;
652
- padding: 0;
653
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
654
- color: #333;
655
- }
656
-
657
- /* ๋ฒ„ํŠผ ์ƒ‰์ƒ๋„ ๊ธฐ์กด์˜ ์ง™์€ ๋ถ‰์€-์ฃผํ™ฉ โ†’ ํŒŒ์Šคํ…” ๊ณ„์—ด๋กœ ์—ฐํ•˜๊ฒŒ */
658
- button, .btn {
659
- background: linear-gradient(
660
- 90deg,
661
- rgba(255, 210, 220, 0.7),
662
- rgba(255, 190, 200, 0.7)
663
- ) !important;
664
- border: none;
665
- color: #333; /* ๊ธ€์ž ์ž˜ ๋ณด์ด๋„๋ก ์•ฝ๊ฐ„ ์ง„ํ•œ ๊ธ€์”จ */
666
- padding: 12px 24px;
667
- text-transform: uppercase;
668
- font-weight: bold;
669
- letter-spacing: 1px;
670
- border-radius: 5px;
671
- cursor: pointer;
672
- transition: transform 0.2s ease-in-out;
673
- }
674
-
675
- button:hover, .btn:hover {
676
- transform: scale(1.03);
677
- }
678
-
679
- #examples_container {
680
- margin: auto;
681
- width: 90%;
682
- }
683
-
684
- #examples_row {
685
- justify-content: center;
686
- }
687
- """
688
-
689
- title_html = """
690
- <h1 align="center" style="margin-bottom: 0.2em; font-size: 1.6em;"> ๐Ÿค— Gemma3-uncensored-R27B </h1>
691
- <p align="center" style="font-size:1.1em; color:#555;">
692
- โœ…Agentic AI Platform โœ…Reasoning & Uncensored โœ…Multimodal & VLM โœ…Deep-Research & RAG <br>
693
- Operates on an โœ…'NVIDIA A100 GPU' as an independent local server, enhancing security and preventing information leakage.<br>
694
- @Based by 'MS Gemma-3-27b' / @Powered by 'MOUSE-II'(VIDRAFT)
695
- </p>
696
- """
697
-
698
- with gr.Blocks(css=css, title="Gemma3-uncensored-R27B") as demo:
699
- gr.Markdown(title_html)
700
-
701
- # ์›น์„œ์น˜ ์˜ต์…˜์€ ํ™”๋ฉด์— ํ‘œ์‹œ (ํ•˜์ง€๋งŒ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ, ํ† ํฐ ์Šฌ๋ผ์ด๋” ๋“ฑ์€ ๊ฐ์ถค)
702
- web_search_checkbox = gr.Checkbox(
703
- label="Deep Research",
704
- value=False
705
- )
706
-
707
- # ๋‚ด๋ถ€์ ์œผ๋กœ ์“ฐ์ด์ง€๋งŒ ํ™”๋ฉด์—๋Š” ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์„ค์ •
708
- system_prompt_box = gr.Textbox(
709
- lines=3,
710
- value="๋ฐ˜๋“œ์‹œ ํ•œ๊ธ€๋กœ ๋‹ต๋ณ€ํ•˜๋ผ. You are a deep thinking AI, you may use extremely long chains of thought to deeply consider the problem and deliberate with yourself via systematic reasoning processes to help come to a correct solution prior to answering. Please answer in Korean.You have the ability to read English sources, but you **must always speak in Korean**.Even if the search results are in English, answer in Korean.",
711
- visible=False # ํ™”๋ฉด์—์„œ ๊ฐ์ถค
712
- )
713
-
714
- max_tokens_slider = gr.Slider(
715
- label="Max New Tokens",
716
- minimum=100,
717
- maximum=8000,
718
- step=50,
719
- value=1000,
720
- visible=False # ํ™”๋ฉด์—์„œ ๊ฐ์ถค
721
- )
722
-
723
- web_search_text = gr.Textbox(
724
- lines=1,
725
- label="(Unused) Web Search Query",
726
- placeholder="No direct input needed",
727
- visible=False # ํ™”๋ฉด์—์„œ ๊ฐ์ถค
728
- )
729
-
730
- # ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ
731
- chat = gr.ChatInterface(
732
- fn=run,
733
- type="messages",
734
- chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
735
- textbox=gr.MultimodalTextbox(
736
- file_types=[
737
- ".webp", ".png", ".jpg", ".jpeg", ".gif",
738
- ".mp4", ".csv", ".txt", ".pdf"
739
- ],
740
- file_count="multiple",
741
- autofocus=True
742
- ),
743
- multimodal=True,
744
- additional_inputs=[
745
- system_prompt_box,
746
- max_tokens_slider,
747
- web_search_checkbox,
748
- web_search_text,
749
- ],
750
- stop_btn=False,
751
- title='<a href="https://discord.gg/openfreeai" target="_blank">https://discord.gg/openfreeai</a>',
752
- examples=examples,
753
- run_examples_on_click=False,
754
- cache_examples=False,
755
- css_paths=None,
756
- delete_cache=(1800, 1800),
757
- )
758
-
759
- # ์˜ˆ์ œ ์„น์…˜ - ์ด๋ฏธ ChatInterface์— examples๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ๋Š” ์„ค๋ช…๋งŒ ํ‘œ์‹œ
760
- with gr.Row(elem_id="examples_row"):
761
- with gr.Column(scale=12, elem_id="examples_container"):
762
- gr.Markdown("### Example Inputs (click to load)")
763
-
764
-
765
- if __name__ == "__main__":
766
- # ๋กœ์ปฌ์—์„œ๋งŒ ์‹คํ–‰ ์‹œ
767
- demo.launch()
768
-