fix(memoir): 改善 story 合并决策,少生碎片篇

以前模型只看到很短预览,还容易被引导成新建 story。现在优先用已有摘要、
按需带正文片段,并区分「像续写同一主题」和「像换了一件事」;
beliefs/summary 更鼓励接着写, career/童年等仍可按新事件新开。
This commit is contained in:
Kevin
2026-04-03 11:02:05 +08:00
parent 545d5a4ae0
commit b853b986dd
7 changed files with 715 additions and 49 deletions

View File

@@ -436,6 +436,40 @@ def get_narrative_merge_json_prompt(
"""
def story_route_merge_hint_for_category(chapter_category: str) -> str:
"""按章节类目的 append/new 倾向(与 StoryRouteAgent 路由提示共用)。"""
cc = (chapter_category or "").strip()
if cc in ("beliefs", "summary"):
return (
"### 本章类别路由倾向(强主题容器)\n"
"- 多条短感悟、同一价值维度、同一总结脉络的补充 → **优先 append_story**"
"选最匹配的一条候选 id。\n"
"- 仅在用户明确讲述**与所有候选主题明显不相关**、且可独立成篇的长经历时,才用 new_story。"
)
if cc == "family":
return (
"### 本章类别路由倾向(家庭)\n"
"- 原则性反思、关系模式、相处之道的补充 → **倾向 append_story**。\n"
"- **明确的新事件链**(新场景、新时间线、不同人物组合的新经历)→ 可 new_story。"
)
if cc in (
"childhood",
"education",
"career_early",
"career_achievement",
"career_challenge",
):
return (
"### 本章类别路由倾向(经历叙事)\n"
"- 以具体事件链为主:**不同事件 / 时期 / 地点** → 可 new_story。\n"
"- 明显是**同一段经历的续叙、补充细节** → append_story。"
)
return (
"### 本章类别路由倾向(一般)\n"
"- 同时参考「主题连续性」与「事件切换」两类信号做判断。"
)
def get_story_route_prompt(
*,
chapter_category: str,
@@ -448,15 +482,24 @@ def get_story_route_prompt(
「故事」= 可独立讲述的一段人生经历;进入本步的批次已归入具体 chapter category
(含模型返回 none 或零散档案启发式时映射的 summary
"""
return f"""你是回忆录编辑助手。根据本批用户口述与候选故事列表,决定:
- append_story内容明显延续、补充某一已有故事的主题与时间线且能对应到具体 candidate id
- new_story新话题、新人生阶段片段或与所有候选故事都不够贴合
merge_hint = story_route_merge_hint_for_category(chapter_category)
return f"""你是回忆录编辑助手。根据本批用户口述与【候选故事】决定 append_story 或 new_story。
**JSON 输出**:接口已启用 `response_format=json_object`,只输出下面 schema 的一个合法 JSON 对象,不要 markdown。
「故事」在此指:**可独立讲述的一段人生经历**——单一主题或同一事件链;不要假设本批里包含多个互不相关的故事(多段由系统其它步骤处理)。
## 两层决策标准(必须先在心里过一遍)
1. **主题连续性信号**:价值观、关系模式、长期总结、同一反思维度;口述是否像在**同一主题容器**里加厚?
2. **事件切换信号**:是否出现**新人物组合、新地点、新时间段、新事件因果链**,与候选正文明显是**另一段经历**
**路由边界(必须遵守)**:仅根据下方「本批口述合并文本」判断 new_story 与 append_story不得将系统检索摘要、记忆摘录、图谱事实或其它非用户口述材料当作本批口述内容来匹配候选故事
- 类别 **beliefs / summary**:更重主题连续性;除非事件切换信号极强,否则倾向 append
- 类别 **career_* / childhood / education**:更重事件链;不同事件可 new同一经历续聊则 append。
- 类别 **family**:两类信号兼顾——原则/关系反思倾向 append明确新事件链可 new。
{merge_hint}
**路由边界(必须遵守)**:仅根据下方「本批口述合并文本」判断;不得将系统检索摘要、记忆摘录等当作本批口述内容来匹配候选。
**候选故事说明**:列表项可能含 `summary` 或 `body_for_route`(正文摘要);仅含 `preview` 者为索引项,信息不全。**append 时优先匹配带 summary 或 body 的条目**;索引项仅作候选 id 备忘。
当前章节(写作容器):
- category: {chapter_category}
@@ -465,7 +508,7 @@ def get_story_route_prompt(
【本批口述合并文本】
{batch_transcript}
【候选故事】(仅允许在 append 时选择其中的 idid 必须原样复制)
【候选故事】append 时 target_story_id 必须来自下列 id原样复制)
{candidate_stories_json}
## 输出 JSON仅此一个对象不要 markdown
@@ -476,7 +519,8 @@ def get_story_route_prompt(
}}
规则:
- 若无法自信匹配某一候选,选 new_story
- **不要**只因「不太确定」就选 new_story在主题可并入某一候选时应 append_story
- 仅当口述与**所有**候选在两层标准下都明显不兼容时,才选 new_story。
"""
@@ -488,17 +532,28 @@ def get_story_batch_plan_prompt(
candidate_stories_json: str,
) -> str:
"""同一章节类别下多 segment划分为若干写入单元每单元 new 或 append。输出严格 JSON。"""
merge_hint = story_route_merge_hint_for_category(chapter_category)
return f"""你是回忆录编辑助手。下面同一章节类别下有一批**按时间顺序**的用户口述片段(每段有 id 与文本)。
**JSON 输出**:接口已启用 `response_format=json_object`,只输出下面 schema 的一个合法 JSON 对象,不要 markdown。
## 两层决策标准(每一块都要应用)
1. **主题连续性信号**:价值观、关系模式、长期总结、同一反思维度。
2. **事件切换信号**:新人物组合、新地点、新时间段、新事件因果链。
各类别倾向与单段路由一致beliefs/summary 重主题连续性career/childhood/education 重事件链family 兼顾。
{merge_hint}
## 「故事」定义(必须遵守)
一段「故事」= **可独立讲述的一段人生经历**:单一主题或同一事件链,能单独成篇。若话题切换、时间线跳到另一件事、人物/主线明显变化,应作为**新的故事**new_story而不是塞进同一段 append
一段「故事」= **可独立讲述的一段人生经历**。**同一主题容器内的连续口述**应并入同一块 append而不是切碎成多个 new_story
## 任务
将本批 segment **划分为连续若干块**(每块包含至少一个 segment顺序不能打乱每个 segment 必须恰好属于一块)。对每一块决定:
- **append_story**内容明显延续、补充**某一已有候选故事**的主题与时间线,且能对应到具体 candidate id
- **new_story**新话题、与所有候选故事都不够贴合、或应独立成篇的片段
将本批 segment **划分为连续若干块**(每块至少一个 segment顺序不能打乱每个 segment 必须恰好属于一块)。对每一块决定:
- **append_story**与某一候选在两层标准下可合并,且能对应到具体 candidate id
- **new_story**该块与**所有**候选都明显不兼容,或确认为独立新经历
**候选故事说明**:条目可能含 `summary`/`body_for_route`;仅 `preview` 者为索引项。**优先用带摘要/正文的条目做 append 目标**。
当前章节(写作容器):
- category: {chapter_category}
@@ -507,7 +562,7 @@ def get_story_batch_plan_prompt(
【本批口述片段】JSON 数组,顺序即口述顺序)
{segments_json}
【候选故事】(仅允许在 append 时选择其中的 idid 必须原样复制)
【候选故事】append 时 target_story_id 必须来自下列 id原样复制)
{candidate_stories_json}
## 输出 JSON仅此一个对象不要 markdown
@@ -524,7 +579,7 @@ def get_story_batch_plan_prompt(
规则:
- `units` 中所有 `segment_ids` 拼接后,必须**不重不漏**地覆盖本批全部 id且顺序与【本批口述片段】数组一致
- 若无法自信匹配某一候选,对该块选 new_story
- **不要**仅因不确定就对整块选 new_story能并入候选时应 append_story
"""