Files
life-echo/.cursor/plans/story_route_append_context.plan.md

129 lines
8.1 KiB
Markdown
Raw Normal View History

---
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)` | **34 个**预算相关字段(见下) |
| 测试 | 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`**12002000**(落地取单值如 **1600**
- `story_route_candidate_total_max_chars`**1200018000**(如 **16000**
- head/tail按现有「长文才切」思路配一对合理默认如各 **600800**
## 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 优先不带 bodysummary 短则带 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 测试