Allow empty text for non-target AI rewrites
This commit is contained in:
@@ -794,6 +794,10 @@ AI получает JSON-массив:
|
|||||||
2026-07-30 один пост (`id=2348`) был платно отправлен в Sonnet 4.6 983 раза,
|
2026-07-30 один пост (`id=2348`) был платно отправлен в Sonnet 4.6 983 раза,
|
||||||
потому что failed снова попадал в claim и цикл сразу продолжал работу. Failed
|
потому что failed снова попадал в claim и цикл сразу продолжал работу. Failed
|
||||||
посты нужно возвращать в `pending` только явным ручным действием.
|
посты нужно возвращать в `pending` только явным ручным действием.
|
||||||
|
Sonnet 4.6 в этом кейсе возвращал `category_id=18` (`Не целевой контент`) и
|
||||||
|
пустой `text`, объясняя отказ в `notes`; это валидный AI-отказ, а не ошибка
|
||||||
|
рерайта. Проверка минимальной длины текста применяется только к целевым
|
||||||
|
категориям.
|
||||||
После failed AI batch worker помечает конкретные посты как `failed` и продолжает
|
После failed AI batch worker помечает конкретные посты как `failed` и продолжает
|
||||||
обрабатывать другие `pending` посты. Ошибка показывается на `/workers` из
|
обрабатывать другие `pending` посты. Ошибка показывается на `/workers` из
|
||||||
heartbeat meta и отправляется Telegram-уведомлением получателям
|
heartbeat meta и отправляется Telegram-уведомлением получателям
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ As of 2026-07-29:
|
|||||||
- Current live writer model setting checked on 2026-07-29: `ai_writer_model = anthropic/claude-sonnet-4-6`.
|
- Current live writer model setting checked on 2026-07-29: `ai_writer_model = anthropic/claude-sonnet-4-6`.
|
||||||
- Sonnet 4.6 can return `rewrites` as a JSON string instead of a list. `validate_rewrites()` in `src/vk_parser_app/workers/ai_writer.py` now parses stringified `rewrites` before validation.
|
- Sonnet 4.6 can return `rewrites` as a JSON string instead of a list. `validate_rewrites()` in `src/vk_parser_app/workers/ai_writer.py` now parses stringified `rewrites` before validation.
|
||||||
- 2026-07-30 incident: post `2348` was retried 983 times by AI writer because automatic claim included `rewrite_status='failed'`; estimated internal cost was `$16.465977`. Live setting `ai_writer_enabled` was switched to `false` manually to stop spend. Automatic AI qualifier/writer claims must not include `failed`; failed posts require an explicit manual reset to `pending`. AI writer/qualifier keep running after a failed batch, show the error on `/workers`, and send a Telegram alert via daily-report/TG-poster bot recipients.
|
- 2026-07-30 incident: post `2348` was retried 983 times by AI writer because automatic claim included `rewrite_status='failed'`; estimated internal cost was `$16.465977`. Live setting `ai_writer_enabled` was switched to `false` manually to stop spend. Automatic AI qualifier/writer claims must not include `failed`; failed posts require an explicit manual reset to `pending`. AI writer/qualifier keep running after a failed batch, show the error on `/workers`, and send a Telegram alert via daily-report/TG-poster bot recipients.
|
||||||
|
- Sonnet 4.6 may return `category_id=18` (`Не целевой контент`) with empty `text` and the explanation in `notes`. This is valid AI rejection, not a writer error; `validate_rewrites()` allows short/empty text only for non-target category.
|
||||||
|
|
||||||
## Prompt Architecture
|
## Prompt Architecture
|
||||||
|
|
||||||
|
|||||||
@@ -152,8 +152,6 @@ def validate_rewrites(
|
|||||||
if post_id not in expected_ids:
|
if post_id not in expected_ids:
|
||||||
raise ValueError(f"AI returned unexpected id={post_id}")
|
raise ValueError(f"AI returned unexpected id={post_id}")
|
||||||
text = strip_leading_emoji_markers(str(item.get("text") or "").strip())
|
text = strip_leading_emoji_markers(str(item.get("text") or "").strip())
|
||||||
if len(text) < 40:
|
|
||||||
raise ValueError(f"AI returned too short rewrite for id={post_id}")
|
|
||||||
by_id = {int(c["id"]): c for c in categories if c.get("id") is not None}
|
by_id = {int(c["id"]): c for c in categories if c.get("id") is not None}
|
||||||
by_name = {str(c["name"]).lower(): c for c in categories if c.get("name")}
|
by_name = {str(c["name"]).lower(): c for c in categories if c.get("name")}
|
||||||
if item.get("category_id") is not None:
|
if item.get("category_id") is not None:
|
||||||
@@ -170,6 +168,8 @@ def validate_rewrites(
|
|||||||
seen.add(post_id)
|
seen.add(post_id)
|
||||||
category_tag = normalize_hash_tag(category_row.get("tag") or category_row.get("name") or "", "category")
|
category_tag = normalize_hash_tag(category_row.get("tag") or category_row.get("name") or "", "category")
|
||||||
reject_by_category = int(category_row["id"]) == NON_TARGET_CATEGORY_ID or category_tag.lower() in NON_TARGET_CATEGORY_TAGS
|
reject_by_category = int(category_row["id"]) == NON_TARGET_CATEGORY_ID or category_tag.lower() in NON_TARGET_CATEGORY_TAGS
|
||||||
|
if not reject_by_category and len(text) < 40:
|
||||||
|
raise ValueError(f"AI returned too short rewrite for id={post_id}")
|
||||||
out.append(
|
out.append(
|
||||||
{
|
{
|
||||||
"id": post_id,
|
"id": post_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user