Nikolay Ponomarev commited on
Commit
410e024
·
1 Parent(s): 42bde7a

Item Search

Browse files
Files changed (1) hide show
  1. app.py +168 -36
app.py CHANGED
@@ -13,6 +13,8 @@ DEFAULT_INTENT_MODEL = os.getenv("INTENT_MODEL", "joeddav/xlm-roberta-large-xnli
13
 
14
  # 2) Checklist generator (instruct)
15
  DEFAULT_GEN_MODEL = os.getenv("GEN_MODEL", "Qwen/Qwen2.5-0.5B-Instruct")
 
 
16
 
17
  # 3) QA over checklist
18
  DEFAULT_QA_MODEL = os.getenv("QA_MODEL", "deepset/xlm-roberta-base-squad2")
@@ -107,16 +109,23 @@ def build_checklist_prompt(user_goal: str, theme: str | None, style: str, constr
107
  "Составь практичный чек-лист на русском языке.\n"
108
  "Верни ТОЛЬКО чек-лист без вступлений.\n"
109
  "Запрещено:\n"
 
110
  "- плейсхолдеры и шаблоны: никаких '[секунды]', '[какой-то текст]', '{...}', '<...>'\n"
111
- "- таблицы, 'Расчёт', 'Банк', 'Сообщение', поля для заполнения\n"
112
  "Формат строго:\n"
113
- "- [ ] пункт\n"
114
  " - подпункт (если нужно)\n"
 
115
  "Требования:\n"
116
  f"- стиль: {style_hint}\n"
117
  "- 12–18 пунктов\n"
118
- "- без нумерации (никаких '1.')\n"
 
 
119
  "- пункты конкретные и выполнимые\n"
 
 
 
120
  "- в конце 2 блока:\n"
121
  "Проверка готовности:\n"
122
  "- ... (3–5 вопросов)\n"
@@ -146,24 +155,47 @@ def clean_checklist(text: str) -> str:
146
  text = re.sub(r"<extra_id_\d+>", "", text).strip()
147
 
148
  cleaned = []
 
 
149
  for ln in text.splitlines():
150
  s = ln.rstrip()
151
-
152
  if not s.strip():
153
  continue
154
 
155
- # Cut obvious "form-like" garbage blocks
156
  low = s.strip().lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  if low.startswith(("расч", "банк", "сообщение", "время ", "план поездки", "расход")):
158
  continue
159
 
160
- # Remove square-bracket placeholders but keep "[ ]" checkbox
 
161
  s2 = re.sub(r"\[(?!\s*\])([^\]]+)\]", "", s).strip()
162
 
163
- # Normalize bullets
 
 
 
164
  s2 = s2.replace("•", "-").replace("–", "-")
 
165
 
166
- # Keep only checklist or the two required blocks
167
  if s2.startswith("- [ ]") or s2.startswith(" -") or s2.startswith("Проверка готовности") or s2.startswith("Риски"):
168
  cleaned.append(s2)
169
 
@@ -174,38 +206,132 @@ def clean_checklist(text: str) -> str:
174
  raw = [l.strip() for l in text.splitlines() if l.strip()]
175
  raw = [re.sub(r"^\d+[\).\s]+", "", r).strip(" -•\t") for r in raw]
176
  raw = [r for r in raw if r]
177
- raw = [f"- [ ] {r}" for r in raw[:18]]
178
  out = "\n".join(raw).strip()
179
 
180
  return out
181
 
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  def polish_russian_same_model(checklist_text: str) -> str:
184
- """Second pass with SAME generator (still transformer #2) to polish grammar."""
185
- if not checklist_text or "- [ ]" not in checklist_text:
186
  return checklist_text
187
 
188
  polish_prompt = (
189
- "Исправь орфографию, грамматику и стилистику русского языка.\n"
190
- ИЧЕГО НЕ ДОБАВЛЯЙ и не меняй смысл.\n"
191
- "Сохрани формат чек-листа:\n"
192
- "- [ ] пункт\n"
193
- " - подпункт\n"
194
- "А также блоки 'Проверка готовности' и 'Риски и как снизить'.\n\n"
 
 
 
 
195
  f"{checklist_text}\n"
196
  )
197
  polish_prompt = apply_chat_template_if_available(polish_prompt)
198
 
199
  outp = gen_pipe(
200
  polish_prompt,
201
- max_new_tokens=260,
202
  do_sample=False,
203
  return_full_text=False,
204
  )
205
  text2 = (outp[0].get("generated_text") or "").strip()
206
  text2 = clean_checklist(text2)
 
207
 
208
- # keep only if not worse
209
  if text2.count("- [ ]") >= checklist_text.count("- [ ]"):
210
  return text2
211
  return checklist_text
@@ -243,21 +369,24 @@ def generate_checklist(user_goal: str, category: str, style: str, constraints: s
243
  # Generation (primary)
244
  out = gen_pipe(
245
  prompt,
246
- max_new_tokens=520,
247
  do_sample=True,
248
- temperature=0.6,
249
- top_p=0.9,
250
- repetition_penalty=1.08,
251
  return_full_text=False,
252
  )
253
  text = (out[0].get("generated_text") or "").strip()
254
  text = clean_checklist(text)
 
255
 
256
  # Retry if too short/poor format
257
- if text.count("- [ ]") < 8:
258
  retry_prompt = (
259
- "Сделай чек-лист строго в формате '- [ ] ...' (12–18 пунктов) на русском.\n"
260
- "Без плейсхолдеров в квадратных скобках. Без таблиц. Безасчёт/Банк/Сообщение'.\n"
 
 
261
  "В конце добавь:\n"
262
  "Проверка готовности: (3–5 вопросов)\n"
263
  "Риски и как снизить: (3–6 пунктов)\n\n"
@@ -269,26 +398,30 @@ def generate_checklist(user_goal: str, category: str, style: str, constraints: s
269
  retry_prompt = apply_chat_template_if_available(retry_prompt)
270
  out2 = gen_pipe(
271
  retry_prompt,
272
- max_new_tokens=620,
273
  do_sample=True,
274
  temperature=0.75,
275
- top_p=0.92,
276
- repetition_penalty=1.10,
277
  return_full_text=False,
278
  )
279
  text2 = (out2[0].get("generated_text") or "").strip()
280
  text2 = clean_checklist(text2)
 
281
  if text2.count("- [ ]") > text.count("- [ ]"):
282
  text = text2
283
 
 
 
 
284
  # Polish Russian (still same generator = transformer #2)
285
  text = polish_russian_same_model(text)
286
 
287
  # Final guard
288
- if text.count("- [ ]") < 6:
289
  text = (
290
- "- [ ] Не удалось корректно сформировать чек-лист.\n"
291
- "- [ ] Попробуйте уточнить цель (страна/срок/статус визы/бюджет) и нажать ещё раз.\n"
292
  )
293
 
294
  meta = {
@@ -331,7 +464,8 @@ def answer_question(question: str, checklist_state: str, meta_state: dict | None
331
 
332
  user_prompt = (
333
  "Ответь на вопрос по чек-листу. Пиши по-русски, кратко и практично.\n"
334
- "Если ответа нет в чек-листе — предложи 3–6 дополнительных пунктов, которые стоит добавить.\n"
 
335
  "Не используй плейсхолдеры в квадратных скобках.\n\n"
336
  f"Цель: {goal}\n"
337
  f"Тема: {theme}\n\n"
@@ -342,7 +476,7 @@ def answer_question(question: str, checklist_state: str, meta_state: dict | None
342
  prompt = apply_chat_template_if_available(user_prompt)
343
  gen_out = gen_pipe(
344
  prompt,
345
- max_new_tokens=260,
346
  do_sample=False,
347
  return_full_text=False,
348
  )[0]["generated_text"].strip()
@@ -366,7 +500,6 @@ with gr.Blocks(title="Умный чек-лист (3 Transformers)") as demo:
366
  meta_state = gr.State(value=None)
367
 
368
  with gr.Tab("1) Создать чек-лист"):
369
- # Top row: inputs left, helper mini-blocks right
370
  with gr.Row():
371
  with gr.Column(scale=3):
372
  user_goal = gr.Textbox(
@@ -412,7 +545,6 @@ with gr.Blocks(title="Умный чек-лист (3 Transformers)") as demo:
412
  interactive=False,
413
  )
414
 
415
- # Big checklist output BELOW (full width)
416
  checklist_out = gr.Code(label="Чек-лист", language="markdown")
417
 
418
  gen_btn.click(
 
13
 
14
  # 2) Checklist generator (instruct)
15
  DEFAULT_GEN_MODEL = os.getenv("GEN_MODEL", "Qwen/Qwen2.5-0.5B-Instruct")
16
+ # If your Space can handle it, this is often better structured:
17
+ # DEFAULT_GEN_MODEL = os.getenv("GEN_MODEL", "Qwen/Qwen2.5-1.5B-Instruct")
18
 
19
  # 3) QA over checklist
20
  DEFAULT_QA_MODEL = os.getenv("QA_MODEL", "deepset/xlm-roberta-base-squad2")
 
109
  "Составь практичный чек-лист на русском языке.\n"
110
  "Верни ТОЛЬКО чек-лист без вступлений.\n"
111
  "Запрещено:\n"
112
+ "- любые вступления/комментарии (например: 'Конечно!', 'Вот исправленный текст', 'не меняю смысл')\n"
113
  "- плейсхолдеры и шаблоны: никаких '[секунды]', '[какой-то текст]', '{...}', '<...>'\n"
114
+ "- таблицы, 'Расчёт', 'Банк', 'Сообщение', поля для заполнения\n\n"
115
  "Формат строго:\n"
116
+ "- [ ] (P0) пункт\n"
117
  " - подпункт (если нужно)\n"
118
+ "Где P0 = срочно/критично, P1 = важно, P2 = можно позже.\n\n"
119
  "Требования:\n"
120
  f"- стиль: {style_hint}\n"
121
  "- 12–18 пунктов\n"
122
+ "- без нумерации (никаких '1.'), только '- [ ]'\n"
123
+ "- КАЖДЫЙ пункт должен начинаться с (P0) или (P1) или (P2)\n"
124
+ "- Сначала выведи все P0, потом P1, потом P2\n"
125
  "- пункты конкретные и выполнимые\n"
126
+ "- для пунктов про документы/банки/счета/визы/страховку ОБЯЗАТЕЛЬНО добавь 2–5 подпунктов\n"
127
+ "- подпункты начинай с ' - '\n"
128
+ "- если тема релокации/иммиграции: обязательно охвати документы, финансы, связь, жильё, медицину, язык\n"
129
  "- в конце 2 блока:\n"
130
  "Проверка готовности:\n"
131
  "- ... (3–5 вопросов)\n"
 
155
  text = re.sub(r"<extra_id_\d+>", "", text).strip()
156
 
157
  cleaned = []
158
+ current_group = "OTHER"
159
+
160
  for ln in text.splitlines():
161
  s = ln.rstrip()
 
162
  if not s.strip():
163
  continue
164
 
 
165
  low = s.strip().lower()
166
+
167
+ # 1) Cut conversational/assistant boilerplate
168
+ bad_prefixes = (
169
+ "конечно",
170
+ "вот исправленный",
171
+ "вот исправленный текст",
172
+ "исправленный текст",
173
+ "ничего не добавляя",
174
+ "не меняю смысл",
175
+ "я не меняю смысл",
176
+ "готово",
177
+ "итог",
178
+ "результат",
179
+ )
180
+ if low.startswith(bad_prefixes):
181
+ continue
182
+
183
+ # 2) Cut obvious "form-like" garbage blocks
184
  if low.startswith(("расч", "банк", "сообщение", "время ", "план поездки", "расход")):
185
  continue
186
 
187
+ # 3) Remove square-bracket placeholders but keep "[ ]" checkbox.
188
+ # This removes [секунды], [какой-то текст], etc.
189
  s2 = re.sub(r"\[(?!\s*\])([^\]]+)\]", "", s).strip()
190
 
191
+ # 4) If model wrapped whole item into [ ... ]: "- [ ] [Сделайте ...]"
192
+ s2 = re.sub(r"^\-\s*\[\s*\]\s*\[(.+)\]\s*$", r"- [ ] \1", s2).strip()
193
+
194
+ # Normalize bullets & spacing
195
  s2 = s2.replace("•", "-").replace("–", "-")
196
+ s2 = re.sub(r"\s+", " ", s2)
197
 
198
+ # Keep only checklist lines + the two required blocks
199
  if s2.startswith("- [ ]") or s2.startswith(" -") or s2.startswith("Проверка готовности") or s2.startswith("Риски"):
200
  cleaned.append(s2)
201
 
 
206
  raw = [l.strip() for l in text.splitlines() if l.strip()]
207
  raw = [re.sub(r"^\d+[\).\s]+", "", r).strip(" -•\t") for r in raw]
208
  raw = [r for r in raw if r]
209
+ raw = [f"- [ ] (P1) {r}" for r in raw[:18]]
210
  out = "\n".join(raw).strip()
211
 
212
  return out
213
 
214
 
215
+ def split_tail_sections(text: str):
216
+ """Split checklist body from tail sections (Проверка готовности / Риски...) to keep them at bottom."""
217
+ lines = text.splitlines()
218
+ idx = None
219
+ for i, ln in enumerate(lines):
220
+ if ln.strip().startswith("Проверка готовности"):
221
+ idx = i
222
+ break
223
+ if idx is None:
224
+ return text, ""
225
+ return "\n".join(lines[:idx]).strip(), "\n".join(lines[idx:]).strip()
226
+
227
+
228
+ def sort_by_priority(text: str) -> str:
229
+ """Ensure P0 -> P1 -> P2 ordering (keeps tail sections at the very end)."""
230
+ body, tail = split_tail_sections(text)
231
+ if not body:
232
+ return text
233
+
234
+ groups = {"P0": [], "P1": [], "P2": [], "OTHER": []}
235
+ current = "OTHER"
236
+
237
+ for ln in body.splitlines():
238
+ s = ln.strip()
239
+ if s.startswith("- [ ]"):
240
+ if "(P0)" in s:
241
+ current = "P0"
242
+ elif "(P1)" in s:
243
+ current = "P1"
244
+ elif "(P2)" in s:
245
+ current = "P2"
246
+ else:
247
+ current = "OTHER"
248
+ groups[current].append(ln)
249
+ else:
250
+ groups[current].append(ln)
251
+
252
+ out_lines = []
253
+ for k in ["P0", "P1", "P2", "OTHER"]:
254
+ if groups[k]:
255
+ out_lines.extend(groups[k])
256
+
257
+ out = "\n".join(out_lines).strip()
258
+ if tail:
259
+ out = (out + "\n\n" + tail).strip()
260
+ return out
261
+
262
+
263
+ def ensure_doc_finance_subpoints(text: str) -> str:
264
+ """
265
+ If user asked for relocation/travel and the checklist contains a 'documents' or 'accounts' item without subpoints,
266
+ add a minimal set of subpoints using deterministic rules (no extra transformer).
267
+ """
268
+ lines = text.splitlines()
269
+ out = []
270
+ i = 0
271
+ while i < len(lines):
272
+ ln = lines[i]
273
+ out.append(ln)
274
+
275
+ if ln.startswith("- [ ]"):
276
+ low = ln.lower()
277
+
278
+ # detect if next line(s) already has subpoints
279
+ j = i + 1
280
+ has_sub = False
281
+ while j < len(lines) and lines[j].startswith(" -"):
282
+ has_sub = True
283
+ break
284
+
285
+ # Add subpoints if missing and keyword matches
286
+ if not has_sub and any(k in low for k in ["документ", "виза", "страхов", "паспорт"]):
287
+ out.extend([
288
+ " - Проверь срок действия загранпаспорта и требования к остаточному сроку",
289
+ " - Составь список нужных документов и сделай копии (бумага + облако)",
290
+ " - Уточни требования по визе/ВНЖ и собери подтверждения (финансы, жильё, приглашение)",
291
+ ])
292
+
293
+ if not has_sub and any(k in low for k in ["счёт", "счет", "банк", "карта", "финанс"]):
294
+ out.extend([
295
+ " - Проверь лимиты, комиссии и возможность работы карт за границей",
296
+ " - Подготовь резервный способ доступа к деньгам (вторая карта/наличные/перевод)",
297
+ " - Включи уведомления и двухфакторную защиту в банковских приложениях",
298
+ ])
299
+
300
+ i += 1
301
+
302
+ return "\n".join(out).strip()
303
+
304
+
305
  def polish_russian_same_model(checklist_text: str) -> str:
306
+ """Second pass with SAME generator (still transformer #2) to polish grammar without 'boilerplate'."""
307
+ if not checklist_text or checklist_text.count("- [ ]") < 6:
308
  return checklist_text
309
 
310
  polish_prompt = (
311
+ "Отредактируй текст чек-листа: исправь грамматику и стиль русского языка.\n"
312
+ "ВАЖНО:\n"
313
+ "- Верни ТОЛЬКО чек-лист.\n"
314
+ "- Никаких вступлений (например: 'Конечно', 'Вот исправленный текст').\n"
315
+ "- Никаких комментариев (например: 'не меняю смысл').\n"
316
+ "- Ничего не добавляй и не удаляй по смыслу.\n"
317
+ "- Сохрани ф��рмат:\n"
318
+ " - [ ] (P0/P1/P2) пункт\n"
319
+ " - подпункт\n"
320
+ "- Сохрани блоки 'Проверка готовности' и 'Риски и как снизить'.\n\n"
321
  f"{checklist_text}\n"
322
  )
323
  polish_prompt = apply_chat_template_if_available(polish_prompt)
324
 
325
  outp = gen_pipe(
326
  polish_prompt,
327
+ max_new_tokens=300,
328
  do_sample=False,
329
  return_full_text=False,
330
  )
331
  text2 = (outp[0].get("generated_text") or "").strip()
332
  text2 = clean_checklist(text2)
333
+ text2 = sort_by_priority(text2)
334
 
 
335
  if text2.count("- [ ]") >= checklist_text.count("- [ ]"):
336
  return text2
337
  return checklist_text
 
369
  # Generation (primary)
370
  out = gen_pipe(
371
  prompt,
372
+ max_new_tokens=650,
373
  do_sample=True,
374
+ temperature=0.65,
375
+ top_p=0.92,
376
+ repetition_penalty=1.10,
377
  return_full_text=False,
378
  )
379
  text = (out[0].get("generated_text") or "").strip()
380
  text = clean_checklist(text)
381
+ text = sort_by_priority(text)
382
 
383
  # Retry if too short/poor format
384
+ if text.count("- [ ]") < 10:
385
  retry_prompt = (
386
+ "Сделай чек-лист строго в формате '- [ ] (P0/P1/P2) ...' (12–18 пунктов) на русском.\n"
387
+ "Сначала P0, затем P1, затем P2.\n"
388
+ "Без вступлений. Без комментариев. Без плейсхолдеров в квадратных скобках.\n"
389
+ "Для документов/банков/счётов/визы/страховки добавь подпункты (2–5).\n"
390
  "В конце добавь:\n"
391
  "Проверка готовности: (3–5 вопросов)\n"
392
  "Риски и как снизить: (3–6 пунктов)\n\n"
 
398
  retry_prompt = apply_chat_template_if_available(retry_prompt)
399
  out2 = gen_pipe(
400
  retry_prompt,
401
+ max_new_tokens=750,
402
  do_sample=True,
403
  temperature=0.75,
404
+ top_p=0.93,
405
+ repetition_penalty=1.12,
406
  return_full_text=False,
407
  )
408
  text2 = (out2[0].get("generated_text") or "").strip()
409
  text2 = clean_checklist(text2)
410
+ text2 = sort_by_priority(text2)
411
  if text2.count("- [ ]") > text.count("- [ ]"):
412
  text = text2
413
 
414
+ # Deterministic subpoints for docs/finance if missing
415
+ text = ensure_doc_finance_subpoints(text)
416
+
417
  # Polish Russian (still same generator = transformer #2)
418
  text = polish_russian_same_model(text)
419
 
420
  # Final guard
421
+ if text.count("- [ ]") < 8:
422
  text = (
423
+ "- [ ] (P0) Не удалось корректно сформировать чек-лист.\n"
424
+ "- [ ] (P0) Попробуйте уточнить цель (страна/город, статус визы, бюджет, сроки) и нажать ещё раз.\n"
425
  )
426
 
427
  meta = {
 
464
 
465
  user_prompt = (
466
  "Ответь на вопрос по чек-листу. Пиши по-русски, кратко и практично.\n"
467
+ "Если ответа нет в чек-листе — предложи 3–6 дополнительных пунктов (с приоритетом P0/P1/P2).\n"
468
+ "Не используй вступления и комментарии.\n"
469
  "Не используй плейсхолдеры в квадратных скобках.\n\n"
470
  f"Цель: {goal}\n"
471
  f"Тема: {theme}\n\n"
 
476
  prompt = apply_chat_template_if_available(user_prompt)
477
  gen_out = gen_pipe(
478
  prompt,
479
+ max_new_tokens=320,
480
  do_sample=False,
481
  return_full_text=False,
482
  )[0]["generated_text"].strip()
 
500
  meta_state = gr.State(value=None)
501
 
502
  with gr.Tab("1) Создать чек-лист"):
 
503
  with gr.Row():
504
  with gr.Column(scale=3):
505
  user_goal = gr.Textbox(
 
545
  interactive=False,
546
  )
547
 
 
548
  checklist_out = gr.Code(label="Чек-лист", language="markdown")
549
 
550
  gen_btn.click(