Disable AI workers after failed batch

This commit is contained in:
Your Name
2026-07-30 09:01:53 +05:00
parent 33c55074aa
commit bb67e9509b
4 changed files with 28 additions and 1 deletions
+3
View File
@@ -794,6 +794,9 @@ 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` только явным ручным действием.
После любого failed AI batch worker дополнительно выключает свой флаг
`ai_writer_enabled=false` / `ai_qualifier_enabled=false`; включать обратно
только после просмотра ошибки.
### 10.2. Вход в модель ### 10.2. Вход в модель
+1 -1
View File
@@ -102,7 +102,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` to stop spend. Automatic AI qualifier/writer claims must not include `failed`; failed posts require an explicit manual reset to `pending`. - 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` to stop spend. Automatic AI qualifier/writer claims must not include `failed`; failed posts require an explicit manual reset to `pending`. AI writer/qualifier now also set their own `*_enabled=false` after any failed AI batch, so one broken prompt/model/post cannot burn money indefinitely.
## Prompt Architecture ## Prompt Architecture
+12
View File
@@ -291,6 +291,17 @@ class AIQualifierWorker:
error[:1000], error[:1000],
) )
async def disable_after_error(self, error: str) -> None:
await self.pool.execute(
"""
UPDATE app_settings
SET value_json='false'::jsonb,
updated_at=NOW()
WHERE key='ai_qualifier_enabled'
"""
)
await self.heartbeat.beat(self.pool, status="disabled_after_error", meta={"error": error[:300]}, force=True)
async def apply_results(self, batch_id: int, results: list[dict], model: str, prompt: str, min_score: int) -> None: async def apply_results(self, batch_id: int, results: list[dict], model: str, prompt: str, min_score: int) -> None:
for item in results: for item in results:
score = int(item["score"]) score = int(item["score"])
@@ -412,6 +423,7 @@ class AIQualifierWorker:
except Exception as exc: except Exception as exc:
await self.mark_posts_failed(post_ids, str(exc)) await self.mark_posts_failed(post_ids, str(exc))
await self.finish_batch(batch_id, "failed", error=str(exc)) await self.finish_batch(batch_id, "failed", error=str(exc))
await self.disable_after_error(str(exc))
logger.exception("AI qualifier batch failed: id={} error={}", batch_id, exc) logger.exception("AI qualifier batch failed: id={} error={}", batch_id, exc)
return True return True
+12
View File
@@ -372,6 +372,17 @@ class AIWriterWorker:
error[:1000], error[:1000],
) )
async def disable_after_error(self, error: str) -> None:
await self.pool.execute(
"""
UPDATE app_settings
SET value_json='false'::jsonb,
updated_at=NOW()
WHERE key='ai_writer_enabled'
"""
)
await self.heartbeat.beat(self.pool, status="disabled_after_error", meta={"error": error[:300]}, force=True)
async def apply_rewrites(self, batch_id: int, rewrites: list[dict], model: str, prompt: str) -> None: async def apply_rewrites(self, batch_id: int, rewrites: list[dict], model: str, prompt: str) -> None:
for item in rewrites: for item in rewrites:
final_text = build_publication_text(item["text"], item["category_tag"], item["source_tag"]) final_text = build_publication_text(item["text"], item["category_tag"], item["source_tag"])
@@ -518,6 +529,7 @@ class AIWriterWorker:
except Exception as exc: except Exception as exc:
await self.mark_posts_failed(post_ids, str(exc)) await self.mark_posts_failed(post_ids, str(exc))
await self.finish_batch(batch_id, "failed", response=response, error=str(exc), usage=usage) await self.finish_batch(batch_id, "failed", response=response, error=str(exc), usage=usage)
await self.disable_after_error(str(exc))
logger.exception("AI writer batch failed: id={} error={}", batch_id, exc) logger.exception("AI writer batch failed: id={} error={}", batch_id, exc)
return True return True