Skip to content

Commit 835b5ce

Browse files
committed
initial commit
1 parent 6f5d459 commit 835b5ce

File tree

7 files changed

+654
-1
lines changed

7 files changed

+654
-1
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ ssl
55
__pycache__
66
/VERSION.json
77
.env
8-
/whisper_asr_model_cache
8+
/whisper_asr_model_cache
9+
/app/questions_generator/vkr_examples/
10+
/app/questions_generator/rut5-base/

app/questions_generator/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM python:3.10-slim
2+
3+
# 1. System deps
4+
RUN apt-get update && apt-get install -y --no-install-recommends \
5+
git wget gcc g++ \
6+
libprotobuf-dev protobuf-compiler \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
# 2. Workdir
10+
WORKDIR /app
11+
12+
# 3. Python deps
13+
COPY requirements.txt .
14+
RUN pip install --no-cache-dir --upgrade pip \
15+
&& pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \
16+
&& pip install --no-cache-dir -r requirements.txt
17+
18+
# 4. NLTK
19+
RUN python -m nltk.downloader punkt stopwords
20+
21+
# 5. Copy local model
22+
COPY rut5-base/ /app/rut5-base/
23+
24+
# 6. Copy project
25+
COPY . .
26+
27+
# 7. Run
28+
CMD ["python", "run.py"]

app/questions_generator/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Запуск
2+
3+
## Загрузка модели локально (единоразово)
4+
- `powershell -ExecutionPolicy ByPass -c "irm https://hf.co/cli/install.ps1 | iex"` (windows)
5+
- `curl -LsSf https://hf.co/cli/install.sh | bash` (linux/macos)
6+
- `cd app\questions_generator`
7+
- `hf download cointegrated/rut5-base-multitask --local-dir rut5-base`
8+
## Выбор файла ВКР
9+
- заменить в `run.py` в функции `main` путь для файла ВКР
10+
## Запуск
11+
- `docker build -t vkr-generator .`
12+
- `docker run -it --rm vkr-generator`
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import re
2+
from typing import List, Dict
3+
from nltk.tokenize import sent_tokenize, word_tokenize
4+
from nltk.corpus import stopwords
5+
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
6+
7+
8+
class VkrQuestionGenerator:
9+
"""
10+
Генератор вопросов по тексту ВКР.
11+
Основан на гибридном подходе: NLTK + rut5-base-multitask.
12+
"""
13+
def __init__(self, vkr_text: str, model_path: str = "./rut5-base"):
14+
self.vkr_text = vkr_text
15+
self.sentences = sent_tokenize(vkr_text)
16+
self.stopwords = set(stopwords.words("russian"))
17+
18+
# ---- Модель rut5 ----
19+
self.tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)
20+
self.model = AutoModelForSeq2SeqLM.from_pretrained(model_path)
21+
22+
# ---------------------------------------------------------
23+
# --- 1. ЭВРИСТИКА: Извлечение ключевых частей ВКР ---
24+
# ---------------------------------------------------------
25+
26+
def extract_section(self, title: str) -> str:
27+
"""
28+
Универсальный метод извлечения раздела по заголовку.
29+
"""
30+
pattern = rf"{title}.*?(?=\n[A-ZА-Я][^\n]*\n)"
31+
m = re.search(pattern, self.vkr_text, re.DOTALL | re.IGNORECASE)
32+
return m.group(0) if m else ""
33+
34+
def extract_intro(self) -> str:
35+
return self.extract_section("Введение")
36+
37+
def extract_conclusion(self) -> str:
38+
return self.extract_section("Заключение")
39+
40+
def extract_methodology(self) -> str:
41+
return self.extract_section("Методолог")
42+
43+
# ---------------------------------------------------------
44+
# --- 2. ЭВРИСТИКА: Поиск ключевых концепций ---
45+
# ---------------------------------------------------------
46+
47+
def extract_keywords(self, text: str) -> List[str]:
48+
tokens = word_tokenize(text.lower())
49+
return [
50+
t for t in tokens
51+
if t.isalnum() and t not in self.stopwords and len(t) > 4
52+
]
53+
54+
# ---------------------------------------------------------
55+
# --- 3. Генерация вопросов через rut5 (режим ask) ---
56+
# ---------------------------------------------------------
57+
58+
def llm_generate_question(self, text_fragment: str) -> str:
59+
"""
60+
Генерация вопроса по фрагменту текста через rut5 ask
61+
"""
62+
prompt = f"ask: {text_fragment}"
63+
enc = self.tokenizer(prompt, return_tensors="pt", truncation=True)
64+
out = self.model.generate(
65+
**enc,
66+
max_length=64,
67+
num_beams=5,
68+
early_stopping=True
69+
)
70+
return self.tokenizer.decode(out[0], skip_special_tokens=True)
71+
72+
# ---------------------------------------------------------
73+
# --- 4. ЭВРИСТИЧЕСКИЕ ШАБЛОНЫ (из документа) ---
74+
# ---------------------------------------------------------
75+
76+
def heuristic_questions(self) -> List[str]:
77+
"""
78+
Генерация вопросов по эвристикам из загруженных PDF.
79+
"""
80+
intro = self.extract_intro()
81+
conc = self.extract_conclusion()
82+
meth = self.extract_methodology()
83+
keywords = self.extract_keywords(self.vkr_text)
84+
85+
q = []
86+
87+
# --- По связям между разделами ---
88+
if intro and conc:
89+
q.append("Как сформулированные во введении задачи связаны с выводами работы?")
90+
91+
# --- По методологии ---
92+
if meth:
93+
for kw in keywords[:3]:
94+
q.append(f"Почему был выбран метод {kw} и где он применён в работе?")
95+
96+
# --- По выводам ---
97+
if conc:
98+
q.append("На основании каких данных был сделан ключевой вывод в заключении?")
99+
100+
# --- Общие вопросы (из документа) ---
101+
q.extend([
102+
"Есть ли опенсорс аналоги упомянутых решений?",
103+
"В чем практическая значимость представленного метода?",
104+
"Какие ограничения имеет разработанный подход?",
105+
"Для каких дополнительных задач можно применить полученные результаты?",
106+
])
107+
108+
return q
109+
110+
# ---------------------------------------------------------
111+
# --- 5. Гибридная генерация: LLM + эвристики ---
112+
# ---------------------------------------------------------
113+
114+
def generate_llm_questions(self, count=5) -> List[str]:
115+
"""
116+
Генерация N вопросов через rut5 по ключевым фрагментам документа.
117+
"""
118+
q = []
119+
fragments = self.sentences[:40] # первые ~40 предложений для контекста
120+
121+
step = max(1, len(fragments) // count)
122+
123+
for i in range(0, len(fragments), step):
124+
frag = fragments[i]
125+
try:
126+
llm_q = self.llm_generate_question(frag)
127+
if len(llm_q) > 10:
128+
q.append(llm_q)
129+
except:
130+
continue
131+
132+
if len(q) >= count:
133+
break
134+
135+
return q
136+
137+
# ---------------------------------------------------------
138+
# --- 6. Главный метод ---
139+
# ---------------------------------------------------------
140+
141+
def generate_all(self) -> List[str]:
142+
"""
143+
Генерирует полный набор вопросов:
144+
- эвристические
145+
- модельные (LLM)
146+
"""
147+
result = []
148+
result.extend(self.heuristic_questions())
149+
result.extend(self.generate_llm_questions(count=7))
150+
return list(dict.fromkeys(result)) # убрать дубли
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
transformers
2+
sentencepiece
3+
nltk
4+
huggingface_hub
5+
python-docx

app/questions_generator/run.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from generator import VkrQuestionGenerator
2+
from validator import VkrQuestionValidator
3+
import sys
4+
import os
5+
from docx import Document
6+
7+
8+
def load_vkr_text(path: str) -> str:
9+
if not os.path.exists(path):
10+
print(f"[ERROR] Файл '{path}' не найден.")
11+
sys.exit(1)
12+
13+
document = Document(path)
14+
text = []
15+
for paragraph in document.paragraphs:
16+
text.append(paragraph.text)
17+
18+
return '\n'.join(text)
19+
20+
21+
def main():
22+
print("=== Загрузка текста ВКР ===")
23+
text = load_vkr_text("vkr_examples/VKR1.docx")
24+
25+
print("=== Инициализация генератора ===")
26+
gen = VkrQuestionGenerator(text, model_path="/app/rut5-base")
27+
28+
print("=== Инициализация валидатора ===")
29+
validator = VkrQuestionValidator(text)
30+
31+
print("=== Генерация вопросов ===")
32+
questions = gen.generate_all()
33+
34+
print("\n=== Результаты ===")
35+
for q in questions:
36+
rel = validator.check_relevance(q)
37+
clr = validator.check_clarity(q)
38+
diff = validator.check_difficulty(q)
39+
40+
status = "✔ OK" if (rel and clr and diff) else "✖ FAIL"
41+
42+
print(f"\n[{status}] {q}")
43+
print(f" - relevance: {rel}")
44+
print(f" - clarity: {clr}")
45+
print(f" - difficulty:{diff}")
46+
47+
print("\n=== Готово ===")
48+
49+
50+
if __name__ == "__main__":
51+
main()

0 commit comments

Comments
 (0)