fix(worker+scheduler+inbox): три фикса часовой сводки (t-260930162615-fb58be)
1) per-call timeout LLM-вызова (SIMBIOT_LLM_CALL_TIMEOUT, 90с default, max_retries=1): роутер-лог показал — зависший тик = POST в ollama принят, но байт стрима не пришёл (DONE без TTFT), соседние вызовы ходили за 1-3с. Один висящий вызов больше не убивает весь тик по уровневому таймауту (300/2880с) — режется сам вызов, event-loop завершает тик штатно, spend-хвост и задачи не теряются (e-260930161900-baaecb, e-260930162600-cbb7e2, e-260930153143-85246b). 2) обрезка TG-инбокса (e-260930154125-082a2a): приём 4000→10000 симв., показ в контексте тика 300→1200 — длинные сообщения Alex больше не теряют хвост. 3) loss-of-claim в батче: TimeoutExpired от isolated_runner пролетал мимо TaskFailed-блока run_level_tick — claimed-задачи висели в running до сирот-чистки (час+). Теперь claim сразу возвращается TaskPromoted в очередь (reason с причиной смерти воркера). Тесты 8/8 (hermetic), живой вызов модели с timeout=90 — ok.
This commit is contained in:
parent
2f4e3d22a7
commit
3361b671ed
3 changed files with 39 additions and 9 deletions
|
|
@ -88,7 +88,16 @@ def _wire_tool_budget(agent, max_calls: int = 30):
|
|||
|
||||
def build_level_model_from_payload(payload: dict):
|
||||
"""Модель для воркера — та же сборка, что и в runtime.build_level_model:
|
||||
9router понимает chat/completions (usage там же), Responses API — шим без usage."""
|
||||
9router понимает chat/completions (usage там же), Responses API — шим без usage.
|
||||
|
||||
SIMBIOT_LLM_CALL_TIMEOUT (e-260930161900-6da5c9, e-260930153143-85246b):
|
||||
per-call таймаут LLM-вызова в сек. Роутер-лог показал: зависший тик = POST
|
||||
в ollama-апстрим принят, но байт стрима так и не пришёл (DONE без TTFT),
|
||||
при этом соседние вызовы ходили за 1-3с. Таймаут уровня (300/2880с) убивает
|
||||
ВЕСЬ тик из-за одного висящего вызова; теперь вызов режется (до 90с,
|
||||
hour 180с), исключение отлавливает event-loop — тик получает результат
|
||||
с пометкой «не успел», spend-хвост и задачи не теряются.
|
||||
"""
|
||||
from strands.models.openai import OpenAIModel
|
||||
import os
|
||||
params = {}
|
||||
|
|
@ -98,9 +107,15 @@ def build_level_model_from_payload(payload: dict):
|
|||
model_id = payload.get("model") or "auto"
|
||||
if model_id == "none":
|
||||
model_id = "auto"
|
||||
# per-call таймаут: один зависший апстрим-вызов не должен убивать весь тик
|
||||
call_timeout = int(os.environ.get("SIMBIOT_LLM_CALL_TIMEOUT", "90"))
|
||||
return OpenAIModel(
|
||||
# timeout открытого клиента (openai SDK) режет каждый chat.completions
|
||||
# вызов; при зависании апстрима получаем APITimeoutError за N сек и
|
||||
# strands event-loop завершает тик штатно (без потери предыдущих шагов)
|
||||
client_args={"api_key": os.environ.get("OPENAI_API_KEY", ""),
|
||||
"base_url": os.environ.get("OPENAI_BASE_URL", "http://127.0.0.1:20128/v1")},
|
||||
"base_url": os.environ.get("OPENAI_BASE_URL", "http://127.0.0.1:20128/v1"),
|
||||
"timeout": call_timeout, "max_retries": 1},
|
||||
model_id=model_id,
|
||||
**({"params": params} if params else {}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ def build_context(level_cfg, model: dict, batch=None):
|
|||
tag = (" [голосовое, расшифровка не удалась — попроси прислать текстом]"
|
||||
if m.get("transcribed") else
|
||||
" [голосовое, ещё не расшифровано]")
|
||||
lines.append(f"{i}. [{m_id}] от {who}{tag}: {m.get('text','')[:300]}")
|
||||
lines.append(f"{i}. [{m_id}] от {who}{tag}: {m.get('text','')[:1200]}")
|
||||
batch_ids = ",".join(ids)
|
||||
# t-260930064924: батч-триаж — ВСЕ неотвеченные обрабатываются ОДНИМ тиком.
|
||||
# Раньше мин-уровень отвечал по одному за тик (m-516173222..228 — 7 тиков
|
||||
|
|
@ -417,11 +417,26 @@ def run_level_tick(level_cfg, seq: int, model: dict):
|
|||
if not claimed:
|
||||
claimed = None
|
||||
prompt = build_context(level_cfg, model, batch)
|
||||
try:
|
||||
text, usage, trace_id = isolated_runner.run_llm_tick(
|
||||
level_cfg, prompt,
|
||||
session_id=f"s-{level_cfg.name}-{seq}",
|
||||
trace_prefix=f"{level_cfg.name}-{seq}",
|
||||
)
|
||||
except Exception as e:
|
||||
# e-260930162600-cbb7e2/cbb7e2-семейство: TimeoutExpired от isolated_runner
|
||||
# пролетал МИМО блока text-is-None ниже — claimed-задачи висели в running
|
||||
# до сирот-чистки (час+), очередь казалась «не стартующей» (85246b).
|
||||
# Возвращаем claim в очередь сразу (TaskPromoted — тот же механизм, что
|
||||
# orphan-requeue: ready + сброс уровня; следующий тик возьмёт заново),
|
||||
# исключение летит дальше — CycleError/эскалация как раньше.
|
||||
for tid in (claimed or []):
|
||||
size = next((t.get("size", "M") for t in batch if t.get("id") == tid), "M")
|
||||
es.append_event("task", tid, "TaskPromoted",
|
||||
{"to": size, "from": size,
|
||||
"reason": f"тик-воркер умер ({str(e)[:120]}) — "
|
||||
f"claim возвращён в очередь без потери"})
|
||||
raise
|
||||
if text is None:
|
||||
# воркер упал (таймаут/краш) — НИ одна задача не выполнена с его
|
||||
# точки зрения, но executor мог частично поработать инструментами.
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ def receive_message(update: dict):
|
|||
"user_id": user.get("id"),
|
||||
"username": user.get("username"),
|
||||
"first_name": user.get("first_name"),
|
||||
"text": (msg.get("text") or msg.get("caption") or "")[:4000],
|
||||
"text": (msg.get("text") or msg.get("caption") or "")[:10000],
|
||||
"kind": kind,
|
||||
"file_id": file_id,
|
||||
# file_unique_id: ключ аудио-кэша и идемпотентности расшифровки
|
||||
|
|
|
|||
Loading…
Reference in a new issue