Disable AI workers after failed batch
This commit is contained in:
@@ -794,6 +794,9 @@ AI получает JSON-массив:
|
||||
2026-07-30 один пост (`id=2348`) был платно отправлен в Sonnet 4.6 983 раза,
|
||||
потому что failed снова попадал в claim и цикл сразу продолжал работу. Failed
|
||||
посты нужно возвращать в `pending` только явным ручным действием.
|
||||
После любого failed AI batch worker дополнительно выключает свой флаг
|
||||
`ai_writer_enabled=false` / `ai_qualifier_enabled=false`; включать обратно
|
||||
только после просмотра ошибки.
|
||||
|
||||
### 10.2. Вход в модель
|
||||
|
||||
|
||||
@@ -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`.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -291,6 +291,17 @@ class AIQualifierWorker:
|
||||
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:
|
||||
for item in results:
|
||||
score = int(item["score"])
|
||||
@@ -412,6 +423,7 @@ class AIQualifierWorker:
|
||||
except Exception as exc:
|
||||
await self.mark_posts_failed(post_ids, 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)
|
||||
return True
|
||||
|
||||
|
||||
@@ -372,6 +372,17 @@ class AIWriterWorker:
|
||||
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:
|
||||
for item in rewrites:
|
||||
final_text = build_publication_text(item["text"], item["category_tag"], item["source_tag"])
|
||||
@@ -518,6 +529,7 @@ class AIWriterWorker:
|
||||
except Exception as 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.disable_after_error(str(exc))
|
||||
logger.exception("AI writer batch failed: id={} error={}", batch_id, exc)
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user