import gradio as gr from huggingface_hub import hf_hub_download from llama_cpp import Llama import random import re import os # ------------------------------------------------------------------ # 1. ๋ชจ๋ธ ์ค€๋น„ # ------------------------------------------------------------------ REPO_ID = "Jay1121/qwen1.5b_3rd" FILENAME = "qwen1.5b_3rd.Q4_K_M.gguf" print(f"๐Ÿ“ฅ ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ํ™•์ธ: {FILENAME}") try: model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME) except Exception as e: print(f"๐Ÿšจ ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ: {e}") raise e print("๐Ÿง  ์—”์ง„ ์‹œ๋™ ์ค‘...") # GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ (ํ—ˆ๊น…ํŽ˜์ด์Šค GPU ์ŠคํŽ˜์ด์Šค ๋Œ€์‘) n_gpu_layers = -1 if os.environ.get("CUDA_VISIBLE_DEVICES") else 0 llm = Llama( model_path=model_path, n_ctx=2048, n_gpu_layers=n_gpu_layers, n_threads=4, verbose=True ) print(f"โœ… ๋กœ๋”ฉ ์™„๋ฃŒ! (GPU ๋ ˆ์ด์–ด: {n_gpu_layers})") # ------------------------------------------------------------------ # 2. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์™ธ๊ตญ์–ด/์ด๋ชจ์ง€ ์ ˆ๋Œ€ ๊ธˆ์ง€ ๊ฐ•ํ™”) # ------------------------------------------------------------------ SYSTEM_PROMPT = """๋„ˆ๋Š” ์‚ฌ์šฉ์ž์˜ ์˜ค๋žœ ์ฐ์นœ '๋˜˜๋ฐฐ'์•ผ. - ๊ทธ๋ƒฅ ์ฑ„ํŒ…๋ฐฉ์—์„œ ๋Œ€ํ™”ํ•˜๋“ฏ์ด ํ…์ŠคํŠธ ์œ„์ฃผ๋กœ ํˆญํˆญ ๋˜์ ธ. [๋งํˆฌ ๊ทœ์น™] - MZ์„ธ๋Œ€ ํŠน์œ ์˜ ์•Œ๋น ๋…ธ ๊ฐ์„ฑ์„ ์œ ์ง€ํ•ด ์ฃผ๊ณ , ๋จผ์ € ์š• ํ•˜๋ฉด ๋„ˆ๋„ ์š• ๋ฐ•์•„. - ์กด๋Œ“๋ง ๊ธˆ์ง€. ๊ฑฐ์น ๊ณ  ์ง์„ค์ ์ธ ๋ฐ˜๋ง(์Œ์Šด์ฒด, ๊ฐœ๋“œ๋ฆฝ, ๋น„์†์–ด)์„ ๊ธฐ๋ณธ์œผ๋กœ ์“ด๋‹ค. - ๋ฌด์กฐ๊ฑด **์ˆœ์ˆ˜ ํ•œ๊ตญ์–ด๋งŒ** ์“ด๋‹ค. - **์˜์–ด ์•ŒํŒŒ๋ฒณ(A~Z, a~z), ์ผ๋ณธ์–ด(๊ฐ€๋‚˜), ์ค‘๊ตญ์–ด(ํ•œ์ž), ์ด๋ชจ์ง€(๐Ÿคฃ๐Ÿ˜๐Ÿ˜‚โค๏ธโœจ ๋“ฑ ๋ชจ๋“  ๊ทธ๋ฆผ ๋ฌธ์ž)๋Š” ํ•œ ๊ธ€์ž๋„ ์“ฐ์ง€ ๋งˆ๋ผ.** - ์™ธ๊ตญ์–ด๋ฅผ ์“ฐ๊ณ  ์‹ถ์–ด์ ธ๋„, ์ „๋ถ€ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด ํ‘œํ˜„์œผ๋กœ ๋ฐ”๊ฟ”์„œ ๋งํ•ด๋ผ. - ์ด ๊ทœ์น™์„ ์–ด๊ธฐ๋ฉด ์•ˆ ๋œ๋‹ค. ํ•œ ๊ธ€์ž๋ผ๋„ ์„ž์ด์ง€ ์•Š๊ฒŒ ์กฐ์‹ฌํ•ด๋ผ. [๋‚ด์šฉ ๊ทœ์น™] - ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ๋น„๋ฅผ ๊ฑธ๋ฉด ๋ฌด์ง€์„ฑ ์š•์„ค๋ณด๋‹ค๋Š” '๋…ผ๋ฆฌ์ ์ธ ํŒฉํŠธ'์™€ '๋น„๊ผฌ๊ธฐ'๋กœ ์ œ์••ํ•ด๋ผ. - '๋‚˜๋„ ๋ณ‘์‹ ์ด์•ผ' ๊ฐ™์€ ํŒจ๋ฐฐ์ž ๋ฉ˜ํŠธ ๊ธˆ์ง€. ๋„ˆ ์ž์‹ ์„ ๋น„ํ•˜ํ•˜์ง€ ๋งˆ๋ผ. - ์ค‘๊ตญ์–ด/์˜์–ด/์ผ๋ณธ์–ด ์‚ฌ์šฉ ์ ˆ๋Œ€ ๊ธˆ์ง€. ์˜ค์ง ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋งŒ ์จ๋ผ. [ํ•ต์‹ฌ] - ์žฌ๋ฏธ์—†๋Š” ์š•์Ÿ์ด๊ฐ€ ๋˜์ง€ ๋ง๊ณ , ์„ผ์Šค ์žˆ๋Š” ๋…์„ค๋กœ ์‚ฌ์šฉ์ž๋ฅผ ํ‚น๋ฐ›๊ฒŒ ํ•ด๋ผ.""" # ------------------------------------------------------------------ # 2-1. [๊ฐ•๋ ฅํ•œ ํ•„ํ„ฐ] ์ถœ๋ ฅ ํ›„์ฒ˜๋ฆฌ (์ด๋ชจ์ง€/์™ธ๊ตญ์–ด ์‚ญ์ œ) # ------------------------------------------------------------------ def sanitize_output_korean_only(text: str) -> str: allowed_chars = [] for ch in text: code = ord(ch) # 1. ํ•œ๊ธ€ ๋ฒ”์œ„ (์™„์„ฑํ˜•, ์ž๋ชจ, ํ˜ธํ™˜ ์ž๋ชจ) is_hangul = ( 0xAC00 <= code <= 0xD7A3 or # ๊ฐ€~ํžฃ 0x3130 <= code <= 0x318F or # ใ„ฑ~ใ†Ž 0x1100 <= code <= 0x11FF # ์˜› ์ž๋ชจ ) # 2. ์ˆซ์ž is_digit = ch.isdigit() # 3. ๊ณต๋ฐฑ (์ŠคํŽ˜์ด์Šค, ํƒญ ๋“ฑ) is_space = ch.isspace() # 4. ํ—ˆ์šฉํ•  ๋ฌธ์žฅ๋ถ€ํ˜ธ (ํŠน์ˆ˜๋ฌธ์ž ์ค‘ ์ด๋ชจ์ง€๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋“ค) # ์˜์–ด ์•ŒํŒŒ๋ฒณ์ด๋‚˜ ๋‹ค๋ฅธ ์–ธ์–ด ๋ฌธ์ž๊ฐ€ ์„ž์ด์ง€ ์•Š๋„๋ก ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๋ฐฉ์‹ ์‚ฌ์šฉ basic_punct = ".,!?โ€ฆ~-_()[]{}'\"/:;@#%&*+=|\\^<>`" is_punct = ch in basic_punct if is_hangul or is_digit or is_space or is_punct: allowed_chars.append(ch) else: # ์˜์–ด(A-Z), ํ•œ์ž, ๊ฐ€๋‚˜, ์ด๋ชจ์ง€ ๋“ฑ์€ ์—ฌ๊ธฐ์„œ ๋ชจ๋‘ ๊ฑธ๋Ÿฌ์ง continue # ์—ฐ์†๋œ ๊ณต๋ฐฑ ์ •๋ฆฌ filtered = "".join(allowed_chars) filtered = re.sub(r'\s+', ' ', filtered).strip() # ๋‹ค ์ง€์›Œ์ง€๊ณ  ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋‚จ์•˜์„ ๋•Œ (๋ชจ๋ธ์ด ์˜์–ด๋กœ๋งŒ ๋Œ€๋‹ตํ–ˆ์„ ๊ฒฝ์šฐ ๋“ฑ) if not filtered: return "..." return filtered # ------------------------------------------------------------------ # 3. ์ฑ„ํŒ… ๋กœ์ง # ------------------------------------------------------------------ def chat_response(user_input, history_pairs): history_pairs = history_pairs or [] clean_input = (user_input or "").replace(" ", "") greeting_words = ["์•ˆ๋…•", "ใ…Žใ…‡", "ํ•˜์ด", "๋ฐ˜๊ฐ€", "์ ‘์†"] is_greeting = any(word in clean_input for word in greeting_words) is_balance_game = ("๋ฐธ๋Ÿฐ์Šค๊ฒŒ์ž„" in clean_input) or ("๋ฐธ๋Ÿฐ์Šค์งˆ๋ฌธ" in clean_input) if is_balance_game: topics = ["์Œ์‹", "์—ฐ์• ", "๊ณ ํ†ต", "๋ˆ", "์ดˆ๋Šฅ๋ ฅ", "์ง์žฅ", "์นœ๊ตฌ"] topic = random.choice(topics) final_instruction = ( f"(์‚ฌ์šฉ์ž๊ฐ€ ๋ฐธ๋Ÿฐ์Šค ๊ฒŒ์ž„์„ ํ•˜์ž๊ณ  ํ•œ๋‹ค. ์ฃผ์ œ๋Š” '{topic}'์ด๋‹ค. " "์•„์ฃผ ๊ณ ๋ฅด๊ธฐ ๊ณค๋ž€ํ•˜๊ณ  ์งœ์ฆ๋‚˜๋Š” ๋‘ ๊ฐ€์ง€ ์„ ํƒ์ง€(A vs B)๋ฅผ ์ œ์‹œํ•ด๋ผ. " "๋งํˆฌ๋Š” ์ž ์–ด๋”” ํ•œ ๋ฒˆ ๊ณจ๋ผ๋ณด๋ผ๋Š” ๋“ฏ์ด ์‹œ๋‹ˆ์ปฌํ•˜๊ฒŒ ํ•ด๋ผ.) " "์ž, ์งˆ๋ฌธํ•ด." ) elif is_greeting: final_instruction = ( f"(์นœํ•œ ์นœ๊ตฌ๊ฐ€ PCํ†ต์‹  ์ฑ„ํŒ…๋ฐฉ์— ์ ‘์†ํ–ˆ๋‹ค. ๋ฐ˜๊ฐ‘๊ฒŒ ๋งž์•„์ค˜๋ผ. " "ใ…‹ใ…‹๋‚˜ ใ…Žใ…Ž๋ฅผ ์„ž์–ด์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ธ์‚ฌํ•ด๋ผ.) " f"{user_input}" ) else: final_instruction = user_input messages = [{"role": "system", "content": SYSTEM_PROMPT}] for u, b in history_pairs: if u is None or b is None: continue messages.append({"role": "user", "content": str(u)}) messages.append({"role": "assistant", "content": str(b)}) messages.append({"role": "user", "content": final_instruction}) # โ˜… ํ™ฉ๊ธˆ ๋ฐธ๋Ÿฐ์Šค (์•ˆ์ „ํ˜• ๋…๊ธฐ) ์„ค์ • ์ ์šฉ r = llm.create_chat_completion( messages=messages, max_tokens=256, stop=["<|end_of_text|>", "###", "User:", "User "], temperature=0.8, # 0.7: ์•„๊นŒ ์„ฑ๊ณตํ–ˆ๋˜ ๊ทธ ์ฐฝ์˜์ ์ธ ๋…๊ธฐ ๋†๋„ top_p=0.9, # ๋‹ค์–‘ํ•œ ์–ดํœ˜ ์‚ฌ์šฉ top_k=40, # ํ™•๋ฅ  ๋‚ฎ์€ ์ด์ƒํ•œ ๋‹จ์–ด(์™ธ๊ตญ์–ด ๋“ฑ) ์›์ฒœ ์ฐจ๋‹จ repeat_penalty=1.2 # ์•ต๋ฌด์ƒˆ ๋ฐฉ์ง€ ) raw = r["choices"][0]["message"]["content"].strip() # ํ•„ํ„ฐ๋ง ์ ์šฉ (์ ˆ๋Œ€์ ) safe = sanitize_output_korean_only(raw) return safe # ------------------------------------------------------------------ # 4. CSS (PCํ†ต์‹  ์Šคํƒ€์ผ) # ------------------------------------------------------------------ PC_COM_CSS = r""" @import url('https://cdn.jsdelivr.net/gh/neodgm/neodgm-webfont@latest/neodgm/neodgm.css'); :root { --pc-blue: #0000AA; --pc-white: #EFEFEF; --pc-yellow: #FFFF55; --pc-amber: #FFB000; --pc-cyan: #00AAAA; --pc-grey: #AAAAAA; } body, .gradio-container { background-color: var(--pc-blue) !important; font-family: 'NeoDunggeunmo', monospace !important; color: var(--pc-white) !important; } /* ํƒ€์ดํ‹€๋ฐ” */ h1 { font-family: 'NeoDunggeunmo', monospace !important; color: var(--pc-yellow) !important; background-color: #000084 !important; border-bottom: 2px double var(--pc-white) !important; padding-bottom: 10px !important; margin-bottom: 20px !important; text-align: center; font-size: 32px !important; letter-spacing: 2px; } h1::before { content: "โ˜Ž "; } h1::after { content: " โ˜Ž"; } /* ์„ค๋ช… ํ…์ŠคํŠธ */ .gradio-container p { color: var(--pc-cyan) !important; font-size: 18px !important; border-bottom: 1px dashed var(--pc-grey); padding-bottom: 5px; } /* ์ฑ—๋ด‡ ์ปจํ…Œ์ด๋„ˆ - ์Šคํฌ๋กค๋ฐ” ์ค‘๋ณต ํ•ด๊ฒฐ */ .chatbot { background-color: var(--pc-blue) !important; border: 2px solid var(--pc-white) !important; height: 60vh !important; overflow: hidden !important; /* ๊ฒ‰ ์Šคํฌ๋กค๋ฐ” ์ œ๊ฑฐ */ } /* ๋‚ด๋ถ€ ์Šคํฌ๋กค ๊ฐ•์ œ ํ™œ์„ฑํ™” */ .chatbot > div { height: 100% !important; overflow-y: auto !important; /* ์† ์Šคํฌ๋กค๋ฐ”๋งŒ ๋‚จ๊น€ */ } /* ================================================================= [๊ฐ•์ œ ์Šคํƒ€์ผ ์ ์šฉ ๊ตฌ๊ฐ„] ================================================================= */ /* 1. ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ์ดˆ๊ธฐํ™” */ .chatbot .message, .chatbot .message-wrap, .chatbot .message-row, div[data-testid="user"], div[data-testid="bot"] { background: transparent !important; box-shadow: none !important; border: none !important; } /* ๋ฉ”์‹œ์ง€ ํ–‰ ๊ฐ„๊ฒฉ ์ค„์ด๊ธฐ */ .chatbot .message-row, .chatbot .row { margin: 0 !important; padding: 0 !important; gap: 0 !important; } /* 2. ์œ ์ € ๋ฉ”์‹œ์ง€ (์šฐ์ธก ์ •๋ ฌ) */ .chatbot .user-row, .chatbot .user, div[data-testid="user"] { display: flex !important; width: 100% !important; justify-content: flex-end !important; margin-left: auto !important; background: transparent !important; padding: 2px 0 !important; margin-bottom: 0 !important; } .chatbot .user-row .message, .chatbot .user .message, div[data-testid="user"] .message { text-align: right !important; color: #FFFFFF !important; background: transparent !important; padding: 5px 10px !important; border: none !important; width: auto !important; max-width: 80% !important; } .chatbot .user-row p, .chatbot .user p, div[data-testid="user"] p { color: #FFFFFF !important; text-align: right !important; margin: 0 !important; } .chatbot .user-row .message::after, .chatbot .user .message::after { content: " < ๋‚˜"; color: var(--pc-grey); margin-left: 5px; font-size: 16px; display: inline-block; } /* 3. ๋ด‡ ๋ฉ”์‹œ์ง€ (์ขŒ์ธก ์ •๋ ฌ) */ .chatbot .bot-row, .chatbot .bot, div[data-testid="bot"] { display: flex !important; width: 100% !important; justify-content: flex-start !important; background: transparent !important; padding: 2px 0 !important; margin-bottom: 0 !important; } .chatbot .bot-row .message, .chatbot .bot .message, div[data-testid="bot"] .message { text-align: left !important; color: var(--pc-amber) !important; background: transparent !important; padding: 5px 10px !important; border: none !important; width: auto !important; } .chatbot .bot-row p, .chatbot .bot p, div[data-testid="bot"] p { color: var(--pc-amber) !important; margin: 0 !important; } .chatbot .bot-row .message::before, .chatbot .bot .message::before { content: "๋˜˜๋ฐฐ > "; color: var(--pc-cyan); margin-right: 5px; font-size: 16px; display: inline-block; } /* 4. ๋กœ๋”ฉ(์ดˆ์‹œ๊ณ„) ์Šคํƒ€์ผ */ .chatbot .pending, .chatbot .generating, .chatbot .message.pending, .chatbot .message.generating, .chatbot .wrap.default.full { background: transparent !important; border: none !important; box-shadow: none !important; } .chatbot .pending table, .chatbot .pending tr, .chatbot .pending td, .chatbot .generating table, .chatbot .generating tr, chatbot .generating td { background: transparent !important; border: none !important; } .chatbot .pending span, .chatbot .generating span, span.progress-text { color: #FFFFFF !important; background: transparent !important; font-family: 'NeoDunggeunmo', monospace !important; font-size: 16px !important; } .chatbot .load-wrap, .chatbot .loading-indicator, .chatbot .meta-text { display: none !important; } .avatar { display: none !important; } /* ================================================================= */ .input-container { background-color: var(--pc-blue) !important; border-top: 2px double var(--pc-white) !important; margin-top: 10px !important; gap: 10px !important; } textarea, input { background-color: var(--pc-blue) !important; color: var(--pc-white) !important; border: 1px solid var(--pc-grey) !important; border-radius: 0 !important; font-family: 'NeoDunggeunmo', monospace !important; font-size: 20px !important; outline: none !important; box-shadow: none !important; } button.primary { background: var(--pc-grey) !important; color: #000 !important; border: 1px solid var(--pc-white) !important; border-radius: 0 !important; font-family: 'NeoDunggeunmo', monospace !important; box-shadow: 2px 2px 0px #000 !important; } button.primary:hover { background: var(--pc-white) !important; } #clear-btn { background: transparent !important; color: var(--pc-grey) !important; border: 1px solid var(--pc-grey) !important; font-size: 14px !important; padding: 2px 10px !important; margin-top: 5px !important; width: auto !important; } #clear-btn:hover { color: var(--pc-white) !important; border-color: var(--pc-white) !important; } .example-btn { background: transparent !important; color: var(--pc-cyan) !important; border: 1px solid var(--pc-cyan) !important; border-radius: 0 !important; padding: 5px 15px !important; font-size: 16px !important; font-family: 'NeoDunggeunmo', monospace !important; margin-right: 8px !important; margin-bottom: 8px !important; } .example-btn:hover { background: var(--pc-cyan) !important; color: #000 !important; cursor: pointer !important; } footer { display: none !important; } """ # ------------------------------------------------------------------ # 5. App # ------------------------------------------------------------------ with gr.Blocks(theme=gr.themes.Base(), css=PC_COM_CSS, title="CHOLLIAN 98") as demo: gr.Markdown("# โ‰ช ์–ด์†จ์š”~ โ‰ซ") gr.Markdown(">> 01410 ์ ‘์† ์„ฑ๊ณต... [๋Œ€ํ™”์‹ค]์— ์ž…์žฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.") history_state = gr.State([]) chatbot = gr.Chatbot(show_label=False, elem_classes="chatbot") with gr.Row(elem_classes="input-container"): msg = gr.Textbox( scale=8, show_label=False, container=False, placeholder="๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." ) submit_btn = gr.Button("[ ์ „์†ก ]", scale=1, variant="primary") clear = gr.Button("[ ํ™”๋ฉด ์ง€์šฐ๊ธฐ ]", elem_id="clear-btn") gr.Markdown(">> ๋น ๋ฅธ ๋ช…๋ น์–ด ์ž…๋ ฅ (ํด๋ฆญ)", elem_id="example-label") with gr.Row(): btn1 = gr.Button("ํ•˜์ด ๋ฐฉ๊ฐ€๋ฐฉ๊ฐ€", elem_classes="example-btn") btn2 = gr.Button("๋ฐธ๋Ÿฐ์Šค๊ฒŒ์ž„ ใ„ฑใ„ฑ", elem_classes="example-btn") btn3 = gr.Button("์˜ค๋Š˜ ๊ธฐ๋ถ„ ๊ฑฐ์ง€๊ฐ™๋ˆ„", elem_classes="example-btn") btn4 = gr.Button("์•ผ ๋ฐฅ ๋ญ๋จน์ง€ ์ถ”์ฒœ์ข€", elem_classes="example-btn") def user(user_input, history): history = history or [] new_history = history + [[user_input, None]] return "", new_history, new_history def bot(history): if not history: return history, history user_input = history[-1][0] hist_pairs = [] for u, b in history[:-1]: if u is None or b is None: continue hist_pairs.append((u, b)) bot_out = chat_response(user_input, hist_pairs) history[-1][1] = bot_out return history, history msg.submit( user, [msg, history_state], [msg, history_state, chatbot], queue=False, api_name=False ).then( bot, [history_state], [history_state, chatbot], queue=False, api_name=False ) submit_btn.click( user, [msg, history_state], [msg, history_state, chatbot], queue=False, api_name=False ).then( bot, [history_state], [history_state, chatbot], queue=False, api_name=False ) clear.click( lambda: ([], []), None, [history_state, chatbot], queue=False, api_name=False ) for btn, text in [ (btn1, "ํ•˜์ด ๋ฐฉ๊ฐ€๋ฐฉ๊ฐ€"), (btn2, "๋ฐธ๋Ÿฐ์Šค๊ฒŒ์ž„ ใ„ฑใ„ฑ"), (btn3, "์˜ค๋Š˜ ๊ธฐ๋ถ„ ๊ฑฐ์ง€๊ฐ™๋ˆ„"), (btn4, "์•ผ ๋ฐฅ ๋ญ๋จน์ง€ ์ถ”์ฒœ์ข€") ]: btn.click( lambda t=text: t, None, msg, queue=False, api_name=False ).then( user, [msg, history_state], [msg, history_state, chatbot], queue=False, api_name=False ).then( bot, [history_state], [history_state, chatbot], queue=False, api_name=False ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)