feat(api): 叙事 prompt、职业上下文、读路径章节、WS 解耦与错误脱敏
- 回忆录:事实边界补充允许清单;传记文体示例与 JSON 叙事要求对齐 - default 职业提示 occupation_context;cadre/military 退休语境 - GET 章节读路径零写入,prepare_chapter_read_view + markdown_for_response - 文本归一抽到 core/text_normalize;移除弃用 reply 策略与 recompose_chapters_for_story - ConversationService:WS 连接/用户段落/结束对话;对外错误固定文案 - 测试:HTTP 脱敏契约、章节读视图、occupation 与 background_voice
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from celery import shared_task
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.chapter_pipeline_lock import (
|
||||
acquire_chapter_pipeline_lock,
|
||||
@@ -14,8 +13,7 @@ from app.core.db import get_sync_db
|
||||
from app.core.logging import get_logger
|
||||
from app.core.memory_compaction_schedule import schedule_memory_compaction_run
|
||||
from app.features.memoir import repo as memoir_repo
|
||||
from app.features.memoir.models import Chapter, ChapterStoryLink
|
||||
from app.features.story.models import Story
|
||||
from app.features.memoir.models import Chapter
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -83,56 +81,3 @@ def recompose_chapter(self, chapter_id: str) -> dict:
|
||||
"composed" if composed else "empty",
|
||||
)
|
||||
return {"status": "composed" if composed else "empty", "chapter_id": chapter_id}
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3, default_retry_delay=30)
|
||||
def recompose_chapters_for_story(self, story_id: str) -> dict:
|
||||
"""
|
||||
按 story 找出 dirty 章节并物化。
|
||||
|
||||
.. deprecated::
|
||||
请改用 `recompose_chapter`(按章聚合)+ `enqueue_story_post_commit_effects`;
|
||||
保留兼容,待调用方全部迁移后删除。
|
||||
"""
|
||||
user_id: str | None = None
|
||||
try:
|
||||
with get_sync_db() as session:
|
||||
story = session.get(Story, story_id)
|
||||
user_id = str(story.user_id) if story else None
|
||||
stmt = (
|
||||
select(Chapter.id)
|
||||
.join(
|
||||
ChapterStoryLink,
|
||||
ChapterStoryLink.chapter_id == Chapter.id,
|
||||
)
|
||||
.where(
|
||||
ChapterStoryLink.story_id == story_id,
|
||||
Chapter.markdown_compose_dirty.is_(True),
|
||||
)
|
||||
)
|
||||
ids = list(session.scalars(stmt).all())
|
||||
for cid in ids:
|
||||
memoir_repo.compose_chapter_from_story_links_sync(session, cid)
|
||||
session.commit()
|
||||
if user_id:
|
||||
schedule_memory_compaction_run(
|
||||
user_id,
|
||||
{
|
||||
"trigger_source": "chapter_recompose",
|
||||
"trigger_time": datetime.now(timezone.utc).isoformat(),
|
||||
"pipeline_run_id": str(self.request.id),
|
||||
"story_ids": [story_id],
|
||||
"recomposed_chapter_ids": ids,
|
||||
},
|
||||
)
|
||||
logger.info(
|
||||
"recompose_chapters_for_story: story={} recomposed_chapters={}",
|
||||
story_id,
|
||||
ids,
|
||||
)
|
||||
return {"story_id": story_id, "recomposed_chapter_ids": ids}
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"recompose_chapters_for_story failed story={} err={}", story_id, exc
|
||||
)
|
||||
raise self.retry(exc=exc) from exc
|
||||
|
||||
@@ -314,6 +314,7 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
|
||||
user_profile = ""
|
||||
user_birth_year = None
|
||||
background_voice = "default"
|
||||
user_occupation = ""
|
||||
if user_obj:
|
||||
user_birth_year = user_obj.birth_year
|
||||
user_profile = format_user_profile_context(
|
||||
@@ -323,6 +324,7 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
|
||||
occupation=user_obj.occupation,
|
||||
)
|
||||
background_voice = infer_background_voice(user_obj.occupation)
|
||||
user_occupation = user_obj.occupation or ""
|
||||
|
||||
story_dispatch_ids: Set[str] = set()
|
||||
|
||||
@@ -382,6 +384,7 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
|
||||
user_birth_year=user_birth_year,
|
||||
llm=llm,
|
||||
background_voice=background_voice,
|
||||
occupation=user_occupation,
|
||||
)
|
||||
story_dispatch_ids |= disp
|
||||
db.flush()
|
||||
@@ -512,6 +515,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
|
||||
user_profile = ""
|
||||
user_birth_year = None
|
||||
background_voice = "default"
|
||||
user_occupation = ""
|
||||
if user_obj:
|
||||
user_birth_year = user_obj.birth_year
|
||||
user_profile = format_user_profile_context(
|
||||
@@ -521,6 +525,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
|
||||
occupation=user_obj.occupation,
|
||||
)
|
||||
background_voice = infer_background_voice(user_obj.occupation)
|
||||
user_occupation = user_obj.occupation or ""
|
||||
|
||||
class _Seg:
|
||||
def __init__(self, text: str):
|
||||
@@ -538,6 +543,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
|
||||
user_birth_year=user_birth_year,
|
||||
llm=llm,
|
||||
background_voice=background_voice,
|
||||
occupation=user_occupation,
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(chapter)
|
||||
|
||||
Reference in New Issue
Block a user