Nikolay Ponomarev commited on
Commit
5183c26
·
1 Parent(s): bf83283

inance help

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -7
  2. app.py +104 -150
  3. start.sh +1 -1
Dockerfile CHANGED
@@ -19,15 +19,10 @@ RUN chmod +x /app/start.sh
19
  # Ollama server
20
  ENV OLLAMA_HOST=0.0.0.0:11434
21
 
22
- # CPU-friendly: маленький контекст (по умолчанию в Ollama 4096) :contentReference[oaicite:4]{index=4}
23
  ENV OLLAMA_CONTEXT_LENGTH=4096
24
-
25
- # CPU-friendly: не раздувать параллелизм и память :contentReference[oaicite:5]{index=5}
26
  ENV OLLAMA_NUM_PARALLEL=1
27
  ENV OLLAMA_MAX_LOADED_MODELS=1
28
-
29
- # Можно держать модель в памяти подольше (уменьшает “долгий первый ответ”),
30
- # но это увеличивает расход RAM. Документация/faq упоминают keep_alive как env в некоторых сборках. :contentReference[oaicite:6]{index=6}
31
  ENV OLLAMA_KEEP_ALIVE=10m
32
 
33
  # Gradio on Spaces
@@ -40,6 +35,5 @@ ENV PIPELINE=single
40
  ENV NUM_CTX=4096
41
  ENV MAX_TOKENS=1024
42
  ENV LITELLM_TIMEOUT=3600
43
- ENV AGENT_MAX_STEPS=1
44
 
45
  CMD ["/app/start.sh"]
 
19
  # Ollama server
20
  ENV OLLAMA_HOST=0.0.0.0:11434
21
 
22
+ # CPU-friendly
23
  ENV OLLAMA_CONTEXT_LENGTH=4096
 
 
24
  ENV OLLAMA_NUM_PARALLEL=1
25
  ENV OLLAMA_MAX_LOADED_MODELS=1
 
 
 
26
  ENV OLLAMA_KEEP_ALIVE=10m
27
 
28
  # Gradio on Spaces
 
35
  ENV NUM_CTX=4096
36
  ENV MAX_TOKENS=1024
37
  ENV LITELLM_TIMEOUT=3600
 
38
 
39
  CMD ["/app/start.sh"]
app.py CHANGED
@@ -2,33 +2,29 @@ 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 (через env)
13
  # ----------------------------
14
- # CPU-safe дефолт: небольшая модель из Ollama library
15
- # Можно заменить в Space Variables: MODEL_NAME=qwen2.5-coder:7b или llama3.1:8b (но на CPU будет медленно)
16
  MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5-coder:3b")
17
-
18
  OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/")
19
 
20
- # CPU-safe дефолты: маленький контекст + умеренный вывод
21
  NUM_CTX = int(os.getenv("NUM_CTX", "4096"))
22
  MAX_TOKENS = int(os.getenv("MAX_TOKENS", "1024"))
 
23
 
24
- # На CPU даже 3B иногда может “долго думать”, поэтому таймаут повышаем
25
- LITELLM_TIMEOUT = int(os.getenv("LITELLM_TIMEOUT", "3600")) # секунд
26
-
27
- # На CPU лучше избегать многошаговых агентов
28
- AGENT_MAX_STEPS = int(os.getenv("AGENT_MAX_STEPS", "1"))
29
-
30
- # single = 1 вызов модели (лучше для CPU)
31
- # multi = ваш 3-агентный пайплайн (дольше)
32
  PIPELINE = os.getenv("PIPELINE", "single").strip().lower()
33
 
34
 
@@ -43,8 +39,20 @@ def make_model():
43
  )
44
 
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def _strip_html(raw_html: str) -> str:
47
- """Грубое преобразование HTML -> текст, чтобы агент не тащил сырой HTML в ответ."""
48
  raw_html = ihtml.unescape(raw_html)
49
  raw_html = re.sub(r"(?is)<(script|style).*?>.*?</\1>", " ", raw_html)
50
  raw_html = re.sub(r"(?is)<br\s*/?>", "\n", raw_html)
@@ -55,10 +63,9 @@ def _strip_html(raw_html: str) -> str:
55
  return raw_html.strip()
56
 
57
 
58
- @tool
59
  def web_search(query: str) -> str:
60
  """
61
- Ищет краткую информацию в интернете по текстовому запросу (через DuckDuckGo HTML)
62
  и возвращает очищенный текст (без сырого HTML).
63
 
64
  Args:
@@ -80,83 +87,74 @@ def web_search(query: str) -> str:
80
 
81
  def _friendly_error(e: Exception) -> str:
82
  return (
83
- "### Ошибка при обращении к модели (Ollama)\n\n"
84
- "На CPU большие запросы/модели могут работать очень медленно, и запрос не успевает завершиться.\n\n"
85
- "**Что сделать:**\n"
86
- "- Используйте меньшую модель (например `qwen2.5-coder:3b` или даже `qwen2.5-coder:1.5b`).\n"
87
- "- Держите `NUM_CTX=4096` и `MAX_TOKENS=512..1024`.\n"
88
- "- Оставьте `PIPELINE=single`.\n\n"
89
  "Текст ошибки:\n"
90
  f"```text\n{repr(e)}\n```"
91
  )
92
 
93
 
94
- def _web_hints(allow_internet: bool):
95
- if allow_internet:
96
- triage = """
97
- Интернет доступен ТОЛЬКО через инструмент web_search(query: str).
98
- ОБЯЗАТЕЛЬНО: минимум 2 раза вызвать web_search:
99
- 1) "emergency financial assistance <регион>" или "rent assistance <регион>";
100
- 2) "how to avoid financial aid scams" или "debt relief scams warning".
101
- Не вставляй сырой HTML — только извлечённые выводы.
102
- """
103
- actions = """
104
- Интернет — только через web_search(query: str).
105
- Используй результаты поиска, чтобы дополнить список вариа��тов помощи и добавить предупреждения о мошенничестве.
106
- """
107
- writer = """
108
- Интернет — только через web_search(query: str).
109
- Можно уточнить формулировки и типовые источники помощи, но не вставляй сырой HTML.
110
- """
111
- return triage, actions, writer
112
- else:
113
- common = """
114
- Интернет недоступен, инструмент web_search отсутствует.
115
- Опирайся только на свои знания и общие принципы.
116
- """
117
- return common, common, common
118
 
 
 
119
 
120
- def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, allow_internet: bool):
121
- model = make_model()
122
- tools = [web_search] if allow_internet else []
123
- web_hint_triage, web_hint_actions, web_hint_writer = _web_hints(allow_internet)
124
 
125
- # ----------------------------
126
- # CPU-friendly: single-pass pipeline
127
- # ----------------------------
128
- if PIPELINE != "multi":
129
- agent = CodeAgent(
130
- tools=tools,
131
- model=model,
132
- add_base_tools=False,
133
- max_steps=AGENT_MAX_STEPS,
134
- )
135
 
136
- prompt = f"""
137
- Ты помощник по финансовой навигации.
138
- Всегда отвечай на РУССКОМ языке.
139
- {web_hint_writer}
 
140
 
141
- Сформируй ГОТОВЫЙ ОТЧЁТ ДЛЯ ЧЕЛОВЕКА в формате Markdown.
142
 
143
- Важные правила:
144
- - НЕ проси и НЕ предлагай вводить чувствительные данные: номера карт, CVV, пароли, коды из SMS, полные паспорта/ID.
145
- - Можно спрашивать безопасные категории: диапазоны сумм, статус аренды, сроки просрочек, примерные расходы.
146
- - Фокус: кризисная поддержка, бюджет, варианты помощи. Никаких инвестсоветов.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- Структура отчёта:
149
  # План финансовой помощи
150
  ## Важно
151
- - дисклеймер: не юр/фин совет; при угрозе выселения/насилия/суицида — обращаться в местные службы
152
- - не сообщать коды, CVV, пароли; осторожно с предоплатами и мгновенными списаниями
153
  ## Сводка ситуации
154
  ## Приоритеты
155
  ### Сегодня (24–72 часа)
156
  ### На неделе
157
  ### В течение месяца
158
  ## Варианты помощи в регионе
159
- - если интернет включён — добавь 38 вариантов/категорий по региону (гос/НКО/жильё/коммуналка/долги)
160
  ## Пошаговый план
161
  ## Мини-бюджет на 30 дней
162
  ## Анти-мошенничество
@@ -167,33 +165,22 @@ def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, al
167
  Регион: {region}
168
  Описание ситуации:
169
  {case_description}
 
 
170
  """
171
  try:
172
- out = agent.run(textwrap.dedent(prompt))
173
- return str(out).strip() if out is not None else ""
174
  except Exception as e:
175
  return _friendly_error(e)
176
 
177
  # ----------------------------
178
- # Multi-agent pipeline (ваш исходный подход, но max_steps=1)
179
  # ----------------------------
180
- try:
181
- # Agent 1: Triage
182
- triage = CodeAgent(tools=tools, model=model, add_base_tools=False, max_steps=AGENT_MAX_STEPS)
183
- triage_prompt = f"""
184
- Ты агент Triage.
185
- Всегда отвечай на РУССКОМ языке.
186
- {web_hint_triage}
187
-
188
- Цель: быстро разобрать ситуацию человека и определить приоритеты.
189
- Правила:
190
- - НЕ проси чувствительные данные: номера карт, CVV, пароли, SMS-коды, полный паспорт/ID.
191
- - Можно безопасные категории: диапазоны сумм, тип дохода, примерные расходы, статус аренды.
192
-
193
- Сформируй структуру:
194
  - Краткое резюме (2–4 предложения)
195
  - Допущения (список)
196
- - Приоритеты: "сегодня", "на неделе", "в течение месяца"
197
  - Риски (выселение/отключения/штрафы/коллекторы/мошенники/перегрузка)
198
  - Какие данные подготовить (безопасный список)
199
  - Вопросы для уточнения (до 8)
@@ -202,54 +189,21 @@ def run_fin_aid_multi_agent(case_description: str, region: str, urgency: str, al
202
  Регион: {region}
203
  Описание:
204
  {case_description}
205
- """
206
- triage_result = triage.run(textwrap.dedent(triage_prompt))
207
 
208
- # Agent 2: Actions
209
- actions_agent = CodeAgent(tools=tools, model=model, add_base_tools=False, max_steps=AGENT_MAX_STEPS)
210
- actions_prompt = f"""
211
  Ты агент Actions.
212
- Всегда отвечай на РУССКОМ языке.
213
- {web_hint_actions}
214
-
215
- На основе результата Triage сформируй ПИТОНОВСКИЙ СПИСОК словарей actions (и больше ничего).
216
- Поля:
217
- - "bucket": "urgent" | "short_term" | "mid_term"
218
- - "title"
219
- - "steps" (3–8)
220
- - "expected_outcome"
221
- - "documents"
222
- - "warnings"
223
-
224
- Дополнительно: список resources (тоже питоновский список словарей):
225
- - "type": "government" | "ngo" | "debt_counseling" | "housing" | "utilities" | "other"
226
- - "name"
227
- - "what_it_helps_with"
228
- - "how_to_start"
229
- - "notes"
230
-
231
- Вход (Triage):
232
- {triage_result}
233
  """
234
- actions_struct = actions_agent.run(textwrap.dedent(actions_prompt))
235
 
236
- # Agent 3: Writer
237
- writer = CodeAgent(tools=tools, model=model, add_base_tools=False, max_steps=AGENT_MAX_STEPS)
238
- writer_prompt = f"""
239
  Ты агент Writer.
240
- Всегда отвечай на РУССКОМ языке.
241
- {web_hint_writer}
242
-
243
- Сгенерируй Python-код, который создаёт переменную report (Markdown строка)
244
- и возвращает её как последнее выражение.
245
 
246
- Формат:
247
- report = \"\"\"
248
- ...markdown...
249
- \"\"\"
250
- report
251
-
252
- Структура отчёта:
253
  # План финансовой помощи
254
  ## Важно
255
  ## Сводка ситуации
@@ -262,32 +216,31 @@ report
262
  ## Мини-бюджет на 30 дней
263
  ## Анти-мошенничество
264
  ## Что подготовить
265
- ## Вопросы для уточнения
266
-
267
- ВАЖНО:
268
- - НЕ вставляй Python-структуры dict/list в Markdown — только текст.
269
- - НЕ запрашивай чувствительные данные.
270
-
271
- Вход (Triage):
272
- {triage_result}
273
 
274
- Вход (Actions структуры):
275
- {actions_struct}
276
  """
277
- result = writer.run(textwrap.dedent(writer_prompt))
278
- return str(result).strip() if result is not None else ""
279
 
 
 
 
 
 
 
 
 
 
280
  except Exception as e:
281
  return _friendly_error(e)
282
 
283
 
284
- with gr.Blocks(title="Financial Aid Navigator (CPU-friendly)") as demo:
285
  gr.Markdown("# Financial Aid Navigator (Ollama on CPU)")
286
  gr.Markdown(
287
  f"- Model: `{MODEL_NAME}`\n"
288
  f"- Ollama: `{OLLAMA_BASE}`\n"
289
- f"- PIPELINE: `{PIPELINE}` (single = быстрее на CPU)\n"
290
- f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{LITELLM_TIMEOUT}`\n"
291
  )
292
 
293
  region = gr.Textbox(
@@ -309,7 +262,7 @@ with gr.Blocks(title="Financial Aid Navigator (CPU-friendly)") as demo:
309
  ),
310
  )
311
  allow_internet = gr.Checkbox(
312
- label="Разрешить агенту поиск в интернете (web_search)",
313
  value=False,
314
  )
315
 
@@ -317,11 +270,12 @@ with gr.Blocks(title="Financial Aid Navigator (CPU-friendly)") as demo:
317
  output = gr.Markdown()
318
 
319
  run_btn.click(
320
- fn=run_fin_aid_multi_agent,
321
  inputs=[case_description, region, urgency, allow_internet],
322
  outputs=[output],
323
  )
324
 
 
325
  demo.queue(max_size=20)
326
 
327
 
 
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 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
 
 
39
  )
40
 
41
 
42
+ def llm_text(model: LiteLLMModel, user_prompt: str, system_prompt: str | None = None) -> str:
43
+ messages = []
44
+ if system_prompt:
45
+ messages.append({"role": "system", "content": system_prompt})
46
+ messages.append({"role": "user", "content": user_prompt})
47
+
48
+ resp = model(messages)
49
+ # smolagents модели иногда возвращают объект с .content, иногда уже строку
50
+ if hasattr(resp, "content"):
51
+ return (resp.content or "").strip()
52
+ return str(resp).strip()
53
+
54
+
55
  def _strip_html(raw_html: str) -> str:
 
56
  raw_html = ihtml.unescape(raw_html)
57
  raw_html = re.sub(r"(?is)<(script|style).*?>.*?</\1>", " ", raw_html)
58
  raw_html = re.sub(r"(?is)<br\s*/?>", "\n", raw_html)
 
63
  return raw_html.strip()
64
 
65
 
 
66
  def web_search(query: str) -> str:
67
  """
68
+ Ищет краткую информацию в интернете по текстовому запросу (DuckDuckGo HTML)
69
  и возвращает очищенный текст (без сырого HTML).
70
 
71
  Args:
 
87
 
88
  def _friendly_error(e: Exception) -> str:
89
  return (
90
+ "### Ошибка\n\n"
91
+ "Запрос к Ollama/модели не выполнился.\n\n"
92
+ "На CPU чаще всего помогает:\n"
93
+ "- Модель поменьше (`qwen2.5-coder:1.5b` или `qwen2.5-coder:3b`)\n"
94
+ "- `NUM_CTX=2048..4096`\n"
95
+ "- `MAX_TOKENS=512..1024`\n\n"
96
  "Текст ошибки:\n"
97
  f"```text\n{repr(e)}\n```"
98
  )
99
 
100
 
101
+ def build_web_context(region: str, allow_internet: bool) -> str:
102
+ if not allow_internet:
103
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ q1 = f"emergency financial assistance {region}"
106
+ q2 = "how to avoid financial aid scams"
107
 
108
+ try:
109
+ r1 = web_search(q1)
110
+ except Exception as e:
111
+ r1 = f"(web_search ошибка по запросу '{q1}': {repr(e)})"
112
 
113
+ try:
114
+ r2 = web_search(q2)
115
+ except Exception as e:
116
+ r2 = f"(web_search ошибка по запросу '{q2}': {repr(e)})"
 
 
 
 
 
 
117
 
118
+ return (
119
+ "## Результаты web_search (для внутреннего использования в ответе)\n"
120
+ f"### Поиск 1: {q1}\n{r1}\n\n"
121
+ f"### Поиск 2: {q2}\n{r2}\n"
122
+ )
123
 
 
124
 
125
+ def run_fin_aid(
126
+ case_description: str,
127
+ region: str,
128
+ urgency: str,
129
+ allow_internet: bool,
130
+ ):
131
+ model = make_model()
132
+ web_ctx = build_web_context(region, allow_internet)
133
+
134
+ system = (
135
+ "Ты помощник по финансовой навигации. Всегда отвечай на РУССКОМ.\n"
136
+ "Правила безопасности:\n"
137
+ "- НЕ проси и НЕ предлагай вводить чувствительные данные: номера карт, CVV, пароли, коды из SMS, полный паспорт/ID.\n"
138
+ "- Можно спрашивать безопасные категории: диапазоны сумм, сроки, тип дохода, примерные расходы.\n"
139
+ "- Никаких инвестсоветов. Фокус: кризисная поддержка, бюджет, варианты помощи.\n"
140
+ )
141
+
142
+ if PIPELINE != "multi":
143
+ prompt = f"""
144
+ Сформируй ГОТОВЫЙ отчёт для человека в Markdown.
145
 
146
+ Структура:
147
  # План финансовой помощи
148
  ## Важно
149
+ - дисклеймер: не юр/фин совет; при угрозе выселения/насилия/суицида — местные службы
150
+ - не сообщать коды, CVV, пароли; осторожно с предоплатами и "мгновенными списаниями"
151
  ## Сводка ситуации
152
  ## Приоритеты
153
  ### Сегодня (24–72 часа)
154
  ### На неделе
155
  ### В течение месяца
156
  ## Варианты помощи в регионе
157
+ - 510 пунктов (гос/НКО/жильё/коммуналка/долги/соцслужбы). Если точных названий нет — категории.
158
  ## Пошаговый план
159
  ## Мини-бюджет на 30 дней
160
  ## Анти-мошенничество
 
165
  Регион: {region}
166
  Описание ситуации:
167
  {case_description}
168
+
169
+ {web_ctx}
170
  """
171
  try:
172
+ return llm_text(model, textwrap.dedent(prompt), system_prompt=system)
 
173
  except Exception as e:
174
  return _friendly_error(e)
175
 
176
  # ----------------------------
177
+ # Multi pipeline (3 вызова модели) — медленнее на CPU
178
  # ----------------------------
179
+ triage_prompt = f"""
180
+ Ты агент Triage. Сформируй структуру:
 
 
 
 
 
 
 
 
 
 
 
 
181
  - Краткое резюме (2–4 предложения)
182
  - Допущения (список)
183
+ - Приоритеты по срочности: сегодня / на неделе / в течение месяца
184
  - Риски (выселение/отключения/штрафы/коллекторы/мошенники/перегрузка)
185
  - Какие данные подготовить (безопасный список)
186
  - Вопросы для уточнения (до 8)
 
189
  Регион: {region}
190
  Описание:
191
  {case_description}
 
 
192
 
193
+ {web_ctx}
194
+ """
195
+ actions_prompt = """
196
  Ты агент Actions.
197
+ На основе Triage дай:
198
+ 1) Список конкретных действий по 3 корзинам: urgent / short_term / mid_term.
199
+ 2) Список вариантов помощи (resources): government/ngo/debt_counseling/housing/utilities/other.
200
+ Формат Markdown, без Python-структур.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  """
 
202
 
203
+ writer_prompt = """
 
 
204
  Ты агент Writer.
205
+ Собери финальный отчёт в Markdown по шаблону:
 
 
 
 
206
 
 
 
 
 
 
 
 
207
  # План финансовой помощи
208
  ## Важно
209
  ## Сводка ситуации
 
216
  ## Мини-бюджет на 30 дней
217
  ## Анти-мошенничество
218
  ## Что подготовить
219
+ ## Вопросы для уточнения (до 8)
 
 
 
 
 
 
 
220
 
221
+ НЕ проси чувствительные данные.
 
222
  """
 
 
223
 
224
+ try:
225
+ triage = llm_text(model, textwrap.dedent(triage_prompt), system_prompt=system)
226
+ actions = llm_text(model, actions_prompt + "\n\nВход (Triage):\n" + triage, system_prompt=system)
227
+ final_report = llm_text(
228
+ model,
229
+ writer_prompt + "\n\nВход (Triage):\n" + triage + "\n\nВход (Actions):\n" + actions,
230
+ system_prompt=system,
231
+ )
232
+ return final_report
233
  except Exception as e:
234
  return _friendly_error(e)
235
 
236
 
237
+ with gr.Blocks(title="Financial Aid Navigator (CPU)") as demo:
238
  gr.Markdown("# Financial Aid Navigator (Ollama on CPU)")
239
  gr.Markdown(
240
  f"- Model: `{MODEL_NAME}`\n"
241
  f"- Ollama: `{OLLAMA_BASE}`\n"
242
+ f"- PIPELINE: `{PIPELINE}` (single рекомендуем на CPU)\n"
243
+ f"- NUM_CTX: `{NUM_CTX}`, MAX_TOKENS: `{MAX_TOKENS}`, TIMEOUT: `{LITELLM_TIMEOUT}`"
244
  )
245
 
246
  region = gr.Textbox(
 
262
  ),
263
  )
264
  allow_internet = gr.Checkbox(
265
+ label="Разрешить поиск в интернете (DuckDuckGo через requests)",
266
  value=False,
267
  )
268
 
 
270
  output = gr.Markdown()
271
 
272
  run_btn.click(
273
+ fn=run_fin_aid,
274
  inputs=[case_description, region, urgency, allow_internet],
275
  outputs=[output],
276
  )
277
 
278
+ # В вашей версии Gradio concurrency_count не поддерживается — оставляем совместимо
279
  demo.queue(max_size=20)
280
 
281
 
start.sh CHANGED
@@ -36,7 +36,7 @@ else
36
  echo "[start.sh] Model already present. Skipping pull."
37
  fi
38
 
39
- # Warmup (чтобы первый реальный запрос был быстрее)
40
  echo "[start.sh] Warming up model..."
41
  python3 - << 'PY'
42
  import os, json, urllib.request
 
36
  echo "[start.sh] Model already present. Skipping pull."
37
  fi
38
 
39
+ # Warmup
40
  echo "[start.sh] Warming up model..."
41
  python3 - << 'PY'
42
  import os, json, urllib.request