Files
life-echo/api/app/tasks/story_title_tasks.py
Kevin ac436b87a2 feat(api): 收敛对话与记忆流程边界,引入 LLM 网关与专用服务
- MemoryService 异步路径委托 MemoryIngestService / MemoryRetrievalService;富化派发经 MemoryEnrichmentScheduler
- WebSocket pipeline 经 ChatTurnService 与显式 DTO 编排单轮对话;回忆录片段入队由 MemoirIngestScheduler 封装
- 新增 LlmGateway(LlmUseCase),各 agent、任务与适配器对齐 ports
- 补充 memory 提示适配、runtime 类型、memory-retrieval 文档、ai-touchpoints 说明与扫描脚本及配套测试

Made-with: Cursor
2026-04-30 09:17:01 +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.llm_gateway import LlmGateway, LlmUseCase
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 = LlmGateway().langchain_llm_for(LlmUseCase("story_title"))
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