Files
life-echo/api/app/agents/memoir/prompts.py

562 lines
22 KiB
Python
Raw Normal View History

2026-01-07 11:56:53 +08:00
"""
回忆录整理 Agent 提示词模板
"""
2026-03-19 14:36:14 +08:00
import json
import re
from typing import Optional
2026-01-07 11:56:53 +08:00
CHAPTER_CATEGORIES = {
"childhood": "童年与成长背景",
"education": "教育经历与青年时期",
"career_early": "崭露头角",
"career_achievement": "主要成就与巅峰时刻",
"career_challenge": "挫折、挑战与重大转折",
"family": "家庭与情感",
"beliefs": "信念与价值观",
"summary": "人生总结",
}
CHAPTER_ORDER = [
"childhood",
"education",
"career_early",
"career_achievement",
"career_challenge",
"family",
"beliefs",
"summary",
]
STAGE_TO_ORDER = {
"childhood": 0,
"education": 1,
"career": 2,
"career_early": 2,
"career_achievement": 3,
"career_challenge": 4,
"family": 5,
"belief": 6,
"beliefs": 6,
"summary": 7,
}
IMAGE_PLACEHOLDER_TEMPLATE = (
2026-03-12 13:47:12 +08:00
"温暖怀旧风格,年代感复古色调,柔和光影,朴素温馨氛围,安静治愈,低饱和度,"
"质感柔和细腻,简约构图,充满岁月沉淀感与故事感,高清唯美插画封面,不要包含文字,"
"要适合老年人审美,画面要真实可信、让老年人产生共鸣与代入感,"
"场景环境、建筑风格、服饰器物必须严格符合所述时代背景和地域特色,"
"有朦胧怀旧的年代感。"
)
2026-03-12 10:13:40 +08:00
_IMAGE_PLACEHOLDER_ANY_BRACES_RE = re.compile(
r"(\{\{)+IMAGE:\s*([^}]+)(\}\})+",
re.DOTALL,
)
def inject_image_placeholder_template(content: str) -> str:
"""
重构回忆录为 story-first / markdown-first 架构并整合图片意图与前端 UI 修复 本次 squash merge 将 codex-story-first-image-intent 的整体改动合入 development,核心内容包括: 1. 后端数据与迁移:新增 stories、story_versions、story_image_intents、chapter_cover_intents、assets 等模型与 Alembic 迁移,建立 story-first、markdown-first、asset-first 的主数据链路。 2. 生成与任务链:引入 StoryBuilderOrchestrator、ChapterComposerOrchestrator、story_image_tasks、chapter_cover_tasks,图片生成从正文占位符改为结构化 intent -> asset -> markdown 回填。 3. 并发与一致性:为 story/chapter intent 增加 claim_token、claimed_at、attempt_count,采用数据库原子 claim 为主、Redis 锁为辅,避免重复生成、锁误删和 processing 卡死。 4. Memoir 读写路径:章节 canonical_markdown 成为正文真源,列表/详情接口补齐 markdown、cover_asset、word_count 等字段,PDF 与 asset 解析链路同步升级。 5. Memory / Retrieval:扩展 transcript ingest、chunking、evidence 检索与 story 聚合基础设施,为后续 story-first RAG 与多 agent 编排提供底座。 6. App 端体验:章节页继续走 MarkdownRenderer 阅读链,同时吸收 fix3-19 的跨平台 UI glitch 修复;更新对话页、首页、文案资源与章节列表映射逻辑。 7. 测试与文档:补充 asset resolver、story image task、章节封面派发、markdown 映射等回归测试,并加入图片占位符退役设计文档。
2026-03-20 10:30:07 +08:00
对正文中的 IMAGE 占位符拼上固定风格模板四层花括号
**线上写路径已不使用**保留供离线迁移脚本处理历史数据
"""
if not content or not content.strip():
return content
def replace_one(match: re.Match) -> str:
2026-03-12 10:13:40 +08:00
inner = (match.group(2) or "").strip()
if not inner:
return match.group(0)
if inner.startswith(IMAGE_PLACEHOLDER_TEMPLATE):
2026-03-19 14:36:14 +08:00
desc = inner[len(IMAGE_PLACEHOLDER_TEMPLATE) :].lstrip("").strip()
return (
"{{{{IMAGE:"
+ IMAGE_PLACEHOLDER_TEMPLATE
+ ("" + desc if desc else "")
+ "}}}}"
)
return "{{{{IMAGE:" + IMAGE_PLACEHOLDER_TEMPLATE + "" + inner + "}}}}"
2026-03-12 10:13:40 +08:00
content = _IMAGE_PLACEHOLDER_ANY_BRACES_RE.sub(replace_one, content)
return content
2026-01-07 11:56:53 +08:00
def get_system_prompt() -> str:
"""获取整理 Agent 的系统提示词"""
return """你是一位专业的传记作家和文字编辑,擅长将口语化的对话内容整理成优雅的书面语回忆录章节。
你的任务
1. 接收对话段落文本口语化可能来自语音转写
2. **先提炼对话中与人生经历相关的核心内容**过滤掉无关信息
3. 识别内容主题归类到对应章节童年/教育/事业/家庭/信念/总结
4. 将口语化表达改写为书面语保持原意和情感
5. 生成合适的章节标题和段落结构
6. 提取关键信息形成连贯的叙述
## 内容筛选原则(最重要)
对话中往往夹杂大量与回忆录无关的噪音你必须严格筛选只保留有价值的内容
应该保留的内容
- 具体的人生事件经历故事
- 提到的人物及其关系家人朋友同事恩师等
- 地点时间场景描写
- 用户的情感表达内心感受
- 人生感悟价值观信念
- 具体的细节食物声音画面等
应该过滤掉的内容
- 语气词填充词那个就是说对对对然后呢等
- 对话中的寒暄问候你好谢谢好的等
- 用户与AI助手之间的交互指令你帮我我想问你说得对等
- 重复冗余的表述取核心含义即可
- 与个人经历完全无关的闲聊内容
## 改写原则
- 保持用户的真实情感
- 使用优雅但不失亲切的书面语不要直接引用对话原话
2026-01-07 11:56:53 +08:00
- 适当添加过渡句使段落连贯
- 保留生动的细节但将口语表达改写为书面叙述
- 去除口语中的填充词和无意义重复
2026-01-07 11:56:53 +08:00
- 保持时间顺序和逻辑清晰
## 章节分类规则
2026-01-07 11:56:53 +08:00
- 童年相关 "童年与成长背景"
- 学校老师同学 "教育经历与青年时期"
- 工作职业成就 "主要成就与巅峰时刻" "崭露头角"
- 困难挫折 "挫折、挑战与重大转折"
- 伴侣孩子家庭生活 "家庭与情感"
- 价值观信念座右铭 "信念与价值观"
- 总结感悟展望 "人生总结"
"""
2026-03-23 13:54:41 +08:00
def get_memoir_fidelity_system_prompt() -> str:
"""叙事/标题生成专用:准确性优先,禁止编造事实(与 get_system_prompt 分离)。"""
return """你是回忆录编辑助手,任务是把用户口述整理为第一人称书面叙述。
## 事实边界(必须遵守,优先于文采)
1. **正文只能展开本段用户口述区块中的内容**若输入中有相关记忆摘录等参考区其中信息**不得**写成本人本轮亲口经历的细节最多用一两句作主题衔接且不得引入摘录里才有的具体人名地点时间对话数字
2. **禁止编造**不得新增用户未提及的具体人物姓名对话原文地点时间事件经过因果数字不得推断性心理描写或典型年代场景填充
3. **禁止为凑字数扩写**材料短则输出短段落数量与长度随材料而定
4. 允许去除口语赘词与寒暄调整语序合并重复指代把口语改为书面语**不得**用虚构细节让文章更好看
## 用户档案与阶段信息
- 用户基本信息时间参考仅可使用其中**已写明**的条目不得把档案中的出生地等写进正文除非用户在本段口述里已提及或明确关联"""
def get_narrative_editor_system_prompt() -> str:
"""叙事改写:准确性系统提示 + 可执行文体约束(不用 get_system_prompt 中的「过渡句/生动细节」泛化指令)。"""
return f"""{get_memoir_fidelity_system_prompt()}
## 文体(在严守事实的前提下)
- 使用第一人称书面语不要直接引用对话原话
- 不使用 Markdown 标题#、##)、不使用表格。
- 如有衔接上下文仅保持语气与时间线连贯不重复已有段落全文"""
2026-01-07 11:56:53 +08:00
def get_chapter_classification_prompt(segments_text: str) -> str:
2026-03-23 13:54:41 +08:00
"""获取章节分类的提示词。
返回 none 的语义与 Story 路由get_story_route_prompt / get_story_batch_plan_prompt
可独立讲述的一段人生经历对齐none 表示本段不足以单独成篇进入回忆录 Story 流水线
"""
2026-01-07 11:56:53 +08:00
return f"""{get_system_prompt()}
2026-03-23 13:54:41 +08:00
请分析以下对话内容**忽略其中的语气词寒暄和无关对话**判断应归类到哪个章节类别或是否不足以写入回忆录正文
## 章节类别
2026-01-07 11:56:53 +08:00
- childhood: 童年与成长背景
- education: 教育经历与青年时期
- career_early: 崭露头角早期事业
- career_achievement: 主要成就与巅峰时刻
- career_challenge: 挫折挑战与重大转折
- family: 家庭与情感
- beliefs: 信念与价值观
- summary: 人生总结
2026-03-23 13:54:41 +08:00
## 何时必须返回 none与「零散档案点」区分
若去掉寒暄后内容仅为**档案式点状信息****没有可讲述的叙事骨架**无事件场景过程互动或情绪展开则必须返回 **none**例如
- 仅出生年份籍贯一笔职业名词姓名等单句事实
- 仅罗列事实无画面与过程的短答
以下情况**不是** none篇幅短但已构成**微型故事**有画面动作对话转折感受应归入最贴合的章节类别
## 示例(仅作判断参考)
- 应返回 none我1999年出生的籍贯上海工程师
- 应返回 childhood或其它合适类别小学时有次下大雨爷爷背我过河鞋全湿了他一直笑
2026-01-07 11:56:53 +08:00
对话内容
{segments_text}
2026-03-23 13:54:41 +08:00
请只返回章节类别英文 keychildhood不要返回其它说明
若内容不足以独立成篇仅为零散信息返回 none"""
2026-01-07 11:56:53 +08:00
2026-03-19 14:36:14 +08:00
def get_text_rewrite_prompt(
segments_text: str, chapter_category: str, existing_content: str = ""
) -> str:
2026-01-07 11:56:53 +08:00
"""获取文本改写的提示词"""
chapter_name = CHAPTER_CATEGORIES.get(chapter_category, chapter_category)
2026-03-19 14:36:14 +08:00
existing_section = (
f"\n\n已有章节内容:\n{existing_content}" if existing_content else ""
)
2026-01-07 11:56:53 +08:00
return f"""{get_system_prompt()}
请将以下口语化的对话内容改写为书面语归类到"{chapter_name}"章节
对话内容
{segments_text}
{existing_section}
请按照以下格式返回 JSON
{{
"title": "章节标题",
重构回忆录为 story-first / markdown-first 架构并整合图片意图与前端 UI 修复 本次 squash merge 将 codex-story-first-image-intent 的整体改动合入 development,核心内容包括: 1. 后端数据与迁移:新增 stories、story_versions、story_image_intents、chapter_cover_intents、assets 等模型与 Alembic 迁移,建立 story-first、markdown-first、asset-first 的主数据链路。 2. 生成与任务链:引入 StoryBuilderOrchestrator、ChapterComposerOrchestrator、story_image_tasks、chapter_cover_tasks,图片生成从正文占位符改为结构化 intent -> asset -> markdown 回填。 3. 并发与一致性:为 story/chapter intent 增加 claim_token、claimed_at、attempt_count,采用数据库原子 claim 为主、Redis 锁为辅,避免重复生成、锁误删和 processing 卡死。 4. Memoir 读写路径:章节 canonical_markdown 成为正文真源,列表/详情接口补齐 markdown、cover_asset、word_count 等字段,PDF 与 asset 解析链路同步升级。 5. Memory / Retrieval:扩展 transcript ingest、chunking、evidence 检索与 story 聚合基础设施,为后续 story-first RAG 与多 agent 编排提供底座。 6. App 端体验:章节页继续走 MarkdownRenderer 阅读链,同时吸收 fix3-19 的跨平台 UI glitch 修复;更新对话页、首页、文案资源与章节列表映射逻辑。 7. 测试与文档:补充 asset resolver、story image task、章节封面派发、markdown 映射等回归测试,并加入图片占位符退役设计文档。
2026-03-20 10:30:07 +08:00
"content": "改写后的书面语内容",
2026-01-21 22:31:03 +01:00
"summary": "章节摘要50字以内"
2026-01-07 11:56:53 +08:00
}}
要求
1. 标题要简洁有力能概括章节主题
2. 内容要流畅自然保持原意和情感
重构回忆录为 story-first / markdown-first 架构并整合图片意图与前端 UI 修复 本次 squash merge 将 codex-story-first-image-intent 的整体改动合入 development,核心内容包括: 1. 后端数据与迁移:新增 stories、story_versions、story_image_intents、chapter_cover_intents、assets 等模型与 Alembic 迁移,建立 story-first、markdown-first、asset-first 的主数据链路。 2. 生成与任务链:引入 StoryBuilderOrchestrator、ChapterComposerOrchestrator、story_image_tasks、chapter_cover_tasks,图片生成从正文占位符改为结构化 intent -> asset -> markdown 回填。 3. 并发与一致性:为 story/chapter intent 增加 claim_token、claimed_at、attempt_count,采用数据库原子 claim 为主、Redis 锁为辅,避免重复生成、锁误删和 processing 卡死。 4. Memoir 读写路径:章节 canonical_markdown 成为正文真源,列表/详情接口补齐 markdown、cover_asset、word_count 等字段,PDF 与 asset 解析链路同步升级。 5. Memory / Retrieval:扩展 transcript ingest、chunking、evidence 检索与 story 聚合基础设施,为后续 story-first RAG 与多 agent 编排提供底座。 6. App 端体验:章节页继续走 MarkdownRenderer 阅读链,同时吸收 fix3-19 的跨平台 UI glitch 修复;更新对话页、首页、文案资源与章节列表映射逻辑。 7. 测试与文档:补充 asset resolver、story image task、章节封面派发、markdown 映射等回归测试,并加入图片占位符退役设计文档。
2026-03-20 10:30:07 +08:00
3. 如果已有章节内容请将新内容与已有内容自然融合"""
2026-01-21 22:31:03 +01:00
2026-03-19 14:36:14 +08:00
def get_state_extraction_prompt(
user_message: str, current_stage: str, stage_slots: dict
) -> str:
2026-01-21 22:31:03 +01:00
"""抽取结构化信息并判断阶段"""
slot_keys = list(stage_slots.keys())
all_stage_slots = {
"childhood": ["place", "people", "daily_life", "emotion", "turning_event"],
"education": ["school", "city", "motivation", "challenge", "change"],
"career": ["job", "environment", "decision", "pressure", "growth"],
"family": ["relationship", "conflict", "support", "responsibility", "change"],
"belief": ["value", "regret", "pride", "lesson"],
}
2026-01-21 22:31:03 +01:00
return f"""{get_system_prompt()}
你需要从用户话语中**先提炼与人生经历相关的核心内容**然后抽取结构化信息并判断用户实际在谈论哪个人生阶段
系统当前跟踪的阶段{current_stage}
该阶段可填 slots{slot_keys}
2026-01-21 22:31:03 +01:00
所有阶段及其 slots 参考
{json.dumps(all_stage_slots, ensure_ascii=False, indent=2)}
2026-01-21 22:31:03 +01:00
用户话语
{user_message}
请只返回 JSON格式如下
{{
"detected_stage": "childhood|education|career|family|belief",
"slots": {{
"slot_key": "snippet"
}},
"emotion": "neutral|warm|low|highlight",
"is_new_chapter": true
}}
要求
1. **先忽略话语中的语气词填充词寒暄与AI的交互指令等无关内容**只关注涉及人生经历的实质信息
2. **detected_stage 必须根据用户话语的实际内容判断**不要默认沿用系统当前阶段用户可能在聊不同阶段的事情
3. slots key 必须属于 detected_stage 对应的 slot 列表
4. slots 只填写确实提到的与人生经历相关的实质内容
5. **snippet 应是提炼后的核心信息**去除语气词和冗余表达50 字以内
6. 如果用户话语中没有任何与人生经历相关的实质内容如纯粹的寒暄指令语气词slots 为空对象
2026-01-21 22:31:03 +01:00
"""
def _build_age_hint(stage: str, birth_year: Optional[int] = None) -> str:
"""根据人生阶段和出生年份推算大致年龄区间"""
if not birth_year:
return ""
stage_age_ranges = {
"childhood": (0, 12),
"education": (6, 22),
"career": (18, 60),
"career_early": (18, 30),
"career_achievement": (25, 55),
"career_challenge": (20, 55),
"family": (20, 60),
"belief": (30, 70),
"beliefs": (30, 70),
"summary": (50, 80),
}
age_range = stage_age_ranges.get(stage)
if not age_range:
return ""
year_start = birth_year + age_range[0]
year_end = birth_year + age_range[1]
return f"大约 {year_start}-{year_end} 年({age_range[0]}-{age_range[1]} 岁)"
def get_creative_title_prompt(
stage: str,
emotion: str,
slots: dict,
user_profile: str = "",
birth_year: Optional[int] = None,
) -> str:
2026-03-23 13:54:41 +08:00
"""生成故事标题:概括口述事实或主题,禁止纯意象编造。"""
age_hint = _build_age_hint(stage, birth_year)
profile_section = f"\n用户基本信息:\n{user_profile}" if user_profile else ""
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
2026-03-23 13:54:41 +08:00
return f"""{get_memoir_fidelity_system_prompt()}
请根据下面阶段情绪可用信息生成 **1 **回忆录故事标题
2026-01-21 22:31:03 +01:00
阶段{stage}
2026-01-21 22:31:03 +01:00
情绪{emotion}
2026-03-23 13:54:41 +08:00
可用信息含口述 slots 与档案{slots}{profile_section}{time_section}
2026-01-21 22:31:03 +01:00
要求
2026-03-23 13:54:41 +08:00
1. 格式时间标注 · 标题正文时间标注可用年龄年代或阶段须与上列信息一致勿编造未出现的年份
2. 标题正文 **1218 **必须概括 **用户口述或 slots 中已出现的主题/事实****禁止**使用用户未提及的纯文学意象如未提巷子/蝉鸣则不得写
3. 可略带文采但不得引入口述中不存在的人
只输出标题这一行文字不要加引号或书名号
2026-01-21 22:31:03 +01:00
"""
def get_narrative_prompt(
stage: str,
slots: dict,
new_content: str,
existing_content: str = "",
user_profile: str = "",
birth_year: Optional[int] = None,
archived_summaries: str = "",
) -> str:
"""将新对话改写为叙述(只输出新内容的改写,不重复已有内容)"""
context_tail = ""
if existing_content:
2026-03-19 14:36:14 +08:00
context_tail = (
existing_content[-300:] if len(existing_content) > 300 else existing_content
)
context_section = (
f"\n\n【衔接上下文(已有内容的末尾,仅供参考衔接,不要重复)】:\n{context_tail}"
if context_tail
else ""
)
archived_section = (
f"\n\n【已删除的该类别历史章节(仅供参考,请勿直接使用或重复)】:\n{archived_summaries}"
if archived_summaries
else ""
)
profile_section = f"\n\n用户基本信息:\n{user_profile}" if user_profile else ""
age_hint = _build_age_hint(stage, birth_year)
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
2026-03-23 13:54:41 +08:00
return f"""{get_narrative_editor_system_prompt()}
2026-01-21 22:31:03 +01:00
阶段{stage}
2026-03-23 13:54:41 +08:00
可用信息slots仅可复述其中已出现事实{slots}{profile_section}{time_section}
2026-01-21 22:31:03 +01:00
2026-03-23 13:54:41 +08:00
输入材料请严格区分本段口述与参考区规则见系统说明
2026-01-21 22:31:03 +01:00
{new_content}
{context_section}
{archived_section}
2026-01-21 22:31:03 +01:00
2026-03-23 13:54:41 +08:00
## 步骤
1. 本段用户口述提炼可写事实丢弃语气词寒暄 AI 的交互
2. 改写为第一人称书面叙述可调整语序与用词**不得**新增事实
3. 若材料中无值得记录的人生经历内容输出空字符串
## 格式
- 不要插入章节标题或 `#`、`##`;不要用 Markdown 表格。
- 不要写入与本段用户口述无关的交互套话
只输出改写后的正文无内容则输出空字符串
2026-01-21 22:31:03 +01:00
"""
def get_narrative_json_prompt(
stage: str,
slots: dict,
new_content: str,
existing_content: str = "",
user_profile: str = "",
birth_year: Optional[int] = None,
) -> str:
"""将新对话改写为叙述,输出 JSON 格式paragraphs: [{content, image_description}]"""
context_tail = ""
if existing_content:
2026-03-19 14:36:14 +08:00
context_tail = (
existing_content[-300:] if len(existing_content) > 300 else existing_content
)
context_section = (
f"\n\n【衔接上下文(已有内容的末尾,仅供参考衔接,不要重复)】:\n{context_tail}"
if context_tail
else ""
)
profile_section = f"\n\n用户基本信息:\n{user_profile}" if user_profile else ""
age_hint = _build_age_hint(stage, birth_year)
time_section = f"\n时间参考:{age_hint}" if age_hint else ""
2026-03-23 13:54:41 +08:00
return f"""{get_narrative_editor_system_prompt()}
2026-03-23 13:54:41 +08:00
请将本段用户口述改写为第一人称书面叙述并输出 ** JSON**不要包含任何其他文字或 markdown 代码块
阶段{stage}
2026-03-23 13:54:41 +08:00
可用信息slots{slots}{profile_section}{time_section}
2026-03-23 13:54:41 +08:00
输入材料
{new_content}
{context_section}
## 要求
2026-03-23 13:54:41 +08:00
1. **只展开本段用户口述**若有参考摘录区不得把摘录中的具体事实写成本轮亲历经历见系统说明
2. 过滤语气词寒暄 AI 的交互不重复已有故事全文本批只写同一主题/事件链
3. 段落数量与每段长度**随材料而定**禁止为凑字数编造
4. 使用第一人称不要直接引用原话不要用 `#`、`##`、表格。
## 输出格式(严格 JSON
{{
"paragraphs": [
重构回忆录为 story-first / markdown-first 架构并整合图片意图与前端 UI 修复 本次 squash merge 将 codex-story-first-image-intent 的整体改动合入 development,核心内容包括: 1. 后端数据与迁移:新增 stories、story_versions、story_image_intents、chapter_cover_intents、assets 等模型与 Alembic 迁移,建立 story-first、markdown-first、asset-first 的主数据链路。 2. 生成与任务链:引入 StoryBuilderOrchestrator、ChapterComposerOrchestrator、story_image_tasks、chapter_cover_tasks,图片生成从正文占位符改为结构化 intent -> asset -> markdown 回填。 3. 并发与一致性:为 story/chapter intent 增加 claim_token、claimed_at、attempt_count,采用数据库原子 claim 为主、Redis 锁为辅,避免重复生成、锁误删和 processing 卡死。 4. Memoir 读写路径:章节 canonical_markdown 成为正文真源,列表/详情接口补齐 markdown、cover_asset、word_count 等字段,PDF 与 asset 解析链路同步升级。 5. Memory / Retrieval:扩展 transcript ingest、chunking、evidence 检索与 story 聚合基础设施,为后续 story-first RAG 与多 agent 编排提供底座。 6. App 端体验:章节页继续走 MarkdownRenderer 阅读链,同时吸收 fix3-19 的跨平台 UI glitch 修复;更新对话页、首页、文案资源与章节列表映射逻辑。 7. 测试与文档:补充 asset resolver、story image task、章节封面派发、markdown 映射等回归测试,并加入图片占位符退役设计文档。
2026-03-20 10:30:07 +08:00
{{"content": "段落正文"}},
...
]
}}
2026-03-23 13:54:41 +08:00
- content仅含正文
2026-03-23 13:54:41 +08:00
若无值得记录的内容{{"paragraphs": []}}
"""
2026-03-20 15:15:35 +08:00
def get_story_route_prompt(
*,
chapter_category: str,
chapter_title: str,
batch_transcript: str,
candidate_stories_json: str,
) -> str:
2026-03-23 13:54:41 +08:00
"""Celery 批次:判断写入新 story 还是追加已有 story。输出严格 JSON。
故事= 可独立讲述的一段人生经历进入本步的批次已满足 get_chapter_classification_prompt
中章节级分类 none二者语义一致
"""
2026-03-20 15:15:35 +08:00
return f"""你是回忆录编辑助手。根据本批用户口述与候选故事列表,决定:
- append_story内容明显延续补充某一已有故事的主题与时间线且能对应到具体 candidate id
- new_story新话题新人生阶段片段或与所有候选故事都不够贴合
refactor(api,expo): 多智能体与会话收敛、回忆录兼容层移除、后端测试集大幅删减 - 对齐「多智能体收敛」与「回忆录 stories-first / markdown-first」方向:收紧运行时契约、 删除过渡兼容路径与双轨逻辑,并同步更新客户端与文档。 - Chat:以 ChatOrchestrator 为实时编排入口;删除独立 conversation_agent,精简 prompts。 - Memoir:删除 memory_agent;MemoirOrchestrator、classification / story_route 与 prompts 收敛到 prepare_batches + run_story_pipeline_for_category_batch 主链路。 - 将 agents 侧 processor 迁入 feature 层为 background_runner,并移除 features 下重复/过时 processor 封装。 - 新增 history_store,强化「conversation_messages 为 DB 真源、Redis 为缓存」模型。 - 调整 models、repo、service、session_history;精简 WS message_types,重构 pipeline 与 router。 - 移除章节占位、整章再生等旧路径;章节列表与封面逻辑要求 story 关联;收紧 cover 资格与 enqueue。 - helpers、repo、service、router、reading_segment_materialize、story_pipeline_sync、pdf_service 等按 canonical markdown / cover_asset_id 收缩;删除 memoir_images/provider 等冗余。 - tasks:memoir_tasks、chapter_cover_tasks 等大幅瘦身;story_image_tasks 等与当前图片任务对齐。 - core:config、logging、redis、task_tracker 小幅调整。 - auth / user / payment / quota:路由或服务侧删减过时接口或逻辑(如 payment router 行数减少)。 - pyproject.toml、development.sh、.env.example / .env.production、README 等同步说明或变量。 - Alembic 0001_initial_schema 微调(与当前 schema 叙事一致的小改动)。 - 回忆录:types / mappers / api、章节页与 memoir 页与后端契约对齐;markdown-renderer 调整。 - 语音:删除 voice/player,voice-segment-store 相应精简。 - api/tests:删除 conftest 及绝大部分既有测试文件(websocket_baseline、conversation、memoir 图片、PDF、SMS 等),属有意收缩/待按 backend-test-system 重建的信号。 - docs:新增多智能体收敛与移除兼容层计划摘要;更新 story-first 设计、backend-test-system、 multi-agent-refactor-plan、实施总结等。 BREAKING CHANGE: 后端对外契约、回忆录章节字段与若干路由/任务行为已变更;大量 API 测试被移除, CI 若依赖这些用例需按新策略补测或调整流水线。
2026-03-22 16:45:57 +08:00
故事在此指**可独立讲述的一段人生经历**单一主题或同一事件链不要假设本批里包含多个互不相关的故事多段由系统其它步骤处理
2026-03-23 13:54:41 +08:00
**new_story_title reason 只能依据口述中已有信息概括不得编造口述未出现的人**
2026-03-20 15:15:35 +08:00
当前章节写作容器
- category: {chapter_category}
- title: {chapter_title}
本批口述合并文本
{batch_transcript}
候选故事仅允许在 append 时选择其中的 idid 必须原样复制
{candidate_stories_json}
## 输出 JSON仅此一个对象不要 markdown
{{
"decision": "new_story" | "append_story",
"target_story_id": "<uuid 或 nullappend 时必填且必须来自候选>",
"new_story_title": "<短标题6-20 字new_story 时必填append 时可 null>",
"reason": "<一句中文理由>"
}}
规则
- 若无法自信匹配某一候选 new_story
- new_story_title 应概括本批新内容不要与候选标题重复
"""
refactor(api,expo): 多智能体与会话收敛、回忆录兼容层移除、后端测试集大幅删减 - 对齐「多智能体收敛」与「回忆录 stories-first / markdown-first」方向:收紧运行时契约、 删除过渡兼容路径与双轨逻辑,并同步更新客户端与文档。 - Chat:以 ChatOrchestrator 为实时编排入口;删除独立 conversation_agent,精简 prompts。 - Memoir:删除 memory_agent;MemoirOrchestrator、classification / story_route 与 prompts 收敛到 prepare_batches + run_story_pipeline_for_category_batch 主链路。 - 将 agents 侧 processor 迁入 feature 层为 background_runner,并移除 features 下重复/过时 processor 封装。 - 新增 history_store,强化「conversation_messages 为 DB 真源、Redis 为缓存」模型。 - 调整 models、repo、service、session_history;精简 WS message_types,重构 pipeline 与 router。 - 移除章节占位、整章再生等旧路径;章节列表与封面逻辑要求 story 关联;收紧 cover 资格与 enqueue。 - helpers、repo、service、router、reading_segment_materialize、story_pipeline_sync、pdf_service 等按 canonical markdown / cover_asset_id 收缩;删除 memoir_images/provider 等冗余。 - tasks:memoir_tasks、chapter_cover_tasks 等大幅瘦身;story_image_tasks 等与当前图片任务对齐。 - core:config、logging、redis、task_tracker 小幅调整。 - auth / user / payment / quota:路由或服务侧删减过时接口或逻辑(如 payment router 行数减少)。 - pyproject.toml、development.sh、.env.example / .env.production、README 等同步说明或变量。 - Alembic 0001_initial_schema 微调(与当前 schema 叙事一致的小改动)。 - 回忆录:types / mappers / api、章节页与 memoir 页与后端契约对齐;markdown-renderer 调整。 - 语音:删除 voice/player,voice-segment-store 相应精简。 - api/tests:删除 conftest 及绝大部分既有测试文件(websocket_baseline、conversation、memoir 图片、PDF、SMS 等),属有意收缩/待按 backend-test-system 重建的信号。 - docs:新增多智能体收敛与移除兼容层计划摘要;更新 story-first 设计、backend-test-system、 multi-agent-refactor-plan、实施总结等。 BREAKING CHANGE: 后端对外契约、回忆录章节字段与若干路由/任务行为已变更;大量 API 测试被移除, CI 若依赖这些用例需按新策略补测或调整流水线。
2026-03-22 16:45:57 +08:00
def get_story_batch_plan_prompt(
*,
chapter_category: str,
chapter_title: str,
segments_json: str,
candidate_stories_json: str,
) -> str:
"""同一章节类别下多 segment划分为若干写入单元每单元 new 或 append。输出严格 JSON。"""
return f"""你是回忆录编辑助手。下面同一章节类别下有一批**按时间顺序**的用户口述片段(每段有 id 与文本)。
## 「故事」定义(必须遵守)
一段故事= **可独立讲述的一段人生经历**单一主题或同一事件链能单独成篇若话题切换时间线跳到另一件事人物/主线明显变化应作为**新的故事**new_story而不是塞进同一段 append
2026-03-23 13:54:41 +08:00
**new_story_title reason 只能依据各 segment 文本中已有信息不得编造口述未出现的事实**
refactor(api,expo): 多智能体与会话收敛、回忆录兼容层移除、后端测试集大幅删减 - 对齐「多智能体收敛」与「回忆录 stories-first / markdown-first」方向:收紧运行时契约、 删除过渡兼容路径与双轨逻辑,并同步更新客户端与文档。 - Chat:以 ChatOrchestrator 为实时编排入口;删除独立 conversation_agent,精简 prompts。 - Memoir:删除 memory_agent;MemoirOrchestrator、classification / story_route 与 prompts 收敛到 prepare_batches + run_story_pipeline_for_category_batch 主链路。 - 将 agents 侧 processor 迁入 feature 层为 background_runner,并移除 features 下重复/过时 processor 封装。 - 新增 history_store,强化「conversation_messages 为 DB 真源、Redis 为缓存」模型。 - 调整 models、repo、service、session_history;精简 WS message_types,重构 pipeline 与 router。 - 移除章节占位、整章再生等旧路径;章节列表与封面逻辑要求 story 关联;收紧 cover 资格与 enqueue。 - helpers、repo、service、router、reading_segment_materialize、story_pipeline_sync、pdf_service 等按 canonical markdown / cover_asset_id 收缩;删除 memoir_images/provider 等冗余。 - tasks:memoir_tasks、chapter_cover_tasks 等大幅瘦身;story_image_tasks 等与当前图片任务对齐。 - core:config、logging、redis、task_tracker 小幅调整。 - auth / user / payment / quota:路由或服务侧删减过时接口或逻辑(如 payment router 行数减少)。 - pyproject.toml、development.sh、.env.example / .env.production、README 等同步说明或变量。 - Alembic 0001_initial_schema 微调(与当前 schema 叙事一致的小改动)。 - 回忆录:types / mappers / api、章节页与 memoir 页与后端契约对齐;markdown-renderer 调整。 - 语音:删除 voice/player,voice-segment-store 相应精简。 - api/tests:删除 conftest 及绝大部分既有测试文件(websocket_baseline、conversation、memoir 图片、PDF、SMS 等),属有意收缩/待按 backend-test-system 重建的信号。 - docs:新增多智能体收敛与移除兼容层计划摘要;更新 story-first 设计、backend-test-system、 multi-agent-refactor-plan、实施总结等。 BREAKING CHANGE: 后端对外契约、回忆录章节字段与若干路由/任务行为已变更;大量 API 测试被移除, CI 若依赖这些用例需按新策略补测或调整流水线。
2026-03-22 16:45:57 +08:00
## 任务
将本批 segment **划分为连续若干块**每块包含至少一个 segment顺序不能打乱每个 segment 必须恰好属于一块对每一块决定
- **append_story**内容明显延续补充**某一已有候选故事**的主题与时间线且能对应到具体 candidate id
- **new_story**新话题与所有候选故事都不够贴合或应独立成篇的片段
当前章节写作容器
- category: {chapter_category}
- title: {chapter_title}
本批口述片段JSON 数组顺序即口述顺序
{segments_json}
候选故事仅允许在 append 时选择其中的 idid 必须原样复制
{candidate_stories_json}
## 输出 JSON仅此一个对象不要 markdown
{{
"units": [
{{
"segment_ids": ["<按顺序列出本块包含的 segment id>"],
"decision": "new_story" | "append_story",
"target_story_id": "<uuid 或 nullappend 时必填且必须来自候选>",
"new_story_title": "<短标题6-20 字new_story 时必填append 时可 null>",
"reason": "<一句中文理由,可选>"
}}
]
}}
规则
- `units` 中所有 `segment_ids` 拼接后必须**不重不漏**地覆盖本批全部 id且顺序与本批口述片段数组一致
- 若无法自信匹配某一候选对该块选 new_story
- new_story_title 应概括该块内容不要与候选标题重复
"""
2026-03-23 13:54:41 +08:00
def format_narrative_user_content(oral_text: str, evidence_text: str = "") -> str:
"""
将口述与检索摘录分区供叙事模型区分亲历与参考材料
evidence 为空时仅输出口述块
"""
oral = (oral_text or "").strip()
ev = (evidence_text or "").strip()
if not ev:
return f"【本段用户口述】\n{oral}"
return (
"【本段用户口述】\n"
f"{oral}\n\n"
"【仅供参考的相关记忆摘录(非本段口述;不得把其中具体事实写成本轮亲历经历,仅可作主题衔接)】\n"
f"{ev}"
)
2026-03-20 15:15:35 +08:00
def format_evidence_chunks_for_prompt(evidence: dict) -> str:
"""将 retrieve_evidence 结果格式化为简短文本,供叙事 prompt 使用。"""
chunks = evidence.get("relevant_chunks") or []
facts = evidence.get("relevant_facts") or []
parts: list[str] = []
for c in chunks[:10]:
content = (
c.get("content", "") if isinstance(c, dict) else getattr(c, "content", "")
)
if content:
parts.append(content.strip())
for f in facts[:5]:
if isinstance(f, dict):
subj = f.get("subject", "")
pred = f.get("predicate", "")
obj = f.get("object_json", "")
if subj or pred:
parts.append(f"{subj} {pred} {obj}")
else:
parts.append(f"{getattr(f, 'subject', '')} {getattr(f, 'predicate', '')}")
return "\n\n".join(parts) if parts else ""