Wplotnikow commited on
Commit
d0e25a2
·
verified ·
1 Parent(s): 373e7a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -27
app.py CHANGED
@@ -3,47 +3,64 @@ import glob
3
  from docx import Document
4
  from sklearn.feature_extraction.text import TfidfVectorizer
5
  from sklearn.metrics.pairwise import cosine_similarity
6
-
7
  import torch
8
  from transformers import T5ForConditionalGeneration, T5Tokenizer
9
 
 
 
 
 
 
 
 
 
 
 
10
  def get_blocks_from_docx():
11
  docx_list = glob.glob("*.docx")
12
  if not docx_list:
13
- return []
14
  doc = Document(docx_list[0])
15
  blocks = []
 
16
  for p in doc.paragraphs:
17
  txt = p.text.strip()
18
- # Исключаем короткие и заголовочные блоки
19
  if (
20
  txt
21
  and not (len(txt) <= 3 and txt.isdigit())
22
- and not (
23
- len(txt) < 35
24
- and txt == txt.upper()
25
- and txt.endswith(('.', ':', '?', '!')) is False
26
- )
27
  and len(txt.split()) > 3
28
  ):
29
  blocks.append(txt)
 
 
 
30
  for table in doc.tables:
31
  for row in table.rows:
32
  row_text = " | ".join(cell.text.strip() for cell in row.cells if cell.text.strip())
33
- if row_text and len(row_text) > 20 and len(row_text.split()) > 3:
34
  blocks.append(row_text)
 
 
 
35
  seen = set()
36
- uniq_blocks = []
 
37
  for b in blocks:
38
- if b not in seen and len(b) > 0:
39
- uniq_blocks.append(b)
40
  seen.add(b)
41
- return uniq_blocks
42
-
43
- blocks = get_blocks_from_docx()
 
 
 
44
 
45
- if not blocks:
 
 
46
  blocks = ["База знаний пуста: проверьте содержание и формат вашего .docx!"]
 
47
 
48
  vectorizer = TfidfVectorizer(lowercase=True).fit(blocks)
49
  matrix = vectorizer.transform(blocks)
@@ -65,30 +82,46 @@ def rut5_answer(question, context):
65
  return tokenizer.decode(output_ids[0], skip_special_tokens=True)
66
 
67
  def ask_chatbot(question):
68
- # Регистронезависимый поиск! (lowercase everywhere)
69
  question = question.strip()
70
  if not question:
71
  return "Пожалуйста, введите вопрос."
72
- if not blocks or blocks == ["База знаний пуста: проверьте содержание и формат вашего .docx!"]:
73
  return "Ошибка: база знаний пуста. Проверьте .docx и перезапустите Space."
 
74
  user_vec = vectorizer.transform([question.lower()])
75
  sims = cosine_similarity(user_vec, matrix)
76
  n_blocks = min(3, len(blocks))
 
 
 
77
  top_idxs = list(reversed(sims.argsort()[-n_blocks:]))
 
78
  context_blocks = []
79
  for rank, idx in enumerate(top_idxs):
80
- # ПОНИЖЕННЫЙ ПОРОГ! Если ничего не найдено по порогу, всегда берём самый первый (лучший) блок
81
- if sims[idx] > 0.05 or (rank == 0):
82
- if len(blocks[idx].split()) > 3 and len(blocks[idx]) > 20:
83
- context_blocks.append(blocks[idx])
84
  context = " ".join(context_blocks)
85
- # Если даже так не вышло значит база совсем пуста
86
- if not context:
87
- return "Не найден ни один фрагмент для ответа. Проверьте, что в .docx есть содержательные абзацы."
 
 
 
 
 
 
 
 
 
 
 
88
  answer = rut5_answer(question, context)
89
- # Защита: требуем минимум два предложения ("." хотя бы 2 раза)
90
  if len(answer.strip().split()) < 8 or answer.count('.') < 2:
91
- answer += "\n\n" + context
 
 
 
92
  return answer
93
 
94
  EXAMPLES = [
 
3
  from docx import Document
4
  from sklearn.feature_extraction.text import TfidfVectorizer
5
  from sklearn.metrics.pairwise import cosine_similarity
 
6
  import torch
7
  from transformers import T5ForConditionalGeneration, T5Tokenizer
8
 
9
+ def is_header(txt):
10
+ # Абсолютно короткая фраза без знака препинания и вся в верхнем регистре — заголовок
11
+ if not txt or len(txt) < 35:
12
+ if txt == txt.upper() and not txt.endswith(('.', ':', '?', '!')):
13
+ return True
14
+ # Также часто заголовок — просто пара слов с заглавных (мало слов и нет в конце точки):
15
+ if txt.istitle() and len(txt.split()) < 6 and not txt.endswith(('.', ':', '?', '!')):
16
+ return True
17
+ return False
18
+
19
  def get_blocks_from_docx():
20
  docx_list = glob.glob("*.docx")
21
  if not docx_list:
22
+ return [], []
23
  doc = Document(docx_list[0])
24
  blocks = []
25
+ non_header_blocks = []
26
  for p in doc.paragraphs:
27
  txt = p.text.strip()
 
28
  if (
29
  txt
30
  and not (len(txt) <= 3 and txt.isdigit())
 
 
 
 
 
31
  and len(txt.split()) > 3
32
  ):
33
  blocks.append(txt)
34
+ if not is_header(txt) and len(txt) > 25:
35
+ non_header_blocks.append(txt)
36
+ # Таблицы
37
  for table in doc.tables:
38
  for row in table.rows:
39
  row_text = " | ".join(cell.text.strip() for cell in row.cells if cell.text.strip())
40
+ if row_text and len(row_text.split()) > 3 and len(row_text) > 25:
41
  blocks.append(row_text)
42
+ if not is_header(row_text):
43
+ non_header_blocks.append(row_text)
44
+ # Убираем дубли
45
  seen = set()
46
+ blocks_clean = []
47
+ non_hdr_clean = []
48
  for b in blocks:
49
+ if b not in seen:
50
+ blocks_clean.append(b)
51
  seen.add(b)
52
+ seen = set()
53
+ for b in non_header_blocks:
54
+ if b not in seen:
55
+ non_hdr_clean.append(b)
56
+ seen.add(b)
57
+ return blocks_clean, non_hdr_clean
58
 
59
+ blocks, normal_blocks = get_blocks_from_docx()
60
+ if not blocks or not normal_blocks:
61
+ # Если ничего не нашли — фэйк заглушка
62
  blocks = ["База знаний пуста: проверьте содержание и формат вашего .docx!"]
63
+ normal_blocks = ["База знаний пуста: проверьте содержание и формат вашего .docx!"]
64
 
65
  vectorizer = TfidfVectorizer(lowercase=True).fit(blocks)
66
  matrix = vectorizer.transform(blocks)
 
82
  return tokenizer.decode(output_ids[0], skip_special_tokens=True)
83
 
84
  def ask_chatbot(question):
 
85
  question = question.strip()
86
  if not question:
87
  return "Пожалуйста, введите вопрос."
88
+ if not normal_blocks or normal_blocks == ["База знаний пуста: проверьте содержание и формат вашего .docx!"]:
89
  return "Ошибка: база знаний пуста. Проверьте .docx и перезапустите Space."
90
+
91
  user_vec = vectorizer.transform([question.lower()])
92
  sims = cosine_similarity(user_vec, matrix)
93
  n_blocks = min(3, len(blocks))
94
+ if n_blocks == 0:
95
+ return "База знаний пуста: загрузите методичку с осмысленными абзацами!"
96
+ # Получаем индексы лучших блоков среди ВСЕХ
97
  top_idxs = list(reversed(sims.argsort()[-n_blocks:]))
98
+ # Для генерации контекста используем все блоки, но...
99
  context_blocks = []
100
  for rank, idx in enumerate(top_idxs):
101
+ if 0 <= idx < len(blocks):
102
+ context_blocks.append(blocks[idx])
 
 
103
  context = " ".join(context_blocks)
104
+ # ...для финального ответа ищем САМЫЙ релевантный не-заголовок (абзац)!
105
+ # (обычно первый релевантен)
106
+ best_normal_block = ""
107
+ max_sim = -1
108
+ for idx, nb in enumerate(normal_blocks):
109
+ v_nb = vectorizer.transform([nb.lower()])
110
+ sim = cosine_similarity(user_vec, v_nb)[0]
111
+ if sim > max_sim:
112
+ max_sim = sim
113
+ best_normal_block = nb
114
+ # Если совсем всё плохо — fallback на обычный context
115
+ if not best_normal_block:
116
+ best_normal_block = context_blocks if context_blocks else ""
117
+ # Генерируем развернутый ответ с подложкой из максимального контекста
118
  answer = rut5_answer(question, context)
119
+ # Если слишком кратко дублируем релевантный фрагмент (абзац)
120
  if len(answer.strip().split()) < 8 or answer.count('.') < 2:
121
+ answer += "\n\n" + best_normal_block
122
+ # Финальный ответ — если сгенерированный ответ случайно "превратился" в заголовок, заменяем его на абзац!
123
+ if is_header(answer):
124
+ answer = best_normal_block
125
  return answer
126
 
127
  EXAMPLES = [