Nikolay Ponomarev commited on
Commit
b5c2355
·
1 Parent(s): c752070

inance help

Browse files
Files changed (1) hide show
  1. app.py +63 -139
app.py CHANGED
@@ -8,27 +8,20 @@ import gradio as gr
8
 
9
  from smolagents import LiteLLMModel
10
 
11
- # (опционально) меньше шума в логах
12
  warnings.filterwarnings("ignore", category=UserWarning, module="pydantic")
13
 
14
-
15
- # ----------------------------
16
- # Config (через env)
17
- # ----------------------------
18
- MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5-coder:3b")
19
  OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/")
20
 
21
- # CPU-friendly дефолты
22
- NUM_CTX = int(os.getenv("NUM_CTX", "4096"))
23
- MAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024"))
24
  LITELLM_TIMEOUT = int(os.getenv("LITELLM_TIMEOUT", "3600"))
25
 
26
- # single = 1 вызов модели (рекомендуется на CPU)
27
- # multi = 3 вызова модели (triage/actions/writer) — медленнее
28
  PIPELINE = os.getenv("PIPELINE", "single").strip().lower()
29
 
30
 
31
  def make_model():
 
32
  return LiteLLMModel(
33
  model_id=f"ollama_chat/{MODEL_NAME}",
34
  api_base=OLLAMA_BASE,
@@ -47,7 +40,6 @@ def llm_text(model: LiteLLMModel, user_prompt: str, system_prompt: str | None =
47
  messages.append({"role": "user", "content": user_prompt})
48
 
49
  resp = model(messages)
50
- # smolagents модели иногда возвращают объект с .content, иногда уже строку
51
  if hasattr(resp, "content"):
52
  return (resp.content or "").strip()
53
  return str(resp).strip()
@@ -66,8 +58,7 @@ def _strip_html(raw_html: str) -> str:
66
 
67
  def web_search(query: str) -> str:
68
  """
69
- Ищет краткую информацию в интернете по текстовому запросу (DuckDuckGo HTML)
70
- и возвращает очищенный текст (без сырого HTML).
71
 
72
  Args:
73
  query: Текстовый поисковый запрос.
@@ -82,23 +73,23 @@ def web_search(query: str) -> str:
82
  headers={"User-Agent": "financial-aid-navigator-demo"},
83
  )
84
  resp.raise_for_status()
85
- text = _strip_html(resp.text)
86
- return text[:4500]
87
 
88
 
89
  def _friendly_error(e: Exception) -> str:
90
  return (
91
  "### Ошибка\n\n"
92
- "Запрос к Ollama/модели не выполнился.\n\n"
93
- "На CPU чаще всего помогает:\n"
94
- "- Модель поменьше (`qwen2.5-coder:1.5b` или `qwen2.5-coder:3b`)\n"
95
- "- `NUM_CTX=2048..4096`\n"
96
- "- `MAX_TOKENS=512..1024`\n\n"
97
  "Текст ошибки:\n"
98
  f"```text\n{repr(e)}\n```"
99
  )
100
 
101
 
 
 
 
 
 
102
  def build_web_context(region: str, allow_internet: bool) -> str:
103
  if not allow_internet:
104
  return ""
@@ -106,56 +97,49 @@ def build_web_context(region: str, allow_internet: bool) -> str:
106
  q1 = f"emergency financial assistance {region}"
107
  q2 = "how to avoid financial aid scams"
108
 
109
- try:
110
- r1 = web_search(q1)
111
- except Exception as e:
112
- r1 = f"(web_search ошибка по запросу '{q1}': {repr(e)})"
113
 
114
- try:
115
- r2 = web_search(q2)
116
- except Exception as e:
117
- r2 = f"(web_search ошибка по запросу '{q2}': {repr(e)})"
118
 
119
  return (
120
- "## Результаты web_search (для внутреннего использования в ответе)\n"
121
  f"### Поиск 1: {q1}\n{r1}\n\n"
122
  f"### Поиск 2: {q2}\n{r2}\n"
123
  )
124
 
125
 
126
- def run_fin_aid(
127
- case_description: str,
128
- region: str,
129
- urgency: str,
130
- allow_internet: bool,
131
- ):
132
- model = make_model()
133
- web_ctx = build_web_context(region, allow_internet)
134
-
135
- system = (
136
- "Ты помощник по финансовой навигации. Всегда отвечай на РУССКОМ.\n"
137
- "Правила безопасности:\n"
138
- "- НЕ проси и НЕ предлагай вводить чувствительные данные: номера карт, CVV, пароли, коды из SMS, полный паспорт/ID.\n"
139
- "- Можно спрашивать безопасные категории: диапазоны сумм, сроки, тип дохода, примерные расходы.\n"
140
- "- Никаких инвестсоветов. Фокус: кризисная поддержка, бюджет, варианты помощи.\n"
141
- )
 
142
 
143
- if PIPELINE != "multi":
144
  prompt = f"""
145
- Сформируй ГОТОВЫЙ отчёт для человека в Markdown.
146
 
147
  Структура:
148
  # План финансовой помощи
149
  ## Важно
150
- - дисклеймер: не юр/фин совет; при угрозе выселения/насилия/суицида — местные службы
151
- - не сообщать коды, CVV, пароли; осторожно с предоплатами и "мгновенными списаниями"
152
  ## Сводка ситуации
153
  ## Приоритеты
154
  ### Сегодня (24–72 часа)
155
  ### На неделе
156
  ### В течение месяца
157
  ## Варианты помощи в регионе
158
- - 5–10 пунктов (гос/НКО/жильё/коммуналка/долги/соцслужбы). Если точных названий нет — категории.
159
  ## Пошаговый план
160
  ## Мини-бюджет на 30 дней
161
  ## Анти-мошенничество
@@ -169,120 +153,60 @@ def run_fin_aid(
169
 
170
  {web_ctx}
171
  """
172
- try:
173
- return llm_text(model, textwrap.dedent(prompt), system_prompt=system)
174
- except Exception as e:
175
- return _friendly_error(e)
176
-
177
- # ----------------------------
178
- # Multi pipeline (3 вызова модели) — медленнее на CPU
179
- # ----------------------------
180
- triage_prompt = f"""
181
- Ты агент Triage. Сформируй структуру:
182
- - Краткое резюме (2–4 предложения)
183
- - Допущения (список)
184
- - Приоритеты по срочности: сегодня / на неделе / в течение месяца
185
- - Риски (выселение/отключения/штрафы/коллекторы/мошенники/перегрузка)
186
- - Какие данные подготовить (безопасный список)
187
- - Вопросы для уточнения (до 8)
188
 
189
- Срочность: {urgency}
190
- Регион: {region}
191
- Описание:
192
- {case_description}
193
-
194
- {web_ctx}
195
- """
196
- actions_prompt = """
197
- Ты агент Actions.
198
- На основе Triage дай:
199
- 1) Список конкретных действий по 3 корзинам: urgent / short_term / mid_term.
200
- 2) Список вариантов помощи (resources): government/ngo/debt_counseling/housing/utilities/other.
201
- Формат — Markdown, без Python-структур.
202
- """
203
-
204
- writer_prompt = """
205
- Ты агент Writer.
206
- Собери финальный отчёт в Markdown по шаблону:
207
-
208
- # План финансовой помощи
209
- ## Важно
210
- ## Сводка ситуации
211
- ## Приоритеты
212
- ### Сегодня (24–72 часа)
213
- ### На неделе
214
- ### В течение месяца
215
- ## Варианты помощи в регионе
216
- ## Пошаговый план
217
- ## Мини-бюджет на 30 дней
218
- ## Анти-мошенничество
219
- ## Что подготовить
220
- ## Вопросы для уточнения (до 8)
221
-
222
- НЕ проси чувствительные данные.
223
- """
224
-
225
- try:
226
- triage = llm_text(model, textwrap.dedent(triage_prompt), system_prompt=system)
227
- actions = llm_text(model, actions_prompt + "\n\nВход (Triage):\n" + triage, system_prompt=system)
228
- final_report = llm_text(
229
- model,
230
- writer_prompt + "\n\nВход (Triage):\n" + triage + "\n\nВход (Actions):\n" + actions,
231
- system_prompt=system,
232
- )
233
- return final_report
234
  except Exception as e:
 
235
  return _friendly_error(e)
236
 
237
 
238
- with gr.Blocks(title="Financial Aid Navigator (CPU)") as demo:
239
- gr.Markdown("# Financial Aid Navigator (Ollama on CPU)")
 
 
240
  gr.Markdown(
241
  f"- Model: `{MODEL_NAME}`\n"
242
  f"- Ollama: `{OLLAMA_BASE}`\n"
243
- f"- PIPELINE: `{PIPELINE}` (single рекомендуем на CPU)\n"
244
- f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{LITELLM_TIMEOUT}`"
245
  )
246
 
247
- region = gr.Textbox(
248
- label="Регион",
249
- placeholder="Например: Нидерланды, Амстердам / Казахстан, Алматы / Польша, Варшава",
250
- lines=1,
251
- )
 
 
252
  urgency = gr.Dropdown(
253
  ["срочно (24–72 часа)", "в течение недели", "не срочно (в течение месяца)"],
254
  value="в течение недели",
255
  label="Срочность",
256
  )
257
- case_description = gr.Textbox(
258
- label="Описание ситуации",
259
- lines=9,
260
- placeholder=(
261
- "Опишите кратко: что случилось, есть ли долги/просрочки, аренда/ипотека, "
262
- "примерный уровень доходов/расходов (можно диапазонами), что самое срочное."
263
- ),
264
- )
265
- allow_internet = gr.Checkbox(
266
- label="Разрешить поиск в интернете (DuckDuckGo через requests)",
267
- value=False,
268
- )
269
 
270
  run_btn = gr.Button("Сформировать план помощи")
271
  output = gr.Markdown()
272
 
 
273
  run_btn.click(
274
  fn=run_fin_aid,
275
  inputs=[case_description, region, urgency, allow_internet],
276
- outputs=[output],
 
277
  )
278
 
279
- # В вашей версии Gradio concurrency_count не поддерживается оставляем совместимо
280
- demo.queue(max_size=20)
281
-
282
 
283
  def main():
284
- demo.launch(show_error=True)
285
-
 
286
 
287
  if __name__ == "__main__":
288
  main()
 
8
 
9
  from smolagents import LiteLLMModel
10
 
 
11
  warnings.filterwarnings("ignore", category=UserWarning, module="pydantic")
12
 
13
+ MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5-coder:1.5b")
 
 
 
 
14
  OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/")
15
 
16
+ NUM_CTX = int(os.getenv("NUM_CTX", "2048"))
17
+ MAX_TOKENS = int(os.getenv("MAX_TOKENS", "512"))
 
18
  LITELLM_TIMEOUT = int(os.getenv("LITELLM_TIMEOUT", "3600"))
19
 
 
 
20
  PIPELINE = os.getenv("PIPELINE", "single").strip().lower()
21
 
22
 
23
  def make_model():
24
+ # ВАЖНО: фикс “string indices must be integers” на некоторых связках smolagents/litellm
25
  return LiteLLMModel(
26
  model_id=f"ollama_chat/{MODEL_NAME}",
27
  api_base=OLLAMA_BASE,
 
40
  messages.append({"role": "user", "content": user_prompt})
41
 
42
  resp = model(messages)
 
43
  if hasattr(resp, "content"):
44
  return (resp.content or "").strip()
45
  return str(resp).strip()
 
58
 
59
  def web_search(query: str) -> str:
60
  """
61
+ Поиск через DuckDuckGo HTML.
 
62
 
63
  Args:
64
  query: Текстовый поисковый запрос.
 
73
  headers={"User-Agent": "financial-aid-navigator-demo"},
74
  )
75
  resp.raise_for_status()
76
+ return _strip_html(resp.text)[:4500]
 
77
 
78
 
79
  def _friendly_error(e: Exception) -> str:
80
  return (
81
  "### Ошибка\n\n"
82
+ "Бэкенд получил запрос, но он завершился ошибкой.\n\n"
 
 
 
 
83
  "Текст ошибки:\n"
84
  f"```text\n{repr(e)}\n```"
85
  )
86
 
87
 
88
+ def ping():
89
+ print("[ping] clicked", flush=True)
90
+ return "pong ✅ (кнопка работает, бэкенд отвечает)"
91
+
92
+
93
  def build_web_context(region: str, allow_internet: bool) -> str:
94
  if not allow_internet:
95
  return ""
 
97
  q1 = f"emergency financial assistance {region}"
98
  q2 = "how to avoid financial aid scams"
99
 
100
+ print(f"[web] search 1: {q1}", flush=True)
101
+ r1 = web_search(q1)
 
 
102
 
103
+ print(f"[web] search 2: {q2}", flush=True)
104
+ r2 = web_search(q2)
 
 
105
 
106
  return (
107
+ "## Результаты web_search (внутренний контекст)\n"
108
  f"### Поиск 1: {q1}\n{r1}\n\n"
109
  f"### Поиск 2: {q2}\n{r2}\n"
110
  )
111
 
112
 
113
+ def run_fin_aid(case_description: str, region: str, urgency: str, allow_internet: bool):
114
+ # ЭТО ДОЛЖНО ПЕЧАТАТЬСЯ СРАЗУ ПРИ КЛИКЕ
115
+ print("[run] button clicked", flush=True)
116
+ print(f"[run] region={region!r} urgency={urgency!r} internet={allow_internet}", flush=True)
117
+
118
+ try:
119
+ model = make_model()
120
+ system = (
121
+ "Ты помощник по финансовой навигации. Всегда отвечай на РУССКОМ.\n"
122
+ "Безопасность:\n"
123
+ "- НЕ проси номера карт, CVV, пароли, SMS-коды, полный паспорт/ID.\n"
124
+ "- Можно спрашивать безопасные категории: диапазоны сумм, сроки, статус аренды.\n"
125
+ "- Никаких инвестсоветов. Фокус: кризисная поддержка, бюджет, варианты помощи.\n"
126
+ )
127
+
128
+ web_ctx = build_web_context(region, allow_internet)
129
+ print("[run] web_ctx ready", flush=True)
130
 
 
131
  prompt = f"""
132
+ Сформируй ГОТОВЫЙ отчёт в Markdown.
133
 
134
  Структура:
135
  # План финансовой помощи
136
  ## Важно
 
 
137
  ## Сводка ситуации
138
  ## Приоритеты
139
  ### Сегодня (24–72 часа)
140
  ### На неделе
141
  ### В течение месяца
142
  ## Варианты помощи в регионе
 
143
  ## Пошаговый план
144
  ## Мини-бюджет на 30 дней
145
  ## Анти-мошенничество
 
153
 
154
  {web_ctx}
155
  """
156
+ print("[run] calling model...", flush=True)
157
+ out = llm_text(model, textwrap.dedent(prompt), system_prompt=system)
158
+ print("[run] model returned", flush=True)
159
+ return out
 
 
 
 
 
 
 
 
 
 
 
 
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  except Exception as e:
162
+ print("[run] ERROR:", repr(e), flush=True)
163
  return _friendly_error(e)
164
 
165
 
166
+ print("[boot] app.py loaded", flush=True)
167
+
168
+ with gr.Blocks(title="Financial Aid Navigator (CPU debug)") as demo:
169
+ gr.Markdown("# Financial Aid Navigator (CPU debug)")
170
  gr.Markdown(
171
  f"- Model: `{MODEL_NAME}`\n"
172
  f"- Ollama: `{OLLAMA_BASE}`\n"
173
+ f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{LITELLM_TIMEOUT}`\n"
174
+ f"- PIPELINE: `{PIPELINE}`"
175
  )
176
 
177
+ with gr.Row():
178
+ ping_btn = gr.Button("Ping (проверка клика)")
179
+ ping_out = gr.Markdown()
180
+
181
+ ping_btn.click(fn=ping, inputs=None, outputs=ping_out, queue=False)
182
+
183
+ region = gr.Textbox(label="Регион", lines=1)
184
  urgency = gr.Dropdown(
185
  ["срочно (24–72 часа)", "в течение недели", "не срочно (в течение месяца)"],
186
  value="в течение недели",
187
  label="Срочность",
188
  )
189
+ case_description = gr.Textbox(label="Описание ситуации", lines=8)
190
+ allow_internet = gr.Checkbox(label="Разрешить поиск в интернете", value=False)
 
 
 
 
 
 
 
 
 
 
191
 
192
  run_btn = gr.Button("Сформировать план помощи")
193
  output = gr.Markdown()
194
 
195
+ # queue=False чтобы исключить зависания очереди на вашей версии gradio
196
  run_btn.click(
197
  fn=run_fin_aid,
198
  inputs=[case_description, region, urgency, allow_internet],
199
+ outputs=output,
200
+ queue=False,
201
  )
202
 
203
+ # очередь можно включить позже, когда всё заработает
204
+ # demo.queue(max_size=20)
 
205
 
206
  def main():
207
+ server_name = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
208
+ server_port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
209
+ demo.launch(server_name=server_name, server_port=server_port, show_error=True)
210
 
211
  if __name__ == "__main__":
212
  main()