dish0nest2 commited on
Commit
a74d9eb
·
1 Parent(s): 5fa110b
Files changed (1) hide show
  1. app.py +121 -115
app.py CHANGED
@@ -2,11 +2,15 @@ import os
2
  import re
3
  import html as ihtml
4
  import textwrap
 
5
  import requests
6
  import gradio as gr
7
 
8
  from smolagents import CodeAgent, LiteLLMModel, tool
9
 
 
 
 
10
 
11
  # ----------------------------
12
  # Config (CPU-friendly defaults)
@@ -24,7 +28,7 @@ TIMEOUT = int(os.getenv("LITELLM_TIMEOUT", "3600"))
24
 
25
 
26
  def make_model(model_name: str) -> LiteLLMModel:
27
- # flatten_messages_as_text=False помогает избежать проблем формата messages
28
  return LiteLLMModel(
29
  model_id=f"ollama_chat/{model_name}",
30
  api_base=OLLAMA_BASE,
@@ -50,13 +54,13 @@ def _strip_html(raw_html: str) -> str:
50
 
51
  @tool
52
  def web_search(query: str) -> str:
53
- """Поиск в интернете (DuckDuckGo HTML), возвращает очищенный текст.
54
 
55
  Args:
56
  query: Текстовый поисковый запрос.
57
 
58
  Returns:
59
- Очищенный текст (обрезан до ~4500 символов).
60
  """
61
  resp = requests.get(
62
  "https://duckduckgo.com/html/",
@@ -76,12 +80,11 @@ def extract_facts(case_description: str) -> str:
76
  case_description: Текст пользователя.
77
 
78
  Returns:
79
- Строка с фактами + списком недостающих уточнений.
80
  """
81
  text = (case_description or "").strip()
82
  low = text.lower()
83
 
84
- # Ключевые признаки
85
  flags = []
86
  if re.search(r"\b(просроч|просрочка|пропустил платеж)\b", low):
87
  flags.append("просрочка/пропуск платежа")
@@ -91,33 +94,57 @@ def extract_facts(case_description: str) -> str:
91
  flags.append("ипотека")
92
  if re.search(r"\b(кредит|займ|мфо)\b", low):
93
  flags.append("кредит/займ/МФО")
94
- if re.search(r"\b(штраф|пени|неустойк)\b", low):
95
- flags.append("штрафы/пени")
96
 
97
- # Числа (в т.ч. суммы)
98
  nums = re.findall(r"\d[\d\s]{2,}", text)
99
  nums = [re.sub(r"\s+", "", n) for n in nums][:8]
100
 
101
- missing = []
102
- if "просроч" in low:
103
- missing.append("Сколько дней/месяцев просрочка?")
104
- if "коллект" in low:
105
- missing.append("Кто кредитор/банк/МФО? (без номеров договоров)")
106
- missing.append("Поступают ли угрозы/давление/звонки родственникам?")
107
- if not nums:
108
- missing.append("Какая сумма долга/минимального платежа/просрочки? (можно диапазоном)")
109
- missing.append("Какой официальный доход/примерные обязательные расходы? (можно диапазоном)")
110
 
111
  return (
112
  "ФАКТЫ ИЗ ОПИСАНИЯ (без домыслов):\n"
113
  f"- Исходный текст: {text}\n"
114
- f"- Найденные признаки: {', '.join(flags) if flags else 'не выявлено'}\n"
115
- f"- Найденные числа: {', '.join(nums) if nums else 'нет'}\n"
116
- f"- Чего не хватает (уточнить безопасно): {('; '.join(missing)) if missing else 'ничего'}\n"
117
  "Правило: нельзя добавлять факты, которых нет в исходном тексте."
118
  )
119
 
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, allow_internet: bool):
122
  case_description = (case_description or "").strip()
123
  region = (region or "").strip()
@@ -126,105 +153,93 @@ def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, al
126
  if not case_description:
127
  return "Пожалуйста, заполните поле «Описание ситуации»."
128
 
129
- # Всегда даём extract_facts; web_search — только если allow_internet
130
  tools = [extract_facts] + ([web_search] if allow_internet else [])
131
 
132
  # ----------------------------
133
- # Agent 1: TRIAGE
134
  # ----------------------------
135
  triage_agent = CodeAgent(
136
  tools=tools,
137
  model=make_model(TRIAGE_MODEL),
138
  add_base_tools=False,
139
- max_steps=2, # достаточно для одной попытки исправить код, но без лишних итераций
140
  additional_authorized_imports=[],
141
  )
142
 
143
  triage_prompt = f"""
144
- Ты агент TRIAGE (финансовая помощь). Всегда отвечай на РУССКОМ.
145
-
146
- КРИТИЧЕСКИ ВАЖНО: Ты работаешь как CodeAgent.
147
- Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
148
- Ничего вне блока не пиши.
149
-
150
- ОБЯЗАТЕЛЬНО закончить через final_answer(triage_text).
151
 
152
  Правила:
153
  - НЕ выдумывай факты. Используй только входные данные и extract_facts().
154
  - НЕ предполагай взлом/мошенничество/кражу данных, если пользователь этого не писал.
155
- - НЕ проси чувствительные данные: номера карт, CVV, пароли, SMS-коды, номера документов.
156
- - Можно просить безопасные данные: суммы диапазонами, сроки, название кредитора без номеров.
157
 
158
- Если web_search доступен (есть в globals), ОБЯЗАТЕЛЬНО сделай 2 вызова:
159
- 1) web_search(f"emergency financial assistance {region}")
160
- 2) web_search("how to avoid financial aid scams")
161
- Не вставляй большие куски текста используй только 3–6 коротких тезисов.
162
-
163
- Нужно сформировать triage_text в структуре:
164
- ## Сводка ситуации
165
- ## Допущения (только если явно помечаешь как допущение)
166
- ## Приоритеты
167
- - Сегодня (24–72 часа)
168
- - На неделе
169
- - В течение месяца
170
- ## Риски
171
- ## Какие данные подготовить (безопасно)
172
- ## Вопросы для уточнения (до 8)
 
 
 
 
 
 
173
 
174
  Входные данные:
175
  urgency = \"\"\"{urgency}\"\"\"
176
  region = \"\"\"{region}\"\"\"
177
  case_description = \"\"\"{case_description}\"\"\"
178
  """
179
-
180
- triage_result = triage_agent.run(textwrap.dedent(triage_prompt))
 
 
181
 
182
  # ----------------------------
183
- # Agent 2: ACTIONS
184
  # ----------------------------
185
  actions_agent = CodeAgent(
186
  tools=tools,
187
  model=make_model(ACTIONS_MODEL),
188
  add_base_tools=False,
189
- max_steps=2,
190
  additional_authorized_imports=[],
191
  )
192
 
193
  actions_prompt = f"""
194
- Ты агент ACTIONS (финансовая помощь). Всегда отвечай на РУССКОМ.
195
-
196
- КРИТИЧЕСКИ ВАЖНО: Ты работаешь как CodeAgent.
197
- Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
198
- Ничего вне блока не пиши.
199
-
200
- ОБЯЗАТЕЛЬНО закончить через final_answer(actions_text).
201
 
202
  Правила:
203
- - НЕ выдумывай факты. Опирайся на triage_result и, при необходимости, extract_facts(case_description).
204
- - НЕ проси чувствительные данные: номера карт, CVV, пароли, SMS-коды, номера документов.
205
- - Дай конкретные действия, учитывая срочность, если есть риск визитов коллекторов/эскалации взыскания.
206
-
207
- Если web_search доступен, можно добавить 2–4 уточнённых категории помощи/анти-скам (без длинных вставок).
208
-
209
- Сформируй actions_text (Markdown) с блоками:
210
- ## Действия
211
- ### Срочно (urgent)
212
- - 4–8 пунктов
213
- ### На неделе (short_term)
214
- - 48 пунктов
215
- ### В течение месяца (mid_term)
216
- - 3–6 пунктов
217
-
218
- ## Варианты помощи в регионе
219
- - 6–10 пунктов (категории: соцзащита/муниципалитет/НКО/юристы по долгам/банк/МФО/коммуналка/жильё)
220
-
221
- ## Анти-мошенничество
222
- - 6–10 коротких пунктов, релевантных долгам и “помощи за предоплату”
223
-
224
- ## Уточняющие вопросы (до 8)
225
- - только по делу
226
-
227
- Входные данные:
228
  urgency = \"\"\"{urgency}\"\"\"
229
  region = \"\"\"{region}\"\"\"
230
  case_description = \"\"\"{case_description}\"\"\"
@@ -232,56 +247,52 @@ case_description = \"\"\"{case_description}\"\"\"
232
  triage_result:
233
  {triage_result}
234
  """
235
-
236
- actions_result = actions_agent.run(textwrap.dedent(actions_prompt))
 
 
237
 
238
  # ----------------------------
239
- # Agent 3: WRITER (final)
240
  # ----------------------------
241
  writer_agent = CodeAgent(
242
  tools=tools,
243
  model=make_model(WRITER_MODEL),
244
  add_base_tools=False,
245
- max_steps=2,
246
  additional_authorized_imports=[],
247
  )
248
 
249
  writer_prompt = f"""
250
- Ты агент WRITER (финансовая помощь). Всегда отвечай на РУССКОМ.
251
-
252
- КРИТИЧЕСКИ ВАЖНО: Это финальный шаг.
253
- Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
254
- Ничего вне блока не пиши.
255
-
256
- ОБЯЗАТЕЛЬНО закончить через final_answer(report).
257
 
258
  Правила:
259
  - НЕ выдумывай факты.
260
  - НЕ проси номера карт/CVV/пароли/SMS-коды/номера документов.
261
- - Не делай большой таблицы на 30 дней — только краткий “мини-бюджет” и пример.
262
- - Для кейса “просрочка + коллекторы” приоритеты должны отражать безопасность, деэскалацию, фиксацию коммуникации и легальные шаги.
263
 
264
- Сформируй report (Markdown) строго по структуре:
 
 
265
 
266
  # План финансовой помощи
267
  ## Важно
268
- - не юр/фин совет
269
- - безопасность: не сообщать коды/пароли/CVV/SMS/номера документов
270
  ## Сводка ситуации
271
  ## Приоритеты
272
  ### Сегодня (24–72 часа)
273
  ### На неделе
274
  ### В течение месяца
275
  ## Пошаговый план
276
- (используй actions_result)
277
  ## Варианты помощи в регионе
278
  ## Мини-бюджет на 30 дней
279
- - 5–8 правил + 1 короткий пример расчёта (без таблицы на 30 строк)
280
  ## Анти-мошенничество
281
  ## Что подготовить
282
  ## Вопросы для уточнения
283
 
284
- Входные данные:
 
 
285
  urgency = \"\"\"{urgency}\"\"\"
286
  region = \"\"\"{region}\"\"\"
287
  case_description = \"\"\"{case_description}\"\"\"
@@ -292,9 +303,11 @@ triage_result:
292
  actions_result:
293
  {actions_result}
294
  """
295
-
296
- final_report = writer_agent.run(textwrap.dedent(writer_prompt))
297
- return str(final_report).strip() if final_report is not None else ""
 
 
298
 
299
 
300
  with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
@@ -308,11 +321,7 @@ with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
308
  f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{TIMEOUT}`"
309
  )
310
 
311
- region = gr.Textbox(
312
- label="Регион",
313
- placeholder="Например: Россия / Нидерланды / Польша",
314
- lines=1,
315
- )
316
  urgency = gr.Dropdown(
317
  ["срочно (24–72 часа)", "в течение недели", "не срочно (в течение месяца)"],
318
  value="в течение недели",
@@ -321,12 +330,9 @@ with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
321
  case_description = gr.Textbox(
322
  label="Описание ситуации",
323
  lines=8,
324
- placeholder="Например: Просрочил платеж, коллекторы стучатся в дверь. Сумма долга примерно ...",
325
- )
326
- allow_internet = gr.Checkbox(
327
- label="Разрешить агенту поиск в интернете (web_search)",
328
- value=False,
329
  )
 
330
 
331
  run_btn = gr.Button("Сформировать план помощи (мультиагент)")
332
  output = gr.Textbox(label="Результат (Markdown)", lines=26)
 
2
  import re
3
  import html as ihtml
4
  import textwrap
5
+ import warnings
6
  import requests
7
  import gradio as gr
8
 
9
  from smolagents import CodeAgent, LiteLLMModel, tool
10
 
11
+ # Убираем шумные предупреждения pydantic в логах (не критично для работы)
12
+ warnings.filterwarnings("ignore", category=UserWarning, module="pydantic")
13
+
14
 
15
  # ----------------------------
16
  # Config (CPU-friendly defaults)
 
28
 
29
 
30
  def make_model(model_name: str) -> LiteLLMModel:
31
+ # flatten_messages_as_text=False помогает избежать ошибок формата messages
32
  return LiteLLMModel(
33
  model_id=f"ollama_chat/{model_name}",
34
  api_base=OLLAMA_BASE,
 
54
 
55
  @tool
56
  def web_search(query: str) -> str:
57
+ """Поиск (DuckDuckGo HTML) -> очищенный текст.
58
 
59
  Args:
60
  query: Текстовый поисковый запрос.
61
 
62
  Returns:
63
+ Очищенный текст (до ~4500 символов).
64
  """
65
  resp = requests.get(
66
  "https://duckduckgo.com/html/",
 
80
  case_description: Текст пользователя.
81
 
82
  Returns:
83
+ Факты + что уточнить (безопасно).
84
  """
85
  text = (case_description or "").strip()
86
  low = text.lower()
87
 
 
88
  flags = []
89
  if re.search(r"\b(просроч|просрочка|пропустил платеж)\b", low):
90
  flags.append("просрочка/пропуск платежа")
 
94
  flags.append("ипотека")
95
  if re.search(r"\b(кредит|займ|мфо)\b", low):
96
  flags.append("кредит/займ/МФО")
 
 
97
 
 
98
  nums = re.findall(r"\d[\d\s]{2,}", text)
99
  nums = [re.sub(r"\s+", "", n) for n in nums][:8]
100
 
101
+ missing = [
102
+ "Сколько дней/месяцев просрочка?",
103
+ "Какая сумма просрочки/долга? (можно диапазоном)",
104
+ "Кто кредитор/банк/МФО? (без номеров договоров)",
105
+ "Есть ли письма/суды/исполнительное производство?",
106
+ "Какой официальный доход/обязательные расходы? (диапазоны)",
107
+ ]
 
 
108
 
109
  return (
110
  "ФАКТЫ ИЗ ОПИСАНИЯ (без домыслов):\n"
111
  f"- Исходный текст: {text}\n"
112
+ f"- Признаки: {', '.join(flags) if flags else 'не выявлено'}\n"
113
+ f"- Числа: {', '.join(nums) if nums else 'нет'}\n"
114
+ f"- Что уточнить (безопасно): {'; '.join(missing)}\n"
115
  "Правило: нельзя добавлять факты, которых нет в исходном тексте."
116
  )
117
 
118
 
119
+ # ВАЖНО: в вашей версии smolagents CodeAgent парсит код из <code>...</code>.
120
+ # Поэтому требуем именно такой формат, и завершаем final_answer(...).
121
+ CODE_FORMAT_RULES = """
122
+ КРИТИЧЕСКИ ВАЖНО: это CodeAgent.
123
+ Ты обязан вернуть ТОЛЬКО Python-код внутри тегов:
124
+
125
+ <code>
126
+ # python code
127
+ </code>
128
+
129
+ Никакого текста до или после <code>...</code>.
130
+ В конце обязательно вызови final_answer(...) и больше ничего.
131
+ Если в предыдущей попытке ты не вернул <code>...</code>, ты обязан исправиться и вернуть только код.
132
+ """
133
+
134
+
135
+ def _friendly_agent_error(where: str, err: Exception) -> str:
136
+ return (
137
+ f"### Ошибка на шаге: {where}\n\n"
138
+ "Модель не вернула корректный блок кода для CodeAgent (или код не распарсился).\n\n"
139
+ "Что помогает:\n"
140
+ "- Поставить модель поменьше: `qwen2.5-coder:1.5b`\n"
141
+ "- Уменьшить `MAX_TOKENS` до 768\n"
142
+ "- Оставить `NUM_CTX` 2048–4096\n\n"
143
+ "Текст ошибки:\n"
144
+ f"```text\n{repr(err)}\n```"
145
+ )
146
+
147
+
148
  def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, allow_internet: bool):
149
  case_description = (case_description or "").strip()
150
  region = (region or "").strip()
 
153
  if not case_description:
154
  return "Пожалуйста, заполните поле «Описание ситуации»."
155
 
 
156
  tools = [extract_facts] + ([web_search] if allow_internet else [])
157
 
158
  # ----------------------------
159
+ # TRIAGE
160
  # ----------------------------
161
  triage_agent = CodeAgent(
162
  tools=tools,
163
  model=make_model(TRIAGE_MODEL),
164
  add_base_tools=False,
165
+ max_steps=5, # даём шанс исправиться, если 1 раз не соблюдёт <code>
166
  additional_authorized_imports=[],
167
  )
168
 
169
  triage_prompt = f"""
170
+ Ты агент TRIAGE (финансовая помощь). Всегда по-русски.
171
+ {CODE_FORMAT_RULES}
 
 
 
 
 
172
 
173
  Правила:
174
  - НЕ выдумывай факты. Используй только входные данные и extract_facts().
175
  - НЕ предполагай взлом/мошенничество/кражу данных, если пользователь этого не писал.
176
+ - НЕ проси номера карт/CVV/пароли/SMS-коды/номера документов.
 
177
 
178
+ Если web_search доступен (проверь: 'web_search' in globals()):
179
+ - сделай РОВНО 2 вызова:
180
+ s1 = web_search(f"emergency financial assistance {region}")
181
+ s2 = web_search("how to avoid financial aid scams")
182
+ Используй только 3–6 коротких тезисов из s1/s2 (не вставляй большие куски).
183
+
184
+ В коде обязательно:
185
+ 1) facts = extract_facts(case_description)
186
+ 2) has_ws = ('web_search' in globals())
187
+ 3) if has_ws: вызвать s1/s2 как выше, иначе s1=s2=""
188
+ 4) сформировать triage_text (Markdown) со структурой:
189
+ ## Сводка ситуации
190
+ ## Допущения (если есть)
191
+ ## Приоритеты
192
+ - Сегодня (24–72 часа)
193
+ - На неделе
194
+ - В течение месяца
195
+ ## Риски
196
+ ## Какие данные подготовить (безопасно)
197
+ ## Вопросы (до 8)
198
+ 5) final_answer(triage_text)
199
 
200
  Входные данные:
201
  urgency = \"\"\"{urgency}\"\"\"
202
  region = \"\"\"{region}\"\"\"
203
  case_description = \"\"\"{case_description}\"\"\"
204
  """
205
+ try:
206
+ triage_result = triage_agent.run(textwrap.dedent(triage_prompt))
207
+ except Exception as e:
208
+ return _friendly_agent_error("TRIAGE", e)
209
 
210
  # ----------------------------
211
+ # ACTIONS
212
  # ----------------------------
213
  actions_agent = CodeAgent(
214
  tools=tools,
215
  model=make_model(ACTIONS_MODEL),
216
  add_base_tools=False,
217
+ max_steps=5,
218
  additional_authorized_imports=[],
219
  )
220
 
221
  actions_prompt = f"""
222
+ Ты агент ACTIONS (финансовая помощь). Всегда по-русски.
223
+ {CODE_FORMAT_RULES}
 
 
 
 
 
224
 
225
  Правила:
226
+ - НЕ выдумывай факты.
227
+ - НЕ проси номера карт/CVV/пароли/SMS-коды/номера документов.
228
+ - Если кейс про просрочку/коллекторов: приоритет деэскалация, фиксация, законные шаги, переговоры с кредитором.
229
+
230
+ В коде обязательно:
231
+ 1) facts = extract_facts(case_description)
232
+ 2) сформировать actions_text (Markdown) с блоками:
233
+ ## Действия
234
+ ### Срочно (urgent) (4–8 пунктов)
235
+ ### На неделе (short_term) (4–8 пунктов)
236
+ ### В течение месяца (mid_term) (3–6 пунктов)
237
+ ## Варианты помощи в регионе (610 категорий)
238
+ ## Анти-мошенничество (6–10 релевантных пунктов для “помощь за предоплату/долги”)
239
+ ## Уточняющие вопросы (до 8)
240
+ 3) final_answer(actions_text)
241
+
242
+ Вход:
 
 
 
 
 
 
 
 
243
  urgency = \"\"\"{urgency}\"\"\"
244
  region = \"\"\"{region}\"\"\"
245
  case_description = \"\"\"{case_description}\"\"\"
 
247
  triage_result:
248
  {triage_result}
249
  """
250
+ try:
251
+ actions_result = actions_agent.run(textwrap.dedent(actions_prompt))
252
+ except Exception as e:
253
+ return _friendly_agent_error("ACTIONS", e)
254
 
255
  # ----------------------------
256
+ # WRITER (FINAL)
257
  # ----------------------------
258
  writer_agent = CodeAgent(
259
  tools=tools,
260
  model=make_model(WRITER_MODEL),
261
  add_base_tools=False,
262
+ max_steps=5,
263
  additional_authorized_imports=[],
264
  )
265
 
266
  writer_prompt = f"""
267
+ Ты агент WRITER (финансовая помощь). Всегда по-русски.
268
+ {CODE_FORMAT_RULES}
 
 
 
 
 
269
 
270
  Правила:
271
  - НЕ выдумывай факты.
272
  - НЕ проси номера карт/CVV/пароли/SMS-коды/номера документов.
273
+ - Не делай таблицу на 30 строк — только краткий мини-бюджет и пример.
 
274
 
275
+ В коде обязательно:
276
+ 1) facts = extract_facts(case_description)
277
+ 2) собрать report (Markdown) по структуре:
278
 
279
  # План финансовой помощи
280
  ## Важно
 
 
281
  ## Сводка ситуации
282
  ## Приоритеты
283
  ### Сегодня (24–72 часа)
284
  ### На неделе
285
  ### В течение месяца
286
  ## Пошаговый план
 
287
  ## Варианты помощи в регионе
288
  ## Мини-бюджет на 30 дней
 
289
  ## Анти-мошенничество
290
  ## Что подготовить
291
  ## Вопросы для уточнения
292
 
293
+ 3) final_answer(report)
294
+
295
+ Вход:
296
  urgency = \"\"\"{urgency}\"\"\"
297
  region = \"\"\"{region}\"\"\"
298
  case_description = \"\"\"{case_description}\"\"\"
 
303
  actions_result:
304
  {actions_result}
305
  """
306
+ try:
307
+ final_report = writer_agent.run(textwrap.dedent(writer_prompt))
308
+ return str(final_report).strip() if final_report is not None else ""
309
+ except Exception as e:
310
+ return _friendly_agent_error("WRITER", e)
311
 
312
 
313
  with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
 
321
  f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{TIMEOUT}`"
322
  )
323
 
324
+ region = gr.Textbox(label="Регион", lines=1, placeholder="Например: Россия")
 
 
 
 
325
  urgency = gr.Dropdown(
326
  ["срочно (24–72 часа)", "в течение недели", "не срочно (в течение месяца)"],
327
  value="в течение недели",
 
330
  case_description = gr.Textbox(
331
  label="Описание ситуации",
332
  lines=8,
333
+ placeholder="Например: Просрочил платеж по ипотеке, коллекторы стучатся в дверь. Сумма ...",
 
 
 
 
334
  )
335
+ allow_internet = gr.Checkbox(label="Разрешить web_search (интернет)", value=False)
336
 
337
  run_btn = gr.Button("Сформировать план помощи (мультиагент)")
338
  output = gr.Textbox(label="Результат (Markdown)", lines=26)