Skip to content

fix: explicit_genre_fallback 保留调用方指定的题材(#135) - #136

Open
PTAbabybearR wants to merge 2 commits into
lingfengQAQ:masterfrom
PTAbabybearR:fix/primary-genre-fallback
Open

fix: explicit_genre_fallback 保留调用方指定的题材(#135)#136
PTAbabybearR wants to merge 2 commits into
lingfengQAQ:masterfrom
PTAbabybearR:fix/primary-genre-fallback

Conversation

@PTAbabybearR

Copy link
Copy Markdown

变更摘要

修复 _route()explicit_genre_fallback 路径下把 fallback 行自身的子流派写进 primary_genre 的问题。

调用方显式指定 genre、但 query 未命中任何路由行关键词时,_fallback_row_for_genre() 会返回
第一个「适用题材」包含该 genre 的行。真实表 references/csv/题材与调性推理.csv 的第一行
GR-001 是「题材/流派=玄幻退婚流,适用题材=玄幻|仙侠」——于是任何 genre=仙侠 且未命中关键词的项目,
primary_genre 都会变成「玄幻退婚流」,并把 TR-001「退婚三年之约」注入
CHAPTER_BRIEF.writing_guidance,影响此后每一章。

该行为与同一函数中 canonical_genre 的处理不一致::201-203 已经实现了「显式 genre 优先」,
所以 canonical_genre 输出是正确的(仙侠),只有 primary_genre 漏掉了这一步——
这说明「显式指定优先」本就是既定设计,本 PR 只是把 primary_genre 对齐过去。

inferred_genre_fallback 路径刻意保持原行为不变:该路径下 genre 只是从自由文本推断出的
粗粒度类别,fallback 行给出的子流派反而更具体、更有信息量。
test_route_infers_canonical_genre_from_spaced_query 已经断言了这个行为(推断出「快穿」时保留行的
「快穿任务」),我在初版改动中一并改了它,被这条测试拦下,随后收窄了修复范围。

关联 Issue

Closes #135

变更类型

  • Bug 修复
  • 测试
  • 新功能
  • 文档
  • 模板 / 题材画像
  • Dashboard
  • 重构
  • 发版 / 打包

影响范围

  • /webnovel-init(题材路由在此产生)
  • Story System / CHAPTER_COMMIT
  • /webnovel-plan
  • /webnovel-write
  • /webnovel-review
  • /webnovel-query 或 memory
  • RAG / reference search
  • Dashboard
  • 文档 / 模板 / references
  • Marketplace / 插件发版

验证方式

  • python -m pytest
  • 已对受影响命令或工作流做 smoke test
  • 数据链有变化时,已在示例项目上运行 preflight
  • UI 有变化时,已检查 Dashboard 行为或截图

补充说明:

  • webnovel-writer/scripts/data_modules/tests/ 全量通过(exit code 0),
    单独跑 test_story_system_engine.py 也全部通过。
  • 新增的 test_explicit_genre_fallback_keeps_requested_genre_not_row_subgenre
    在修复前会失败、修复后通过。
  • 在真实项目上重跑 /webnovel-init 做 smoke test,因为那会覆盖既有项目文件;
    错误产物是在两个已存在的仙侠项目的 MASTER_SETTING.json 中确认的
    (两者 primary_genre / core_tone / pacing_strategy 完全一致)。
  • 顺带一提:在较深的目录下跑测试时,部分用例会因 Windows WinError 206(路径过长)失败,
    与本改动无关;换到短路径后全绿。

兼容性和数据影响

  • 不需要项目数据迁移
  • 现有 .story-system/ 数据保持兼容
  • 现有 .webnovel/ 投影保持兼容
  • 已考虑 Windows 路径和 UTF-8 行为
  • 未包含 API Key、私密小说正文或个人信息

需要注意:已经写入磁盘的错误值不会被本 PR 自动修正
存量项目的 .story-system/MASTER_SETTING.json / MASTER_SETTING.md
仍会保留错误的 primary_genre 与随之注入的 base_context 条目,
需要重新执行一次 story-system --persist,或手工修正。
若认为有必要,可以再补一个迁移/校验提示,我可以另开 PR。

文档

  • 不需要文档更新

Release notes

修复显式指定题材(如「仙侠」)但未命中路由关键词时,primary_genre 被写成 fallback 行子流派
(如「玄幻退婚流」)的问题,避免无关题材的桥段被注入每一章的写作指导。

走 fallback 时,匹配到的行只是被借用其推荐检索表与调性,其「题材/流派」是
该行自身的子流派,可能属于完全不同的题材。真实表中 GR-001(题材/流派=玄幻退婚流,
适用题材=玄幻|仙侠)是第一行,导致任何 genre=仙侠 且未命中关键词的项目
都会把 primary_genre 写成「玄幻退婚流」,并把 TR-001 退婚桥段注入
CHAPTER_BRIEF.writing_guidance。

显式指定 genre 时保留调用方的输入,与 canonical_genre 既有的「显式优先」
逻辑对齐(同函数 :201-203)。

inferred_genre_fallback 保持原行为不变:该路径下 genre 仅由自由文本推断,
fallback 行的子流派反而更具体,此行为由
test_route_infers_canonical_genre_from_spaced_query 断言。

新增回归测试,复刻真实表中 GR-001 的形状——现有
test_story_system_falls_back_to_explicit_genre 的 fixture 里行的「题材/流派」
与传入 genre 相同(都是「现言」),两种实现都能通过,无法暴露该问题。

@lingfengQAQ lingfengQAQ left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢定位与修复,根因分析与 canonical_genre 对齐的思路都是对的,inferred_genre_fallback 路径保持不动的取舍也认同。不过在 PR 分支上按 issue 的真实场景实测后,发现修复相对 #135 自己写的「预期行为」还不完整,故先 Request changes。

实测复现(PR 分支,query="仙侠", genre="仙侠"

route: primary_genre=仙侠 ✅  canonical_genre=仙侠 ✅  route_source=explicit_genre_fallback
master_constraints: core_tone=先压后爆 ❌  pacing_strategy=三章内必须有首次有效反打 ❌
base_context 编号: [TR-001, TR-003, SP-004, SP-087, PA-044, PA-061] ❌ TR-001 仍被注入

遗漏点

issue 的「预期行为」有两条:primary_genre 正确, base_context 不应注入与题材无关的桥段条目。本 PR 只满足了第一条:

  1. 搜索词污染仍在_expand_query 仍把 GR-001 的「默认查询词」退婚|打脸|废材逆袭 拼进检索词;TR-001 的适用题材是 玄幻|仙侠,能通过 genre_filter=仙侠,所以照样被检出注入——每章 writing_guidance 掺退婚桥段的实际痛点没有解决。
  2. master_constraints 污染仍在core_tone(先压后爆)与 pacing_strategy(三章内必须有首次有效反打)仍取自退婚流行,且 core_toneoverride_policy.locked 中,是锁定约束。issue 补充说明里也提到这三个字段是一起错的。
  3. 因此 PR 描述与 Release notes 中「避免无关题材的桥段被注入每一章的写作指导」与实际行为不符。
  4. 新增测试只断言了 route 字段,恰好镜像了同一盲区。

修改建议

在 explicit_genre_fallback 路径下,把 fallback 行的「默认查询词」「核心调性」「节奏策略」一并中和(不采用,或回退到题材级默认),并为新测试补两条断言:base_context 不含 TR-001;core_tone 不为「先压后爆」。

若倾向本 PR 只收窄到 primary_genre,也可以:修正 PR 描述/Release notes 的表述,并另开 issue 跟踪注入问题(同时把 Closes #135 改为 Refs #135,避免主诉求未解决就关闭)。

次要 nit(不阻塞)

  • if route_source == "explicit_genre_fallback" and genre:and genre 恒真(该 route_source 仅在 genre 非空时产生);str(genre).strip() 亦冗余(_validate_explicit_genre_source 已 strip)。留作防御无妨。
  • 测试中 assert route["primary_genre"] != "玄幻退婚流"== "仙侠" 之后属冗余断言。

测试方面:test_story_system_engine.py 全量 15 个用例在 PR 分支通过(单文件跑需 --no-cov,仓库 90% 覆盖率门槛针对全量运行)。

fallback 行仅用于借出推荐检索表:其「默认查询词」(退婚|打脸|废材逆袭)
此前仍会把 TR-001 等退婚流桥段注入 base_context,「核心调性」「节奏策略」
「毒点」也会成为无关题材的锁定约束。题材级调性/节奏仍经裁决规则
(reasoning 层)注入章合同。

回归测试扩充为端到端断言:base_context 不含 TR-001、master_constraints
为空、行级毒点不进 anti_patterns;已验证在旧实现上失败。

Refs lingfengQAQ#135
@lingfengQAQ

Copy link
Copy Markdown
Owner

为了尽快合入,我以维护者身份在本分支追加了提交 43996d5,把评审中提到的遗漏一并补齐(感谢开启 Allow edits by maintainers):

  • explicit_genre_fallback 路径下,fallback 行仅借出「推荐基础/动态检索表」;其「默认查询词」「核心调性」「节奏策略」「毒点」不再采用。题材级的调性与节奏仍会经裁决规则(reasoning 层)注入章合同,master 层不再出现无关子流派的锁定约束。
  • 回归测试扩充为端到端断言:base_context 不含 TR-001、master_constraints.core_tone/pacing_strategy 为空、行级毒点不进 anti_patterns;已验证该测试在旧实现上失败、修复后通过。
  • 真实 CSV 实测(query="仙侠", genre="仙侠"):base_context 由 [TR-001, TR-003, …] 变为 [TR-088, TR-106, SP-087, SP-088, PA-101, PA-078],退婚流条目全部消失。
  • data_modules/tests 全量通过。

如对处理方式有不同意见(例如希望 master 层回填题材级调性而非留空),欢迎在此讨论。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【Bug】explicit_genre_fallback 下 primary_genre 取了 fallback 行的子流派,导致无关题材桥段注入每章 writing_guidance

2 participants