以前模型只看到很短预览,还容易被引导成新建 story。现在优先用已有摘要、 按需带正文片段,并区分「像续写同一主题」和「像换了一件事」; beliefs/summary 更鼓励接着写, career/童年等仍可按新事件新开。
8.1 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Story route append context | 候选载荷增胖(summary 优先 + 可配置预算)+ category-aware prompt 纠偏 + 最小测试集;不扩大路由体系、不改 schema、不接离线合并。 |
|
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(保持现有拼接逻辑)。
内容优先级,严格顺序:
- 若
Story.summary非空且达到summary最小长度阈值(可配置,如 ≥30 字或复用现有惯例),则带summary,本轮不带body_for_route(避免长正文冲淡摘要)。 - 若 summary 缺失或过短,再构造
body_for_route:短正文尽量全文;超长用 head + tail(中间省略说明),长度受story_route_long_body_head_chars/tail与单篇 cap 约束。 - 超 总预算 时,将该条 降级为索引行(仅
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 类与「事件切换信号」段落缓解。
实施顺序
config.py字段- Builder + builder 单测 + 排序单测
- Prompt 改造 + prompt 断言
StoryRouteAgent接线- 行为 A/B + oral invariant + 相关 memoir 测试