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 раза,
|
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. Вход в модель
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user