feat(eval): memoir A/B chapter judging and eval-web parity with dialogue
- Judge baseline excerpt and library chapter separately; build_memoir_compare_summary for gate, nine-dim and leaf deltas. - Memoir SSE chapter payload: baseline_judge, compare_summary, baseline_judge_error. - MemoirJudgeOutput: loose score coercion and post-validate clamp; memoir judge prompt caps from settings. - app-eval-web: two-column MemoirScoreCard layout, MemoirCompareSummary, chapter blocks and CSS. - Add memoir_compare_summary, log_events, celery_log_context, memoir_pipeline_progress; tests and migration 0014. - Misc: memory/evidence and enrichment paths, task/orchestrator updates, internal-eval docs, env examples.
This commit is contained in:
@@ -6,6 +6,7 @@ Story 主插图生成 Celery 任务。
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
@@ -17,6 +18,7 @@ from app.agents.image_prompt import get_image_prompt_orchestrator
|
||||
from app.core.db import get_sync_db
|
||||
from app.core.dependencies import get_image_generator
|
||||
from app.core.logging import get_logger
|
||||
from app.core.memoir_pipeline_progress import merge_fanout_item
|
||||
from app.core.redis_lock import acquire_redis_lock, release_redis_lock
|
||||
from app.features.asset.models import Asset
|
||||
from app.features.memoir.asset_resolver import strip_asset_image_refs_from_markdown
|
||||
@@ -149,15 +151,32 @@ def _claim_story_image_intent_sync(db, story_id: str, claim_token: str):
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3, default_retry_delay=30)
|
||||
def generate_story_image(self, story_id: str):
|
||||
def generate_story_image(
|
||||
self, story_id: str, memoir_correlation_id: str | None = None
|
||||
):
|
||||
"""
|
||||
为 story 生成主插图。
|
||||
从 story_image_intents 原子认领 primary intent,生成后写入 assets 并更新 intent。
|
||||
"""
|
||||
celery_tid = str(self.request.id)
|
||||
t0 = time.perf_counter()
|
||||
logger.info(
|
||||
"event=story_image_task_start story_id={} task_id={} msg=故事主图生成任务开始",
|
||||
story_id,
|
||||
celery_tid,
|
||||
)
|
||||
lock_key = f"lock:story-image:{story_id}"
|
||||
lock_handle = acquire_redis_lock(lock_key, ttl_seconds=STORY_IMAGE_LOCK_TTL_SECONDS)
|
||||
if lock_handle is None:
|
||||
logger.debug("generate_story_image: story={}, reason=locked", story_id)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="locked",
|
||||
)
|
||||
return {"status": "locked"}
|
||||
|
||||
claim_token = uuid.uuid4().hex
|
||||
@@ -171,6 +190,14 @@ def generate_story_image(self, story_id: str):
|
||||
"generate_story_image: story={}, reason=no_claimable_intent",
|
||||
story_id,
|
||||
)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="no_intent",
|
||||
)
|
||||
return {"status": "no_intent"}
|
||||
|
||||
intent, story = row
|
||||
@@ -197,8 +224,25 @@ def generate_story_image(self, story_id: str):
|
||||
len(plain),
|
||||
min_body,
|
||||
)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="skipped_body_too_short",
|
||||
)
|
||||
return {"status": "skipped_body_too_short"}
|
||||
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="running",
|
||||
)
|
||||
|
||||
image_generator = get_image_generator()
|
||||
storage = TencentCosStorageService.from_env()
|
||||
|
||||
@@ -247,6 +291,14 @@ def generate_story_image(self, story_id: str):
|
||||
getattr(intent_db, "status", None),
|
||||
getattr(intent_db, "claim_token", None),
|
||||
)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="superseded_or_cancelled",
|
||||
)
|
||||
return {"status": "superseded_or_cancelled"}
|
||||
|
||||
asset = Asset(
|
||||
@@ -286,11 +338,27 @@ def generate_story_image(self, story_id: str):
|
||||
url,
|
||||
asset_id,
|
||||
)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="success_stale",
|
||||
)
|
||||
return {"status": "success_stale", "asset_id": asset_id}
|
||||
|
||||
ver = db.get(StoryVersion, target_vid)
|
||||
if not ver:
|
||||
db.commit()
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="success_no_snapshot",
|
||||
)
|
||||
return {"status": "success_no_snapshot", "asset_id": asset_id}
|
||||
|
||||
base_md = strip_asset_image_refs_from_markdown(ver.markdown_snapshot or "")
|
||||
@@ -326,10 +394,13 @@ def generate_story_image(self, story_id: str):
|
||||
|
||||
_enqueue_chapter_effects_after_image_backfill(story_id)
|
||||
|
||||
ms = (time.perf_counter() - t0) * 1000
|
||||
logger.info(
|
||||
"generate_story_image: story={}, asset={}",
|
||||
"event=story_image_task_done story_id={} asset_id={} duration_ms={:.1f} "
|
||||
"msg=故事主图生成完成",
|
||||
story_id,
|
||||
asset_id,
|
||||
ms,
|
||||
)
|
||||
logger.debug(
|
||||
"generate_story_image: story={} asset={} url={} cos_key={} prompt_final={}",
|
||||
@@ -339,6 +410,14 @@ def generate_story_image(self, story_id: str):
|
||||
cos_key,
|
||||
prompt_final,
|
||||
)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="success",
|
||||
)
|
||||
return {"status": "success", "asset_id": asset_id}
|
||||
except Exception as exc:
|
||||
if intent is not None:
|
||||
@@ -355,7 +434,23 @@ def generate_story_image(self, story_id: str):
|
||||
intent_db.error = str(exc)
|
||||
intent_db.updated_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
logger.warning("generate_story_image failed: story={}, error={}", story_id, exc)
|
||||
merge_fanout_item(
|
||||
memoir_correlation_id,
|
||||
list_name="story_images",
|
||||
id_field="story_id",
|
||||
item_id=story_id,
|
||||
task_id=celery_tid,
|
||||
status="failure",
|
||||
extra={"error": str(exc)},
|
||||
)
|
||||
ms = (time.perf_counter() - t0) * 1000
|
||||
logger.warning(
|
||||
"event=story_image_task_failed story_id={} duration_ms={:.1f} error={} "
|
||||
"msg=故事主图生成失败",
|
||||
story_id,
|
||||
ms,
|
||||
exc,
|
||||
)
|
||||
raise self.retry(exc=exc) from exc
|
||||
finally:
|
||||
release_redis_lock(lock_handle)
|
||||
|
||||
Reference in New Issue
Block a user