Files
life-echo/api/app/tasks/story_title_tasks.py
Kevin ac49bc7f23 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.
2026-04-10 10:25:15 +08:00

135 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Async story title refinement after new story create (placeholder first)."""
import time
from celery import shared_task
from app.core.db import get_sync_db
from app.core.dependencies import get_llm_provider
from app.core.logging import get_logger
logger = get_logger(__name__)
@shared_task(bind=True, max_retries=2, default_retry_delay=15)
def generate_story_title_after_create(
self,
story_id: str,
chapter_category: str,
oral_scope: str,
user_id: str,
):
"""Replace placeholder title with LLM title when body is long enough."""
from app.agents.chat.prompts_profile import format_user_profile_context
from app.agents.memoir.narrative_agent import NarrativeAgent
from app.features.memoir.state_service import get_or_create_state_sync
from app.features.memoir.story_pipeline_sync import (
_maybe_generate_title,
_placeholder_title,
_slot_snippets_for_narrative,
)
from app.features.story.models import Story
from app.features.user.models import User
t0 = time.perf_counter()
logger.info(
"event=story_title_task_start story_id={} user_id={} chapter_category={} "
"msg=故事标题精修任务开始",
story_id,
user_id,
chapter_category,
)
try:
with get_sync_db() as db:
st = db.get(Story, story_id)
if not st or str(st.user_id) != str(user_id):
ms = (time.perf_counter() - t0) * 1000
logger.info(
"event=story_title_task_skip story_id={} reason=not_found duration_ms={:.1f} "
"msg=标题精修跳过(故事不存在或无权限)",
story_id,
ms,
)
return {"status": "skip_not_found"}
expected_ph = _placeholder_title(chapter_category)
if (st.title or "").strip() and (st.title or "").strip() != expected_ph:
ms = (time.perf_counter() - t0) * 1000
logger.info(
"event=story_title_task_skip story_id={} reason=user_modified duration_ms={:.1f} "
"msg=标题精修跳过(用户已改标题)",
story_id,
ms,
)
return {"status": "skip_user_modified"}
llm = getattr(get_llm_provider(), "langchain_llm", None)
if not llm:
ms = (time.perf_counter() - t0) * 1000
logger.info(
"event=story_title_task_skip story_id={} reason=no_llm duration_ms={:.1f} "
"msg=标题精修跳过(无 LLM",
story_id,
ms,
)
return {"status": "skip_no_llm"}
user_obj = db.get(User, user_id)
user_profile = ""
birth_year = None
if user_obj:
birth_year = user_obj.birth_year
user_profile = format_user_profile_context(
birth_year=user_obj.birth_year,
birth_place=user_obj.birth_place,
grew_up_place=user_obj.grew_up_place,
occupation=user_obj.occupation,
)
state = get_or_create_state_sync(user_id, db)
slot_snippets = _slot_snippets_for_narrative(
state=state,
chapter_category=chapter_category,
user_id=user_id,
)
md = (st.canonical_markdown or "").strip()
new_title = _maybe_generate_title(
NarrativeAgent(),
chapter_category=chapter_category,
md=md,
slot_snippets=slot_snippets,
user_profile=user_profile,
user_birth_year=birth_year,
llm=llm,
oral_scope=oral_scope or "",
)
if not new_title.strip() or new_title.strip() == expected_ph:
ms = (time.perf_counter() - t0) * 1000
logger.info(
"event=story_title_task_skip story_id={} reason=placeholder duration_ms={:.1f} "
"msg=标题精修跳过(仍为占位)",
story_id,
ms,
)
return {"status": "skip_placeholder"}
st.title = new_title
db.commit()
ms = (time.perf_counter() - t0) * 1000
logger.info(
"event=story_title_task_done story_id={} user_id={} duration_ms={:.1f} "
"msg=故事标题精修完成",
story_id,
user_id,
ms,
)
return {"status": "ok", "title": new_title}
except Exception as exc:
ms = (time.perf_counter() - t0) * 1000
logger.warning(
"event=generate_story_title_after_create_failed story_id={} duration_ms={:.1f} err={} "
"msg=故事标题精修失败",
story_id,
ms,
exc,
)
raise self.retry(exc=exc) from exc