|
|
import os, io, time, json, re, requests |
|
|
from io import BytesIO |
|
|
from PIL import Image |
|
|
import streamlit as st |
|
|
from google import genai |
|
|
|
|
|
st.set_page_config(page_title="๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)", page_icon="๐ค", layout="wide") |
|
|
|
|
|
|
|
|
st.sidebar.title("๐ง ์ค์ ") |
|
|
api_key = st.sidebar.text_input("GOOGLE_API_KEY", value=os.getenv("GOOGLE_API_KEY", ""), type="password") |
|
|
model_name = st.sidebar.selectbox("๋ชจ๋ธ", ["gemma-3-27b-it"], index=0) |
|
|
rate_delay = st.sidebar.number_input("ํธ์ถ๊ฐ ๋๊ธฐ(์ด)", value=1.0, step=0.5, min_value=0.0) |
|
|
|
|
|
if not api_key: |
|
|
st.warning("์ฌ์ด๋๋ฐ์ GOOGLE_API_KEY๋ฅผ ์
๋ ฅํ๊ฑฐ๋ ํ๊ฒฝ๋ณ์๋ก ์ค์ ํด ์ฃผ์ธ์.") |
|
|
st.stop() |
|
|
|
|
|
client = genai.Client(api_key=api_key) |
|
|
|
|
|
|
|
|
RETRY_LIMIT = 3 |
|
|
|
|
|
def load_image(source: str) -> Image.Image: |
|
|
"""URL/๋ก์ปฌ ๋ชจ๋ ์ง์ + ํฌ๋ช
๋ฐฐ๊ฒฝ ๋ณด์ .""" |
|
|
session = requests.Session() |
|
|
session.headers.update({"User-Agent": "Mozilla/5.0"}) |
|
|
last_err = None |
|
|
for _ in range(RETRY_LIMIT): |
|
|
try: |
|
|
if source.startswith(("http://", "https://")): |
|
|
resp = session.get(source, timeout=10) |
|
|
resp.raise_for_status() |
|
|
img = Image.open(BytesIO(resp.content)) |
|
|
else: |
|
|
img = Image.open(source) |
|
|
|
|
|
if img.mode in ("RGBA", "LA", "P"): |
|
|
bg = Image.new("RGB", img.size, (255, 255, 255)) |
|
|
img = img.convert("RGBA") |
|
|
bg.paste(img, mask=img.split()[-1] if len(img.split()) > 3 else None) |
|
|
img = bg |
|
|
else: |
|
|
img = img.convert("RGB") |
|
|
return img |
|
|
except Exception as e: |
|
|
last_err = e |
|
|
time.sleep(0.5) |
|
|
raise RuntimeError(f"์ด๋ฏธ์ง ๋ก๋ ์คํจ: {last_err}") |
|
|
|
|
|
RESET_PROMPT = "์ด์ ๋ํ๋ ๋ฌด์ํ๊ณ , ์๋ ์ง์์๋ง ์๋ตํ์ธ์.\n\n" |
|
|
|
|
|
def try_parse_json(raw_text: str): |
|
|
"""์๋ต์์ JSON ์ถ์ถ ์๋.""" |
|
|
pattern = r'```(?:json)?\s*(\{[\s\S]*?\})\s*```|(\{[\s\S]*?\})' |
|
|
for g1, g2 in re.findall(pattern, raw_text): |
|
|
cand = (g1 or g2).strip() |
|
|
try: |
|
|
return json.loads(cand) |
|
|
except json.JSONDecodeError: |
|
|
pass |
|
|
cleaned = re.sub(r'```json|```', '', raw_text).strip() |
|
|
try: |
|
|
return json.loads(cleaned) |
|
|
except json.JSONDecodeError: |
|
|
return None |
|
|
|
|
|
def infer_text(article: str, prompt: str): |
|
|
contents = [RESET_PROMPT + prompt.strip() + "\n\n" + article.strip()] |
|
|
resp = client.models.generate_content(model=model_name, contents=contents) |
|
|
time.sleep(rate_delay) |
|
|
text = (resp.text or "").strip() |
|
|
parsed = try_parse_json(text) |
|
|
return parsed if parsed is not None else {"text": text} |
|
|
|
|
|
def infer_image(image: Image.Image, prompt: str): |
|
|
contents = [RESET_PROMPT + prompt.strip(), image] |
|
|
resp = client.models.generate_content(model=model_name, contents=contents) |
|
|
time.sleep(rate_delay) |
|
|
text = (resp.text or "").strip() |
|
|
parsed = try_parse_json(text) |
|
|
return parsed if parsed is not None else {"text": text} |
|
|
|
|
|
|
|
|
TEXT_PROMPT = ( |
|
|
"๋น์ ์ ๊ธฐ์ฌ ์ ๋ณด์ ๋ถ์ ์ ๋ฌธ๊ฐ์
๋๋ค. ๋ค์ ๊ธฐ์ฌ์์ ์ ๋ณด์์ ์ถ์ถํ๊ณ , " |
|
|
"์ ๋ณด์๋ณ ๋ฌ์ฌ ํ๋ ์์ ๊ธ์ /์ค๋ฆฝ/๋ถ์ ์ผ๋ก ํ์ ํด JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์.\n" |
|
|
'์์: {"sources": ["์ ๋ณด์A","์ ๋ณด์B"], "frames": ["์ค๋ฆฝ","๋ถ์ "]}' |
|
|
) |
|
|
|
|
|
IMAGE_PROMPT = ( |
|
|
"๋น์ ์ ๋ณด๋์ฌ์ง ๋ถ์ ์ ๋ฌธ๊ฐ์
๋๋ค. ์ ๊ณต๋ ์ฌ์ง์์ Donald Trump ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ณ " |
|
|
"๊ฐ์ (emotion: positive/negative/neutral)๊ณผ ์ญ๋์ฑ(dynamism: high/medium/low)์ ํ๊ฐํด " |
|
|
'JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์. Trump๊ฐ ์์ผ๋ฉด {"trump_present": false}๋ง ๋ฐํํ์ธ์.' |
|
|
) |
|
|
|
|
|
|
|
|
st.title("๐ค ๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)") |
|
|
tab_text, tab_img = st.tabs(["๐ ํ
์คํธ ๋ถ์", "๐ผ๏ธ ์ด๋ฏธ์ง ๋ถ์"]) |
|
|
|
|
|
with tab_text: |
|
|
st.subheader("๊ธฐ์ฌ ํ
์คํธ โ ์ ๋ณด์ & ํ๋ ์ ํ์ ") |
|
|
article = st.text_area( |
|
|
"๊ธฐ์ฌ ๋ณธ๋ฌธ", height=180, |
|
|
value="Donald Trump์ Nancy Pelosi๊ฐ ํ์์ฅ์์ ๊ฒฉ๋ ฌํ ๋
ผ์์ ๋ฒ์๋ค..." |
|
|
) |
|
|
user_prompt = st.text_area("ํ๋กฌํํธ(์ต์
)", value=TEXT_PROMPT, height=120) |
|
|
if st.button("ํ
์คํธ ๋ถ์ ์คํ", type="primary", use_container_width=True): |
|
|
if not article.strip(): |
|
|
st.error("๊ธฐ์ฌ ๋ณธ๋ฌธ์ ์
๋ ฅํด ์ฃผ์ธ์.") |
|
|
else: |
|
|
with st.spinner("๋ถ์ ์ค..."): |
|
|
try: |
|
|
result = infer_text(article, user_prompt or TEXT_PROMPT) |
|
|
st.success("์๋ฃ") |
|
|
st.json(result) |
|
|
st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2), |
|
|
file_name="text_result.json", mime="application/json") |
|
|
except Exception as e: |
|
|
st.error(f"์ค๋ฅ: {e}") |
|
|
|
|
|
with tab_img: |
|
|
st.subheader("๋ณด๋์ฌ์ง โ ์ธ๋ฌผ ์กด์ฌยท๊ฐ์ ยท์ญ๋์ฑ ๋ถ์") |
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
img_url = st.text_input("์ด๋ฏธ์ง URL") |
|
|
with col2: |
|
|
file = st.file_uploader("์ด๋ฏธ์ง ์
๋ก๋", type=["jpg","jpeg","png","webp","gif","bmp"]) |
|
|
|
|
|
img_prompt = st.text_area("ํ๋กฌํํธ(์ต์
)", value=IMAGE_PROMPT, height=120) |
|
|
if st.button("์ด๋ฏธ์ง ๋ถ์ ์คํ", type="primary", use_container_width=True): |
|
|
try: |
|
|
if file is not None: |
|
|
image = Image.open(io.BytesIO(file.read())).convert("RGB") |
|
|
elif img_url.strip(): |
|
|
image = load_image(img_url.strip()) |
|
|
else: |
|
|
st.error("์ด๋ฏธ์ง URL์ ์
๋ ฅํ๊ฑฐ๋ ํ์ผ์ ์
๋ก๋ํด ์ฃผ์ธ์.") |
|
|
st.stop() |
|
|
|
|
|
st.image(image, caption="์
๋ ฅ ์ด๋ฏธ์ง", use_container_width=True) |
|
|
with st.spinner("๋ถ์ ์ค..."): |
|
|
result = infer_image(image, img_prompt or IMAGE_PROMPT) |
|
|
st.success("์๋ฃ") |
|
|
st.json(result) |
|
|
st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2), |
|
|
file_name="image_result.json", mime="application/json") |
|
|
except Exception as e: |
|
|
st.error(f"์ค๋ฅ: {e}") |
|
|
|