feat(api+app): 对话阶段化、回忆录流水线与客户端会话体验

- DB: segments 用户输入文本(Alembic 0002)
- Chat: 阶段检测/阶段提示/回复限制,编排与访谈/画像 prompts 调整
- Memoir: 忠实度检查 agent,叙事与分类等链路更新
- Core: agent 日志、Alembic 启动、LangChain/日志/配置等
- Story: time_hints;Memory 检索与相关测试
- Expo: 助手头像、会话页与消息拆分、实时会话与文案/i18n
- Docs/scripts/tests: 迁移脚本、LLM JSON/记忆检索文档、新增单测
This commit is contained in:
Kevin
2026-03-26 12:13:36 +08:00
parent 49b089354c
commit a3f61fcc0f
94 changed files with 3332 additions and 672 deletions

View File

@@ -108,7 +108,7 @@ def _update_task_status_sync(
r.hset(key, task_id, json.dumps(task_info))
r.expire(key, 3600) # 1小时过期
logger.debug("任务状态已更新: task_id=%s status=%s", task_id, status)
logger.debug("任务状态已更新: task_id={} status={}", task_id, status)
except Exception as e:
logger.error(f"更新任务状态失败: {e}")
@@ -248,7 +248,7 @@ def _update_slot_sync(
).model_dump()
slots[stage] = stage_slots
state.slots = slots
state.current_stage = state.current_stage or stage
state.current_stage = stage
db.commit()
db.refresh(state)
return _coerce_state(state)
@@ -283,16 +283,17 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
logger.warning(f"未找到段落: {segment_ids}")
return {"status": "no_segments"}
# Memory ingest: transcript -> memory_sources, chunks, FTS
# Memory ingest 先于回忆录流水线 commit保证后续 retrieve_evidence_sync 可见本批 chunk
# (见 api/docs/memory-retrieval.md
conv_id = getattr(segments[0], "conversation_id", None) or ""
transcript = "\n\n".join(seg.transcript_text or "" for seg in segments)
transcript = "\n\n".join(seg.user_input_text or "" for seg in segments)
if transcript.strip():
try:
from app.features.memory.service import ingest_transcript_sync
ingest_transcript_sync(db, user_id, conv_id, transcript)
except Exception as e:
logger.warning("Memory ingest 跳过: %s", e)
logger.warning("Memory ingest 跳过: {}", e)
llm = _get_llm()
image_settings = MemoirImageSettings.from_env()
@@ -328,7 +329,7 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
) in prepared.category_to_segments.items():
if not _acquire_chapter_lock(user_id, chapter_category):
logger.warning(
"章节锁竞争: category=%s, 延迟重试",
"章节锁竞争: category={}, 延迟重试",
chapter_category,
)
raise self.retry(countdown=10)
@@ -389,11 +390,11 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
try:
generate_story_image.delay(sid)
except Exception as exc:
logger.warning("generate_story_image delay: %s", exc)
logger.warning("generate_story_image delay: {}", exc)
try:
recompose_chapters_for_story.delay(sid)
except Exception as exc:
logger.warning("recompose_chapters_for_story delay: %s", exc)
logger.warning("recompose_chapters_for_story delay: {}", exc)
from app.tasks.chapter_cover_enqueue import (
try_enqueue_generate_chapter_cover,
@@ -452,7 +453,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
class _Seg:
def __init__(self, text: str):
self.id = str(uuid.uuid4())
self.transcript_text = text
self.user_input_text = text
state = _get_or_create_state_sync(user_id, db)
chapter, _, dispatch_ids = run_story_pipeline_for_category_batch(
@@ -475,11 +476,11 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
try:
generate_story_image.delay(sid)
except Exception as exc:
logger.warning("generate_story_image delay: %s", exc)
logger.warning("generate_story_image delay: {}", exc)
try:
recompose_chapters_for_story.delay(sid)
except Exception as exc:
logger.warning("recompose_chapters_for_story delay: %s", exc)
logger.warning("recompose_chapters_for_story delay: {}", exc)
image_settings = MemoirImageSettings.from_env()
if (