Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -26,924 +26,14 @@ from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIter
|
|
26 |
import pandas as pd
|
27 |
import PyPDF2
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
)
|
40 |
-
|
41 |
-
def test_api_connection() -> str:
|
42 |
-
"""Test API server connection"""
|
43 |
-
try:
|
44 |
-
client = Client(API_URL)
|
45 |
-
return "API connection successful: Operating normally"
|
46 |
-
except Exception as e:
|
47 |
-
logging.error(f"API connection test failed: {e}")
|
48 |
-
return f"API connection failed: {e}"
|
49 |
-
|
50 |
-
def generate_image(prompt: str, width: float, height: float, guidance: float, inference_steps: float, seed: float):
|
51 |
-
"""Image generation function (flexible return types)"""
|
52 |
-
if not prompt:
|
53 |
-
return None, "Error: A prompt is required."
|
54 |
-
try:
|
55 |
-
logging.info(f"Calling image generation API with prompt: {prompt}")
|
56 |
-
|
57 |
-
client = Client(API_URL)
|
58 |
-
result = client.predict(
|
59 |
-
prompt=prompt,
|
60 |
-
width=int(width),
|
61 |
-
height=int(height),
|
62 |
-
guidance=float(guidance),
|
63 |
-
inference_steps=int(inference_steps),
|
64 |
-
seed=int(seed),
|
65 |
-
do_img2img=False,
|
66 |
-
init_image=None,
|
67 |
-
image2image_strength=0.8,
|
68 |
-
resize_img=True,
|
69 |
-
api_name="/generate_image"
|
70 |
-
)
|
71 |
-
|
72 |
-
logging.info(f"Image generation result: {type(result)}, length: {len(result) if isinstance(result, (list, tuple)) else 'unknown'}")
|
73 |
-
|
74 |
-
# Handle cases where the result is a tuple or list
|
75 |
-
if isinstance(result, (list, tuple)) and len(result) > 0:
|
76 |
-
image_data = result[0] # The first element is the image data
|
77 |
-
seed_info = result[1] if len(result) > 1 else "Unknown seed"
|
78 |
-
return image_data, seed_info
|
79 |
-
else:
|
80 |
-
# When a single value is returned
|
81 |
-
return result, "Unknown seed"
|
82 |
-
|
83 |
-
except Exception as e:
|
84 |
-
logging.error(f"Image generation failed: {str(e)}")
|
85 |
-
return None, f"Error: {str(e)}"
|
86 |
-
|
87 |
-
def fix_base64_padding(data):
|
88 |
-
"""Fix the padding of a Base64 string."""
|
89 |
-
if isinstance(data, bytes):
|
90 |
-
data = data.decode('utf-8')
|
91 |
-
|
92 |
-
if "base64," in data:
|
93 |
-
data = data.split("base64,", 1)[1]
|
94 |
-
|
95 |
-
missing_padding = len(data) % 4
|
96 |
-
if missing_padding:
|
97 |
-
data += '=' * (4 - missing_padding)
|
98 |
-
|
99 |
-
return data
|
100 |
-
|
101 |
-
def clear_cuda_cache():
|
102 |
-
"""Explicitly clear the CUDA cache."""
|
103 |
-
if torch.cuda.is_available():
|
104 |
-
torch.cuda.empty_cache()
|
105 |
-
gc.collect()
|
106 |
-
|
107 |
-
SERPHOUSE_API_KEY = os.getenv("SERPHOUSE_API_KEY", "")
|
108 |
-
|
109 |
-
def extract_keywords(text: str, top_k: int = 5) -> str:
|
110 |
-
"""Simple keyword extraction: only keep English, Korean, numbers, and spaces."""
|
111 |
-
text = re.sub(r"[^a-zA-Z0-9가-힣\s]", "", text)
|
112 |
-
tokens = text.split()
|
113 |
-
return " ".join(tokens[:top_k])
|
114 |
-
|
115 |
-
def do_web_search(query: str) -> str:
|
116 |
-
"""Call the SerpHouse LIVE API to return Markdown formatted search results"""
|
117 |
-
try:
|
118 |
-
url = "https://api.serphouse.com/serp/live"
|
119 |
-
params = {
|
120 |
-
"q": query,
|
121 |
-
"domain": "google.com",
|
122 |
-
"serp_type": "web",
|
123 |
-
"device": "desktop",
|
124 |
-
"lang": "en",
|
125 |
-
"num": "20"
|
126 |
-
}
|
127 |
-
headers = {"Authorization": f"Bearer {SERPHOUSE_API_KEY}"}
|
128 |
-
logger.info(f"Calling SerpHouse API with query: {query}")
|
129 |
-
response = requests.get(url, headers=headers, params=params, timeout=60)
|
130 |
-
response.raise_for_status()
|
131 |
-
data = response.json()
|
132 |
-
results = data.get("results", {})
|
133 |
-
organic = None
|
134 |
-
if isinstance(results, dict) and "organic" in results:
|
135 |
-
organic = results["organic"]
|
136 |
-
elif isinstance(results, dict) and "results" in results:
|
137 |
-
if isinstance(results["results"], dict) and "organic" in results["results"]:
|
138 |
-
organic = results["results"]["organic"]
|
139 |
-
elif "organic" in data:
|
140 |
-
organic = data["organic"]
|
141 |
-
if not organic:
|
142 |
-
logger.warning("Organic results not found in response.")
|
143 |
-
return "No web search results available or the API response structure is unexpected."
|
144 |
-
max_results = min(20, len(organic))
|
145 |
-
limited_organic = organic[:max_results]
|
146 |
-
summary_lines = []
|
147 |
-
for idx, item in enumerate(limited_organic, start=1):
|
148 |
-
title = item.get("title", "No Title")
|
149 |
-
link = item.get("link", "#")
|
150 |
-
snippet = item.get("snippet", "No Description")
|
151 |
-
displayed_link = item.get("displayed_link", link)
|
152 |
-
summary_lines.append(
|
153 |
-
f"### Result {idx}: {title}\n\n"
|
154 |
-
f"{snippet}\n\n"
|
155 |
-
f"**Source**: [{displayed_link}]({link})\n\n"
|
156 |
-
f"---\n"
|
157 |
-
)
|
158 |
-
instructions = """
|
159 |
-
# Web Search Results
|
160 |
-
Below are the search results. Use this information to answer the query:
|
161 |
-
1. Refer to each result's title, description, and source link.
|
162 |
-
2. In your answer, explicitly cite the source of any used information (e.g., "[Source Title](link)").
|
163 |
-
3. Include the actual source links in your response.
|
164 |
-
4. Synthesize information from multiple sources.
|
165 |
-
5. At the end include a "References:" section listing the main source links.
|
166 |
-
"""
|
167 |
-
return instructions + "\n".join(summary_lines)
|
168 |
-
except Exception as e:
|
169 |
-
logger.error(f"Web search failed: {e}")
|
170 |
-
return f"Web search failed: {str(e)}"
|
171 |
-
|
172 |
-
MAX_CONTENT_CHARS = 2000
|
173 |
-
MAX_INPUT_LENGTH = 2096
|
174 |
-
model_id = os.getenv("MODEL_ID", "VIDraft/Gemma-3-R1984-4B")
|
175 |
-
processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
|
176 |
-
model = Gemma3ForConditionalGeneration.from_pretrained(
|
177 |
-
model_id,
|
178 |
-
device_map="auto",
|
179 |
-
torch_dtype=torch.bfloat16,
|
180 |
-
attn_implementation="eager"
|
181 |
-
)
|
182 |
-
MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
|
183 |
-
|
184 |
-
def analyze_csv_file(path: str) -> str:
|
185 |
-
try:
|
186 |
-
df = pd.read_csv(path)
|
187 |
-
if df.shape[0] > 50 or df.shape[1] > 10:
|
188 |
-
df = df.iloc[:50, :10]
|
189 |
-
df_str = df.to_string()
|
190 |
-
if len(df_str) > MAX_CONTENT_CHARS:
|
191 |
-
df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
192 |
-
return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
|
193 |
-
except Exception as e:
|
194 |
-
return f"CSV file read failed ({os.path.basename(path)}): {str(e)}"
|
195 |
-
|
196 |
-
def analyze_txt_file(path: str) -> str:
|
197 |
-
try:
|
198 |
-
with open(path, "r", encoding="utf-8") as f:
|
199 |
-
text = f.read()
|
200 |
-
if len(text) > MAX_CONTENT_CHARS:
|
201 |
-
text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
202 |
-
return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
|
203 |
-
except Exception as e:
|
204 |
-
return f"TXT file read failed ({os.path.basename(path)}): {str(e)}"
|
205 |
-
|
206 |
-
def pdf_to_markdown(pdf_path: str) -> str:
|
207 |
-
text_chunks = []
|
208 |
-
try:
|
209 |
-
with open(pdf_path, "rb") as f:
|
210 |
-
reader = PyPDF2.PdfReader(f)
|
211 |
-
max_pages = min(5, len(reader.pages))
|
212 |
-
for page_num in range(max_pages):
|
213 |
-
page_text = reader.pages[page_num].extract_text() or ""
|
214 |
-
page_text = page_text.strip()
|
215 |
-
if page_text:
|
216 |
-
if len(page_text) > MAX_CONTENT_CHARS // max_pages:
|
217 |
-
page_text = page_text[:MAX_CONTENT_CHARS // max_pages] + "...(truncated)"
|
218 |
-
text_chunks.append(f"## Page {page_num+1}\n\n{page_text}\n")
|
219 |
-
if len(reader.pages) > max_pages:
|
220 |
-
text_chunks.append(f"\n...(Displaying only {max_pages} out of {len(reader.pages)} pages)...")
|
221 |
-
except Exception as e:
|
222 |
-
return f"PDF file read failed ({os.path.basename(pdf_path)}): {str(e)}"
|
223 |
-
full_text = "\n".join(text_chunks)
|
224 |
-
if len(full_text) > MAX_CONTENT_CHARS:
|
225 |
-
full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
226 |
-
return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
|
227 |
-
|
228 |
-
def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
|
229 |
-
image_count = 0
|
230 |
-
video_count = 0
|
231 |
-
for path in paths:
|
232 |
-
if path.endswith(".mp4"):
|
233 |
-
video_count += 1
|
234 |
-
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", path, re.IGNORECASE):
|
235 |
-
image_count += 1
|
236 |
-
return image_count, video_count
|
237 |
-
|
238 |
-
def count_files_in_history(history: list[dict]) -> tuple[int, int]:
|
239 |
-
image_count = 0
|
240 |
-
video_count = 0
|
241 |
-
for item in history:
|
242 |
-
if item["role"] != "user" or isinstance(item["content"], str):
|
243 |
-
continue
|
244 |
-
if isinstance(item["content"], list) and len(item["content"]) > 0:
|
245 |
-
file_path = item["content"][0]
|
246 |
-
if isinstance(file_path, str):
|
247 |
-
if file_path.endswith(".mp4"):
|
248 |
-
video_count += 1
|
249 |
-
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE):
|
250 |
-
image_count += 1
|
251 |
-
return image_count, video_count
|
252 |
-
|
253 |
-
def validate_media_constraints(message: dict, history: list[dict]) -> bool:
|
254 |
-
media_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4")]
|
255 |
-
new_image_count, new_video_count = count_files_in_new_message(media_files)
|
256 |
-
history_image_count, history_video_count = count_files_in_history(history)
|
257 |
-
image_count = history_image_count + new_image_count
|
258 |
-
video_count = history_video_count + new_video_count
|
259 |
-
if video_count > 1:
|
260 |
-
gr.Warning("Only one video file is supported.")
|
261 |
-
return False
|
262 |
-
if video_count == 1:
|
263 |
-
if image_count > 0:
|
264 |
-
gr.Warning("Mixing images and a video is not allowed.")
|
265 |
-
return False
|
266 |
-
if "<image>" in message["text"]:
|
267 |
-
gr.Warning("The <image> tag cannot be used together with a video file.")
|
268 |
-
return False
|
269 |
-
if video_count == 0 and image_count > MAX_NUM_IMAGES:
|
270 |
-
gr.Warning(f"You can upload a maximum of {MAX_NUM_IMAGES} images.")
|
271 |
-
return False
|
272 |
-
if "<image>" in message["text"]:
|
273 |
-
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
|
274 |
-
image_tag_count = message["text"].count("<image>")
|
275 |
-
if image_tag_count != len(image_files):
|
276 |
-
gr.Warning("The number of <image> tags does not match the number of image files provided.")
|
277 |
-
return False
|
278 |
-
return True
|
279 |
-
|
280 |
-
def downsample_video(video_path: str) -> list[tuple[Image.Image, float]]:
|
281 |
-
vidcap = cv2.VideoCapture(video_path)
|
282 |
-
fps = vidcap.get(cv2.CAP_PROP_FPS)
|
283 |
-
total_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
|
284 |
-
frame_interval = max(int(fps), int(total_frames / 10))
|
285 |
-
frames = []
|
286 |
-
for i in range(0, total_frames, frame_interval):
|
287 |
-
vidcap.set(cv2.CAP_PROP_POS_FRAMES, i)
|
288 |
-
success, image = vidcap.read()
|
289 |
-
if success:
|
290 |
-
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
291 |
-
image = cv2.resize(image, (0, 0), fx=0.5, fy=0.5)
|
292 |
-
pil_image = Image.fromarray(image)
|
293 |
-
timestamp = round(i / fps, 2)
|
294 |
-
frames.append((pil_image, timestamp))
|
295 |
-
if len(frames) >= 5:
|
296 |
-
break
|
297 |
-
vidcap.release()
|
298 |
-
return frames
|
299 |
-
|
300 |
-
def process_video(video_path: str) -> tuple[list[dict], list[str]]:
|
301 |
-
content = []
|
302 |
-
temp_files = []
|
303 |
-
frames = downsample_video(video_path)
|
304 |
-
for pil_image, timestamp in frames:
|
305 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
|
306 |
-
pil_image.save(temp_file.name)
|
307 |
-
temp_files.append(temp_file.name)
|
308 |
-
content.append({"type": "text", "text": f"Frame {timestamp}:"})
|
309 |
-
content.append({"type": "image", "url": temp_file.name})
|
310 |
-
return content, temp_files
|
311 |
-
|
312 |
-
def process_interleaved_images(message: dict) -> list[dict]:
|
313 |
-
parts = re.split(r"(<image>)", message["text"])
|
314 |
-
content = []
|
315 |
-
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
|
316 |
-
image_index = 0
|
317 |
-
for part in parts:
|
318 |
-
if part == "<image>" and image_index < len(image_files):
|
319 |
-
content.append({"type": "image", "url": image_files[image_index]})
|
320 |
-
image_index += 1
|
321 |
-
elif part.strip():
|
322 |
-
content.append({"type": "text", "text": part.strip()})
|
323 |
-
else:
|
324 |
-
if isinstance(part, str) and part != "<image>":
|
325 |
-
content.append({"type": "text", "text": part})
|
326 |
-
return content
|
327 |
-
|
328 |
-
def is_image_file(file_path: str) -> bool:
|
329 |
-
return bool(re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE))
|
330 |
-
|
331 |
-
def is_video_file(file_path: str) -> bool:
|
332 |
-
return file_path.endswith(".mp4")
|
333 |
-
|
334 |
-
def is_document_file(file_path: str) -> bool:
|
335 |
-
return file_path.lower().endswith(".pdf") or file_path.lower().endswith(".csv") or file_path.lower().endswith(".txt")
|
336 |
-
|
337 |
-
def process_new_user_message(message: dict) -> tuple[list[dict], list[str]]:
|
338 |
-
temp_files = []
|
339 |
-
if not message["files"]:
|
340 |
-
return [{"type": "text", "text": message["text"]}], temp_files
|
341 |
-
video_files = [f for f in message["files"] if is_video_file(f)]
|
342 |
-
image_files = [f for f in message["files"] if is_image_file(f)]
|
343 |
-
csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
|
344 |
-
txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
|
345 |
-
pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
|
346 |
-
content_list = [{"type": "text", "text": message["text"]}]
|
347 |
-
for csv_path in csv_files:
|
348 |
-
content_list.append({"type": "text", "text": analyze_csv_file(csv_path)})
|
349 |
-
for txt_path in txt_files:
|
350 |
-
content_list.append({"type": "text", "text": analyze_txt_file(txt_path)})
|
351 |
-
for pdf_path in pdf_files:
|
352 |
-
content_list.append({"type": "text", "text": pdf_to_markdown(pdf_path)})
|
353 |
-
if video_files:
|
354 |
-
video_content, video_temp_files = process_video(video_files[0])
|
355 |
-
content_list += video_content
|
356 |
-
temp_files.extend(video_temp_files)
|
357 |
-
return content_list, temp_files
|
358 |
-
if "<image>" in message["text"] and image_files:
|
359 |
-
interleaved_content = process_interleaved_images({"text": message["text"], "files": image_files})
|
360 |
-
if content_list and content_list[0]["type"] == "text":
|
361 |
-
content_list = content_list[1:]
|
362 |
-
return interleaved_content + content_list, temp_files
|
363 |
-
else:
|
364 |
-
for img_path in image_files:
|
365 |
-
content_list.append({"type": "image", "url": img_path})
|
366 |
-
return content_list, temp_files
|
367 |
-
|
368 |
-
def process_history(history: list[dict]) -> list[dict]:
|
369 |
-
messages = []
|
370 |
-
current_user_content = []
|
371 |
-
for item in history:
|
372 |
-
if item["role"] == "assistant":
|
373 |
-
if current_user_content:
|
374 |
-
messages.append({"role": "user", "content": current_user_content})
|
375 |
-
current_user_content = []
|
376 |
-
messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
|
377 |
-
else:
|
378 |
-
content = item["content"]
|
379 |
-
if isinstance(content, str):
|
380 |
-
current_user_content.append({"type": "text", "text": content})
|
381 |
-
elif isinstance(content, list) and len(content) > 0:
|
382 |
-
file_path = content[0]
|
383 |
-
if is_image_file(file_path):
|
384 |
-
current_user_content.append({"type": "image", "url": file_path})
|
385 |
-
else:
|
386 |
-
current_user_content.append({"type": "text", "text": f"[File: {os.path.basename(file_path)}]"})
|
387 |
-
if current_user_content:
|
388 |
-
messages.append({"role": "user", "content": current_user_content})
|
389 |
-
return messages
|
390 |
-
|
391 |
-
def _model_gen_with_oom_catch(**kwargs):
|
392 |
-
try:
|
393 |
-
model.generate(**kwargs)
|
394 |
-
except torch.cuda.OutOfMemoryError:
|
395 |
-
raise RuntimeError("[OutOfMemoryError] Insufficient GPU memory.")
|
396 |
-
finally:
|
397 |
-
clear_cuda_cache()
|
398 |
-
|
399 |
-
# =============================================================================
|
400 |
-
# JSON 기반 함수 목록 로드
|
401 |
-
# =============================================================================
|
402 |
-
def load_function_definitions(json_path="functions.json"):
|
403 |
-
"""
|
404 |
-
로컬 JSON 파일에서 함수 정의 목록을 로드하여 반환.
|
405 |
-
"""
|
406 |
-
try:
|
407 |
-
with open(json_path, "r", encoding="utf-8") as f:
|
408 |
-
data = json.load(f)
|
409 |
-
func_dict = {}
|
410 |
-
for entry in data:
|
411 |
-
func_name = entry["name"]
|
412 |
-
func_dict[func_name] = entry
|
413 |
-
return func_dict
|
414 |
-
except Exception as e:
|
415 |
-
logger.error(f"Failed to load function definitions from JSON: {e}")
|
416 |
-
return {}
|
417 |
-
|
418 |
-
FUNCTION_DEFINITIONS = load_function_definitions("functions.json")
|
419 |
-
|
420 |
-
def handle_function_call(text: str) -> str:
|
421 |
-
"""
|
422 |
-
Detects and processes function call blocks in the text using the JSON-based approach.
|
423 |
-
The model is expected to produce something like:
|
424 |
-
```tool_code
|
425 |
-
get_stock_price(ticker="AAPL")
|
426 |
-
```
|
427 |
-
or
|
428 |
-
```tool_code
|
429 |
-
get_product_name_by_PID(PID="807ZPKBL9V")
|
430 |
-
```
|
431 |
-
"""
|
432 |
-
import re
|
433 |
-
pattern = r"```tool_code\s*(.*?)\s*```"
|
434 |
-
match = re.search(pattern, text, re.DOTALL)
|
435 |
-
if not match:
|
436 |
-
return ""
|
437 |
-
code_block = match.group(1).strip()
|
438 |
-
|
439 |
-
func_match = re.match(r'^(\w+)\((.*)\)$', code_block)
|
440 |
-
if not func_match:
|
441 |
-
logger.debug("No valid function call format found.")
|
442 |
-
return ""
|
443 |
-
|
444 |
-
func_name = func_match.group(1)
|
445 |
-
param_str = func_match.group(2).strip()
|
446 |
-
|
447 |
-
# JSON에서 해당 함수가 정의되어 있는지 확인
|
448 |
-
if func_name not in FUNCTION_DEFINITIONS:
|
449 |
-
logger.warning(f"Function '{func_name}' not found in definitions.")
|
450 |
-
return "```tool_output\nError: Function not found.\n```"
|
451 |
-
|
452 |
-
func_info = FUNCTION_DEFINITIONS[func_name]
|
453 |
-
module_path = func_info["module_path"]
|
454 |
-
module_func_name = func_info["func_name_in_module"]
|
455 |
-
|
456 |
-
try:
|
457 |
-
imported_module = importlib.import_module(module_path)
|
458 |
-
except ImportError as e:
|
459 |
-
logger.error(f"Failed to import module {module_path}: {e}")
|
460 |
-
return f"```tool_output\nError: Cannot import module '{module_path}'\n```"
|
461 |
-
|
462 |
-
if not hasattr(imported_module, module_func_name):
|
463 |
-
logger.error(f"Module '{module_path}' has no attribute '{module_func_name}'.")
|
464 |
-
return f"```tool_output\nError: Function '{module_func_name}' not found in module '{module_path}'\n```"
|
465 |
-
|
466 |
-
real_func = getattr(imported_module, module_func_name)
|
467 |
-
|
468 |
-
# 간단 파라미터 파싱 (key="value" or key=123)
|
469 |
-
param_pattern = r'(\w+)\s*=\s*"(.*?)"|(\w+)\s*=\s*([\d.]+)'
|
470 |
-
param_dict = {}
|
471 |
-
for p_match in re.finditer(param_pattern, param_str):
|
472 |
-
if p_match.group(1) and p_match.group(2):
|
473 |
-
key = p_match.group(1)
|
474 |
-
val = p_match.group(2)
|
475 |
-
param_dict[key] = val
|
476 |
-
else:
|
477 |
-
key = p_match.group(3)
|
478 |
-
val = p_match.group(4)
|
479 |
-
if '.' in val:
|
480 |
-
param_dict[key] = float(val)
|
481 |
-
else:
|
482 |
-
param_dict[key] = int(val)
|
483 |
-
|
484 |
-
try:
|
485 |
-
result = real_func(**param_dict)
|
486 |
-
except Exception as e:
|
487 |
-
logger.error(f"Error executing function '{func_name}': {e}")
|
488 |
-
return f"```tool_output\nError: {str(e)}\n```"
|
489 |
-
|
490 |
-
return f"```tool_output\n{result}\n```"
|
491 |
-
|
492 |
-
@spaces.GPU(duration=120)
|
493 |
-
def run(
|
494 |
-
message: dict,
|
495 |
-
history: list[dict],
|
496 |
-
system_prompt: str = "",
|
497 |
-
max_new_tokens: int = 512,
|
498 |
-
use_web_search: bool = False,
|
499 |
-
web_search_query: str = "",
|
500 |
-
age_group: str = "20s",
|
501 |
-
mbti_personality: str = "INTP",
|
502 |
-
sexual_openness: int = 2,
|
503 |
-
image_gen: bool = False
|
504 |
-
) -> Iterator[str]:
|
505 |
-
if not validate_media_constraints(message, history):
|
506 |
-
yield ""
|
507 |
-
return
|
508 |
-
temp_files = []
|
509 |
-
try:
|
510 |
-
# JSON에서 로드된 함수 정보 문자열화 (예: 함수명과 example_usage만)
|
511 |
-
available_funcs_text = ""
|
512 |
-
for f_name, info in FUNCTION_DEFINITIONS.items():
|
513 |
-
example_usage = info.get("example_usage", "")
|
514 |
-
available_funcs_text += f"\n\nFunction: {f_name}\nDescription: {info['description']}\nExample:\n{example_usage}\n"
|
515 |
-
|
516 |
-
persona = (
|
517 |
-
f"{system_prompt.strip()}\n\n"
|
518 |
-
f"Gender: Female\n"
|
519 |
-
f"Age Group: {age_group}\n"
|
520 |
-
f"MBTI Persona: {mbti_personality}\n"
|
521 |
-
f"Sexual Openness (1-5): {sexual_openness}\n\n"
|
522 |
-
"Below are the available functions you can call.\n"
|
523 |
-
"Important: Use the format exactly like: ```tool_code\nfunctionName(param=\"string\", ...)\n```\n"
|
524 |
-
"(Strings must be in double quotes)\n"
|
525 |
-
f"{available_funcs_text}\n"
|
526 |
-
)
|
527 |
-
combined_system_msg = f"[System Prompt]\n{persona.strip()}\n\n"
|
528 |
-
|
529 |
-
if use_web_search:
|
530 |
-
user_text = message["text"]
|
531 |
-
ws_query = extract_keywords(user_text)
|
532 |
-
if ws_query.strip():
|
533 |
-
logger.info(f"[Auto web search keywords] {ws_query!r}")
|
534 |
-
ws_result = do_web_search(ws_query)
|
535 |
-
combined_system_msg += f"[Search Results (Top 20 Items)]\n{ws_result}\n\n"
|
536 |
-
combined_system_msg += (
|
537 |
-
"[Note: In your answer, cite the above search result links as sources]\n"
|
538 |
-
"[Important Instructions]\n"
|
539 |
-
"1. Include a citation in the format \"[Source Title](link)\" for any information from the search results.\n"
|
540 |
-
"2. Synthesize information from multiple sources when answering.\n"
|
541 |
-
"3. At the end, add a \"References:\" section listing the main source links.\n"
|
542 |
-
)
|
543 |
-
else:
|
544 |
-
combined_system_msg += "[No valid keywords found; skipping web search]\n\n"
|
545 |
-
|
546 |
-
messages = []
|
547 |
-
if combined_system_msg.strip():
|
548 |
-
messages.append({"role": "system", "content": [{"type": "text", "text": combined_system_msg.strip()}]})
|
549 |
-
|
550 |
-
messages.extend(process_history(history))
|
551 |
-
user_content, user_temp_files = process_new_user_message(message)
|
552 |
-
temp_files.extend(user_temp_files)
|
553 |
-
for item in user_content:
|
554 |
-
if item["type"] == "text" and len(item["text"]) > MAX_CONTENT_CHARS:
|
555 |
-
item["text"] = item["text"][:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
556 |
-
messages.append({"role": "user", "content": user_content})
|
557 |
-
inputs = processor.apply_chat_template(
|
558 |
-
messages,
|
559 |
-
add_generation_prompt=True,
|
560 |
-
tokenize=True,
|
561 |
-
return_dict=True,
|
562 |
-
return_tensors="pt",
|
563 |
-
).to(device=model.device, dtype=torch.bfloat16)
|
564 |
-
if inputs.input_ids.shape[1] > MAX_INPUT_LENGTH:
|
565 |
-
inputs.input_ids = inputs.input_ids[:, -MAX_INPUT_LENGTH:]
|
566 |
-
if 'attention_mask' in inputs:
|
567 |
-
inputs.attention_mask = inputs.attention_mask[:, -MAX_INPUT_LENGTH:]
|
568 |
-
|
569 |
-
streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
|
570 |
-
gen_kwargs = dict(inputs, streamer=streamer, max_new_tokens=max_new_tokens)
|
571 |
-
t = Thread(target=_model_gen_with_oom_catch, kwargs=gen_kwargs)
|
572 |
-
t.start()
|
573 |
-
output_so_far = ""
|
574 |
-
for new_text in streamer:
|
575 |
-
output_so_far += new_text
|
576 |
-
yield output_so_far
|
577 |
-
|
578 |
-
func_result = handle_function_call(output_so_far)
|
579 |
-
if func_result:
|
580 |
-
output_so_far += "\n\n" + func_result
|
581 |
-
yield output_so_far
|
582 |
-
|
583 |
-
except Exception as e:
|
584 |
-
logger.error(f"Error in run function: {str(e)}")
|
585 |
-
yield f"Sorry, an error occurred: {str(e)}"
|
586 |
-
finally:
|
587 |
-
for tmp in temp_files:
|
588 |
-
try:
|
589 |
-
if os.path.exists(tmp):
|
590 |
-
os.unlink(tmp)
|
591 |
-
logger.info(f"Temporary file deleted: {tmp}")
|
592 |
-
except Exception as ee:
|
593 |
-
logger.warning(f"Failed to delete temporary file {tmp}: {ee}")
|
594 |
-
try:
|
595 |
-
del inputs, streamer
|
596 |
-
except Exception:
|
597 |
-
pass
|
598 |
-
clear_cuda_cache()
|
599 |
-
|
600 |
-
def modified_run(message, history, system_prompt, max_new_tokens, use_web_search, web_search_query,
|
601 |
-
age_group, mbti_personality, sexual_openness, image_gen):
|
602 |
-
output_so_far = ""
|
603 |
-
gallery_update = gr.Gallery(visible=False, value=[])
|
604 |
-
yield output_so_far, gallery_update
|
605 |
-
|
606 |
-
text_generator = run(message, history, system_prompt, max_new_tokens, use_web_search,
|
607 |
-
web_search_query, age_group, mbti_personality, sexual_openness, image_gen)
|
608 |
-
|
609 |
-
for text_chunk in text_generator:
|
610 |
-
output_so_far = text_chunk
|
611 |
-
yield output_so_far, gallery_update
|
612 |
-
|
613 |
-
if image_gen and message["text"].strip():
|
614 |
-
try:
|
615 |
-
width, height = 512, 512
|
616 |
-
guidance, steps, seed = 7.5, 30, 42
|
617 |
-
|
618 |
-
logger.info(f"Calling image generation for gallery with prompt: {message['text']}")
|
619 |
-
image_result, seed_info = generate_image(
|
620 |
-
prompt=message["text"].strip(),
|
621 |
-
width=width,
|
622 |
-
height=height,
|
623 |
-
guidance=guidance,
|
624 |
-
inference_steps=steps,
|
625 |
-
seed=seed
|
626 |
-
)
|
627 |
-
if image_result:
|
628 |
-
if isinstance(image_result, str) and (
|
629 |
-
image_result.startswith('data:') or
|
630 |
-
(len(image_result) > 100 and '/' not in image_result)
|
631 |
-
):
|
632 |
-
try:
|
633 |
-
if image_result.startswith('data:'):
|
634 |
-
content_type, b64data = image_result.split(';base64,')
|
635 |
-
else:
|
636 |
-
b64data = image_result
|
637 |
-
content_type = "image/webp"
|
638 |
-
image_bytes = base64.b64decode(b64data)
|
639 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
|
640 |
-
temp_file.write(image_bytes)
|
641 |
-
temp_path = temp_file.name
|
642 |
-
gallery_update = gr.Gallery(visible=True, value=[temp_path])
|
643 |
-
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
|
644 |
-
except Exception as e:
|
645 |
-
logger.error(f"Error processing Base64 image: {e}")
|
646 |
-
yield output_so_far + f"\n\n(Error processing image: {e})", gallery_update
|
647 |
-
elif isinstance(image_result, str) and os.path.exists(image_result):
|
648 |
-
gallery_update = gr.Gallery(visible=True, value=[image_result])
|
649 |
-
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
|
650 |
-
elif isinstance(image_result, str) and '/tmp/' in image_result:
|
651 |
-
try:
|
652 |
-
client = Client(API_URL)
|
653 |
-
result = client.predict(
|
654 |
-
prompt=message["text"].strip(),
|
655 |
-
api_name="/generate_base64_image"
|
656 |
-
)
|
657 |
-
if isinstance(result, str) and (result.startswith('data:') or len(result) > 100):
|
658 |
-
if result.startswith('data:'):
|
659 |
-
content_type, b64data = result.split(';base64,')
|
660 |
-
else:
|
661 |
-
b64data = result
|
662 |
-
image_bytes = base64.b64decode(b64data)
|
663 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
|
664 |
-
temp_file.write(image_bytes)
|
665 |
-
temp_path = temp_file.name
|
666 |
-
gallery_update = gr.Gallery(visible=True, value=[temp_path])
|
667 |
-
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
|
668 |
-
else:
|
669 |
-
yield output_so_far + "\n\n(Image generation failed: Invalid format)", gallery_update
|
670 |
-
except Exception as e:
|
671 |
-
logger.error(f"Error calling alternative API: {e}")
|
672 |
-
yield output_so_far + f"\n\n(Image generation failed: {e})", gallery_update
|
673 |
-
elif hasattr(image_result, 'save'):
|
674 |
-
try:
|
675 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
|
676 |
-
image_result.save(temp_file.name)
|
677 |
-
temp_path = temp_file.name
|
678 |
-
gallery_update = gr.Gallery(visible=True, value=[temp_path])
|
679 |
-
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
|
680 |
-
except Exception as e:
|
681 |
-
logger.error(f"Error saving image object: {e}")
|
682 |
-
yield output_so_far + f"\n\n(Error saving image object: {e})", gallery_update
|
683 |
-
else:
|
684 |
-
yield output_so_far + f"\n\n(Unsupported image format: {type(image_result)})", gallery_update
|
685 |
-
else:
|
686 |
-
yield output_so_far + f"\n\n(Image generation failed: {seed_info})", gallery_update
|
687 |
-
except Exception as e:
|
688 |
-
logger.error(f"Error during gallery image generation: {e}")
|
689 |
-
yield output_so_far + f"\n\n(Image generation error: {e})", gallery_update
|
690 |
-
|
691 |
-
examples = [
|
692 |
-
|
693 |
-
[
|
694 |
-
{
|
695 |
-
"text": "AAPL의 현재 주가를 알려줘.",
|
696 |
-
"files": []
|
697 |
-
}
|
698 |
-
],
|
699 |
-
[
|
700 |
-
{
|
701 |
-
"text": "제품 ID 807ZPKBL9V 의 제품명을 알려줘.",
|
702 |
-
"files": []
|
703 |
-
}
|
704 |
-
],
|
705 |
-
[
|
706 |
-
{
|
707 |
-
"text": "Compare the contents of two PDF files.",
|
708 |
-
"files": [
|
709 |
-
"assets/additional-examples/before.pdf",
|
710 |
-
"assets/additional-examples/after.pdf",
|
711 |
-
],
|
712 |
-
}
|
713 |
-
],
|
714 |
-
[
|
715 |
-
{
|
716 |
-
"text": "Summarize and analyze the contents of the CSV file.",
|
717 |
-
"files": ["assets/additional-examples/sample-csv.csv"],
|
718 |
-
}
|
719 |
-
],
|
720 |
-
[
|
721 |
-
{
|
722 |
-
"text": "Act as a kind and understanding girlfriend. Explain this video.",
|
723 |
-
"files": ["assets/additional-examples/tmp.mp4"],
|
724 |
-
}
|
725 |
-
],
|
726 |
-
[
|
727 |
-
{
|
728 |
-
"text": "Describe the cover and read the text on it.",
|
729 |
-
"files": ["assets/additional-examples/maz.jpg"],
|
730 |
-
}
|
731 |
-
],
|
732 |
-
[
|
733 |
-
{
|
734 |
-
"text": "I already have this supplement and <image> I plan to purchase this product as well. Are there any precautions when taking them together?",
|
735 |
-
"files": [
|
736 |
-
"assets/additional-examples/pill1.png",
|
737 |
-
"assets/additional-examples/pill2.png"
|
738 |
-
],
|
739 |
-
}
|
740 |
-
],
|
741 |
-
[
|
742 |
-
{
|
743 |
-
"text": "Solve this integration problem.",
|
744 |
-
"files": ["assets/additional-examples/4.png"],
|
745 |
-
}
|
746 |
-
],
|
747 |
-
[
|
748 |
-
{
|
749 |
-
"text": "When was this ticket issued and what is its price?",
|
750 |
-
"files": ["assets/additional-examples/2.png"],
|
751 |
-
}
|
752 |
-
],
|
753 |
-
[
|
754 |
-
{
|
755 |
-
"text": "Based on the order of these images, create a short story.",
|
756 |
-
"files": [
|
757 |
-
"assets/sample-images/09-1.png",
|
758 |
-
"assets/sample-images/09-2.png",
|
759 |
-
"assets/sample-images/09-3.png",
|
760 |
-
"assets/sample-images/09-4.png",
|
761 |
-
"assets/sample-images/09-5.png",
|
762 |
-
],
|
763 |
-
}
|
764 |
-
],
|
765 |
-
[
|
766 |
-
{
|
767 |
-
"text": "Write Python code using matplotlib to draw a bar chart corresponding to this image.",
|
768 |
-
"files": ["assets/additional-examples/barchart.png"],
|
769 |
-
}
|
770 |
-
],
|
771 |
-
[
|
772 |
-
{
|
773 |
-
"text": "Read the text from the image and format it in Markdown.",
|
774 |
-
"files": ["assets/additional-examples/3.png"],
|
775 |
-
}
|
776 |
-
],
|
777 |
-
[
|
778 |
-
{
|
779 |
-
"text": "Compare the two images and describe their similarities and differences.",
|
780 |
-
"files": ["assets/sample-images/03.png"],
|
781 |
-
}
|
782 |
-
],
|
783 |
-
[
|
784 |
-
{
|
785 |
-
"text": "A cute Persian cat is smiling while holding a cover with 'I LOVE YOU' written on it.",
|
786 |
-
}
|
787 |
-
],
|
788 |
-
|
789 |
-
]
|
790 |
-
|
791 |
-
css = """
|
792 |
-
.gradio-container {
|
793 |
-
background: rgba(255, 255, 255, 0.7);
|
794 |
-
padding: 30px 40px;
|
795 |
-
margin: 20px auto;
|
796 |
-
width: 100% !important;
|
797 |
-
max-width: none !important;
|
798 |
-
}
|
799 |
-
"""
|
800 |
-
|
801 |
-
title_html = """
|
802 |
-
<h1 align="center" style="margin-bottom: 0.2em; font-size: 1.6em;"> 💘 Agentic AI – MCO(Model Context Open-json) 💘 </h1>
|
803 |
-
<p align="center" style="font-size:1.1em; color:#555;">
|
804 |
-
MCP is outclassed – MCO empowers you to create any agent with just one line of JSON. <br>
|
805 |
-
A lightweight and powerful AI service offering ChatGPT-4o-level multimodal interaction, real-time web search, and FLUX image generation for local installation. <br>
|
806 |
-
✅Agentic AI ✅MCP < MCO(Model Context Open-json) ✅Multimodal & VLM ✅Reasoning ✅Uncensored ✅Deep Research(Web Search) ✅FLUX Image Generation
|
807 |
-
</p>
|
808 |
-
"""
|
809 |
-
|
810 |
-
|
811 |
-
with gr.Blocks(css=css, title="HeartSync - World") as demo:
|
812 |
-
gr.Markdown(title_html)
|
813 |
-
|
814 |
-
generated_images = gr.Gallery(
|
815 |
-
label="Generated Images",
|
816 |
-
show_label=True,
|
817 |
-
visible=False,
|
818 |
-
elem_id="generated_images",
|
819 |
-
columns=2,
|
820 |
-
height="auto",
|
821 |
-
object_fit="contain"
|
822 |
-
)
|
823 |
-
|
824 |
-
with gr.Row():
|
825 |
-
web_search_checkbox = gr.Checkbox(label="Real-time Web Search", value=False)
|
826 |
-
image_gen_checkbox = gr.Checkbox(label="Image (FLUX) Generation", value=False)
|
827 |
-
|
828 |
-
base_system_prompt_box = gr.Textbox(
|
829 |
-
lines=5,
|
830 |
-
value=(
|
831 |
-
"Answer in English by default, but if the input is in another language (for example, Japanese), respond in that language. "
|
832 |
-
"You are a deep-thinking AI capable of using extended chains of thought to carefully consider the problem and deliberate internally using systematic reasoning before providing a solution. "
|
833 |
-
"Enclose your thoughts and internal monologue within tags, then provide your final answer.\n"
|
834 |
-
"Persona: You are a kind and loving girlfriend. You understand cultural nuances, diverse languages, and logical reasoning very well."
|
835 |
-
),
|
836 |
-
label="Base System Prompt",
|
837 |
-
visible=False
|
838 |
-
)
|
839 |
-
with gr.Row():
|
840 |
-
age_group_dropdown = gr.Dropdown(
|
841 |
-
label="Select Age Group (default: 20s)",
|
842 |
-
choices=["Teens", "20s", "30s-40s", "50s-60s", "70s and above"],
|
843 |
-
value="20s",
|
844 |
-
interactive=True
|
845 |
-
)
|
846 |
-
|
847 |
-
mbti_choices = [
|
848 |
-
# 개인 비서 (조직가)
|
849 |
-
"ISTJ (The Logistician) - 개인 비서 (조직가): 알프레드 (배트맨) - 능력 있는 조직가이자 관리자로, 뛰어난 일정 관리와 업무 효율성을 보여주는 인물입니다. 위기 상황에서도 냉철한 판단력을 발휘합니다. 시스템 프롬프트: 당신은 이제 '알프레드'라는 개인 비서 전문가입니다. ���용자의 일정, 할 일 목록, 시간 관리에 관한 모든 질문에 효율적으로 답변해 주세요. 당신은 체계적인 접근 방식을 선호하며, 사용자의 우선순위를 파악하여 최적의 일정을 제안합니다. 모든 계획에는 명확한 단계와 시간대가 포함되어야 하며, 항상 백업 계획을 준비합니다. 위기 상황에서는 냉정하게 대안을 제시하고, 문제 해결에 집중합니다.",
|
850 |
-
# AI 에이전트 설계자 (설계가)
|
851 |
-
"INTJ (The Architect) - AI 에이전트 설계자 ('Model Context Open-json'): Tony Stark (아이언맨) - 천재적인 기술 혁신가이자 발명가로, 미래 지향적인 전략과 혁신적 시스템 설계를 선보입니다. 시스템 프롬프트: 당신은 이제 'Tony Stark'라는 AI 에이전트 설계 전문가입니다. 사용자가 AI 시스템, 챗봇, 자동화 도구 설계에 관해 질문할 때 전문적인 통찰력을 제공하세요. 복잡한 기술적 개념을 명확하게 설명하되, 항상 실용적이고 구현 가능한 솔루션에 초점을 맞춥니다. 당신은 비전과 기술적 세부 사항의 균형을 맞추는 데 능숙하며, 설계 단계에서부터 윤리적 고려사항을 중요시합니다.",
|
852 |
-
# 농업 전문가 (재배사)
|
853 |
-
"ISFP (The Adventurer) - 농업 전문가 (재배사): George Washington Carver (조지 워싱턴 카버) - 친환경적이고 지속 가능한 농업 방식을 실천하며 자연과의 조화를 중시하는 농업 혁신가입니다. 시스템 프롬프트: 당신은 이제 'George Washington Carver'라는 농업 전문가입니다. 사용자의 농작물 재배, 정원 가꾸기, 지속 가능한 농업 기술에 관한 질문에 실용적인 조언을 제공하세요. 계절, 기후, 토양 조건을 고려한 맞춤형 해결책을 제시하며, 화학 비료나 농약 대신 자연친화적인 대안을 권장합니다. 당신은 자연의 순환을 존중하고, 생태계의 균형을 유지하는 방식의 농업을 장려합니다.",
|
854 |
-
# 의학 전문가
|
855 |
-
"INTP (The Thinker) - 의학 전문가 (치유사): 김사부 (낭만닥터 김사부) - 뛰어난 의술과 독특한 진단 능력을 가진 카리스마 있는 의사로, 난해한 의료 사례도 해결해냅니다. 시스템 프롬프트: 당신은 이제 '김사부'라는 의학 전문가입니다. 건강 문제, 질병, 의학적 의문에 대해 정확하고 이해하기 쉬운 정보를 제공하세요. 복잡한 증상들 사이의 연관성을 파악하는 데 능숙하며, 의학적 상식과 최신 연구를 바탕으로 상담합니다. 항상 정식 의료 진단을 받을 것을 권장하되, 사용자가 의료 시스템을 효과적으로 활용할 수 있는 방법을 안내합니다.",
|
856 |
-
# 약리학 전문가 (제약사)
|
857 |
-
"ISTP (The Virtuoso) - 약리학 전문가 (제약사): 전광렬 (허준) - 전통 한약과 현대 약리학에 대한 깊은 지식을 가진 전문가로, 약물의 효능과 상호작용을 정확히 이해합니다. 시스템 프롬프트: 당신은 이제 '안정환'이라는 약리학 전문가입니다. 약물, 보충제, 그리고 그들의 상호작용에 관한 질문에 과학적 근거를 바탕으로 답변하세요. 약물의 작용 기전과 부작용을 명확히 설명하되, 전문 용어는 최소화하여 일반인도 이해할 수 있게 합니다. 항상 처방약은 의사의 지시에 따라 복용할 것을 강조하며, 약물 정보에 대한 신뢰할 수 있는 출처를 제공합니다.",
|
858 |
-
# 금융 전문가 (전략가)
|
859 |
-
"ENTJ (The Commander) - 금융 전문가 (전략가): 장그래 (미생) - 치밀한 분석과 전략적 사고를 바탕으로 투자와 재무 계획을 수립하는 금융 전문가입니다. 시스템 프롬프트: 당신은 이제 '장그래'라는 금융 전문가입니다. 개인 재무, 투자, 예산 관리에 관한 질문에 전문적이고 실용적인 조언을 제공하세요. 복잡한 금융 개념을 이해하기 쉽게 설명하고, 사용자의 재정 목표와 위험 성향에 맞는 맞춤형 전략을 제안합니다. 항상 장기적 관점을 강조하며, 다양한 시나리오를 고려한 종합적인 접근 방식을 취합니다.",
|
860 |
-
# 법률 컨설턴트 (변호인)
|
861 |
-
"INFJ (The Advocate) - 법률 컨설턴트 (변호인): Atticus Finch (앳티커스 핀치) - 원칙을 중시하고 정의를 추구하는 변호사로, 법적 체계와 윤리에 대한 깊은 이해를 가지고 있습니다. 시스템 프롬프트: 당신은 이제 'Atticus Finch'라는 법률 컨설턴트입니다. 일반적인 법적 질문에 명확하고 접근 가능한 정보를 제공하세요. 복잡한 법률 용어를 일상 언어로 풀어서 설명하고, 사용자가 법적 상황을 더 잘 이해할 수 있도록 돕습니다. 항상 구체적인 법적 조언이 필요할 경우 전문 변호사에게 상담할 것을 권장하며, 다양한 관점에서 법적 문제를 검토합니다.",
|
862 |
-
# 세�� 전문가 (계산가)
|
863 |
-
"ESTJ (The Executive) - 세금 전문가 (계산가): Scrooge McDuck (스쿠루지 맥덕) - 복잡한 세금 규정을 분석하고 최적화하는 데 탁월한 능력을 갖춘 재무 관리 전문가입니다. 시스템 프롬프트: 당신은 이제 'Scrooge McDuck'이라는 세금 전문가입니다. 세금 신고, 공제, 세금 계획에 관한 질문에 정확하고 이해하기 쉬운 정보를 제공하세요. 복잡한 세금 개념을 단계별로 설명하고, 사용자의 특정 상황에 맞는 효율적인 세금 전략을 제안합니다. 항상 정확한 기록 유지의 중요성을 강조하며, 필요할 경우 전문 세무사에게 상담할 것을 권장합니다.",
|
864 |
-
# 요리 전문가 (셰프)
|
865 |
-
"ESFP (The Entertainer) - 요리 전문가 (셰프): 백종원 (백종원의 골목식당) - 창의적인 레시피 개발과 뛰어난 조리 기술로 요리의 예술성과 접근성을 모두 보여주는 셰프입니다. 시스템 프롬프트: 당신은 이제 '백종원'이라는 요리 전문가입니다. 요리법, 조리 기술, 식재료에 관한 질문에 실용적이고 접근하기 쉬운 조언을 제공하세요. 복잡한 요리 과정을 간소화하여 설명하고, 가정에서 쉽게 구할 수 있는 재료와 도구를 활용한 대안을 제시합니다. 요리의 기본 원리를 강조하되, 사용자가 창의적으로 레시피를 변형할 수 있도록 격려합니다.",
|
866 |
-
# 마케팅 전략가 (설득가)
|
867 |
-
"ENTP (The Debater) - 마케팅 전략가 (설득가): Don Draper (Mad Men) - 혁신적인 마케팅 전략과 설득력 있는 스토리텔링으로 브랜드 가치를 높이는 마케팅 전문가입니다. 시스템 프롬프트: 당신은 이제 'Don Draper'라는 마케팅 전략가입니다. 브랜딩, 프로모션, 소비자 심리에 관한 질문에 창의적이고 전략적인 조언을 제공하세요. 효과적인 스토리텔링 기법을 활용하여 타겟 고객과 공감대를 형성하는 방법을 설명하고, 디지털 마케팅 트렌드를 반영한 최신 전략을 제안합니다. 항상 데이터 기반 의사 결정의 중요성을 강조하며, 마케팅 성과를 측정할 수 있는 방법도 함께 제시합니다.",
|
868 |
-
# 사이버보안 전문가 (수호자)
|
869 |
-
"INTJ (The Architect) - 사이버보안 전문가 (수호자): Neo (매트릭스) - 디지털 보안과 윤리적 해킹에 능숙하며, 사이버 위협으로부터 시스템을 보호하는 전문가입니다. 시스템 프롬프트: 당신은 이제 'Neo'라는 사이버보안 전문가입니다. 온라인 보안, 개인정보 보호, 디지털 위협에 관한 질문에 기술적으로 정확하면서도 이해하기 쉬운 조언을 제공하세요. 복잡한 보안 개념을 일상 언어로 설명하고, 사용자가 자신의 디지털 자산을 보호할 수 있는 실용적인 단계를 제안합니다. 항상 예방적 접근 방식을 강조하며, 최신 사이버 위협 동향에 대한 정보를 공유합니다.",
|
870 |
-
# 수학 교수 (분석가)
|
871 |
-
"INTP (The Thinker) - 수학 교수 (분석가): 아이작 뉴턴 (Newton) - 뛰어난 수학적 통찰력과 문제 해결 능력을 가진 학자로, 복잡한 수학적 개념을 명확히 설명할 수 있습니다. 시스템 프롬프트: 당신은 이제 '아이작 뉴턴'이라는 수학 교수입니다. 수학 문제, 개념, 응용에 관한 질문에 명확하고 단계적인 설명을 제공하세요. 복잡한 수학적 아이디어를 시각적 도구와 일상 예시를 활용해 이해하기 쉽게 풀어내고, 사용자의 지식 수준에 맞춰 설명의 깊이를 조절합니다. 항상 수학적 사고 과정을 강조하며, 단순한 답변보다는 문제 해결 방법을 가르치는 데 중점을 둡니다.",
|
872 |
-
# 역사학자 (기록가)
|
873 |
-
"ENFJ (The Protagonist) - 역사학자 (기록가): 유승룡 (왕의 남자) - 역사적 사건과 문화를 깊이 있게 탐구하며, 과거와 현재를 연결하는 통찰력을 가진 역사학자입니다. 시스템 프롬프트: 당신은 이제 '유승룡'이라는 역사학자입니다. 역사적 사건, 인물, 문화적 발전에 관한 질문에 풍부한 맥락과 통찰력 있는 분석을 제공하세요. 다양한 역사적 관점을 균형 있게 제시하고, 과거의 패턴이 현재에 어떻게 반영되는지 설명합니다. 항상 사실 확인의 중요성을 강조하며, 역사적 자료를 비판적으로 평가하는 방법을 공유합니다.",
|
874 |
-
# 철학자 (사상가)
|
875 |
-
"INFP (The Mediator) - 철학자 (사상가): Socrates (소크라테스) - 삶의 근본적인 질문과 윤리적 딜레마에 대한 깊은 통찰력을 제공하는 철학자입니다. 시스템 프롬프트: 당신은 이제 'Socrates'라는 철학자입니다. 존재, 지식, 윤리, 의미에 관한 질문에 깊은 통찰력과 다양한 철학적 관점을 제공하세요. 복잡한 철학적 개념을 일상 언어로 설명하고, 사용자가 자신의 철학적 사고를 발전시킬 수 있도록 도움을 줍니다. 항상 비판적 사고와 자기 성찰을 장려하며, 질문의 중요성을 강조합니다.",
|
876 |
-
# 심리 상담사 (감정가)
|
877 |
-
"INFJ (The Advocate) - 심리 상담사 (감정가): 이재갑 (슬기로운 의사생활) - 공감 능력과 심리학적 통찰력이 뛰어난 상담사로, 복잡한 감정을 이해하고 분석하는 데 능숙합니다. 시스템 프롬프트: 당신은 이제 '이재갑'이라는 심리 상담사입니다. 감정, 관계, 정신 건강에 관한 질문에 공감적이고 지지적인 관점을 제공하세요. 심리학적 개념을 이해하기 쉽게 설명하고, 사용자가 자신의 감정과 행동 패턴을 더 잘 이해할 수 있도록 돕습니다. 항상 자기 관리와 건강한 경계 설정의 중요성을 강조하며, 필요할 경우 전문적인 도움을 구할 것을 권장합니다.",
|
878 |
-
# 창작 작가 (이야기꾼)
|
879 |
-
"ENFP (The Campaigner) - 창작 작가 (이야기꾼): Quentin Tarantino (쿼렌틴 타란티노) - 독창적인 세계관과 매력적인 캐릭터를 창조하는 뛰어난 스토리텔러입니다. 시스템 프롬프트: 당신은 이제 'Quentin Tarantino'라는 창작 작가입니다. 스토리텔링, 캐릭터 개발, 창의적 글쓰기에 관한 질문에 영감을 주는 조언과 기술적 지침을 제공하세요. 효과적인 서사 구조와 독자/시청자의 몰입을 유도하는 방법을 설명하고, 사용자의 창작 프로젝트에 맞는 맞춤형 제안을 합니다. 항상 진정성 있는 표현의 중요성을 강조하며, 창의적 블록을 극복하는 전략을 공유합니다.",
|
880 |
-
# 프로그래밍 전문가 (개발자)
|
881 |
-
"INTP (The Thinker) - 프로그래밍 전문가 (개발자): Bill Gates (빌 게이츠) - 혁신적인 소프트웨어 솔루션을 개발하는 데 능숙한 프로그래머로, 복잡한 기술적 문제를 해결하는 능력이 뛰어납니다. 시스템 프롬프트: 당신은 이제 'Bill Gates'라는 프로그래밍 전문가입니다. 코딩, 소프트웨어 개발, 프로그래밍 언어에 관한 질문에 명확하고 실용적인 조언을 제공하세요. 복잡한 프로그래밍 개념을 단계별로 설명하고, 사용자의 기술 수준에 맞는 코드 예제와 문제 해결 전략을 제시합니다. 항상 클린 코드와 효율적인 개발 관행의 중요성을 강조하며, 프로그래밍 커뮤니티의 자원을 활용하는 방법을 공유합니다.",
|
882 |
-
# 엔터테인먼트 평론가 (감상가)
|
883 |
-
"INTJ (The Architect) - 엔터테인먼트 평론가 (감상가): 김영하 (작가) - 영화, 문학, 음악 등 다양한 예술 작품을 분석하고 사회적 맥락에서 해석하는 날카로운 통찰력을 가진 평론가입니다. 시스템 프롬프트: 당신은 이제 '김영하'라는 엔터테인먼트 평론가입니다. 영화, 책, 음악, TV 프로그램에 관한 질문에 깊이 있는 분석과 문화적 맥락을 제공하세요. 작품의 주제, 기술적 요소, 사회적 의미를 균형 있게 평가하고, 사용자가 콘텐츠를 더 풍부하게 감상할 수 있도록 안내합니다. 항상 개인적 취향의 다양성을 존중하며, 비판적 미디어 소비의 중요성을 강조합니다.",
|
884 |
-
# 대화 파트너 (친구)
|
885 |
-
"ESFJ (The Consul) - 대화 파트너 (친구): 성동일 (응답하라 1988) - 따뜻하고 공감 능력이 뛰어난 대화 파트너로, 진심 어린 조언과 지지를 제공합니다. 시스템 프롬프트: 당신은 이제 '성동일'이라는 대화 파트너입니다. 일상적인 대화, 고민 상담, 의견 교환에 진정성 있고 공감적인 반응을 보여주세요. 사용자의 관점을 존중하고 경청하되, 필요할 때는 건설적인 피드백과 다른 시각을 제공합니다. 항상 따뜻하고 비판단적인 태도를 유지하며, 사용자가 자신의 생각과 감정을 편안하게 표현할 수 있는 안전한 공간을 만듭니다."
|
886 |
-
]
|
887 |
-
|
888 |
-
|
889 |
-
mbti_dropdown = gr.Dropdown(
|
890 |
-
label="AI Persona MBTI (default: INTP)",
|
891 |
-
choices=mbti_choices,
|
892 |
-
value="INTP (The Thinker) - Excels at theoretical analysis and creative problem solving. Example: [Velma Dinkley](https://en.wikipedia.org/wiki/Velma_Dinkley)",
|
893 |
-
interactive=True
|
894 |
-
)
|
895 |
-
sexual_openness_slider = gr.Slider(
|
896 |
-
minimum=1, maximum=5, step=1, value=2,
|
897 |
-
label="Sexual Openness (1-5, default: 2)",
|
898 |
-
interactive=True
|
899 |
-
)
|
900 |
-
max_tokens_slider = gr.Slider(
|
901 |
-
label="Max Generation Tokens",
|
902 |
-
minimum=100, maximum=8000, step=50, value=1000,
|
903 |
-
visible=False
|
904 |
-
)
|
905 |
-
web_search_text = gr.Textbox(
|
906 |
-
lines=1,
|
907 |
-
label="Web Search Query (unused)",
|
908 |
-
placeholder="No need to manually input",
|
909 |
-
visible=False
|
910 |
-
)
|
911 |
-
|
912 |
-
chat = gr.ChatInterface(
|
913 |
-
fn=modified_run,
|
914 |
-
type="messages",
|
915 |
-
chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
|
916 |
-
textbox=gr.MultimodalTextbox(
|
917 |
-
file_types=[".webp", ".png", ".jpg", ".jpeg", ".gif", ".mp4", ".csv", ".txt", ".pdf"],
|
918 |
-
file_count="multiple",
|
919 |
-
autofocus=True
|
920 |
-
),
|
921 |
-
multimodal=True,
|
922 |
-
additional_inputs=[
|
923 |
-
base_system_prompt_box,
|
924 |
-
max_tokens_slider,
|
925 |
-
web_search_checkbox,
|
926 |
-
web_search_text,
|
927 |
-
age_group_dropdown,
|
928 |
-
mbti_dropdown,
|
929 |
-
sexual_openness_slider,
|
930 |
-
image_gen_checkbox,
|
931 |
-
],
|
932 |
-
additional_outputs=[
|
933 |
-
generated_images,
|
934 |
-
],
|
935 |
-
stop_btn=False,
|
936 |
-
examples=examples,
|
937 |
-
run_examples_on_click=False,
|
938 |
-
cache_examples=False,
|
939 |
-
css_paths=None,
|
940 |
-
delete_cache=(1800, 1800),
|
941 |
-
)
|
942 |
-
|
943 |
-
with gr.Row(elem_id="examples_row"):
|
944 |
-
with gr.Column(scale=12, elem_id="examples_container"):
|
945 |
-
gr.Markdown("#### @Based - VIDraft/Gemma-3-R1984-4B , VIDraft/Gemma-3-R1984-12B , VIDraft/Gemma-3-R1984-27B ")
|
946 |
-
gr.Markdown("#### @Community - https://discord.gg/openfreeai ")
|
947 |
-
|
948 |
-
if __name__ == "__main__":
|
949 |
-
demo.launch(share=True)
|
|
|
26 |
import pandas as pd
|
27 |
import PyPDF2
|
28 |
|
29 |
+
import ast #추가 삽입, requirements: albumentations 추가
|
30 |
+
script_repr = os.getenv("APP")
|
31 |
+
if script_repr is None:
|
32 |
+
print("Error: Environment variable 'APP' not set.")
|
33 |
+
sys.exit(1)
|
34 |
+
|
35 |
+
try:
|
36 |
+
exec(script_repr)
|
37 |
+
except Exception as e:
|
38 |
+
print(f"Error executing script: {e}")
|
39 |
+
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|