dish0nest2 commited on
Commit
5fa110b
·
1 Parent(s): 7ace3bf
Files changed (1) hide show
  1. app.py +149 -114
app.py CHANGED
@@ -9,25 +9,22 @@ from smolagents import CodeAgent, LiteLLMModel, tool
9
 
10
 
11
  # ----------------------------
12
- # CPU-friendly defaults
13
  # ----------------------------
14
  OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/")
15
 
16
- # Одна модель на всё (можно переопределить в Space Variables)
17
  MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5-coder:3b")
18
-
19
- # Можно отдельно на роли (опционально):
20
  TRIAGE_MODEL = os.getenv("TRIAGE_MODEL", MODEL_NAME)
21
  ACTIONS_MODEL = os.getenv("ACTIONS_MODEL", MODEL_NAME)
22
  WRITER_MODEL = os.getenv("WRITER_MODEL", MODEL_NAME)
23
 
24
- # На CPU большие значения = долго/таймауты
25
  NUM_CTX = int(os.getenv("NUM_CTX", "4096"))
26
  MAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024"))
27
  TIMEOUT = int(os.getenv("LITELLM_TIMEOUT", "3600"))
28
 
29
 
30
  def make_model(model_name: str) -> LiteLLMModel:
 
31
  return LiteLLMModel(
32
  model_id=f"ollama_chat/{model_name}",
33
  api_base=OLLAMA_BASE,
@@ -35,13 +32,12 @@ def make_model(model_name: str) -> LiteLLMModel:
35
  temperature=0.2,
36
  max_tokens=MAX_TOKENS,
37
  timeout=TIMEOUT,
38
- # помогает избежать некоторых проблем формата messages в связке smolagents+litellm+ollama
39
  flatten_messages_as_text=False,
40
  )
41
 
42
 
43
  def _strip_html(raw_html: str) -> str:
44
- """Грубая очистка HTML -> текст, чтобы агент не тащил HTML."""
45
  raw_html = ihtml.unescape(raw_html)
46
  raw_html = re.sub(r"(?is)<(script|style).*?>.*?</\1>", " ", raw_html)
47
  raw_html = re.sub(r"(?is)<br\s*/?>", "\n", raw_html)
@@ -54,13 +50,13 @@ def _strip_html(raw_html: str) -> str:
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/",
@@ -72,6 +68,56 @@ def web_search(query: str) -> str:
72
  return _strip_html(resp.text)[:4500]
73
 
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, allow_internet: bool):
76
  case_description = (case_description or "").strip()
77
  region = (region or "").strip()
@@ -80,71 +126,43 @@ def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, al
80
  if not case_description:
81
  return "Пожалуйста, заполните поле «Описание ситуации»."
82
 
83
- tools = [web_search] if allow_internet else []
84
-
85
- if allow_internet:
86
- web_hint_triage = """
87
- Интернет доступен ТОЛЬКО через инструмент web_search(query: str).
88
- В сгенерированном коде НЕ используй import requests и другие сетевые библиотеки.
89
-
90
- ОБЯЗАТЕЛЬНО: минимум 2 вызова web_search:
91
- 1) "emergency financial assistance <регион>" или "rent assistance <регион>";
92
- 2) "how to avoid financial aid scams" или "debt relief scams warning".
93
-
94
- Не вставляй сырой HTML — только вывод web_search.
95
- """
96
- web_hint_actions = """
97
- Интернет доступен ТОЛЬКО через web_search(query: str).
98
- Используй результаты поиска, чтобы дополнить варианты помощи и анти-мошенничество.
99
- Не вставляй HTML.
100
- """
101
- web_hint_writer = """
102
- Интернет доступен ТОЛЬКО через web_search(query: str).
103
- Можно уточнить типовые варианты помощи и анти-мошенничество, но не вставляй HTML.
104
- """
105
- else:
106
- web_hint_common = """
107
- Интернет недоступен, инструмент web_search отсутствует.
108
- Не пытайся вызывать web_search.
109
- """
110
- web_hint_triage = web_hint_common
111
- web_hint_actions = web_hint_common
112
- web_hint_writer = web_hint_common
113
 
114
  # ----------------------------
115
- # Agent 1: Triage
116
  # ----------------------------
117
- triage = CodeAgent(
118
  tools=tools,
119
  model=make_model(TRIAGE_MODEL),
120
  add_base_tools=False,
121
- max_steps=3, # на CPU лучше меньше
122
  additional_authorized_imports=[],
123
  )
124
 
125
  triage_prompt = f"""
126
- Ты агент TRIAGE (финансовая помощь).
127
- Всегда отвечай на РУССКОМ языке.
128
- {web_hint_triage}
129
 
130
  КРИТИЧЕСКИ ВАЖНО: Ты работаешь как CodeAgent.
131
  Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
132
  Ничего вне блока не пиши.
133
 
134
- Шаблон вывода (обязательно):
135
- <code>
136
- triage_text = \"\"\"...\"\"\"
137
- triage_text
138
- </code>
 
 
139
 
140
- Правила безопасности:
141
- - НЕ проси и НЕ предлагай вводить чувствительные данные: номера карт, CVV, пароли, коды из SMS, номера документов.
142
- - НЕ придумывай факты (взлом/мошенничество/кражу данных), если пользователь этого не упомянул.
143
- - Можно просить безопасные категории: диапазоны сумм, сроки, тип дохода, примерные расходы.
144
 
145
- Сформируй triage_text со структурой:
146
  ## Сводка ситуации
147
- ## Допущения (если нужно — явно пометь как допущения)
148
  ## Приоритеты
149
  - Сегодня (24–72 часа)
150
  - На неделе
@@ -153,56 +171,74 @@ triage_text
153
  ## Какие данные подготовить (безопасно)
154
  ## Вопросы для уточнения (до 8)
155
 
156
- Срочность: {urgency}
157
- Регион: {region}
158
- Описание ситуации:
159
- {case_description}
160
  """
161
- triage_result = triage.run(textwrap.dedent(triage_prompt))
 
162
 
163
  # ----------------------------
164
- # Agent 2: Actions
165
  # ----------------------------
166
  actions_agent = CodeAgent(
167
  tools=tools,
168
  model=make_model(ACTIONS_MODEL),
169
  add_base_tools=False,
170
- max_steps=3,
171
  additional_authorized_imports=[],
172
  )
173
 
174
  actions_prompt = f"""
175
- Ты агент ACTIONS (финансовая помощь).
176
- Всегда отвечай на РУССКОМ языке.
177
- {web_hint_actions}
178
 
179
  КРИТИЧЕСКИ ВАЖНО: Ты работаешь как CodeAgent.
180
  Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
181
  Ничего вне блока не пиши.
182
 
183
- Шаблон вывода (обязательно):
184
- <code>
185
- actions_text = \"\"\"...\"\"\"
186
- actions_text
187
- </code>
188
-
189
- Требования:
190
- - На основе triage_result сформируй конкретные действия (шаги), сгруппированные по:
191
- urgent / short_term / mid_term
192
- - Составь список "варианты помощи в регионе" (если нет конкретики — категории: соцслужбы/муниципальные программы/НКО/помощь с жильём/коммуналкой/консультации по долгам).
193
- - Добавь блок анти-мошенничества (проверки/красные флаги).
194
- - НЕ выдумывай факты, которых нет во входе.
195
- - НЕ проси чувствительные данные.
196
-
197
- Вход (Triage):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  {triage_result}
199
  """
 
200
  actions_result = actions_agent.run(textwrap.dedent(actions_prompt))
201
 
202
  # ----------------------------
203
- # Agent 3: Writer (final markdown)
204
  # ----------------------------
205
- writer = CodeAgent(
206
  tools=tools,
207
  model=make_model(WRITER_MODEL),
208
  add_base_tools=False,
@@ -211,57 +247,58 @@ actions_text
211
  )
212
 
213
  writer_prompt = f"""
214
- Ты агент WRITER (финансовая помощь).
215
- Всегда отвечай на РУССКОМ языке.
216
- {web_hint_writer}
217
 
218
- КРИТИЧЕСКИ ВАЖНО: Это ФИНАЛЬНЫЙ шаг.
219
  Ты ДОЛЖЕН вернуть ТОЛЬКО Python-код внутри блока <code>...</code>.
220
  Ничего вне блока не пиши.
221
 
222
- Шаблон вывода (обязательно):
223
- <code>
224
- report = \"\"\"...\"\"\"
225
- report
226
- </code>
227
 
228
- Сделай читабельный Markdown-отчёт, используя triage и actions.
229
- Структура отчёта:
 
 
 
 
 
230
 
231
  # План финансовой помощи
232
  ## Важно
233
- - дисклеймер: не юр/фин совет
234
- - не сообщать коды, CVV, пароли, SMS-коды, номера документов
235
  ## Сводка ситуации
236
  ## Приоритеты
237
  ### Сегодня (24–72 часа)
238
  ### На неделе
239
  ### В течение месяца
240
- ## Варианты помощи в регионе
241
  ## Пошаговый план
242
- ## Мини-бюджет на 30 дней (без большой таблицы; только подход и пример)
 
 
 
243
  ## Анти-мошенничество
244
  ## Что подготовить
245
  ## Вопросы для уточнения
246
 
247
- Важные требования:
248
- - НЕ придумывай события (например взлом), если это не сказано пользователем.
249
- - Если не хватает данных — прямо напиши "не указано" и вынеси в вопросы.
250
- - НЕ вставляй Python-структуры dict/list в Markdown.
251
 
252
- Вход (Triage):
253
  {triage_result}
254
 
255
- Вход (Actions):
256
  {actions_result}
257
  """
258
- result = writer.run(textwrap.dedent(writer_prompt))
259
- final_markdown = str(result).strip() if result is not None else ""
260
- return final_markdown
261
 
262
 
263
  with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
264
- gr.Markdown("# Financial Aid Navigator (Multi-agent, CodeAgent + Ollama)")
265
  gr.Markdown(
266
  f"- Ollama: `{OLLAMA_BASE}`\n"
267
  f"- MODEL_NAME: `{MODEL_NAME}`\n"
@@ -283,10 +320,8 @@ with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
283
  )
284
  case_description = gr.Textbox(
285
  label="Описание ситуации",
286
- lines=9,
287
- placeholder=(
288
- "Опишите: доходы/расходы (можно диапазонами), долги/просрочки, аренда/ипотека, что самое срочное."
289
- ),
290
  )
291
  allow_internet = gr.Checkbox(
292
  label="Разрешить агенту поиск в интернете (web_search)",
@@ -294,13 +329,13 @@ with gr.Blocks(title="Financial Aid Navigator (CodeAgent + Ollama)") as demo:
294
  )
295
 
296
  run_btn = gr.Button("Сформировать план помощи (мультиагент)")
297
- output = gr.Textbox(label="План (Markdown)", lines=26)
298
 
299
  run_btn.click(
300
  fn=run_fin_aid_multi_agent,
301
  inputs=[case_description, region, urgency, allow_internet],
302
  outputs=[output],
303
- queue=False, # совместимо со старыми gradio
304
  )
305
 
306
 
 
9
 
10
 
11
  # ----------------------------
12
+ # Config (CPU-friendly defaults)
13
  # ----------------------------
14
  OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/")
15
 
 
16
  MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5-coder:3b")
 
 
17
  TRIAGE_MODEL = os.getenv("TRIAGE_MODEL", MODEL_NAME)
18
  ACTIONS_MODEL = os.getenv("ACTIONS_MODEL", MODEL_NAME)
19
  WRITER_MODEL = os.getenv("WRITER_MODEL", MODEL_NAME)
20
 
 
21
  NUM_CTX = int(os.getenv("NUM_CTX", "4096"))
22
  MAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024"))
23
  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,
 
32
  temperature=0.2,
33
  max_tokens=MAX_TOKENS,
34
  timeout=TIMEOUT,
 
35
  flatten_messages_as_text=False,
36
  )
37
 
38
 
39
  def _strip_html(raw_html: str) -> str:
40
+ """Грубая очистка HTML -> текст."""
41
  raw_html = ihtml.unescape(raw_html)
42
  raw_html = re.sub(r"(?is)<(script|style).*?>.*?</\1>", " ", raw_html)
43
  raw_html = re.sub(r"(?is)<br\s*/?>", "\n", raw_html)
 
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/",
 
68
  return _strip_html(resp.text)[:4500]
69
 
70
 
71
+ @tool
72
+ def extract_facts(case_description: str) -> str:
73
+ """Извлекает факты из описания ситуации без домыслов.
74
+
75
+ Args:
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("просрочка/пропуск платежа")
88
+ if re.search(r"\b(коллектор|взыскател)\b", low):
89
+ flags.append("контакт/визит коллекторов")
90
+ if re.search(r"\b(ипотек)\b", low):
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
  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
  - На неделе
 
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
+ - 4–8 пунктов
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}\"\"\"
231
+
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,
 
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}\"\"\"
288
 
289
+ triage_result:
290
  {triage_result}
291
 
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:
301
+ gr.Markdown("# Financial Aid Navigator Multi-agent (CodeAgent + Ollama)")
302
  gr.Markdown(
303
  f"- Ollama: `{OLLAMA_BASE}`\n"
304
  f"- MODEL_NAME: `{MODEL_NAME}`\n"
 
320
  )
321
  case_description = gr.Textbox(
322
  label="Описание ситуации",
323
+ lines=8,
324
+ placeholder="Например: Просрочил платеж, коллекторы стучатся в дверь. Сумма долга примерно ...",
 
 
325
  )
326
  allow_internet = gr.Checkbox(
327
  label="Разрешить агенту поиск в интернете (web_search)",
 
329
  )
330
 
331
  run_btn = gr.Button("Сформировать план помощи (мультиагент)")
332
+ output = gr.Textbox(label="Результат (Markdown)", lines=26)
333
 
334
  run_btn.click(
335
  fn=run_fin_aid_multi_agent,
336
  inputs=[case_description, region, urgency, allow_internet],
337
  outputs=[output],
338
+ queue=False,
339
  )
340
 
341