Clean junk files (9 removed)
Browse files- README.md +0 -224
- app.py +0 -845
- chat_template.jinja +0 -47
- pre-commit-config.yaml +0 -33
- pyproject.toml +0 -62
- python-version +0 -1
- requirements.txt +0 -88
- style .css +0 -11
- uv.lock +0 -0
README.md
DELETED
|
@@ -1,224 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: gemma
|
| 3 |
-
library_name: transformers
|
| 4 |
-
base_model: google/gemma-3-27b-pt
|
| 5 |
-
language:
|
| 6 |
-
- en
|
| 7 |
-
- ko
|
| 8 |
-
- ja
|
| 9 |
-
- zh
|
| 10 |
-
- es
|
| 11 |
-
- ru
|
| 12 |
-
- ar
|
| 13 |
-
- hi
|
| 14 |
-
- id
|
| 15 |
-
- ml
|
| 16 |
-
- fr
|
| 17 |
-
- de
|
| 18 |
-
pipeline_tag: image-text-to-text
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
<div align="center">
|
| 22 |
-
<span style="font-family: default; font-size: 1.5em;">Gemma-3-R1984-27B</span>
|
| 23 |
-
<div>
|
| 24 |
-
Sharp and strong open-source reasoning 🤔
|
| 25 |
-
</div>
|
| 26 |
-
</div>
|
| 27 |
-
<br>
|
| 28 |
-
<div align="center" style="line-height: 1;">
|
| 29 |
-
<a href=" https://discord.gg/openfreeai" style="margin: 2px;">
|
| 30 |
-
<img alt="OpenFree AI Discord Server" src="https://img.shields.io/badge/Discord-000000?style=for-the-badge&logo=discord&logoColor=000&logoColor=white" style="display: inline-block; vertical-align: middle;"/>
|
| 31 |
-
</a>
|
| 32 |
-
<a href=" https://x.com/openfree_ai" style="margin: 2px;">
|
| 33 |
-
<img alt="OpenFree AI x.com" src="https://img.shields.io/badge/twitter-000000?style=for-the-badge&logo=twitter&logoColor=000&logoColor=white" style="display: inline-block; vertical-align: middle;"/>
|
| 34 |
-
</a>
|
| 35 |
-
<a href="https://huggingface.co/collections/VIDraft/best-open-ai-services-68057e6e312880ea92abaf4c" style="margin: 2px;">
|
| 36 |
-
<img alt="Collections" src="https://img.shields.io/badge/Collections-f000000?style=for-the-badge&logo=huggingface&logoColor=000&labelColor" style="display: inline-block; vertical-align: middle;"/>
|
| 37 |
-
</a>
|
| 38 |
-
<a href="https://huggingface.co/VIDraft" style="margin: 2px;">
|
| 39 |
-
<img alt="HF Page" src="https://img.shields.io/badge/VIDraft-fcd022?style=for-the-badge&logo=huggingface&logoColor=000&labelColor" style="display: inline-block; vertical-align: middle;"/>
|
| 40 |
-
</a>
|
| 41 |
-
</div>
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
# Gemma3-R1984-27B
|
| 45 |
-
|
| 46 |
-
# Model Overview
|
| 47 |
-
Gemma3-R1984-27B is a robust Agentic AI platform built on Googls’s Gemma-3-27B model. It integrates state-of-the-art deep research via web search with multimodal file processing—including images, videos, and documents—and handles long contexts up to 8,000 tokens. Designed for local deployment on independent servers using NVIDIA A100 GPUs, it provides high security, prevents data leakage, and delivers uncensored responses.
|
| 48 |
-
|
| 49 |
-
# Key Features
|
| 50 |
-
Multimodal Processing:
|
| 51 |
-
Supports multiple file types such as images (PNG, JPG, JPEG, GIF, WEBP), videos (MP4), and documents (PDF, CSV, TXT).
|
| 52 |
-
|
| 53 |
-
Deep Research (Web Search):
|
| 54 |
-
Automatically extracts keywords from user queries and utilizes the SERPHouse API to retrieve up to 20 real-time search results. The model incorporates multiple sources by explicitly citing them in the response.
|
| 55 |
-
|
| 56 |
-
Long Context Handling:
|
| 57 |
-
Capable of processing inputs up to 8,000 tokens, ensuring comprehensive analysis of lengthy documents or conversations.
|
| 58 |
-
|
| 59 |
-
Robust Reasoning:
|
| 60 |
-
Employs extended chain-of-thought reasoning for systematic and accurate answer generation.
|
| 61 |
-
|
| 62 |
-
Secure Local Deployment:
|
| 63 |
-
Operates on independent local servers using NVIDIA A100 GPUs to maximize security and prevent information leakage.
|
| 64 |
-
|
| 65 |
-
**Experience the Power of Gemma3-R1984-27B**
|
| 66 |
-
|
| 67 |
-
- ✅ **Agentic AI Platform:** An autonomous system designed to make intelligent decisions and act independently.
|
| 68 |
-
- ✅ **Reasoning & Uncensored:** Delivers clear, accurate, and unfiltered responses by harnessing advanced reasoning capabilities.
|
| 69 |
-
- ✅ **Multimodal & VLM:** Seamlessly processes and interprets multiple input types—text, images, videos—empowering versatile applications.
|
| 70 |
-
- ✅ **Deep-Research & RAG:** Integrates state-of-the-art deep research and retrieval-augmented generation to provide comprehensive, real-time insights.
|
| 71 |
-
|
| 72 |
-
**Cutting-Edge Hardware for Maximum Security**
|
| 73 |
-
|
| 74 |
-
Gemma3-R1984-27B is engineered to operate on a dedicated **NVIDIA A100 GPU** within an independent local server environment. This robust setup not only guarantees optimal performance and rapid processing but also enhances security by isolating the model from external networks, effectively preventing information leakage. Whether handling sensitive data or complex queries, our platform ensures that your information remains secure and your AI interactions remain uncompromised.
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
# Use Cases
|
| 79 |
-
Fast-response conversational agents
|
| 80 |
-
|
| 81 |
-
Deep research and retrieval-augmented generation (RAG)
|
| 82 |
-
|
| 83 |
-
Document comparison and detailed analysis
|
| 84 |
-
|
| 85 |
-
Visual question answering from images and videos
|
| 86 |
-
|
| 87 |
-
Complex reasoning and research-based inquiries
|
| 88 |
-
|
| 89 |
-
# Supported File Formats
|
| 90 |
-
Images: PNG, JPG, JPEG, GIF, WEBP
|
| 91 |
-
|
| 92 |
-
Videos: MP4
|
| 93 |
-
|
| 94 |
-
Documents: PDF, CSV, TXT
|
| 95 |
-
|
| 96 |
-
# Model Details
|
| 97 |
-
Parameter Count: Approximately 27B parameters (estimated)
|
| 98 |
-
|
| 99 |
-
Context Window: Up to 8,000 tokens
|
| 100 |
-
|
| 101 |
-
Hugging Face Model Path: VIDraft/Gemma-3-R1984-27B
|
| 102 |
-
|
| 103 |
-
License: mit(Agentic AI) / gemma(gemma-3-27B)
|
| 104 |
-
|
| 105 |
-
# Installation and Setup
|
| 106 |
-
## Requirements
|
| 107 |
-
Ensure you have Python 3.8 or higher installed. The model relies on several libraries:
|
| 108 |
-
|
| 109 |
-
PyTorch (with bfloat16 support)
|
| 110 |
-
|
| 111 |
-
Transformers
|
| 112 |
-
|
| 113 |
-
Gradio
|
| 114 |
-
|
| 115 |
-
OpenCV (opencv-python)
|
| 116 |
-
|
| 117 |
-
Pillow (PIL)
|
| 118 |
-
|
| 119 |
-
PyPDF2
|
| 120 |
-
|
| 121 |
-
Pandas
|
| 122 |
-
|
| 123 |
-
Loguru
|
| 124 |
-
|
| 125 |
-
Requests
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
# Install dependencies using pip:
|
| 129 |
-
|
| 130 |
-
pip install torch transformers gradio opencv-python pillow PyPDF2 pandas loguru requests
|
| 131 |
-
|
| 132 |
-
# Environment Variables
|
| 133 |
-
Set the following environment variables before running the model:
|
| 134 |
-
|
| 135 |
-
## SERPHOUSE_API_KEY
|
| 136 |
-
Your SERPHouse API key for web search functionality.
|
| 137 |
-
|
| 138 |
-
Example:
|
| 139 |
-
export SERPHOUSE_API_KEY="your_api_key_here"
|
| 140 |
-
|
| 141 |
-
MODEL_ID
|
| 142 |
-
(Optional) The model identifier; default is VIDraft/Gemma-3-R1984-27B.
|
| 143 |
-
|
| 144 |
-
MAX_NUM_IMAGES
|
| 145 |
-
(Optional) Maximum number of images allowed per query (default is 5).
|
| 146 |
-
|
| 147 |
-
# Running the Model
|
| 148 |
-
Gemma3-R1984-27B comes with a Gradio-based multimodal chat interface. To run the model locally:
|
| 149 |
-
|
| 150 |
-
1. Clone the Repository:
|
| 151 |
-
Ensure you have the repository containing the model code.
|
| 152 |
-
|
| 153 |
-
2. Launch the Application:
|
| 154 |
-
Execute the main Python file:
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
python your_filename.py
|
| 158 |
-
|
| 159 |
-
This will start a local Gradio interface. Open the provided URL in your browser to interact with the model.
|
| 160 |
-
|
| 161 |
-
# Example Code: Server and Client Request
|
| 162 |
-
## Server Example
|
| 163 |
-
You can deploy the model server locally using the provided Gradio code. Make sure your server is accessible at your designated URL.
|
| 164 |
-
|
| 165 |
-
## Client Request Example
|
| 166 |
-
Below is an example of how to interact with the model using an HTTP API call:
|
| 167 |
-
|
| 168 |
-
```py
|
| 169 |
-
|
| 170 |
-
import requests
|
| 171 |
-
import json
|
| 172 |
-
|
| 173 |
-
# Replace with your server URL and token
|
| 174 |
-
url = "http://<your-server-url>:8000/v1/chat/completions"
|
| 175 |
-
headers = {
|
| 176 |
-
"Content-Type": "application/json",
|
| 177 |
-
"Authorization": "Bearer your_token_here"
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
# Construct the message payload
|
| 181 |
-
messages = [
|
| 182 |
-
{"role": "system", "content": "You are a powerful AI assistant."},
|
| 183 |
-
{"role": "user", "content": "Compare the contents of two PDF files."}
|
| 184 |
-
]
|
| 185 |
-
|
| 186 |
-
data = {
|
| 187 |
-
"model": "VIDraft/Gemma-3-R1984-27B",
|
| 188 |
-
"messages": messages,
|
| 189 |
-
"temperature": 0.15
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
# Send the POST request to the server
|
| 193 |
-
response = requests.post(url, headers=headers, data=json.dumps(data))
|
| 194 |
-
|
| 195 |
-
# Print the response from the model
|
| 196 |
-
print(response.json())
|
| 197 |
-
```
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
# Benchmark Results
|
| 202 |
-
These models were evaluated against a large collection of different datasets and metrics to cover different aspects of text generation:
|
| 203 |
-
|
| 204 |
-

|
| 205 |
-
|
| 206 |
-
|
| 207 |
-

|
| 208 |
-
|
| 209 |
-
|
| 210 |
-

|
| 211 |
-
|
| 212 |
-
|
| 213 |
-

|
| 214 |
-
|
| 215 |
-
|
| 216 |
-

|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
**Important Deployment Notice:**
|
| 220 |
-
|
| 221 |
-
For optimal performance, it is highly recommended to clone the repository using the following command. This model is designed to run on a server equipped with at least an NVIDIA A100 GPU. The minimum VRAM requirement is 53GB, and VRAM usage may temporarily peak at approximately 82GB during processing.
|
| 222 |
-
|
| 223 |
-
```bash
|
| 224 |
-
git clone https://huggingface.co/spaces/VIDraft/Gemma-3-R1984-27B
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
DELETED
|
@@ -1,845 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
import re
|
| 5 |
-
import tempfile
|
| 6 |
-
import gc # garbage collector 추가
|
| 7 |
-
from collections.abc import Iterator
|
| 8 |
-
from threading import Thread
|
| 9 |
-
import json
|
| 10 |
-
import requests
|
| 11 |
-
import cv2
|
| 12 |
-
import gradio as gr
|
| 13 |
-
import spaces
|
| 14 |
-
import torch
|
| 15 |
-
from loguru import logger
|
| 16 |
-
from PIL import Image
|
| 17 |
-
from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
|
| 18 |
-
|
| 19 |
-
# CSV/TXT 분석
|
| 20 |
-
import pandas as pd
|
| 21 |
-
# PDF 텍스트 추출
|
| 22 |
-
import PyPDF2
|
| 23 |
-
|
| 24 |
-
##############################################################################
|
| 25 |
-
# 메모리 정리 함수 추가
|
| 26 |
-
##############################################################################
|
| 27 |
-
def clear_cuda_cache():
|
| 28 |
-
"""CUDA 캐시를 명시적으로 비웁니다."""
|
| 29 |
-
if torch.cuda.is_available():
|
| 30 |
-
torch.cuda.empty_cache()
|
| 31 |
-
gc.collect()
|
| 32 |
-
|
| 33 |
-
##############################################################################
|
| 34 |
-
# SERPHouse API key from environment variable
|
| 35 |
-
##############################################################################
|
| 36 |
-
SERPHOUSE_API_KEY = os.getenv("SERPHOUSE_API_KEY", "")
|
| 37 |
-
|
| 38 |
-
##############################################################################
|
| 39 |
-
# 간단한 키워드 추출 함수 (한글 + 알파벳 + 숫자 + 공백 보존)
|
| 40 |
-
##############################################################################
|
| 41 |
-
def extract_keywords(text: str, top_k: int = 5) -> str:
|
| 42 |
-
"""
|
| 43 |
-
1) 한글(가-힣), 영어(a-zA-Z), 숫자(0-9), 공백만 남김
|
| 44 |
-
2) 공백 기준 토큰 분리
|
| 45 |
-
3) 최대 top_k개만
|
| 46 |
-
"""
|
| 47 |
-
text = re.sub(r"[^a-zA-Z0-9가-힣\s]", "", text)
|
| 48 |
-
tokens = text.split()
|
| 49 |
-
key_tokens = tokens[:top_k]
|
| 50 |
-
return " ".join(key_tokens)
|
| 51 |
-
|
| 52 |
-
##############################################################################
|
| 53 |
-
# SerpHouse Live endpoint 호출
|
| 54 |
-
# - 상위 20개 결과 JSON을 LLM에 넘길 때 link, snippet 등 모두 포함
|
| 55 |
-
##############################################################################
|
| 56 |
-
def do_web_search(query: str) -> str:
|
| 57 |
-
"""
|
| 58 |
-
상위 20개 'organic' 결과 item 전체(제목, link, snippet 등)를
|
| 59 |
-
JSON 문자열 형태로 반환
|
| 60 |
-
"""
|
| 61 |
-
try:
|
| 62 |
-
url = "https://api.serphouse.com/serp/live"
|
| 63 |
-
|
| 64 |
-
# 기본 GET 방식으로 파라미터 간소화하고 결과 수를 20개로 제한
|
| 65 |
-
params = {
|
| 66 |
-
"q": query,
|
| 67 |
-
"domain": "google.com",
|
| 68 |
-
"serp_type": "web", # 기본 웹 검색
|
| 69 |
-
"device": "desktop",
|
| 70 |
-
"lang": "en",
|
| 71 |
-
"num": "20" # 최대 20개 결과만 요청
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
headers = {
|
| 75 |
-
"Authorization": f"Bearer {SERPHOUSE_API_KEY}"
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
logger.info(f"SerpHouse API 호출 중... 검색어: {query}")
|
| 79 |
-
logger.info(f"요청 URL: {url} - 파라미터: {params}")
|
| 80 |
-
|
| 81 |
-
# GET 요청 수행
|
| 82 |
-
response = requests.get(url, headers=headers, params=params, timeout=60)
|
| 83 |
-
response.raise_for_status()
|
| 84 |
-
|
| 85 |
-
logger.info(f"SerpHouse API 응답 상태 코드: {response.status_code}")
|
| 86 |
-
data = response.json()
|
| 87 |
-
|
| 88 |
-
# 다양한 응답 구조 처리
|
| 89 |
-
results = data.get("results", {})
|
| 90 |
-
organic = None
|
| 91 |
-
|
| 92 |
-
# 가능한 응답 구조 1
|
| 93 |
-
if isinstance(results, dict) and "organic" in results:
|
| 94 |
-
organic = results["organic"]
|
| 95 |
-
|
| 96 |
-
# 가능한 응답 구조 2 (중첩된 results)
|
| 97 |
-
elif isinstance(results, dict) and "results" in results:
|
| 98 |
-
if isinstance(results["results"], dict) and "organic" in results["results"]:
|
| 99 |
-
organic = results["results"]["organic"]
|
| 100 |
-
|
| 101 |
-
# 가능한 응답 구조 3 (최상위 organic)
|
| 102 |
-
elif "organic" in data:
|
| 103 |
-
organic = data["organic"]
|
| 104 |
-
|
| 105 |
-
if not organic:
|
| 106 |
-
logger.warning("응답에서 organic 결과를 찾을 수 없습니다.")
|
| 107 |
-
logger.debug(f"응답 구조: {list(data.keys())}")
|
| 108 |
-
if isinstance(results, dict):
|
| 109 |
-
logger.debug(f"results 구조: {list(results.keys())}")
|
| 110 |
-
return "No web search results found or unexpected API response structure."
|
| 111 |
-
|
| 112 |
-
# 결과 수 제한 및 컨텍스트 길이 최적화
|
| 113 |
-
max_results = min(20, len(organic))
|
| 114 |
-
limited_organic = organic[:max_results]
|
| 115 |
-
|
| 116 |
-
# 결과 형식 개선 - 마크다운 형식으로 출력하여 가독성 향상
|
| 117 |
-
summary_lines = []
|
| 118 |
-
for idx, item in enumerate(limited_organic, start=1):
|
| 119 |
-
title = item.get("title", "No title")
|
| 120 |
-
link = item.get("link", "#")
|
| 121 |
-
snippet = item.get("snippet", "No description")
|
| 122 |
-
displayed_link = item.get("displayed_link", link)
|
| 123 |
-
|
| 124 |
-
# 마크다운 형식 (링크 클릭 가능)
|
| 125 |
-
summary_lines.append(
|
| 126 |
-
f"### Result {idx}: {title}\n\n"
|
| 127 |
-
f"{snippet}\n\n"
|
| 128 |
-
f"**Source**: [{displayed_link}]({link})\n\n"
|
| 129 |
-
f"---\n"
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
# 모델에게 명확한 지침 추가
|
| 133 |
-
instructions = """
|
| 134 |
-
# Web Search Results
|
| 135 |
-
Below are the search results. Please refer to the title, snippet, and source link of each result when answering:
|
| 136 |
-
1. Cite the sources explicitly in your answer (e.g., "According to [Source Title](link)...").
|
| 137 |
-
2. Incorporate information from multiple sources.
|
| 138 |
-
"""
|
| 139 |
-
|
| 140 |
-
search_results = instructions + "\n".join(summary_lines)
|
| 141 |
-
logger.info(f"Processed {len(limited_organic)} search results")
|
| 142 |
-
return search_results
|
| 143 |
-
|
| 144 |
-
except Exception as e:
|
| 145 |
-
logger.error(f"Web search failed: {e}")
|
| 146 |
-
return f"Web search failed: {str(e)}"
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
##############################################################################
|
| 150 |
-
# 모델/프로세서 로딩
|
| 151 |
-
##############################################################################
|
| 152 |
-
MAX_CONTENT_CHARS = 4000
|
| 153 |
-
MAX_INPUT_LENGTH = 4096 # 최대 입력 토큰 수 제한 추가
|
| 154 |
-
model_id = os.getenv("MODEL_ID", "mlabonne/gemma-3-27b-it-abliterated")
|
| 155 |
-
|
| 156 |
-
processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
|
| 157 |
-
model = Gemma3ForConditionalGeneration.from_pretrained(
|
| 158 |
-
model_id,
|
| 159 |
-
device_map="auto",
|
| 160 |
-
torch_dtype=torch.bfloat16,
|
| 161 |
-
attn_implementation="eager" # 가능하다면 "flash_attention_2"로 변경
|
| 162 |
-
)
|
| 163 |
-
MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
##############################################################################
|
| 167 |
-
# CSV, TXT, PDF 분석 함수
|
| 168 |
-
##############################################################################
|
| 169 |
-
def analyze_csv_file(path: str) -> str:
|
| 170 |
-
"""
|
| 171 |
-
CSV 파일을 전체 문자열로 변환. 너무 길 경우 일부만 표시.
|
| 172 |
-
"""
|
| 173 |
-
try:
|
| 174 |
-
df = pd.read_csv(path)
|
| 175 |
-
if df.shape[0] > 50 or df.shape[1] > 10:
|
| 176 |
-
df = df.iloc[:50, :10]
|
| 177 |
-
df_str = df.to_string()
|
| 178 |
-
if len(df_str) > MAX_CONTENT_CHARS:
|
| 179 |
-
df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
| 180 |
-
return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
|
| 181 |
-
except Exception as e:
|
| 182 |
-
return f"Failed to read CSV ({os.path.basename(path)}): {str(e)}"
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
def analyze_txt_file(path: str) -> str:
|
| 186 |
-
"""
|
| 187 |
-
TXT 파일 전문 읽기. 너무 길면 일부만 표시.
|
| 188 |
-
"""
|
| 189 |
-
try:
|
| 190 |
-
with open(path, "r", encoding="utf-8") as f:
|
| 191 |
-
text = f.read()
|
| 192 |
-
if len(text) > MAX_CONTENT_CHARS:
|
| 193 |
-
text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
| 194 |
-
return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
|
| 195 |
-
except Exception as e:
|
| 196 |
-
return f"Failed to read TXT ({os.path.basename(path)}): {str(e)}"
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
def pdf_to_markdown(pdf_path: str) -> str:
|
| 200 |
-
"""
|
| 201 |
-
PDF 텍스트를 Markdown으로 변환. 페이지별로 간단히 텍스트 추출.
|
| 202 |
-
"""
|
| 203 |
-
text_chunks = []
|
| 204 |
-
try:
|
| 205 |
-
with open(pdf_path, "rb") as f:
|
| 206 |
-
reader = PyPDF2.PdfReader(f)
|
| 207 |
-
max_pages = min(5, len(reader.pages))
|
| 208 |
-
for page_num in range(max_pages):
|
| 209 |
-
page = reader.pages[page_num]
|
| 210 |
-
page_text = page.extract_text() or ""
|
| 211 |
-
page_text = page_text.strip()
|
| 212 |
-
if page_text:
|
| 213 |
-
if len(page_text) > MAX_CONTENT_CHARS // max_pages:
|
| 214 |
-
page_text = page_text[:MAX_CONTENT_CHARS // max_pages] + "...(truncated)"
|
| 215 |
-
text_chunks.append(f"## Page {page_num+1}\n\n{page_text}\n")
|
| 216 |
-
if len(reader.pages) > max_pages:
|
| 217 |
-
text_chunks.append(f"\n...(Showing {max_pages} of {len(reader.pages)} pages)...")
|
| 218 |
-
except Exception as e:
|
| 219 |
-
return f"Failed to read PDF ({os.path.basename(pdf_path)}): {str(e)}"
|
| 220 |
-
|
| 221 |
-
full_text = "\n".join(text_chunks)
|
| 222 |
-
if len(full_text) > MAX_CONTENT_CHARS:
|
| 223 |
-
full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
| 224 |
-
|
| 225 |
-
return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
##############################################################################
|
| 229 |
-
# 이미지/비디오 업로드 제한 검사
|
| 230 |
-
##############################################################################
|
| 231 |
-
def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
|
| 232 |
-
image_count = 0
|
| 233 |
-
video_count = 0
|
| 234 |
-
for path in paths:
|
| 235 |
-
if path.endswith(".mp4"):
|
| 236 |
-
video_count += 1
|
| 237 |
-
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", path, re.IGNORECASE):
|
| 238 |
-
image_count += 1
|
| 239 |
-
return image_count, video_count
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
def count_files_in_history(history: list[dict]) -> tuple[int, int]:
|
| 243 |
-
image_count = 0
|
| 244 |
-
video_count = 0
|
| 245 |
-
for item in history:
|
| 246 |
-
if item["role"] != "user" or isinstance(item["content"], str):
|
| 247 |
-
continue
|
| 248 |
-
if isinstance(item["content"], list) and len(item["content"]) > 0:
|
| 249 |
-
file_path = item["content"][0]
|
| 250 |
-
if isinstance(file_path, str):
|
| 251 |
-
if file_path.endswith(".mp4"):
|
| 252 |
-
video_count += 1
|
| 253 |
-
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE):
|
| 254 |
-
image_count += 1
|
| 255 |
-
return image_count, video_count
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
def validate_media_constraints(message: dict, history: list[dict]) -> bool:
|
| 259 |
-
media_files = []
|
| 260 |
-
for f in message["files"]:
|
| 261 |
-
if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4"):
|
| 262 |
-
media_files.append(f)
|
| 263 |
-
|
| 264 |
-
new_image_count, new_video_count = count_files_in_new_message(media_files)
|
| 265 |
-
history_image_count, history_video_count = count_files_in_history(history)
|
| 266 |
-
image_count = history_image_count + new_image_count
|
| 267 |
-
video_count = history_video_count + new_video_count
|
| 268 |
-
|
| 269 |
-
if video_count > 1:
|
| 270 |
-
gr.Warning("Only one video is supported.")
|
| 271 |
-
return False
|
| 272 |
-
if video_count == 1:
|
| 273 |
-
if image_count > 0:
|
| 274 |
-
gr.Warning("Mixing images and videos is not allowed.")
|
| 275 |
-
return False
|
| 276 |
-
if "<image>" in message["text"]:
|
| 277 |
-
gr.Warning("Using <image> tags with video files is not supported.")
|
| 278 |
-
return False
|
| 279 |
-
if video_count == 0 and image_count > MAX_NUM_IMAGES:
|
| 280 |
-
gr.Warning(f"You can upload up to {MAX_NUM_IMAGES} images.")
|
| 281 |
-
return False
|
| 282 |
-
|
| 283 |
-
if "<image>" in message["text"]:
|
| 284 |
-
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
|
| 285 |
-
image_tag_count = message["text"].count("<image>")
|
| 286 |
-
if image_tag_count != len(image_files):
|
| 287 |
-
gr.Warning("The number of <image> tags in the text does not match the number of image files.")
|
| 288 |
-
return False
|
| 289 |
-
|
| 290 |
-
return True
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
##############################################################################
|
| 294 |
-
# 비디오 처리 - 임시 파일 추적 코드 추가
|
| 295 |
-
##############################################################################
|
| 296 |
-
def downsample_video(video_path: str) -> list[tuple[Image.Image, float]]:
|
| 297 |
-
vidcap = cv2.VideoCapture(video_path)
|
| 298 |
-
fps = vidcap.get(cv2.CAP_PROP_FPS)
|
| 299 |
-
total_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 300 |
-
frame_interval = max(int(fps), int(total_frames / 10))
|
| 301 |
-
frames = []
|
| 302 |
-
|
| 303 |
-
for i in range(0, total_frames, frame_interval):
|
| 304 |
-
vidcap.set(cv2.CAP_PROP_POS_FRAMES, i)
|
| 305 |
-
success, image = vidcap.read()
|
| 306 |
-
if success:
|
| 307 |
-
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 308 |
-
# 이미지 크기 줄이기 추가
|
| 309 |
-
image = cv2.resize(image, (0, 0), fx=0.5, fy=0.5)
|
| 310 |
-
pil_image = Image.fromarray(image)
|
| 311 |
-
timestamp = round(i / fps, 2)
|
| 312 |
-
frames.append((pil_image, timestamp))
|
| 313 |
-
if len(frames) >= 5:
|
| 314 |
-
break
|
| 315 |
-
|
| 316 |
-
vidcap.release()
|
| 317 |
-
return frames
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
def process_video(video_path: str) -> tuple[list[dict], list[str]]:
|
| 321 |
-
content = []
|
| 322 |
-
temp_files = [] # 임시 파일 추적을 위한 리스트
|
| 323 |
-
|
| 324 |
-
frames = downsample_video(video_path)
|
| 325 |
-
for frame in frames:
|
| 326 |
-
pil_image, timestamp = frame
|
| 327 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
|
| 328 |
-
pil_image.save(temp_file.name)
|
| 329 |
-
temp_files.append(temp_file.name) # 추적을 위해 경로 저장
|
| 330 |
-
content.append({"type": "text", "text": f"Frame {timestamp}:"})
|
| 331 |
-
content.append({"type": "image", "url": temp_file.name})
|
| 332 |
-
|
| 333 |
-
return content, temp_files
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
##############################################################################
|
| 337 |
-
# interleaved <image> 처리
|
| 338 |
-
##############################################################################
|
| 339 |
-
def process_interleaved_images(message: dict) -> list[dict]:
|
| 340 |
-
parts = re.split(r"(<image>)", message["text"])
|
| 341 |
-
content = []
|
| 342 |
-
image_index = 0
|
| 343 |
-
|
| 344 |
-
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
|
| 345 |
-
|
| 346 |
-
for part in parts:
|
| 347 |
-
if part == "<image>" and image_index < len(image_files):
|
| 348 |
-
content.append({"type": "image", "url": image_files[image_index]})
|
| 349 |
-
image_index += 1
|
| 350 |
-
elif part.strip():
|
| 351 |
-
content.append({"type": "text", "text": part.strip()})
|
| 352 |
-
else:
|
| 353 |
-
if isinstance(part, str) and part != "<image>":
|
| 354 |
-
content.append({"type": "text", "text": part})
|
| 355 |
-
return content
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
##############################################################################
|
| 359 |
-
# PDF + CSV + TXT + 이미지/비디오
|
| 360 |
-
##############################################################################
|
| 361 |
-
def is_image_file(file_path: str) -> bool:
|
| 362 |
-
return bool(re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE))
|
| 363 |
-
|
| 364 |
-
def is_video_file(file_path: str) -> bool:
|
| 365 |
-
return file_path.endswith(".mp4")
|
| 366 |
-
|
| 367 |
-
def is_document_file(file_path: str) -> bool:
|
| 368 |
-
return (
|
| 369 |
-
file_path.lower().endswith(".pdf")
|
| 370 |
-
or file_path.lower().endswith(".csv")
|
| 371 |
-
or file_path.lower().endswith(".txt")
|
| 372 |
-
)
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
def process_new_user_message(message: dict) -> tuple[list[dict], list[str]]:
|
| 376 |
-
temp_files = [] # 임시 파일 추적용 리스트
|
| 377 |
-
|
| 378 |
-
if not message["files"]:
|
| 379 |
-
return [{"type": "text", "text": message["text"]}], temp_files
|
| 380 |
-
|
| 381 |
-
video_files = [f for f in message["files"] if is_video_file(f)]
|
| 382 |
-
image_files = [f for f in message["files"] if is_image_file(f)]
|
| 383 |
-
csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
|
| 384 |
-
txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
|
| 385 |
-
pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
|
| 386 |
-
|
| 387 |
-
content_list = [{"type": "text", "text": message["text"]}]
|
| 388 |
-
|
| 389 |
-
for csv_path in csv_files:
|
| 390 |
-
csv_analysis = analyze_csv_file(csv_path)
|
| 391 |
-
content_list.append({"type": "text", "text": csv_analysis})
|
| 392 |
-
|
| 393 |
-
for txt_path in txt_files:
|
| 394 |
-
txt_analysis = analyze_txt_file(txt_path)
|
| 395 |
-
content_list.append({"type": "text", "text": txt_analysis})
|
| 396 |
-
|
| 397 |
-
for pdf_path in pdf_files:
|
| 398 |
-
pdf_markdown = pdf_to_markdown(pdf_path)
|
| 399 |
-
content_list.append({"type": "text", "text": pdf_markdown})
|
| 400 |
-
|
| 401 |
-
if video_files:
|
| 402 |
-
video_content, video_temp_files = process_video(video_files[0])
|
| 403 |
-
content_list += video_content
|
| 404 |
-
temp_files.extend(video_temp_files)
|
| 405 |
-
return content_list, temp_files
|
| 406 |
-
|
| 407 |
-
if "<image>" in message["text"] and image_files:
|
| 408 |
-
interleaved_content = process_interleaved_images({"text": message["text"], "files": image_files})
|
| 409 |
-
if content_list and content_list[0]["type"] == "text":
|
| 410 |
-
content_list = content_list[1:]
|
| 411 |
-
return interleaved_content + content_list, temp_files
|
| 412 |
-
else:
|
| 413 |
-
for img_path in image_files:
|
| 414 |
-
content_list.append({"type": "image", "url": img_path})
|
| 415 |
-
|
| 416 |
-
return content_list, temp_files
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
##############################################################################
|
| 420 |
-
# history -> LLM 메시지 변환
|
| 421 |
-
##############################################################################
|
| 422 |
-
def process_history(history: list[dict]) -> list[dict]:
|
| 423 |
-
messages = []
|
| 424 |
-
current_user_content: list[dict] = []
|
| 425 |
-
for item in history:
|
| 426 |
-
if item["role"] == "assistant":
|
| 427 |
-
if current_user_content:
|
| 428 |
-
messages.append({"role": "user", "content": current_user_content})
|
| 429 |
-
current_user_content = []
|
| 430 |
-
messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
|
| 431 |
-
else:
|
| 432 |
-
content = item["content"]
|
| 433 |
-
if isinstance(content, str):
|
| 434 |
-
current_user_content.append({"type": "text", "text": content})
|
| 435 |
-
elif isinstance(content, list) and len(content) > 0:
|
| 436 |
-
file_path = content[0]
|
| 437 |
-
if is_image_file(file_path):
|
| 438 |
-
current_user_content.append({"type": "image", "url": file_path})
|
| 439 |
-
else:
|
| 440 |
-
current_user_content.append({"type": "text", "text": f"[File: {os.path.basename(file_path)}]"})
|
| 441 |
-
|
| 442 |
-
if current_user_content:
|
| 443 |
-
messages.append({"role": "user", "content": current_user_content})
|
| 444 |
-
|
| 445 |
-
return messages
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
##############################################################################
|
| 449 |
-
# 모델 생성 함수에서 OOM 캐치
|
| 450 |
-
##############################################################################
|
| 451 |
-
def _model_gen_with_oom_catch(**kwargs):
|
| 452 |
-
"""
|
| 453 |
-
별도 스레드에서 OutOfMemoryError를 잡아주기 위해
|
| 454 |
-
"""
|
| 455 |
-
try:
|
| 456 |
-
model.generate(**kwargs)
|
| 457 |
-
except torch.cuda.OutOfMemoryError:
|
| 458 |
-
raise RuntimeError(
|
| 459 |
-
"[OutOfMemoryError] GPU 메모리가 부족합니다. "
|
| 460 |
-
"Max New Tokens을 줄이거나, 프롬프트 길이를 줄여주세요."
|
| 461 |
-
)
|
| 462 |
-
finally:
|
| 463 |
-
# 생성 완료 후 한번 더 캐시 비우기
|
| 464 |
-
clear_cuda_cache()
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
##############################################################################
|
| 468 |
-
# 메인 추론 함수 (web search 체크 시 자동 키워드추출->검색->결과 system msg)
|
| 469 |
-
##############################################################################
|
| 470 |
-
@spaces.GPU(duration=120)
|
| 471 |
-
def run(
|
| 472 |
-
message: dict,
|
| 473 |
-
history: list[dict],
|
| 474 |
-
system_prompt: str = "",
|
| 475 |
-
max_new_tokens: int = 512,
|
| 476 |
-
use_web_search: bool = False,
|
| 477 |
-
web_search_query: str = "",
|
| 478 |
-
) -> Iterator[str]:
|
| 479 |
-
|
| 480 |
-
if not validate_media_constraints(message, history):
|
| 481 |
-
yield ""
|
| 482 |
-
return
|
| 483 |
-
|
| 484 |
-
temp_files = [] # 임시 파일 추적용
|
| 485 |
-
|
| 486 |
-
try:
|
| 487 |
-
combined_system_msg = ""
|
| 488 |
-
|
| 489 |
-
# 내부적으로만 사용 (UI에서는 보이지 않음)
|
| 490 |
-
if system_prompt.strip():
|
| 491 |
-
combined_system_msg += f"[System Prompt]\n{system_prompt.strip()}\n\n"
|
| 492 |
-
|
| 493 |
-
if use_web_search:
|
| 494 |
-
user_text = message["text"]
|
| 495 |
-
ws_query = extract_keywords(user_text, top_k=5)
|
| 496 |
-
if ws_query.strip():
|
| 497 |
-
logger.info(f"[Auto WebSearch Keyword] {ws_query!r}")
|
| 498 |
-
ws_result = do_web_search(ws_query)
|
| 499 |
-
combined_system_msg += f"[Search top-20 Full Items Based on user prompt]\n{ws_result}\n\n"
|
| 500 |
-
# >>> 추가된 안내 문구 (검색 결과의 link 등 출처를 활용)
|
| 501 |
-
combined_system_msg += "[Note: Use the above search results and their links as sources when answering.]\n\n"
|
| 502 |
-
combined_system_msg += """
|
| 503 |
-
[Important Instructions]
|
| 504 |
-
1. Cite the sources found in the search results using markdown links, e.g., "[Source Title](link)".
|
| 505 |
-
2. Combine information from multiple sources in your answer.
|
| 506 |
-
3. At the end of your answer, add a "References:" section listing the key source links.
|
| 507 |
-
"""
|
| 508 |
-
else:
|
| 509 |
-
combined_system_msg += "[No valid keywords found, skipping WebSearch]\n\n"
|
| 510 |
-
|
| 511 |
-
messages = []
|
| 512 |
-
if combined_system_msg.strip():
|
| 513 |
-
messages.append({
|
| 514 |
-
"role": "system",
|
| 515 |
-
"content": [{"type": "text", "text": combined_system_msg.strip()}],
|
| 516 |
-
})
|
| 517 |
-
|
| 518 |
-
messages.extend(process_history(history))
|
| 519 |
-
|
| 520 |
-
user_content, user_temp_files = process_new_user_message(message)
|
| 521 |
-
temp_files.extend(user_temp_files) # 임시 파일 추적
|
| 522 |
-
|
| 523 |
-
for item in user_content:
|
| 524 |
-
if item["type"] == "text" and len(item["text"]) > MAX_CONTENT_CHARS:
|
| 525 |
-
item["text"] = item["text"][:MAX_CONTENT_CHARS] + "\n...(truncated)..."
|
| 526 |
-
messages.append({"role": "user", "content": user_content})
|
| 527 |
-
|
| 528 |
-
inputs = processor.apply_chat_template(
|
| 529 |
-
messages,
|
| 530 |
-
add_generation_prompt=True,
|
| 531 |
-
tokenize=True,
|
| 532 |
-
return_dict=True,
|
| 533 |
-
return_tensors="pt",
|
| 534 |
-
).to(device=model.device, dtype=torch.bfloat16)
|
| 535 |
-
|
| 536 |
-
# 입력 토큰 수 제한 추가
|
| 537 |
-
if inputs.input_ids.shape[1] > MAX_INPUT_LENGTH:
|
| 538 |
-
inputs.input_ids = inputs.input_ids[:, -MAX_INPUT_LENGTH:]
|
| 539 |
-
if 'attention_mask' in inputs:
|
| 540 |
-
inputs.attention_mask = inputs.attention_mask[:, -MAX_INPUT_LENGTH:]
|
| 541 |
-
|
| 542 |
-
streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
|
| 543 |
-
gen_kwargs = dict(
|
| 544 |
-
inputs=inputs,
|
| 545 |
-
streamer=streamer,
|
| 546 |
-
max_new_tokens=max_new_tokens,
|
| 547 |
-
)
|
| 548 |
-
|
| 549 |
-
t = Thread(target=_model_gen_with_oom_catch, kwargs=gen_kwargs)
|
| 550 |
-
t.start()
|
| 551 |
-
|
| 552 |
-
output = ""
|
| 553 |
-
for new_text in streamer:
|
| 554 |
-
output += new_text
|
| 555 |
-
yield output
|
| 556 |
-
|
| 557 |
-
except Exception as e:
|
| 558 |
-
logger.error(f"Error in run: {str(e)}")
|
| 559 |
-
yield f"Sorry, an error occurred: {str(e)}"
|
| 560 |
-
|
| 561 |
-
finally:
|
| 562 |
-
# 임시 파일 삭제
|
| 563 |
-
for temp_file in temp_files:
|
| 564 |
-
try:
|
| 565 |
-
if os.path.exists(temp_file):
|
| 566 |
-
os.unlink(temp_file)
|
| 567 |
-
logger.info(f"Deleted temp file: {temp_file}")
|
| 568 |
-
except Exception as e:
|
| 569 |
-
logger.warning(f"Failed to delete temp file {temp_file}: {e}")
|
| 570 |
-
|
| 571 |
-
# 명시적 메모리 정리
|
| 572 |
-
try:
|
| 573 |
-
del inputs, streamer
|
| 574 |
-
except:
|
| 575 |
-
pass
|
| 576 |
-
|
| 577 |
-
clear_cuda_cache()
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
##############################################################################
|
| 581 |
-
# 예시들 (모두 영어로)
|
| 582 |
-
##############################################################################
|
| 583 |
-
examples = [
|
| 584 |
-
[
|
| 585 |
-
{
|
| 586 |
-
"text": "Compare the contents of the two PDF files.",
|
| 587 |
-
"files": [
|
| 588 |
-
"assets/additional-examples/before.pdf",
|
| 589 |
-
"assets/additional-examples/after.pdf",
|
| 590 |
-
],
|
| 591 |
-
}
|
| 592 |
-
],
|
| 593 |
-
[
|
| 594 |
-
{
|
| 595 |
-
"text": "Summarize and analyze the contents of the CSV file.",
|
| 596 |
-
"files": ["assets/additional-examples/sample-csv.csv"],
|
| 597 |
-
}
|
| 598 |
-
],
|
| 599 |
-
[
|
| 600 |
-
{
|
| 601 |
-
"text": "Assume the role of a friendly and understanding girlfriend. Describe this video.",
|
| 602 |
-
"files": ["assets/additional-examples/tmp.mp4"],
|
| 603 |
-
}
|
| 604 |
-
],
|
| 605 |
-
[
|
| 606 |
-
{
|
| 607 |
-
"text": "Describe the cover and read the text on it.",
|
| 608 |
-
"files": ["assets/additional-examples/maz.jpg"],
|
| 609 |
-
}
|
| 610 |
-
],
|
| 611 |
-
[
|
| 612 |
-
{
|
| 613 |
-
"text": "I already have this supplement <image> and I plan to buy this product <image>. Are there any precautions when taking them together?",
|
| 614 |
-
"files": ["assets/additional-examples/pill1.png", "assets/additional-examples/pill2.png"],
|
| 615 |
-
}
|
| 616 |
-
],
|
| 617 |
-
[
|
| 618 |
-
{
|
| 619 |
-
"text": "Solve this integral.",
|
| 620 |
-
"files": ["assets/additional-examples/4.png"],
|
| 621 |
-
}
|
| 622 |
-
],
|
| 623 |
-
[
|
| 624 |
-
{
|
| 625 |
-
"text": "When was this ticket issued, and what is its price?",
|
| 626 |
-
"files": ["assets/additional-examples/2.png"],
|
| 627 |
-
}
|
| 628 |
-
],
|
| 629 |
-
[
|
| 630 |
-
{
|
| 631 |
-
"text": "Based on the sequence of these images, create a short story.",
|
| 632 |
-
"files": [
|
| 633 |
-
"assets/sample-images/09-1.png",
|
| 634 |
-
"assets/sample-images/09-2.png",
|
| 635 |
-
"assets/sample-images/09-3.png",
|
| 636 |
-
"assets/sample-images/09-4.png",
|
| 637 |
-
"assets/sample-images/09-5.png",
|
| 638 |
-
],
|
| 639 |
-
}
|
| 640 |
-
],
|
| 641 |
-
[
|
| 642 |
-
{
|
| 643 |
-
"text": "Write Python code using matplotlib to plot a bar chart that matches this image.",
|
| 644 |
-
"files": ["assets/additional-examples/barchart.png"],
|
| 645 |
-
}
|
| 646 |
-
],
|
| 647 |
-
[
|
| 648 |
-
{
|
| 649 |
-
"text": "Read the text in the image and write it out in Markdown format.",
|
| 650 |
-
"files": ["assets/additional-examples/3.png"],
|
| 651 |
-
}
|
| 652 |
-
],
|
| 653 |
-
[
|
| 654 |
-
{
|
| 655 |
-
"text": "What does this sign say?",
|
| 656 |
-
"files": ["assets/sample-images/02.png"],
|
| 657 |
-
}
|
| 658 |
-
],
|
| 659 |
-
[
|
| 660 |
-
{
|
| 661 |
-
"text": "Compare the two images and describe their similarities and differences.",
|
| 662 |
-
"files": ["assets/sample-images/03.png"],
|
| 663 |
-
}
|
| 664 |
-
],
|
| 665 |
-
]
|
| 666 |
-
|
| 667 |
-
##############################################################################
|
| 668 |
-
# Gradio UI (Blocks) 구성 (좌측 사이드 메뉴 없이 전체화면 채팅)
|
| 669 |
-
##############################################################################
|
| 670 |
-
css = """
|
| 671 |
-
/* 1) UI를 처음부터 가장 넓게 (width 100%) 고정하여 표시 */
|
| 672 |
-
.gradio-container {
|
| 673 |
-
background: rgba(255, 255, 255, 0.7); /* 배경 투명도 증가 */
|
| 674 |
-
padding: 30px 40px;
|
| 675 |
-
margin: 20px auto; /* 위아래 여백만 유지 */
|
| 676 |
-
width: 100% !important;
|
| 677 |
-
max-width: none !important; /* 1200px 제한 제거 */
|
| 678 |
-
}
|
| 679 |
-
.fillable {
|
| 680 |
-
width: 100% !important;
|
| 681 |
-
max-width: 100% !important;
|
| 682 |
-
}
|
| 683 |
-
/* 2) 배경을 완전히 투명하게 변경 */
|
| 684 |
-
body {
|
| 685 |
-
background: transparent; /* 완전 투명 배경 */
|
| 686 |
-
margin: 0;
|
| 687 |
-
padding: 0;
|
| 688 |
-
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
| 689 |
-
color: #333;
|
| 690 |
-
}
|
| 691 |
-
/* 버튼 색상 완전히 제거하고 투명하게 */
|
| 692 |
-
button, .btn {
|
| 693 |
-
background: transparent !important; /* 색상 완전히 제거 */
|
| 694 |
-
border: 1px solid #ddd; /* 경계선만 살짝 추가 */
|
| 695 |
-
color: #333;
|
| 696 |
-
padding: 12px 24px;
|
| 697 |
-
text-transform: uppercase;
|
| 698 |
-
font-weight: bold;
|
| 699 |
-
letter-spacing: 1px;
|
| 700 |
-
cursor: pointer;
|
| 701 |
-
}
|
| 702 |
-
button:hover, .btn:hover {
|
| 703 |
-
background: rgba(0, 0, 0, 0.05) !important; /* 호버 시 아주 살짝 어둡게만 */
|
| 704 |
-
}
|
| 705 |
-
|
| 706 |
-
/* examples 관련 모든 색상 제거 */
|
| 707 |
-
#examples_container, .examples-container {
|
| 708 |
-
margin: auto;
|
| 709 |
-
width: 90%;
|
| 710 |
-
background: transparent !important;
|
| 711 |
-
}
|
| 712 |
-
#examples_row, .examples-row {
|
| 713 |
-
justify-content: center;
|
| 714 |
-
background: transparent !important;
|
| 715 |
-
}
|
| 716 |
-
|
| 717 |
-
/* examples 버튼 내부의 모든 색상 제거 */
|
| 718 |
-
.gr-samples-table button,
|
| 719 |
-
.gr-samples-table .gr-button,
|
| 720 |
-
.gr-samples-table .gr-sample-btn,
|
| 721 |
-
.gr-examples button,
|
| 722 |
-
.gr-examples .gr-button,
|
| 723 |
-
.gr-examples .gr-sample-btn,
|
| 724 |
-
.examples button,
|
| 725 |
-
.examples .gr-button,
|
| 726 |
-
.examples .gr-sample-btn {
|
| 727 |
-
background: transparent !important;
|
| 728 |
-
border: 1px solid #ddd;
|
| 729 |
-
color: #333;
|
| 730 |
-
}
|
| 731 |
-
|
| 732 |
-
/* examples 버튼 호버 시에도 색상 없게 */
|
| 733 |
-
.gr-samples-table button:hover,
|
| 734 |
-
.gr-samples-table .gr-button:hover,
|
| 735 |
-
.gr-samples-table .gr-sample-btn:hover,
|
| 736 |
-
.gr-examples button:hover,
|
| 737 |
-
.gr-examples .gr-button:hover,
|
| 738 |
-
.gr-examples .gr-sample-btn:hover,
|
| 739 |
-
.examples button:hover,
|
| 740 |
-
.examples .gr-button:hover,
|
| 741 |
-
.examples .gr-sample-btn:hover {
|
| 742 |
-
background: rgba(0, 0, 0, 0.05) !important;
|
| 743 |
-
}
|
| 744 |
-
|
| 745 |
-
/* 채팅 인터페이스 요소들도 투명하게 */
|
| 746 |
-
.chatbox, .chatbot, .message {
|
| 747 |
-
background: transparent !important;
|
| 748 |
-
}
|
| 749 |
-
|
| 750 |
-
/* 입력창 투명도 조정 */
|
| 751 |
-
.multimodal-textbox, textarea, input {
|
| 752 |
-
background: rgba(255, 255, 255, 0.5) !important;
|
| 753 |
-
}
|
| 754 |
-
|
| 755 |
-
/* 모든 컨테이너 요소에 배경색 제거 */
|
| 756 |
-
.container, .wrap, .box, .panel, .gr-panel {
|
| 757 |
-
background: transparent !important;
|
| 758 |
-
}
|
| 759 |
-
|
| 760 |
-
/* 예제 섹션의 모든 요소에서 배경색 제거 */
|
| 761 |
-
.gr-examples-container, .gr-examples, .gr-sample, .gr-sample-row, .gr-sample-cell {
|
| 762 |
-
background: transparent !important;
|
| 763 |
-
}
|
| 764 |
-
"""
|
| 765 |
-
|
| 766 |
-
title_html = """
|
| 767 |
-
<h1 align="center" style="margin-bottom: 0.2em; font-size: 1.6em;"> 🤗 Gemma3-R1984-27B </h1>
|
| 768 |
-
<p align="center" style="font-size:1.1em; color:#555;">
|
| 769 |
-
✅Agentic AI Platform ✅Reasoning & Uncensored ✅Multimodal & VLM ✅Deep-Research & RAG <br>
|
| 770 |
-
Operates on an NVIDIA A100 GPU as an independent local server, enhancing security and preventing information leakage.<br>
|
| 771 |
-
@Based by 'MS Gemma-3-27b' / @Powered by 'MOUSE-II'(VIDRAFT)
|
| 772 |
-
</p>
|
| 773 |
-
"""
|
| 774 |
-
|
| 775 |
-
with gr.Blocks(css=css, title="Gemma3-R1984-27B") as demo:
|
| 776 |
-
gr.Markdown(title_html)
|
| 777 |
-
|
| 778 |
-
# Display the web search option (while the system prompt and token slider remain hidden)
|
| 779 |
-
web_search_checkbox = gr.Checkbox(
|
| 780 |
-
label="Deep Research",
|
| 781 |
-
value=False
|
| 782 |
-
)
|
| 783 |
-
|
| 784 |
-
# Used internally but not visible to the user
|
| 785 |
-
system_prompt_box = gr.Textbox(
|
| 786 |
-
lines=3,
|
| 787 |
-
value="Please answer in English. You are a deep thinking AI that may use extremely long chains of thought to thoroughly analyze the problem and deliberate using systematic reasoning processes to arrive at a correct solution before answering. You have the ability to read sources in other languages, but you must always answer in English. Even if the search results are in another language, answer in English.",
|
| 788 |
-
visible=False # hidden from view
|
| 789 |
-
)
|
| 790 |
-
|
| 791 |
-
max_tokens_slider = gr.Slider(
|
| 792 |
-
label="Max New Tokens",
|
| 793 |
-
minimum=100,
|
| 794 |
-
maximum=8000,
|
| 795 |
-
step=50,
|
| 796 |
-
value=1000,
|
| 797 |
-
visible=False # hidden from view
|
| 798 |
-
)
|
| 799 |
-
|
| 800 |
-
web_search_text = gr.Textbox(
|
| 801 |
-
lines=1,
|
| 802 |
-
label="(Unused) Web Search Query",
|
| 803 |
-
placeholder="No direct input needed",
|
| 804 |
-
visible=False # hidden from view
|
| 805 |
-
)
|
| 806 |
-
|
| 807 |
-
# Configure the chat interface
|
| 808 |
-
chat = gr.ChatInterface(
|
| 809 |
-
fn=run,
|
| 810 |
-
type="messages",
|
| 811 |
-
chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
|
| 812 |
-
textbox=gr.MultimodalTextbox(
|
| 813 |
-
file_types=[
|
| 814 |
-
".webp", ".png", ".jpg", ".jpeg", ".gif",
|
| 815 |
-
".mp4", ".csv", ".txt", ".pdf"
|
| 816 |
-
],
|
| 817 |
-
file_count="multiple",
|
| 818 |
-
autofocus=True
|
| 819 |
-
),
|
| 820 |
-
multimodal=True,
|
| 821 |
-
additional_inputs=[
|
| 822 |
-
system_prompt_box,
|
| 823 |
-
max_tokens_slider,
|
| 824 |
-
web_search_checkbox,
|
| 825 |
-
web_search_text,
|
| 826 |
-
],
|
| 827 |
-
stop_btn=False,
|
| 828 |
-
title='<a href="https://discord.gg/openfreeai" target="_blank">https://discord.gg/openfreeai</a>',
|
| 829 |
-
examples=examples,
|
| 830 |
-
run_examples_on_click=False,
|
| 831 |
-
cache_examples=False,
|
| 832 |
-
css_paths=None,
|
| 833 |
-
delete_cache=(1800, 1800),
|
| 834 |
-
)
|
| 835 |
-
|
| 836 |
-
# Example section - since examples are already set in ChatInterface, this is for display only
|
| 837 |
-
with gr.Row(elem_id="examples_row"):
|
| 838 |
-
with gr.Column(scale=12, elem_id="examples_container"):
|
| 839 |
-
gr.Markdown("### Example Inputs (click to load)")
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
if __name__ == "__main__":
|
| 843 |
-
# Run locally
|
| 844 |
-
demo.launch(mcp_server=True)
|
| 845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chat_template.jinja
DELETED
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
{{ bos_token }}
|
| 2 |
-
{%- if messages[0]['role'] == 'system' -%}
|
| 3 |
-
{%- if messages[0]['content'] is string -%}
|
| 4 |
-
{%- set first_user_prefix = messages[0]['content'] + '
|
| 5 |
-
|
| 6 |
-
' -%}
|
| 7 |
-
{%- else -%}
|
| 8 |
-
{%- set first_user_prefix = messages[0]['content'][0]['text'] + '
|
| 9 |
-
|
| 10 |
-
' -%}
|
| 11 |
-
{%- endif -%}
|
| 12 |
-
{%- set loop_messages = messages[1:] -%}
|
| 13 |
-
{%- else -%}
|
| 14 |
-
{%- set first_user_prefix = "" -%}
|
| 15 |
-
{%- set loop_messages = messages -%}
|
| 16 |
-
{%- endif -%}
|
| 17 |
-
{%- for message in loop_messages -%}
|
| 18 |
-
{%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}
|
| 19 |
-
{{ raise_exception("Conversation roles must alternate user/assistant/user/assistant/...") }}
|
| 20 |
-
{%- endif -%}
|
| 21 |
-
{%- if (message['role'] == 'assistant') -%}
|
| 22 |
-
{%- set role = "model" -%}
|
| 23 |
-
{%- else -%}
|
| 24 |
-
{%- set role = message['role'] -%}
|
| 25 |
-
{%- endif -%}
|
| 26 |
-
{{ '<start_of_turn>' + role + '
|
| 27 |
-
' + (first_user_prefix if loop.first else "") }}
|
| 28 |
-
{%- if message['content'] is string -%}
|
| 29 |
-
{{ message['content'] | trim }}
|
| 30 |
-
{%- elif message['content'] is iterable -%}
|
| 31 |
-
{%- for item in message['content'] -%}
|
| 32 |
-
{%- if item['type'] == 'image' -%}
|
| 33 |
-
{{ '<start_of_image>' }}
|
| 34 |
-
{%- elif item['type'] == 'text' -%}
|
| 35 |
-
{{ item['text'] | trim }}
|
| 36 |
-
{%- endif -%}
|
| 37 |
-
{%- endfor -%}
|
| 38 |
-
{%- else -%}
|
| 39 |
-
{{ raise_exception("Invalid content type") }}
|
| 40 |
-
{%- endif -%}
|
| 41 |
-
{{ '<end_of_turn>
|
| 42 |
-
' }}
|
| 43 |
-
{%- endfor -%}
|
| 44 |
-
{%- if add_generation_prompt -%}
|
| 45 |
-
{{'<start_of_turn>model
|
| 46 |
-
'}}
|
| 47 |
-
{%- endif -%}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pre-commit-config.yaml
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
repos:
|
| 2 |
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 3 |
-
rev: v5.0.0
|
| 4 |
-
hooks:
|
| 5 |
-
- id: check-executables-have-shebangs
|
| 6 |
-
- id: check-json
|
| 7 |
-
- id: check-merge-conflict
|
| 8 |
-
- id: check-shebang-scripts-are-executable
|
| 9 |
-
- id: check-toml
|
| 10 |
-
- id: check-yaml
|
| 11 |
-
- id: end-of-file-fixer
|
| 12 |
-
- id: mixed-line-ending
|
| 13 |
-
args: ["--fix=lf"]
|
| 14 |
-
- id: requirements-txt-fixer
|
| 15 |
-
- id: trailing-whitespace
|
| 16 |
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
| 17 |
-
rev: v0.9.9
|
| 18 |
-
hooks:
|
| 19 |
-
- id: ruff
|
| 20 |
-
args: ["--fix"]
|
| 21 |
-
- id: ruff-format
|
| 22 |
-
- repo: https://github.com/pre-commit/mirrors-mypy
|
| 23 |
-
rev: v1.15.0
|
| 24 |
-
hooks:
|
| 25 |
-
- id: mypy
|
| 26 |
-
args: ["--ignore-missing-imports"]
|
| 27 |
-
additional_dependencies:
|
| 28 |
-
[
|
| 29 |
-
"types-python-slugify",
|
| 30 |
-
"types-pytz",
|
| 31 |
-
"types-PyYAML",
|
| 32 |
-
"types-requests",
|
| 33 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyproject.toml
DELETED
|
@@ -1,62 +0,0 @@
|
|
| 1 |
-
[project]
|
| 2 |
-
name = "gemma-3-12b-it"
|
| 3 |
-
version = "0.1.0"
|
| 4 |
-
description = ""
|
| 5 |
-
readme = "README.md"
|
| 6 |
-
requires-python = ">=3.10"
|
| 7 |
-
dependencies = [
|
| 8 |
-
"accelerate>=1.4.0",
|
| 9 |
-
"gradio>=5.21.0",
|
| 10 |
-
"hf-transfer>=0.1.9",
|
| 11 |
-
"loguru>=0.7.3",
|
| 12 |
-
"opencv-python-headless>=4.11.0.86",
|
| 13 |
-
"protobuf>=6.30.0",
|
| 14 |
-
"sentencepiece>=0.2.0",
|
| 15 |
-
"spaces>=0.32.0",
|
| 16 |
-
"torch==2.4.0",
|
| 17 |
-
"transformers",
|
| 18 |
-
]
|
| 19 |
-
|
| 20 |
-
[tool.ruff]
|
| 21 |
-
line-length = 119
|
| 22 |
-
|
| 23 |
-
[tool.ruff.lint]
|
| 24 |
-
select = ["ALL"]
|
| 25 |
-
ignore = [
|
| 26 |
-
"COM812", # missing-trailing-comma
|
| 27 |
-
"D203", # one-blank-line-before-class
|
| 28 |
-
"D213", # multi-line-summary-second-line
|
| 29 |
-
"E501", # line-too-long
|
| 30 |
-
"SIM117", # multiple-with-statements
|
| 31 |
-
#
|
| 32 |
-
"D100", # undocumented-public-module
|
| 33 |
-
"D101", # undocumented-public-class
|
| 34 |
-
"D102", # undocumented-public-method
|
| 35 |
-
"D103", # undocumented-public-function
|
| 36 |
-
"D104", # undocumented-public-package
|
| 37 |
-
"D105", # undocumented-magic-method
|
| 38 |
-
"D107", # undocumented-public-init
|
| 39 |
-
"EM101", # raw-string-in-exception
|
| 40 |
-
"FBT001", # boolean-type-hint-positional-argument
|
| 41 |
-
"FBT002", # boolean-default-value-positional-argument
|
| 42 |
-
"PD901", # pandas-df-variable-name
|
| 43 |
-
"PGH003", # blanket-type-ignore
|
| 44 |
-
"PLR0913", # too-many-arguments
|
| 45 |
-
"PLR0915", # too-many-statements
|
| 46 |
-
"TRY003", # raise-vanilla-args
|
| 47 |
-
]
|
| 48 |
-
unfixable = [
|
| 49 |
-
"F401", # unused-import
|
| 50 |
-
]
|
| 51 |
-
|
| 52 |
-
[tool.ruff.lint.pydocstyle]
|
| 53 |
-
convention = "google"
|
| 54 |
-
|
| 55 |
-
[tool.ruff.lint.per-file-ignores]
|
| 56 |
-
"*.ipynb" = ["T201", "T203"]
|
| 57 |
-
|
| 58 |
-
[tool.ruff.format]
|
| 59 |
-
docstring-code-format = true
|
| 60 |
-
|
| 61 |
-
[tool.uv.sources]
|
| 62 |
-
transformers = { git = "https://github.com/huggingface/transformers", rev = "2829013d2d00e63d75a1f6f7a3f003bc60cc69af" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
python-version
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
3.10
|
|
|
|
|
|
requirements.txt
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
accelerate==1.4.0
|
| 2 |
-
aiofiles==23.2.1
|
| 3 |
-
annotated-types==0.7.0
|
| 4 |
-
anyio==4.8.0
|
| 5 |
-
certifi==2025.1.31
|
| 6 |
-
charset-normalizer==3.4.1
|
| 7 |
-
click==8.1.8
|
| 8 |
-
exceptiongroup==1.2.2
|
| 9 |
-
fastapi==0.115.11
|
| 10 |
-
ffmpy==0.5.0
|
| 11 |
-
filelock==3.17.0
|
| 12 |
-
fsspec==2025.3.0
|
| 13 |
-
gradio==5.21.0
|
| 14 |
-
gradio-client==1.7.2
|
| 15 |
-
groovy==0.1.2
|
| 16 |
-
h11==0.14.0
|
| 17 |
-
hf-transfer==0.1.9
|
| 18 |
-
httpcore==1.0.7
|
| 19 |
-
httpx==0.28.1
|
| 20 |
-
huggingface-hub==0.29.2
|
| 21 |
-
idna==3.10
|
| 22 |
-
jinja2==3.1.6
|
| 23 |
-
loguru==0.7.3
|
| 24 |
-
markdown-it-py==3.0.0
|
| 25 |
-
markupsafe==2.1.5
|
| 26 |
-
mdurl==0.1.2
|
| 27 |
-
mpmath==1.3.0
|
| 28 |
-
networkx==3.4.2
|
| 29 |
-
numpy==2.2.3
|
| 30 |
-
nvidia-cublas-cu12==12.1.3.1
|
| 31 |
-
nvidia-cuda-cupti-cu12==12.1.105
|
| 32 |
-
nvidia-cuda-nvrtc-cu12==12.1.105
|
| 33 |
-
nvidia-cuda-runtime-cu12==12.1.105
|
| 34 |
-
nvidia-cudnn-cu12==9.1.0.70
|
| 35 |
-
nvidia-cufft-cu12==11.0.2.54
|
| 36 |
-
nvidia-curand-cu12==10.3.2.106
|
| 37 |
-
nvidia-cusolver-cu12==11.4.5.107
|
| 38 |
-
nvidia-cusparse-cu12==12.1.0.106
|
| 39 |
-
nvidia-nccl-cu12==2.20.5
|
| 40 |
-
nvidia-nvjitlink-cu12==12.8.93
|
| 41 |
-
nvidia-nvtx-cu12==12.1.105
|
| 42 |
-
|
| 43 |
-
# opencv-python 계열은 headless만 유지 (GUI가 필요 없다면)
|
| 44 |
-
opencv-python==4.11.0.86
|
| 45 |
-
|
| 46 |
-
orjson==3.10.15
|
| 47 |
-
packaging==24.2
|
| 48 |
-
pandas==2.2.3
|
| 49 |
-
pillow==11.1.0
|
| 50 |
-
protobuf==6.30.0
|
| 51 |
-
psutil==5.9.8
|
| 52 |
-
pydantic==2.10.6
|
| 53 |
-
pydantic-core==2.27.2
|
| 54 |
-
pydub==0.25.1
|
| 55 |
-
pygments==2.19.1
|
| 56 |
-
python-dateutil==2.9.0.post0
|
| 57 |
-
python-multipart==0.0.20
|
| 58 |
-
pytz==2025.1
|
| 59 |
-
pyyaml==6.0.2
|
| 60 |
-
regex==2024.11.6
|
| 61 |
-
requests==2.32.3
|
| 62 |
-
rich==13.9.4
|
| 63 |
-
ruff==0.9.10
|
| 64 |
-
safehttpx==0.1.6
|
| 65 |
-
safetensors==0.5.3
|
| 66 |
-
semantic-version==2.10.0
|
| 67 |
-
sentencepiece==0.2.0
|
| 68 |
-
shellingham==1.5.4
|
| 69 |
-
six==1.17.0
|
| 70 |
-
sniffio==1.3.1
|
| 71 |
-
spaces==0.32.0
|
| 72 |
-
starlette==0.46.1
|
| 73 |
-
sympy==1.13.3
|
| 74 |
-
tokenizers==0.21.0
|
| 75 |
-
tomlkit==0.13.2
|
| 76 |
-
torch==2.4.0
|
| 77 |
-
tqdm==4.67.1
|
| 78 |
-
transformers @ git+https://github.com/huggingface/transformers@2829013d2d00e63d75a1f6f7a3f003bc60cc69af
|
| 79 |
-
triton==3.0.0
|
| 80 |
-
typer==0.15.2
|
| 81 |
-
typing-extensions==4.12.2
|
| 82 |
-
tzdata==2025.1
|
| 83 |
-
urllib3==2.3.0
|
| 84 |
-
uvicorn==0.34.0
|
| 85 |
-
websockets==15.0.1
|
| 86 |
-
|
| 87 |
-
# PDF 텍스트 추출용
|
| 88 |
-
PyPDF2==3.0.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
style .css
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
h1 {
|
| 2 |
-
text-align: center;
|
| 3 |
-
display: block;
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
#logo {
|
| 7 |
-
display: block;
|
| 8 |
-
margin: 0 auto;
|
| 9 |
-
width: 40%;
|
| 10 |
-
object-fit: contain;
|
| 11 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uv.lock
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|