QAway-to commited on
Commit
56954f5
·
1 Parent(s): 7e14c8d

Updated structure

Browse files
Files changed (4) hide show
  1. app.py +25 -14
  2. core/interviewer.py +40 -26
  3. core/mbti_analyzer.py +12 -6
  4. core/utils.py +3 -18
app.py CHANGED
@@ -1,15 +1,17 @@
 
1
  import gradio as gr
2
  import asyncio
3
  from core.utils import generate_first_question
4
  from core.mbti_analyzer import analyze_mbti
5
  from core.interviewer import generate_question
6
 
7
- # ===============================================================
8
- # 3️⃣ Интерфейс Gradio
9
- # ===============================================================
10
- async def analyze_and_ask_async(user_text, prev_count, user_id="default_user"):
11
  if not user_text.strip():
12
- return "⚠️ Введите ответ.", "", prev_count
 
13
 
14
  try:
15
  n = int(prev_count.split("/")[0]) + 1
@@ -17,18 +19,26 @@ async def analyze_and_ask_async(user_text, prev_count, user_id="default_user"):
17
  n = 1
18
  counter = f"{n}/30"
19
 
 
 
 
20
  mbti_task = asyncio.create_task(analyze_mbti(user_text))
21
  interviewer_task = asyncio.create_task(generate_question(user_id, user_text))
22
 
23
- mbti_text, next_question = await asyncio.gather(mbti_task, interviewer_task)
24
- return mbti_text, next_question, counter
 
 
 
 
 
25
 
26
 
 
 
 
27
  with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as demo:
28
- gr.Markdown(
29
- "## 🧠 MBTI Personality Interviewer\n"
30
- "Определи личностный тип и получи следующий вопрос от интервьюера."
31
- )
32
 
33
  with gr.Row():
34
  with gr.Column(scale=1):
@@ -43,8 +53,9 @@ with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as
43
  interviewer_out = gr.Textbox(label="💬 Следующий вопрос от интервьюера", lines=3)
44
  progress = gr.Textbox(label="⏳ Прогресс", value="0/30")
45
 
46
- btn.click(analyze_and_ask_async, inputs=[inp, progress], outputs=[mbti_out, interviewer_out, progress])
47
- demo.load(lambda: ("", generate_first_question(), "0/30"), None, [mbti_out, interviewer_out, progress])
48
 
49
- demo.launch()
 
50
 
 
 
1
+ # app.py
2
  import gradio as gr
3
  import asyncio
4
  from core.utils import generate_first_question
5
  from core.mbti_analyzer import analyze_mbti
6
  from core.interviewer import generate_question
7
 
8
+ # --------------------------------------------------------------
9
+ # Асинхронная функция для стриминга ответов
10
+ # --------------------------------------------------------------
11
+ async def analyze_and_ask_stream(user_text, prev_count, user_id="default_user"):
12
  if not user_text.strip():
13
+ yield gr.update(value="⚠️ Введите ответ."), gr.update(), gr.update()
14
+ return
15
 
16
  try:
17
  n = int(prev_count.split("/")[0]) + 1
 
19
  n = 1
20
  counter = f"{n}/30"
21
 
22
+ yield gr.update(), gr.update(value="⏳ Анализируем..."), gr.update(value=counter)
23
+
24
+ # Параллельные задачи
25
  mbti_task = asyncio.create_task(analyze_mbti(user_text))
26
  interviewer_task = asyncio.create_task(generate_question(user_id, user_text))
27
 
28
+ # Сначала MBTI
29
+ mbti_text = await mbti_task
30
+ yield gr.update(value=mbti_text), gr.update(value="💭 Интервьюер думает..."), gr.update(value=counter)
31
+
32
+ # Затем вопрос
33
+ next_question = await interviewer_task
34
+ yield gr.update(value=mbti_text), gr.update(value=next_question), gr.update(value=counter)
35
 
36
 
37
+ # --------------------------------------------------------------
38
+ # Gradio интерфейс
39
+ # --------------------------------------------------------------
40
  with gr.Blocks(theme=gr.themes.Soft(), title="MBTI Personality Interviewer") as demo:
41
+ gr.Markdown("## 🧠 MBTI Personality Interviewer\nОпредели личностный тип и получи следующий вопрос от интервьюера.")
 
 
 
42
 
43
  with gr.Row():
44
  with gr.Column(scale=1):
 
53
  interviewer_out = gr.Textbox(label="💬 Следующий вопрос от интервьюера", lines=3)
54
  progress = gr.Textbox(label="⏳ Прогресс", value="0/30")
55
 
56
+ btn.click(analyze_and_ask_stream, inputs=[inp, progress], outputs=[mbti_out, interviewer_out, progress])
 
57
 
58
+ # Первый вопрос при загрузке
59
+ demo.load(lambda: ("", generate_first_question(), "0/30"), inputs=None, outputs=[mbti_out, interviewer_out, progress])
60
 
61
+ demo.queue(streaming=True, max_size=20).launch(server_name="0.0.0.0", server_port=7860)
core/interviewer.py CHANGED
@@ -1,22 +1,16 @@
1
- import torch, asyncio
 
2
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
3
- from peft import PeftModel
4
- from core.utils import clean_question
5
- from core.memory import update_user_context, get_user_context, was_asked
6
 
7
- INTERVIEWER_BASE = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
8
- INTERVIEWER_LORA = "f3nsmart/TinyLlama-MBTI-Interviewer-LoRA"
9
 
10
- print("🔄 Loading interviewer (TinyLlama + LoRA)...")
11
-
12
- tokenizer = AutoTokenizer.from_pretrained(INTERVIEWER_LORA)
13
- base_model = AutoModelForCausalLM.from_pretrained(
14
- INTERVIEWER_BASE,
15
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
16
  device_map="auto"
17
  )
18
- model = PeftModel.from_pretrained(base_model, INTERVIEWER_LORA)
19
-
20
  llm_pipe = pipeline(
21
  "text-generation",
22
  model=model,
@@ -24,29 +18,49 @@ llm_pipe = pipeline(
24
  max_new_tokens=70,
25
  temperature=0.7,
26
  top_p=0.9,
27
- device_map="auto"
28
  )
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  async def generate_question(user_id: str, user_text: str) -> str:
32
- """Асинхронная генерация вопроса"""
33
- history = get_user_context(user_id)
34
- prev_qs = " | ".join(history["questions"][-5:]) # последние 5 вопросов
 
 
35
 
36
  prompt = (
37
  f"The following is an MBTI personality interview.\n"
38
  f"User: {user_text}\n"
39
  f"Interviewer: ask one new, open-ended question starting with 'What', 'Why', 'How', or 'When'. "
40
- f"Ask naturally and concisely, without instructions or explanations.\n"
41
- f"Previous questions: {prev_qs or 'None'}\n"
42
  f"Interviewer:"
43
  )
44
 
45
  loop = asyncio.get_event_loop()
46
- result = await loop.run_in_executor(None, lambda: llm_pipe(prompt)[0]["generated_text"])
47
- cleaned = clean_question(result)
 
 
 
 
48
 
49
- if was_asked(user_id, cleaned):
50
- cleaned = "What new challenges have you faced recently?"
51
- update_user_context(user_id, cleaned, user_text)
52
- return cleaned
 
1
+ # core/interviewer.py
2
+ import asyncio
3
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
 
 
 
4
 
5
+ INTERVIEWER_MODEL = "f3nsmart/TinyLlama-MBTI-Interviewer-LoRA"
 
6
 
7
+ # Инициализация
8
+ tokenizer = AutoTokenizer.from_pretrained(INTERVIEWER_MODEL)
9
+ model = AutoModelForCausalLM.from_pretrained(
10
+ INTERVIEWER_MODEL,
11
+ torch_dtype="auto",
 
12
  device_map="auto"
13
  )
 
 
14
  llm_pipe = pipeline(
15
  "text-generation",
16
  model=model,
 
18
  max_new_tokens=70,
19
  temperature=0.7,
20
  top_p=0.9,
 
21
  )
22
 
23
+ # Память для пользователей
24
+ user_memory = {}
25
+
26
+ def clean_question(text: str) -> str:
27
+ """Удаляет инструкции, оставляя только вопрос"""
28
+ text = text.strip().split("\n")[0]
29
+ text = text.strip('"').strip("'")
30
+ bad_tokens = ["user:", "assistant:", "instruction", "interviewer", "system:"]
31
+ for bad in bad_tokens:
32
+ if bad.lower() in text.lower():
33
+ text = text.split(bad)[-1].strip()
34
+ if not text.endswith("?"):
35
+ text += "?"
36
+ if len(text.split()) < 3:
37
+ return "What do you usually enjoy doing in your free time?"
38
+ return text.strip()
39
 
40
  async def generate_question(user_id: str, user_text: str) -> str:
41
+ """
42
+ Генерирует новый вопрос с учётом предыдущих.
43
+ """
44
+ prev_qs = user_memory.get(user_id, [])
45
+ prev_joined = "; ".join(prev_qs) if prev_qs else "None"
46
 
47
  prompt = (
48
  f"The following is an MBTI personality interview.\n"
49
  f"User: {user_text}\n"
50
  f"Interviewer: ask one new, open-ended question starting with 'What', 'Why', 'How', or 'When'. "
51
+ f"Avoid repeating or rephrasing previous questions.\n"
52
+ f"Previous questions: {prev_joined}\n"
53
  f"Interviewer:"
54
  )
55
 
56
  loop = asyncio.get_event_loop()
57
+ raw = await loop.run_in_executor(None, lambda: llm_pipe(prompt)[0]["generated_text"])
58
+ question = clean_question(raw)
59
+
60
+ valid_starts = ("What", "Why", "How", "When")
61
+ if not question.startswith(valid_starts):
62
+ question = "What motivates you to do the things you enjoy most?"
63
 
64
+ prev_qs.append(question)
65
+ user_memory[user_id] = prev_qs[-10:] # храним последние 10
66
+ return question
 
core/mbti_analyzer.py CHANGED
@@ -1,11 +1,17 @@
 
1
  from transformers import pipeline
2
- from core.utils import format_mbti_output
3
 
4
  MBTI_MODEL = "f3nsmart/MBTIclassifier"
5
  mbti_pipe = pipeline("text-classification", model=MBTI_MODEL, return_all_scores=True)
6
 
7
- async def analyze_mbti(text: str) -> str:
8
- """Асинхронный анализ MBTI"""
9
- loop = __import__("asyncio").get_event_loop()
10
- result = await loop.run_in_executor(None, mbti_pipe, text)
11
- return format_mbti_output(result[0])
 
 
 
 
 
 
1
+ # core/mbti_analyzer.py
2
  from transformers import pipeline
3
+ import asyncio
4
 
5
  MBTI_MODEL = "f3nsmart/MBTIclassifier"
6
  mbti_pipe = pipeline("text-classification", model=MBTI_MODEL, return_all_scores=True)
7
 
8
+ async def analyze_mbti(user_text: str) -> str:
9
+ """
10
+ Асинхронный анализ текста на MBTI типы.
11
+ Возвращает 3 лучших типа с их вероятностями.
12
+ """
13
+ loop = asyncio.get_event_loop()
14
+ res = await loop.run_in_executor(None, lambda: mbti_pipe(user_text)[0])
15
+ res_sorted = sorted(res, key=lambda x: x["score"], reverse=True)
16
+ mbti_text = "\n".join([f"{r['label']} → {r['score']:.3f}" for r in res_sorted[:3]])
17
+ return mbti_text
core/utils.py CHANGED
@@ -1,20 +1,5 @@
1
- def clean_question(text: str) -> str:
2
- text = text.strip().split("\n")[0].strip('"').strip("'")
3
- bad_tokens = ["user:", "assistant:", "instruction", "interviewer", "system:"]
4
- for bad in bad_tokens:
5
- if bad.lower() in text.lower():
6
- text = text.split(bad)[-1].strip()
7
- if "?" not in text:
8
- text = text.rstrip(".") + "?"
9
- if len(text.split()) < 3:
10
- return "What do you usually enjoy doing in your free time?"
11
- return text.strip()
12
 
13
-
14
- def generate_first_question():
15
  return "What do you usually enjoy doing in your free time?"
16
-
17
-
18
- def format_mbti_output(res):
19
- res_sorted = sorted(res, key=lambda x: x["score"], reverse=True)
20
- return "\n".join([f"{r['label']} → {r['score']:.3f}" for r in res_sorted[:3]])
 
1
+ # core/utils.py
 
 
 
 
 
 
 
 
 
 
2
 
3
+ def generate_first_question() -> str:
4
+ """Первый вопрос фиксированный"""
5
  return "What do you usually enjoy doing in your free time?"