diff --git a/backend/app/api/v1/admin_scrape.py b/backend/app/api/v1/admin_scrape.py index c8a1293e..75677466 100644 --- a/backend/app/api/v1/admin_scrape.py +++ b/backend/app/api/v1/admin_scrape.py @@ -268,7 +268,7 @@ def revoke_task( def list_failures( db: Annotated[Session, Depends(get_db)], run_id: int | None = None, - limit: int = Query(default=50, ge=0), + limit: int = Query(default=50, ge=1), ) -> list[dict[str, Any]]: """Per-request failure log for manual browser verification.""" where = "" @@ -1284,9 +1284,7 @@ _FRESHNESS_SOURCES: list[FreshnessSource] = [ ] -def _classify_freshness( - age_days: float | None, fresh_days: float, stale_days: float -) -> str: +def _classify_freshness(age_days: float | None, fresh_days: float, stale_days: float) -> str: """fresh / stale / critical / unknown по возрасту последнего успешного прогона.""" if age_days is None: return "unknown" diff --git a/backend/app/services/chat/orchestrator.py b/backend/app/services/chat/orchestrator.py index f1f5b768..147c6418 100644 --- a/backend/app/services/chat/orchestrator.py +++ b/backend/app/services/chat/orchestrator.py @@ -165,6 +165,18 @@ def orchestrate_chat( continue # Нет tool_calls → финальная проза. + # Проверяем finish_reason: 'length' / 'content_filter' означают обрезанный + # или отфильтрованный ответ — неполная проза клиенту не отдаётся (LLMResult + # docstring §64: «консьюмер ОБЯЗАН проверять даже при ok=True»). + if result.finish_reason not in (None, "stop", "end_turn", "tool_calls"): + logger.warning( + "chat: non-stop finish_reason=%r for cad=%s run=%s → deterministic fallback", + result.finish_reason, + cad_num, + run_id, + ) + return _deterministic(report_dict, message, f"finish_reason:{result.finish_reason}") + answer = (result.content or "").strip() if not answer: # Модель вернула пустоту — пустой ответ клиенту не отдаём, деградируем.