QAway-to commited on
Commit
4c6f761
·
1 Parent(s): a31dc30
Files changed (2) hide show
  1. app.py +16 -14
  2. core/interviewer.py +31 -81
app.py CHANGED
@@ -4,7 +4,7 @@ import asyncio
4
  from itertools import cycle
5
  from core.utils import generate_first_question
6
  from core.mbti_analyzer import analyze_mbti
7
- from core.interviewer import generate_question
8
 
9
  # --------------------------------------------------------------
10
  # 🌀 Асинхронная анимация "Thinking..."
@@ -15,11 +15,12 @@ async def async_loader(update_fn, delay=0.15):
15
  update_fn(f"💭 Interviewer is thinking... {frame}")
16
  await asyncio.sleep(delay)
17
 
18
-
19
  # --------------------------------------------------------------
20
  # ⚙️ Основная логика
21
  # --------------------------------------------------------------
22
  def analyze_and_ask(user_text, prev_count):
 
 
23
  if not user_text.strip():
24
  yield "⚠️ Please enter your answer.", "", prev_count
25
  return
@@ -33,21 +34,21 @@ def analyze_and_ask(user_text, prev_count):
33
  # мгновенный отклик
34
  yield "⏳ Analyzing personality...", "💭 Interviewer is thinking... ⠋", counter
35
 
36
- # анализ MBTI
37
  mbti_gen = analyze_mbti(user_text)
38
  mbti_text = ""
39
  for chunk in mbti_gen:
40
  mbti_text = chunk
41
  yield mbti_text, "💭 Interviewer is thinking... ⠙", counter
42
 
43
- # генерация вопроса новой моделью (без инструкций)
44
  try:
45
- question = generate_question()
 
 
 
46
  except Exception as e:
47
- question = f"⚠️ Question generator error: {e}"
48
-
49
- yield mbti_text, question, counter
50
-
51
 
52
  # --------------------------------------------------------------
53
  # 🧱 Интерфейс Gradio
@@ -55,7 +56,8 @@ def analyze_and_ask(user_text, prev_count):
55
  with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as demo:
56
  gr.Markdown(
57
  "## 🧠 MBTI Personality Interviewer\n"
58
- "Определи личностный тип и получи случайные вопросы MBTI категории."
 
59
  )
60
 
61
  with gr.Row():
@@ -63,25 +65,25 @@ with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as
63
  inp = gr.Textbox(
64
  label="Ваш ответ",
65
  placeholder="Например: I enjoy working with people and organizing events.",
66
- lines=4
67
  )
68
  btn = gr.Button("Анализировать и задать новый вопрос", variant="primary")
69
  with gr.Column(scale=1):
70
  mbti_out = gr.Textbox(label="📊 Анализ MBTI", lines=4)
71
- interviewer_out = gr.Textbox(label="💬 Следующий вопрос", lines=3)
72
  progress = gr.Textbox(label="⏳ Прогресс", value="0/8")
73
 
74
  btn.click(
75
  analyze_and_ask,
76
  inputs=[inp, progress],
77
  outputs=[mbti_out, interviewer_out, progress],
78
- show_progress=True
79
  )
80
 
81
  demo.load(
82
  lambda: ("", generate_first_question(), "0/8"),
83
  inputs=None,
84
- outputs=[mbti_out, interviewer_out, progress]
85
  )
86
 
87
  demo.queue(max_size=32).launch(server_name="0.0.0.0", server_port=7860)
 
4
  from itertools import cycle
5
  from core.utils import generate_first_question
6
  from core.mbti_analyzer import analyze_mbti
7
+ from core.interviewer import stream_question # ✅ теперь используем потоковую версию
8
 
9
  # --------------------------------------------------------------
10
  # 🌀 Асинхронная анимация "Thinking..."
 
15
  update_fn(f"💭 Interviewer is thinking... {frame}")
16
  await asyncio.sleep(delay)
17
 
 
18
  # --------------------------------------------------------------
19
  # ⚙️ Основная логика
20
  # --------------------------------------------------------------
21
  def analyze_and_ask(user_text, prev_count):
22
+ """Основная функция — анализирует ответ и генерирует следующий вопрос (потоково)."""
23
+
24
  if not user_text.strip():
25
  yield "⚠️ Please enter your answer.", "", prev_count
26
  return
 
34
  # мгновенный отклик
35
  yield "⏳ Analyzing personality...", "💭 Interviewer is thinking... ⠋", counter
36
 
37
+ # анализ MBTI (также потоковый)
38
  mbti_gen = analyze_mbti(user_text)
39
  mbti_text = ""
40
  for chunk in mbti_gen:
41
  mbti_text = chunk
42
  yield mbti_text, "💭 Interviewer is thinking... ⠙", counter
43
 
44
+ # генерация вопроса новой моделью (потоково)
45
  try:
46
+ partial_question = ""
47
+ for piece in stream_question(): # 👈 здесь идёт токен-за-токен поток
48
+ partial_question = piece
49
+ yield mbti_text, partial_question, counter
50
  except Exception as e:
51
+ yield mbti_text, f"⚠️ Question generator error: {e}", counter
 
 
 
52
 
53
  # --------------------------------------------------------------
54
  # 🧱 Интерфейс Gradio
 
56
  with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as demo:
57
  gr.Markdown(
58
  "## 🧠 MBTI Personality Interviewer\n"
59
+ "Определи личностный тип и получи вопросы из разных категорий MBTI.\n\n"
60
+ "_Теперь с потоковой генерацией вопросов._"
61
  )
62
 
63
  with gr.Row():
 
65
  inp = gr.Textbox(
66
  label="Ваш ответ",
67
  placeholder="Например: I enjoy working with people and organizing events.",
68
+ lines=4,
69
  )
70
  btn = gr.Button("Анализировать и задать новый вопрос", variant="primary")
71
  with gr.Column(scale=1):
72
  mbti_out = gr.Textbox(label="📊 Анализ MBTI", lines=4)
73
+ interviewer_out = gr.Textbox(label="💬 Следующий вопрос (streaming)", lines=3)
74
  progress = gr.Textbox(label="⏳ Прогресс", value="0/8")
75
 
76
  btn.click(
77
  analyze_and_ask,
78
  inputs=[inp, progress],
79
  outputs=[mbti_out, interviewer_out, progress],
80
+ show_progress=True,
81
  )
82
 
83
  demo.load(
84
  lambda: ("", generate_first_question(), "0/8"),
85
  inputs=None,
86
+ outputs=[mbti_out, interviewer_out, progress],
87
  )
88
 
89
  demo.queue(max_size=32).launch(server_name="0.0.0.0", server_port=7860)
core/interviewer.py CHANGED
@@ -1,94 +1,44 @@
1
  # core/interviewer.py
2
- """
3
- 🇬🇧 Interviewer logic module (no instructions)
4
- Generates random MBTI-style questions using the fine-tuned model.
5
-
6
- 🇷🇺 Модуль интервьюера.
7
- Использует fine-tuned модель для генерации вопросов без инструкций.
8
- """
9
-
10
- import random
11
- import re
12
  import torch
13
- from transformers import AutoModelForSeq2SeqLM, T5Tokenizer
 
14
 
15
- # --------------------------------------------------------------
16
- # 1️⃣ Настройки модели
17
- # --------------------------------------------------------------
18
  QG_MODEL = "f3nsmart/ft-flan-t5-base-qgen_v2"
19
 
20
-
21
- # ✅ Принудительно используем оригинальный SentencePiece-токенайзер
22
  tokenizer = T5Tokenizer.from_pretrained(QG_MODEL, use_fast=False)
23
  model = AutoModelForSeq2SeqLM.from_pretrained(QG_MODEL)
24
-
25
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
26
  model.to(device).eval()
27
- print(f"✅ Loaded interviewer model (slow tokenizer): {QG_MODEL}")
28
- print(f"Device set to use {device}")
29
-
30
- # --------------------------------------------------------------
31
- # 2️⃣ Seed-промпты (без инструкций)
32
- # --------------------------------------------------------------
33
-
34
- # --------------------------------------------------------------
35
- # 2️⃣ Тематические seed-промпты (по осям MBTI, но без прямого упоминания MBTI)
36
- # --------------------------------------------------------------
37
 
38
- BASE_INSTRUCTION = (
39
- "Generate one natural, open-ended question about human thinking, emotions, or decision-making. "
40
- "Avoid mentioning MBTI or personality types directly. "
41
- "Do not ask what type the person belongs to. "
42
- "You may include ideas related to intuition, logic, feelings, perception, or judgment naturally."
43
- )
44
 
45
- PROMPTS = [
46
- f"{BASE_INSTRUCTION} Explore how people usually recharge their energy and interact with others.",
47
- f"{BASE_INSTRUCTION} Explore the difference between noticing small details and seeing the bigger picture.",
48
- f"{BASE_INSTRUCTION} Ask about trusting intuition versus relying on concrete evidence in daily life.",
49
- f"{BASE_INSTRUCTION} Ask about what typically inspires or motivates someone to take action.",
50
- f"{BASE_INSTRUCTION} Create a question about balancing emotions and logic when making decisions.",
51
- f"{BASE_INSTRUCTION} Write about preferences between careful planning and spontaneous action.",
52
- f"{BASE_INSTRUCTION} Explore how individuals deal with uncertainty or unexpected changes.",
53
- f"{BASE_INSTRUCTION} Ask about understanding other people’s emotions or empathy in relationships.",
54
- f"{BASE_INSTRUCTION} Create a question about staying organized versus adapting flexibly to new situations.",
55
- f"{BASE_INSTRUCTION} Explore curiosity, creativity, and how people find meaning in what they do."
56
- ]
57
-
58
-
59
- # --------------------------------------------------------------
60
- # 3️⃣ Очистка текста
61
- # --------------------------------------------------------------
62
- def _clean_question(text: str) -> str:
63
- """Берёт первую фразу с '?'"""
64
- text = text.strip()
65
- m = re.search(r"(.+?\?)", text)
66
- if m:
67
- text = m.group(1)
68
- text = text.replace("\n", " ").strip()
69
- if len(text.split()) < 3:
70
- text = text.capitalize()
71
- if not text.endswith("?"):
72
- text += "?"
73
- return text
74
-
75
-
76
- # --------------------------------------------------------------
77
- # 4️⃣ Генерация вопроса
78
- # --------------------------------------------------------------
79
- def generate_question(user_id: str = "default_user", **kwargs) -> str:
80
- """Генерирует один MBTI-вопрос без инструкций"""
81
- prompt = random.choice(PROMPTS)
82
  inputs = tokenizer(prompt, return_tensors="pt", truncation=True).to(device)
83
  with torch.no_grad():
84
- out = model.generate(
85
- **inputs,
86
- do_sample=True,
87
- top_p=0.9,
88
- temperature=1.1,
89
- top_k = 60,
90
- repetition_penalty=1.5,
91
- max_new_tokens=80,
92
- )
93
- text = tokenizer.decode(out[0], skip_special_tokens=True)
94
- return _clean_question(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # core/interviewer.py
 
 
 
 
 
 
 
 
 
 
2
  import torch
3
+ import threading
4
+ from transformers import AutoModelForSeq2SeqLM, T5Tokenizer, TextIteratorStreamer
5
 
 
 
 
6
  QG_MODEL = "f3nsmart/ft-flan-t5-base-qgen_v2"
7
 
 
 
8
  tokenizer = T5Tokenizer.from_pretrained(QG_MODEL, use_fast=False)
9
  model = AutoModelForSeq2SeqLM.from_pretrained(QG_MODEL)
 
10
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11
  model.to(device).eval()
 
 
 
 
 
 
 
 
 
 
12
 
13
+ print(f"✅ Loaded interviewer model (streaming ready): {QG_MODEL}")
 
 
 
 
 
14
 
15
+ # обычная версия (если нужно fallback)
16
+ def generate_question(prompt: str = "Generate one thoughtful question.") -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  inputs = tokenizer(prompt, return_tensors="pt", truncation=True).to(device)
18
  with torch.no_grad():
19
+ output = model.generate(**inputs, max_new_tokens=80)
20
+ return tokenizer.decode(output[0], skip_special_tokens=True)
21
+
22
+ # потоковая версия
23
+ def stream_question(prompt: str = "Generate one thoughtful question."):
24
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True).to(device)
25
+ streamer = TextIteratorStreamer(tokenizer, skip_special_tokens=True)
26
+ generation_kwargs = dict(
27
+ **inputs,
28
+ streamer=streamer,
29
+ max_new_tokens=80,
30
+ do_sample=True,
31
+ top_p=0.9,
32
+ temperature=1.1,
33
+ top_k=60,
34
+ repetition_penalty=1.3,
35
+ )
36
+
37
+ # модель работает в отдельном потоке
38
+ thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
39
+ thread.start()
40
+
41
+ partial = ""
42
+ for new_text in streamer:
43
+ partial += new_text
44
+ yield partial