Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| from litellm import completion | |
| from dotenv import load_dotenv | |
| import json | |
| import re | |
| load_dotenv() | |
| # ------------------------------- | |
| # 1) ๊ณ ์ 10๊ฐ ์ง๋ฌธ ์ ์ | |
| # ------------------------------- | |
| FIXED_QUESTIONS = [ | |
| "Q1) ๋ณธ์ธ์ ์ ๊ณต/์ ๋ฌด ๋ถ์ผ๋ ๋ฌด์์ธ๊ฐ์?", | |
| "Q2) ์ต๊ทผ ๊ฐ์ฅ ์ง์คํ ํ๋ก์ ํธ๋ ๋ฌด์์ด์๋์?", | |
| "Q3) ํด๋น ํ๋ก์ ํธ์์ ๊ฐ์ฅ ์ด๋ ค์ ๋ ์ ์ ๋ฌด์์ด์๋์?", | |
| "Q4) ์ฆ๊ฒจ ์ฐ๋ ๊ฐ๋ฐ ์คํ(์ธ์ด/ํ๋ ์์ํฌ/๋ผ์ด๋ธ๋ฌ๋ฆฌ)์ ์๋ ค์ฃผ์ธ์.", | |
| "Q5) ํ์ ์ ๊ฐ์ฅ ์ค์ํ๊ฒ ์๊ฐํ๋ ์์น์ ๋ฌด์์ธ๊ฐ์?", | |
| "Q6) ์ฑ๋ฅ ๊ฐ์ ์ ์ํด ๊ฐ์ฅ ์์ฃผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ธ๊ฐ์?", | |
| "Q7) ํ ์คํธ/๊ฒ์ฆ์ ์ด๋ค ๋ฐฉ์์ผ๋ก ์งํํ๋์?", | |
| "Q8) ๋ฐ์ดํฐ/๋ฆฌ์์ค๊ฐ ์ ํ๋ ๋ ์ด๋ค ์ ๋ต์ ์ฐ์๋์?", | |
| "Q9) ์ต๊ทผ ๋ฐฐ์ด ๊ฒ ์ค ๊ฐ์ฅ ์ ์ฉํ๋ ๋ด์ฉ์ ๋ฌด์์ด์๋์?", | |
| "Q10) ์์ผ๋ก ๋ค๋ค๋ณด๊ณ ์ถ์ ์ฃผ์ ๋ ๊ธฐ์ ์ด ์๋์?", | |
| ] | |
| # ------------------------------- | |
| # 2) ๊ผฌ๋ฆฌ์ง๋ฌธ 20๊ฐ ์์ฑ ํ๋กฌํํธ | |
| # (๊ณ ์ 10๋ฌธ๋ต์ ๋ฐํ์ผ๋ก ์ถ์ถ) | |
| # ------------------------------- | |
| FOLLOWUP_SYSTEM = ( | |
| "You are an excellent interviewer. Based on the given 10 Q/A pairs, " | |
| "generate 20 SHORT, concrete, non-overlapping follow-up questions that deeply probe the user's answers. " | |
| "Each question should be standalone and specific. Output as a numbered list 1..20." | |
| ) | |
| def build_followup_user_prompt(qa_pairs): | |
| """ | |
| qa_pairs: list[tuple(question, answer)] | |
| """ | |
| lines = ["Below are 10 Q/A pairs. Generate 20 short follow-up questions.\n"] | |
| for i, (q, a) in enumerate(qa_pairs, 1): | |
| lines.append(f"[Q{i}] {q}") | |
| lines.append(f"[A{i}] {a}\n") | |
| lines.append("Return only the 20 questions as a numbered list (1..20).") | |
| return "\n".join(lines) | |
| def parse_numbered_list_to_lines(text, expected_n=20): | |
| # 1) Remove code fences | |
| text = re.sub(r"^```.*?\n|\n```$", "", text, flags=re.DOTALL).strip() | |
| # 2) Split lines on numbering | |
| # e.g. "1) ..." or "1. ..." or "1 - ..." etc | |
| candidates = re.split(r"(?:^\s*\d+\s*[\)\.\-\:]\s*)", text, flags=re.MULTILINE) | |
| # The split keeps text fragments; we need to reassemble meaningful lines. | |
| # An easier approach is to capture lines that start with a number: | |
| lines = re.findall(r"^\s*\d+\s*[\)\.\-\:]\s*(.+)$", text, flags=re.MULTILINE) | |
| lines = [l.strip() for l in lines if l.strip()] | |
| # Fallback: if no pattern matched, split by newline and filter bullets | |
| if not lines: | |
| for raw in text.splitlines(): | |
| s = raw.strip() | |
| if s and not s.startswith("#"): | |
| lines.append(s) | |
| # Trim to expected_n if overshoot; if undershoot, keep whatever we have | |
| return lines[:expected_n] | |
| # -------------------------------- | |
| # 3) ๋ฉ์ธ ์๋ต ํจ์ | |
| # -------------------------------- | |
| def respond( | |
| message, | |
| history: list[dict[str, str]], | |
| system_message, | |
| max_tokens, | |
| temperature, | |
| top_p, | |
| # (OAuth ๋ฒํผ์ ์ ์งํ๋, ์๋ ๊ตฌํ์์๋ ์ฌ์ฉํ์ง ์์) | |
| hf_token: gr.OAuthToken, | |
| # --- ์ํ ๊ฐ๋ค --- | |
| phase, # 1 -> 2 -> 3 | |
| asked, # ์ง์ ์ ์ง๋ฌธ์ ๋์ก๋์ง ์ฌ๋ถ (True๋ฉด ์ด๋ฒ ์ฌ์ฉ์์ ์ ๋ ฅ์ ๋ต๋ณ์ผ๋ก ๊ฐ์ฃผ) | |
| i1, i2, i3, # ๊ฐ ๋จ๊ณ ์ธ๋ฑ์ค | |
| gen_questions,# 2๋จ๊ณ์์ ์ฌ์ฉํ 20๊ฐ ์ง๋ฌธ (list[str]) | |
| fixed_answers,# 1๋จ๊ณ ๋ต๋ณ(10๊ฐ ์ ์ฅ์ฉ) | |
| gen_answers, # 2๋จ๊ณ ๋ต๋ณ(20๊ฐ ์ ์ฅ์ฉ) | |
| rep_answers, # 3๋จ๊ณ ๋ต๋ณ(10๊ฐ ์ ์ฅ์ฉ) | |
| ): | |
| """ | |
| ๋ํ ํ๋ฆ: | |
| - asked == False: ์ด๋ฒ ํธ์ถ์์๋ '๋ค์ ์ง๋ฌธ'์ ๋ด๋ณด๋ด๊ณ asked=True ๋ก ์ ํ | |
| - asked == True : ์ด๋ฒ ํธ์ถ์ message๋ฅผ '๋ต๋ณ'์ผ๋ก ์ ์ฅํ๊ณ ์ธ๋ฑ์ค๋ฅผ ์ฆ๊ฐ์ํจ ๋ค, ๋ค์ ์ง๋ฌธ์ ๋ด๋ณด๋ด๋ฉฐ asked=True ์ ์ง | |
| """ | |
| model = "gemini/gemini-2.5-flash" | |
| # ์ต์ด ์ง์ (์ฌ์ฉ์ ์ฒซ ๋ฉ์์ง): ์ง๋ฌธ์ ๋์ง ์ฐจ๋ก๋ก ๋ง์ถ๋ค. | |
| if phase is None: | |
| phase = 1 | |
| if asked is None: | |
| asked = False | |
| if i1 is None: | |
| i1 = 0 | |
| if i2 is None: | |
| i2 = 0 | |
| if i3 is None: | |
| i3 = 0 | |
| if gen_questions is None: | |
| gen_questions = [] | |
| if fixed_answers is None: | |
| fixed_answers = [] | |
| if gen_answers is None: | |
| gen_answers = [] | |
| if rep_answers is None: | |
| rep_answers = [] | |
| # ํฌํผ: ํ์ฌ ๋จ๊ณ์์ "๋ค์ ์ง๋ฌธ ํ ์คํธ"๋ฅผ ๋ฆฌํด | |
| def next_question(): | |
| nonlocal phase, i1, i2, i3, gen_questions | |
| if phase == 1: | |
| return FIXED_QUESTIONS[i1] if i1 < len(FIXED_QUESTIONS) else None | |
| elif phase == 2: | |
| return gen_questions[i2] if i2 < len(gen_questions) else None | |
| elif phase == 3: | |
| return FIXED_QUESTIONS[i3] if i3 < len(FIXED_QUESTIONS) else None | |
| return None | |
| # ํฌํผ: 1โ2 ๋จ๊ณ ์ ํ ์ ๊ผฌ๋ฆฌ์ง๋ฌธ 20๊ฐ ์์ฑ | |
| def ensure_followups(): | |
| nonlocal gen_questions | |
| if gen_questions: | |
| return # ์ด๋ฏธ ์์ฑ๋จ | |
| # 1๋จ๊ณ Q/A ํ์ด ๊ตฌ์ฑ | |
| qa_pairs = list(zip(FIXED_QUESTIONS, fixed_answers)) | |
| user_prompt = build_followup_user_prompt(qa_pairs) | |
| followup_msgs = [ | |
| {"role": "system", "content": FOLLOWUP_SYSTEM}, | |
| {"role": "user", "content": user_prompt}, | |
| ] | |
| res = completion( | |
| model=model, | |
| messages=followup_msgs, | |
| temperature=temperature, | |
| top_p=top_p, | |
| # max_tokens=max_tokens, # ํ์ ์ ํด์ | |
| ) | |
| res_json = res.choices[0].message.model_dump() | |
| followup_text = res_json["content"].strip() | |
| gen_questions = parse_numbered_list_to_lines(followup_text, expected_n=20) | |
| # ํน์ 20๊ฐ ๋ฏธ๋ง์ด๋ฉด ๋ณด์ถฉ(๊ฐ๋จํ ๋ฐฑ์ ) | |
| while len(gen_questions) < 20: | |
| gen_questions.append(f"(์ถ๊ฐ) ๊ด์ฌ ์ฃผ์ ์ ๋ํด ๋ ์์ธํ ์ค๋ช ํด ์ฃผ์ค ์ ์๋์? [{len(gen_questions)+1}]") | |
| # -------------------------------- | |
| # (A) asked == False โ ์ง๋ฌธ ๋์ง๊ธฐ | |
| # -------------------------------- | |
| if not asked: | |
| if phase == 1 and i1 == 0: | |
| intro = ( | |
| "์๋ ํ์ธ์! ๋ค์ ์์๋ก ์งํํ ๊ฒ์:\n" | |
| "1) ๊ณ ์ 10๋ฌธํญ์ ๋จผ์ ๋ต๋ณํฉ๋๋ค.\n" | |
| "2) ์ด์ด์ LLM์ด ๋ฐฉ๊ธ ๋ต๋ณ์ ๋ฐํ์ผ๋ก 20๋ฌธํญ์ ์์ฑํด ์ง๋ฌธํฉ๋๋ค.\n" | |
| "3) ๋ง์ง๋ง์ผ๋ก ์ฒ์์ 10๋ฌธํญ์ ๋ค์ ๋ฌป์ต๋๋ค.\n\n" | |
| "๊ทธ๋ผ ์์ํ๊ฒ ์ต๋๋ค!" | |
| ) | |
| # ์ฒซ ์๋ด ํ ์ฒซ ์ง๋ฌธ | |
| q = next_question() | |
| asked = True | |
| yield f"{intro}\n\n{q}", phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| # ๊ทธ ์ธ ์ผ๋ฐ ์ผ์ด์ค: ๋ค์ ์ง๋ฌธ | |
| q = next_question() | |
| if q is not None: | |
| asked = True | |
| yield q, phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| # ์ง๋ฌธ์ด ๋ ์๋ค๋ฉด(๋ชจ๋ ๋จ๊ณ ์๋ฃ) | |
| yield "๋ชจ๋ ์ง๋ฌธ์ด ์๋ฃ๋์์ต๋๋ค. ์ฐธ์ฌํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค! ๐", phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| # -------------------------------- | |
| # (B) asked == True โ ๋ฐฉ๊ธ ๋ฐ์ message๋ฅผ ๋ต๋ณ์ผ๋ก ์ ์ฅํ๊ณ ๋ค์ ์ง๋ฌธ | |
| # -------------------------------- | |
| if asked: | |
| if phase == 1: | |
| # 1๋จ๊ณ ๋ต๋ณ ์ ์ฅ | |
| fixed_answers.append(message) | |
| i1 += 1 | |
| asked = False # ๋ค์ ํด์๋ ์ง๋ฌธ์ ๋ด๋ณด๋ด๋๋ก | |
| if i1 >= len(FIXED_QUESTIONS): | |
| # 2๋จ๊ณ๋ก ์ด๋: ๊ผฌ๋ฆฌ์ง๋ฌธ 20๊ฐ ์์ฑ | |
| phase = 2 | |
| ensure_followups() | |
| # ๊ณง๋ฐ๋ก ๋ค์ ์ง๋ฌธ ๋์ง๊ธฐ | |
| q = next_question() | |
| asked = True | |
| yield f"์ข์ต๋๋ค. 1๋จ๊ณ๋ฅผ ๋ง์ณค์ต๋๋ค. ์ด์ 2๋จ๊ณ(20๋ฌธํญ)๋ก ๋์ด๊ฐ๊ฒ์.\n\n{q}", phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| else: | |
| # ๋ค์ 1๋จ๊ณ ์ง๋ฌธ | |
| q = next_question() | |
| asked = True | |
| yield q, phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| elif phase == 2: | |
| # 2๋จ๊ณ ๋ต๋ณ ์ ์ฅ | |
| gen_answers.append(message) | |
| i2 += 1 | |
| asked = False | |
| if i2 >= len(gen_questions): | |
| # 3๋จ๊ณ๋ก ์ด๋ | |
| phase = 3 | |
| q = next_question() | |
| asked = True | |
| yield f"์ข์์. 2๋จ๊ณ๋ฅผ ๋ง์ณค์ต๋๋ค. ๋ง์ง๋ง์ผ๋ก 1๋จ๊ณ์ 10๋ฌธํญ์ ๋ค์ ๋ฌป๊ฒ ์ต๋๋ค.\n\n{q}", phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| else: | |
| q = next_question() | |
| asked = True | |
| yield q, phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| elif phase == 3: | |
| # 3๋จ๊ณ ๋ต๋ณ ์ ์ฅ | |
| rep_answers.append(message) | |
| i3 += 1 | |
| asked = False | |
| if i3 >= len(FIXED_QUESTIONS): | |
| # ์๋ฃ | |
| summary = { | |
| "phase1_fixed": [{"q": FIXED_QUESTIONS[i], "a": fixed_answers[i]} for i in range(len(fixed_answers))], | |
| "phase2_generated": [{"q": gen_questions[i], "a": gen_answers[i]} for i in range(len(gen_answers))], | |
| "phase3_repeat": [{"q": FIXED_QUESTIONS[i], "a": rep_answers[i]} for i in range(len(rep_answers))], | |
| } | |
| done_text = ( | |
| "๋ชจ๋ ์ง๋ฌธ์ด ์๋ฃ๋์์ต๋๋ค. ์ฐธ์ฌ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค! ๐\n" | |
| "ํ์ํ์๋ค๋ฉด ์๋ JSON ์์ฝ์ ๋ณต์ฌํด๊ฐ์ธ์.\n\n" | |
| + "```json\n" + json.dumps(summary, ensure_ascii=False, indent=2) + "\n```" | |
| ) | |
| yield done_text, phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| else: | |
| q = next_question() | |
| asked = True | |
| yield q, phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| return | |
| # ์์ ๋ง (๋๋ฌํ์ง ์์์ผ ํจ) | |
| yield "์ํ ์ ํ ์ค ์๊ธฐ์น ๋ชปํ ์ํฉ์ด ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.", phase, asked, i1, i2, i3, gen_questions, fixed_answers, gen_answers, rep_answers | |
| # -------------------------------- | |
| # 4) ChatInterface ๊ตฌ์ฑ | |
| # -------------------------------- | |
| with gr.Blocks() as demo: | |
| with gr.Sidebar(): | |
| # ๋ก๊ทธ์ธ ๋ฒํผ ์ ์ง (hf_token์ ํ์ฌ ๊ตฌํ์์ ์ฌ์ฉํ์ง ์์ง๋ง UI๋ ๊ทธ๋๋ก ๋ ) | |
| oauth = gr.LoginButton() | |
| gr.Markdown( | |
| "### ์งํ ์์\n" | |
| "1) ๊ณ ์ 10๋ฌธํญ์ ๋จผ์ ๋ตํ๊ธฐ\n" | |
| "2) LLM์ด ์์ฑํ 20๋ฌธํญ ๊ผฌ๋ฆฌ์ง๋ฌธ์ ๋ตํ๊ธฐ\n" | |
| "3) ๊ณ ์ 10๋ฌธํญ์ ๋ค์ ํ ๋ฒ ๋ตํ๊ธฐ" | |
| ) | |
| phase = gr.State(1) | |
| asked = gr.State(False) | |
| i1 = gr.State(0) | |
| i2 = gr.State(0) | |
| i3 = gr.State(0) | |
| gen_questions= gr.State([]) | |
| fixed_answers= gr.State([]) | |
| gen_answers = gr.State([]) | |
| rep_answers = gr.State([]) | |
| # system/max_tokens/temperature/top_p ์ ๋ ฅ ์ปดํฌ๋ํธ๋ ๋ธ๋ก ์์์ ๋ง๋ค๊ณ ๋๊น๋๋ค | |
| sys_msg = gr.Textbox(value="You are a friendly Chatbot.", label="System message") | |
| max_toks = gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens") | |
| temp = gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature") | |
| top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p (nucleus sampling)") | |
| chatbot = gr.ChatInterface( | |
| respond, | |
| type="messages", | |
| # โ ํ๋ผ๋ฏธํฐ ์์: system_message, max_tokens, temperature, top_p, hf_token, (statesโฆ) | |
| additional_inputs=[ | |
| sys_msg, max_toks, temp, top_p, | |
| oauth, # <-- โ hf_token ์๋ฆฌ์ ๋๊ธฐ! | |
| phase, asked, i1, i2, i3, | |
| gen_questions, fixed_answers, gen_answers, rep_answers, | |
| ], | |
| # โ ์ถ๋ ฅ์๋ ๋์ผํ State ์ธ์คํด์ค ์ฌ์ฌ์ฉ | |
| additional_outputs=[ | |
| phase, asked, i1, i2, i3, | |
| gen_questions, fixed_answers, gen_answers, rep_answers, | |
| ], | |
| ) | |
| chatbot.render() | |
| if __name__ == "__main__": | |
| demo.launch() | |