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

@@ -8,14 +8,17 @@ ClassificationAgent将内容分类到 8 个章节类别,或判定无价值
from __future__ import annotations
import json
import re
from typing import Any, Optional
from app.agents.memoir.prompts import (
CHAPTER_CATEGORIES,
get_chapter_classification_prompt,
get_chapter_classification_json_prompt,
)
from app.core.langchain_llm import invoke_json_object
from app.core.logging import get_logger
from app.features.memoir.memoir_images.json_payload import extract_json_payload
logger = get_logger(__name__)
@@ -89,6 +92,20 @@ def _normalize_llm_category(raw: str) -> str:
return s
def _parse_category_from_llm_response(raw: str) -> str:
"""优先解析 JSON ``{"category": "..."}``,失败则按纯文本 key 处理。"""
s = (raw or "").strip()
if not s:
return ""
try:
data = json.loads(extract_json_payload(s))
if isinstance(data, dict) and "category" in data:
return _normalize_llm_category(str(data["category"]))
except (json.JSONDecodeError, TypeError, ValueError):
pass
return _normalize_llm_category(s)
class ClassificationAgent:
"""将内容分类到 8 个章节类别之一,或判定无价值返回 None"""
@@ -105,7 +122,7 @@ class ClassificationAgent:
"""
if _looks_like_fragment_only(text):
logger.debug(
"零散档案/极短标签句,跳过回忆录 Story: text_len=%s text=%s",
"零散档案/极短标签句,跳过回忆录 Story: text_len={} text={}",
len(text or ""),
text or "",
)
@@ -113,12 +130,17 @@ class ClassificationAgent:
if llm:
try:
prompt = get_chapter_classification_prompt(text)
response = llm.invoke(prompt)
category = _normalize_llm_category(response.content or "")
prompt = get_chapter_classification_json_prompt(text)
raw = invoke_json_object(
llm,
prompt,
max_tokens=256,
agent="ClassificationAgent.classify",
)
category = _parse_category_from_llm_response(raw)
if category == "none":
logger.debug(
"LLM 判定内容不足以成篇,跳过: text_len=%s text=%s",
"LLM 判定内容不足以成篇,跳过: text_len={} text={}",
len(text or ""),
text or "",
)
@@ -126,7 +148,7 @@ class ClassificationAgent:
if category in CHAPTER_CATEGORIES:
return category
except Exception as e:
logger.warning("ClassificationAgent LLM 章节分类失败: %s", e)
logger.warning("ClassificationAgent LLM 章节分类失败: {}", e)
stage = _detect_stage(text, fallback_stage)
return _STAGE_TO_DEFAULT_CATEGORY.get(