11import re
22from typing import List , Dict
3+
34from nltk .tokenize import sent_tokenize , word_tokenize
45from nltk .corpus import stopwords
56from transformers import AutoTokenizer , AutoModelForSeq2SeqLM
67
78
89class VkrQuestionGenerator :
9- """
10- Генератор вопросов по тексту ВКР.
11- Основан на гибридном подходе: NLTK + rut5-base-multitask.
12- """
13- def __init__ (self , vkr_text : str , model_path : str ):
10+ """Гибридный генератор вопросов по ВКР: NLTK + rut5-base-multitask."""
11+
12+ SECTION_PATTERNS : Dict [str , str ] = {
13+ "Введение" : r"Введение.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
14+ "Обзор предметной области" : r"Обзор предметной области.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
15+ "Постановка задачи" : r"Постановка задачи.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
16+ "Метод решения" : r"Метод решения.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
17+ "Исследования" : r"Исследования.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
18+ "Заключение" : r"Заключение.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
19+ "Приложения" : r"Приложения.*?(?=\n[A-ZА-Я][^\n]*\n)" ,
20+ }
21+
22+ def __init__ (self , vkr_text : str , model_path : str = "ai-forever/rut5-base-multitask" ):
1423 self .vkr_text = vkr_text
1524 self .sentences = sent_tokenize (vkr_text )
1625 self .stopwords = set (stopwords .words ("russian" ))
1726
18- # ---- Модель rut5 ----
27+ # Модель rut5-base-multitask для языкового оформления вопросов
1928 self .tokenizer = AutoTokenizer .from_pretrained (model_path , use_fast = False )
2029 self .model = AutoModelForSeq2SeqLM .from_pretrained (model_path )
2130
22- # ---------------------------------------------------------
23- # --- 1. ЭВРИСТИКА: Извлечение ключевых частей ВКР ---
24- # ---------------------------------------------------------
25-
2631 def extract_section (self , title : str ) -> str :
27- """
28- Универсальный метод извлечения раздела по заголовку.
29- """
30- pattern = rf"{ title } .*?(?=\n[A-ZА-Я][^\n]*\n)"
32+ """Извлекает раздел по шаблону заголовка."""
33+ pattern = self .SECTION_PATTERNS .get (title , rf"{ title } .*?(?=\n[A-ZА-Я][^\n]*\n)" )
3134 m = re .search (pattern , self .vkr_text , re .DOTALL | re .IGNORECASE )
3235 return m .group (0 ) if m else ""
3336
@@ -37,77 +40,86 @@ def extract_intro(self) -> str:
3740 def extract_conclusion (self ) -> str :
3841 return self .extract_section ("Заключение" )
3942
40- # ---------------------------------------------------------
41- # --- 2. ЭВРИСТИКА: Поиск ключевых концепций ---
42- # ---------------------------------------------------------
43-
4443 def extract_keywords (self , text : str ) -> List [str ]:
44+ """Извлекает ключевые слова из текста."""
4545 tokens = word_tokenize (text .lower ())
4646 return [
4747 t for t in tokens
4848 if t .isalnum () and t not in self .stopwords and len (t ) > 4
4949 ]
5050
51- # ---------------------------------------------------------
52- # --- 3. Генерация вопросов через rut5 (режим ask) ---
53- # ---------------------------------------------------------
54-
5551 def llm_generate_question (self , text_fragment : str ) -> str :
56- """
57- Генерация вопроса по фрагменту текста через rut5 ask
58- """
52+ """Генерирует формулировку вопроса через rut5 ask."""
5953 prompt = f"ask: { text_fragment } "
6054 enc = self .tokenizer (prompt , return_tensors = "pt" , truncation = True )
6155 out = self .model .generate (
6256 ** enc ,
6357 max_length = 64 ,
6458 num_beams = 5 ,
65- early_stopping = True
59+ early_stopping = True ,
6660 )
6761 return self .tokenizer .decode (out [0 ], skip_special_tokens = True )
6862
69- # ---------------------------------------------------------
70- # --- 4. ЭВРИСТИЧЕСКИЕ ШАБЛОНЫ (из документа) ---
71- # ---------------------------------------------------------
72-
7363 def heuristic_questions (self ) -> List [str ]:
74- """
75- Генерация вопросов по эвристикам из загруженных PDF.
76- """
64+ """Эвристики, завязанные на структуру ВКР."""
7765 intro = self .extract_intro ()
66+ overview = self .extract_section ("Обзор предметной области" )
67+ objectives = self .extract_section ("Постановка задачи" )
68+ method = self .extract_section ("Метод решения" )
69+ research = self .extract_section ("Исследования" )
7870 conc = self .extract_conclusion ()
79- keywords = self .extract_keywords ( self . vkr_text )
71+ apps = self .extract_section ( "Приложения" )
8072
81- q = []
73+ q : List [ str ] = []
8274
83- # --- По связям между разделами ---
75+ # Введение ↔ Заключение
8476 if intro and conc :
85- q .append ("Как сформулированные во введении задачи связаны с выводами работы?" )
86-
87- # --- По выводам ---
88- if conc :
89- q .append ("На основании каких данных был сделан ключевой вывод в заключении?" )
90-
91- # --- Общие вопросы (из документа) ---
77+ q .append (
78+ "Как цель и задачи, сформулированные во введении, отражены в итоговых выводах заключения?"
79+ )
80+
81+ # Обзор предметной области
82+ if overview :
83+ q .append (
84+ "Какие термины и подходы из обзора предметной области легли в основу формальной постановки задачи?"
85+ )
86+
87+ # Постановка задачи
88+ if objectives :
89+ q .append (
90+ "В каких требованиях к решению, указанных в постановке задачи, находят отражение цели работы?"
91+ )
92+
93+ # Метод решения
94+ if method :
95+ q .append (
96+ "Как архитектура и алгоритмы, описанные в разделе «Метод решения», обеспечивают достижение поставленных требований?"
97+ )
98+
99+ # Исследования
100+ if research :
101+ q .append (
102+ "Какие количественные или качественные свойства решения подтверждены в разделе «Исследования» и как они связаны с задачами введения?"
103+ )
104+
105+ # Приложения
106+ if apps :
107+ q .append (
108+ "Какие дополнительные материалы из приложений необходимы для проверки воспроизводимости результатов?"
109+ )
110+
111+ # Обязательные общие вопросы
92112 q .extend ([
93- "Есть ли опенсорс аналоги упомянутых решений?" ,
94- "В чем практическая значимость представленного метода?" ,
95- "Какие ограничения имеет разработанный подход?" ,
96- "Для каких дополнительных задач можно применить полученные результаты?" ,
113+ "Как практическая значимость работы следует из задач и результатов исследования?" ,
114+ "Какие ограничения метода решения указаны в тексте и как они влияют на достижение цели?" ,
97115 ])
98116
99117 return q
100118
101- # ---------------------------------------------------------
102- # --- 5. Гибридная генерация: LLM + эвристики ---
103- # ---------------------------------------------------------
104-
105- def generate_llm_questions (self , count = 5 ) -> List [str ]:
106- """
107- Генерация N вопросов через rut5 по ключевым фрагментам документа.
108- """
109- q = []
110- fragments = self .sentences [:40 ] # первые ~40 предложений для контекста
119+ def generate_llm_questions (self , count : int = 5 ) -> List [str ]:
120+ """Генерирует N вопросов через rut5 по ключевым фрагментам документа."""
121+ q : List [str ] = []
122+ fragments = self .sentences [:40 ]
111123
112124 step = max (1 , len (fragments ) // count )
113125
@@ -117,26 +129,18 @@ def generate_llm_questions(self, count=5) -> List[str]:
117129 llm_q = self .llm_generate_question (frag )
118130 if len (llm_q ) > 10 :
119131 q .append (llm_q )
120- except :
132+ except Exception : # noqa: BLE001
121133 continue
122134
123135 if len (q ) >= count :
124136 break
125137
126138 return q
127139
128- # ---------------------------------------------------------
129- # --- 6. Главный метод ---
130- # ---------------------------------------------------------
131-
132140 def generate_all (self ) -> List [str ]:
133- """
134- Генерирует полный набор вопросов:
135- - эвристические
136- - модельные (LLM)
137- """
138- result = []
141+ """Генерирует полный набор вопросов: эвристики + LLM."""
142+ result : List [str ] = []
139143 result .extend (self .heuristic_questions ())
140- result .extend (["Начало rut5-base-multitask вопросов " ])
144+ result .extend (["--- rut5-base-multitask вопросы --- " ])
141145 result .extend (self .generate_llm_questions (count = 10 ))
142- return list (dict .fromkeys (result )) # убрать дубли
146+ return list (dict .fromkeys (result ))
0 commit comments