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:23:43 +08:00
|
|
|
|
"""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
|
2026-04-30 09:17:01 +08:00
|
|
|
|
from app.core.llm_gateway import LlmGateway, LlmUseCase
|
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:23:43 +08:00
|
|
|
|
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"}
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
user_obj_pre = db.get(User, user_id)
|
|
|
|
|
|
user_language = (
|
|
|
|
|
|
"en"
|
|
|
|
|
|
if user_obj_pre is not None
|
|
|
|
|
|
and str(getattr(user_obj_pre, "language_preference", "zh") or "zh").lower()
|
|
|
|
|
|
== "en"
|
|
|
|
|
|
else "zh"
|
|
|
|
|
|
)
|
|
|
|
|
|
expected_ph_zh = _placeholder_title(chapter_category, language="zh")
|
|
|
|
|
|
expected_ph_en = _placeholder_title(chapter_category, language="en")
|
|
|
|
|
|
expected_ph = _placeholder_title(chapter_category, language=user_language)
|
|
|
|
|
|
current = (st.title or "").strip()
|
|
|
|
|
|
if current and current not in (expected_ph_zh, expected_ph_en):
|
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:23:43 +08:00
|
|
|
|
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"}
|
|
|
|
|
|
|
2026-04-30 09:17:01 +08:00
|
|
|
|
llm = LlmGateway().langchain_llm_for(LlmUseCase("story_title"))
|
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:23:43 +08:00
|
|
|
|
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"}
|
|
|
|
|
|
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
user_obj = user_obj_pre
|
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:23:43 +08:00
|
|
|
|
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,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=user_language,
|
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:23:43 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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 "",
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=user_language,
|
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:23:43 +08:00
|
|
|
|
)
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
if not new_title.strip() or new_title.strip() in (
|
|
|
|
|
|
expected_ph_zh,
|
|
|
|
|
|
expected_ph_en,
|
|
|
|
|
|
expected_ph,
|
|
|
|
|
|
):
|
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:23:43 +08:00
|
|
|
|
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
|