fix(memoir): 改善 story 合并决策,少生碎片篇
以前模型只看到很短预览,还容易被引导成新建 story。现在优先用已有摘要、 按需带正文片段,并区分「像续写同一主题」和「像换了一件事」; beliefs/summary 更鼓励接着写, career/童年等仍可按新事件新开。
This commit is contained in:
128
.cursor/plans/story_route_append_context.plan.md
Normal file
128
.cursor/plans/story_route_append_context.plan.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
name: Story route append context
|
||||
overview: 候选载荷增胖(summary 优先 + 可配置预算)+ category-aware prompt 纠偏 + 最小测试集;不扩大路由体系、不改 schema、不接离线合并。
|
||||
todos:
|
||||
- id: config-budget
|
||||
content: Add Settings — story_route_candidate_body_max_chars, total_max, head/tail, summary_min_len (optional)
|
||||
status: completed
|
||||
- id: payload-builder
|
||||
content: build_route_candidate_rows — fixed sort, summary-first body rules, total budget downgrade to index rows
|
||||
status: completed
|
||||
- id: prompts-merge-bias
|
||||
content: get_story_route_prompt + get_story_batch_plan — two-layer criteria, category blocks, remove default-to-new_story
|
||||
status: completed
|
||||
- id: wire-agent
|
||||
content: StoryRouteAgent decide/plan_batch use new builder + prompts; validate_story_batch_plan unchanged; no pipeline signature change
|
||||
status: completed
|
||||
- id: tests
|
||||
content: Builder tests + prompt contains + beliefs append smoke + career new_story smoke + test_story_route_oral_invariant
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Story Route:候选上下文增胖 + category-aware append 纠偏
|
||||
|
||||
## Ticket / PR 一句话
|
||||
|
||||
用更富的候选 JSON(**summary 优先**、再按需补正文)与 **按类目纠偏** 的提示词,修复 `StoryRouteAgent` 在强主题类目下过度 `new_story`;预算 **Settings 化**,默认值 **保守**;**不改** memoir 流水线签名与 DB schema。
|
||||
|
||||
## 根因(代码事实)
|
||||
|
||||
- `[api/app/agents/memoir/story_route_agent.py](api/app/agents/memoir/story_route_agent.py)`:`preview_chars=220`,同类目下多条短感悟几乎不可区分。
|
||||
- `[api/app/agents/memoir/prompts.py](api/app/agents/memoir/prompts.py)`:**「若无法自信匹配某一候选,选 new_story」** → 与「主题容器逐步加厚」产品预期相反。
|
||||
- `Story.summary` 列存在,**路由未用**;仅截 `canonical_markdown`。
|
||||
|
||||
## 本轮 scope(做)
|
||||
|
||||
|
||||
| 交付物 | 说明 |
|
||||
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `[story_route_agent.py](api/app/agents/memoir/story_route_agent.py)` | 新增 candidate **payload builder**,`_build_candidate_json` 改为走 builder |
|
||||
| `[prompts.py](api/app/agents/memoir/prompts.py)` | `get_story_route_prompt`、`get_story_batch_plan_prompt` 纠偏 + **两层决策标准** + **三类 category 规则** |
|
||||
| `[config.py](api/app/core/config.py)` | **3–4 个**预算相关字段(见下) |
|
||||
| 测试 | builder 单测、prompt 片段断言、**2 条类目行为样例**、`[test_story_route_oral_invariant.py](api/tests/test_story_route_oral_invariant.py)` 回归 |
|
||||
|
||||
|
||||
## 本轮 scope(不做)
|
||||
|
||||
- 离线 / admin 合并历史垃圾 story
|
||||
- schema 变更、二次 merge worker、family 细分子策略扩写、时间跨度阈值
|
||||
- 扩大路由体系(多模型级联、新端口签名等)
|
||||
|
||||
## 1. 候选行结构(summary 主角)
|
||||
|
||||
每条候选字典 **必带**:`id`、`title`、`char_count`、`version_count`、`updated_at`(ISO)、`linked_chapters`(保持现有拼接逻辑)。
|
||||
|
||||
**内容优先级,严格顺序:**
|
||||
|
||||
1. 若 `Story.summary` **非空且达到 `summary` 最小长度阈值**(可配置,如 ≥30 字或复用现有惯例),则带 `summary`,**本轮不带** `body_for_route`(避免长正文冲淡摘要)。
|
||||
2. 若 summary **缺失或过短**,再构造 `body_for_route`:**短正文**尽量全文;超长用 **head + tail**(中间省略说明),长度受 `story_route_long_body_head_chars` / `tail` 与单篇 cap 约束。
|
||||
3. 超 **总预算** 时,将该条 **降级为索引行**(仅 `id`、`title`、`char_count`、极短 `preview`,提示中说明索引项优先匹配带 `summary`/`body` 的条目)。
|
||||
|
||||
**初版默认值(保守,可线上调大):**
|
||||
|
||||
- `story_route_candidate_body_max_chars`:**1200–2000**(落地取单值如 **1600**)
|
||||
- `story_route_candidate_total_max_chars`:**12000–18000**(如 **16000**)
|
||||
- head/tail:按现有「长文才切」思路配一对合理默认(如各 **600–800**)
|
||||
|
||||
## 2. 排序规则(写死,tie-break)
|
||||
|
||||
在进入 builder **之前**对 `candidate_stories` 排序(同 stage 列表):
|
||||
|
||||
`has_summary(desc) → updated_at(desc) → version_count(desc) → char_count(desc) → id(asc)`
|
||||
|
||||
其中 `has_summary`:summary strip 后长度 ≥ 配置的 `summary` 最小长度。
|
||||
|
||||
## 3. 提示词
|
||||
|
||||
### 3.1 去掉保守偏置
|
||||
|
||||
删除「拿不准就 `new_story`」;改为:**先看是否与某候选在主题/事件层级上可合并,再决定**。
|
||||
|
||||
### 3.2 两层决策标准(显式写在 prompt 里)
|
||||
|
||||
- **主题连续性信号**:价值观、关系模式、长期总结、同一反思维度。
|
||||
- **事件切换信号**:新人物组合、新地点、新时间段、新事件因果链。
|
||||
|
||||
指引:`beliefs` / `summary` **更看主题连续性**;`career_*` / `childhood` / `education` **更看事件链**。
|
||||
|
||||
### 3.3 类目规则(第一版只三件)
|
||||
|
||||
- `**beliefs`、`summary`**:**强容器** → 多条短感悟、同一句式起笔、同价值维度 → **强烈倾向 `append_story`**(指向最匹配的一条候选 id)。
|
||||
- `**career_*`、`childhood`、`education**`:**强 episode** → 明确不同事件链可 `new_story`;同一经历续问可 append。
|
||||
- `**family`**:**中性** → 一句话:原则/关系反思倾向 append;**明确的新事件链**可 new;**不**展开长列表例外。
|
||||
|
||||
`get_story_batch_plan_prompt` 与 `get_story_route_prompt` **对齐**上述规则。
|
||||
|
||||
## 4. 接线
|
||||
|
||||
- `[story_pipeline_sync.py](api/app/features/memoir/story_pipeline_sync.py)` **不改** `StoryRouteAgent` 调用签名。
|
||||
- `validate_story_batch_plan`、Pydantic 模型 **不变**。
|
||||
|
||||
## 5. 测试(最小集 + 2 条行为)
|
||||
|
||||
|
||||
| 测试 | 目的 |
|
||||
| -------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Builder | summary 优先不带 body;summary 短则带 body;总预算降级;**排序**稳定 |
|
||||
| Prompt | 类目块、两层标准、`beliefs`/`family` 中性句等 **包含断言** |
|
||||
| **行为 A** | mock LLM:`beliefs` + 两则短感悟口述 + 已有 1 条 story → 期望 **append**(或断言传给 mock 的 payload 含足够 summary/body 且 prompt 强调强容器;实现时二选一并写死断言) |
|
||||
| **行为 B** | mock LLM:`career_achievement`(或 `childhood`)+ 两起明确不同事件 → 允许/期望 **new_story** |
|
||||
| 回归 | `[test_story_route_oral_invariant.py](api/tests/test_story_route_oral_invariant.py)`:路由输入仍 **不含 evidence** |
|
||||
|
||||
|
||||
行为测试若不便绑定真实 LLM,采用 **mock `invoke_json_object` / StoryRouteAgent** 固定返回或 **断言 prompt + candidate JSON 形状**,与现有 `[test_story_route_oral_invariant.py](api/tests/test_story_route_oral_invariant.py)` 风格一致。
|
||||
|
||||
## 6. 风险与验收
|
||||
|
||||
- **Token**:默认保守;观察 staging 日志 `route_decision` / `is_append` 再调 `Settings`。
|
||||
- **过度合并**:靠 episode 类与「事件切换信号」段落缓解。
|
||||
|
||||
## 实施顺序
|
||||
|
||||
1. `config.py` 字段
|
||||
2. Builder + builder 单测 + 排序单测
|
||||
3. Prompt 改造 + prompt 断言
|
||||
4. `StoryRouteAgent` 接线
|
||||
5. 行为 A/B + oral invariant + 相关 memoir 测试
|
||||
|
||||
Reference in New Issue
Block a user