diff --git a/harness/_llm_worker.py b/harness/_llm_worker.py index f162536..65fa428 100644 --- a/harness/_llm_worker.py +++ b/harness/_llm_worker.py @@ -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 {}), ) diff --git a/harness/scheduler.py b/harness/scheduler.py index 7dcc599..f50bf69 100644 --- a/harness/scheduler.py +++ b/harness/scheduler.py @@ -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) - 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}", - ) + 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 мог частично поработать инструментами. diff --git a/harness/tg_inbox.py b/harness/tg_inbox.py index 18ecd04..f18e42d 100644 --- a/harness/tg_inbox.py +++ b/harness/tg_inbox.py @@ -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: ключ аудио-кэша и идемпотентности расшифровки