罗德岛通讯与技术部

世界观注入调研

WORLDVIEW INJECTION

三条决策(用户定):落点 A(写进 lorebook)/ 照搬正文 / 按来源标权威。

状态:🟢 已实施(2026-09-11)· 导入 96 条,实测注入生效 来源:ZOOT!(PC 端 AI 陪伴应用)的 static_knowledge + JourneyKnowledgeProvider 起因:「AI 的知识库有时候不足」——这条正好是对方做得比较扎实的一块。


0.5 实施结果(2026-09-11)

三条决策(用户定):落点 A(写进 lorebook)/ 照搬正文 / 按来源标权威

产出 内容
app/prompts/lorebook_terra.md 393 KB / 96 条,正文合计 11.7 万字(生成物,勿手改)
scripts/import_worldset.py 导入脚本(可重跑,幂等)
app/systems/lorebook.py 新增 TERRA_PATH 接线 + 权威/来源 字段 + 非正典正文标记
tests/test_lorebook_authority.py 19 条守卫

覆盖:组织 28 · 国家 19 · 种族 14(你缺的萨卡兹九王庭等)· 生物 12 · 设定 9(泰拉/经济/货币/战力分级/历史/远程武器)· 城市 3 · 地点 3 · 工业 3 · 化物 3 · 文明 1 · 侵蚀 1。

去重:跳过 34 条撞 world_lore.json 且那边有描述的(主要是种族 —— 实测你的描述质量更好:丰蹄你写分布史与农业迁徙,它写外观与胸围)。 国家和组织不跳过:那边是实体索引{name, sub_factions}), 根本没有 description 字段,同名不等于重复内容。

实测触发(真实调用 get_active_entries):

用户消息 命中 字数
「罗德岛现在有多少人?」 [组织] 罗德岛 3378
「卡兹戴尔是什么样的地方」 [国家] 卡兹戴尔 4377
「海嗣是什么」 [组织] 深海教会 + [生物] 海嗣 389 + 4117
「今天天气不错」 (无命中)

单轮最坏注入约 1.3 万字 ≈ 6700 token(max_entries=3),有冷却兜底。


0.6 ⚠️ 实施中挖出的三个真 bug

启用 字段坏了两层,从来没生效过

LorebookEntry.enabledget_active_entries 里的 if not entry.enabled: continue 一直都在,但:

  1. 字段白名单正则里有 启用if/elif 链里没有对应分支 → 值被静默忽略
  2. 补了分支后仍不生效 —— 构造 LorebookEntry(...)漏了 enabled=enabled 传参 → 落回 dataclass 默认 True

同一个开关坏了两层,全程无报错。已修 + 两条守卫 (白名单里每个字段都必须有处理分支;行为验证中英文 false 形式)。

- 权威: / - 来源: 被当成正文

解析器白名单里没有这两个名字 → 落进 content_lines, 于是全部 93 条的正文都以 权威: 待考 / 来源: zoot-... 开头被注入 prompt。 已加为正式字段。

③ 我自己的测试有幸存者偏差(最值得记的一条)

第一版 test_no_enabled_entry_without_keywords 只断言 "解析出来的条目都有关键词" —— 而被丢弃的条目根本不在解析结果里, 断言不到。于是漏掉了 3 条(战力分级×2 / 历史):

  • 它们没有关键词 → 格式化时不写 - 关键词:
  • 解析器末尾 if not keywords: continue 把它们整条丢掉
  • 文件里能搜到,条目列表里没有 —— 看起来导入了,其实一次都不会注入

改成对总账len(解析结果) == len(文件里的 ## 段落数)

可推广的教训:测「没有坏数据」时不能只检查幸存者; 要对产出与输入对总账 —— 否则「丢弃」本身是不可见的。


0. 一句话结论

它解决"知识库不足"的办法不是补更多知识,而是给"编造的东西"一个带标签的位置。

六层权威里有两层(策划设定 / 未证实传闻 / 待确认提案)专门为"知识库没有、 但剧情需要"的内容准备,并且 prompt 里带一条硬约束:

【泰拉寻旅知识依据】低权威内容不得覆盖高权威事实。

于是 AI 该编的时候照样编,但编造物被降级标注,而不是伪装成官方设定


③ A4(c) 已实施:权威参与排序(2026-09-11)

导入时我给待考条目设的权重是 45~70,而正典(lorebook_common.md 本舰场景) 只有 25~35 —— 按原逻辑(裸 weight 排序)导入内容会把正典挤出 max_entries

这和「低权威不得覆盖高权威」是矛盾的:一条没核对过的整理, 不该比你自己核过的本舰设定更优先。

修法:排序改用 _auth_weight = weight × AUTHORITY_DISCOUNT(默认 0.4)。 待考 70 → 28,落在正典区间(25~35)的中下段,不再压过任何一条正典。

为什么是折扣不是硬过滤:硬过滤会让用户手动打开的条目也永远抽不到 —— 那是「加了开关却接不上」的老毛病(启用 那次就是)。折扣只让它排在正典之后, 仍可被选中:保住正典优先,又不让导入内容变成死数据。

并列时的显式裁决:光靠折扣不够 —— 折扣后总会有相等的情况, 而那样胜负就取决于候选的插入顺序(正典恰好先 extend)。 那是隐式依赖,顺序一改行为就变且没有测试会红。 所以排序键加了 _canon_first(有效权重, 是否正典, 命中数)

实测效果

问题 改动前 改动后
「罗德岛的甲板 [待考] 组织·罗德岛 22.0 抢在前 [正典] 甲板 30.0 第一,罗德岛 22.0 次之
「海嗣是什么」 只有 组织·深海教会 深海教会 + 生物·海嗣 都进来

守卫:tests/test_lorebook_authority.py::TestAuthorityAffectsRanking(6 条), 其中一条专门锁「任何待考的有效权重都不得压过最高的正典」。


1. 对方的结构(已核实)

1.1 世界观是独立命名空间

static_knowledge 表的 operator_id 字段里,世界观记录的取值是字面量 "worldset"

operator_id='worldset', category='worldset/races'        48 条
                       category='worldset/organizations' 28
                       category='worldset/nations'       19
                       category='worldset/creatures'     12
                       category='worldset/settings'       9
                       category='worldset/originium'      4
                       category='worldset/artifacts'      3
                       category='worldset/cities'         3
                       category='worldset/locations'      3
                       category='worldset/industry'       3
                       category='worldset/corrosion'      1
                       category='worldset/civilization'   1

共 12 类 134 条(另有 34,297 条角色/剧情知识走别的 category)。

1.2 每条自带关键词表(入库时抽好,不是运行时猜)

raw_data 是 JSON,三个字段:

{
  "key": ["阿斯兰", "狮"],           // ← 触发关键词
  "comment": "[种族]:阿斯兰",         // ← 分类标签
  "content": "# 阿斯兰\n\n外观:..."  // ← 正文
}

1.3 ⭐ 六层权威(核心)

JourneyKnowledgeProvider.render() 的分组与硬约束:

官方事实 / 当前世界线事实 / 已观察状态 / 策划设定 / 未证实传闻 / 待确认提案
【泰拉寻旅知识依据】低权威内容不得覆盖高权威事实。

排序键 (authority, score);条目字段 authority / unverified / source / content

作用
官方事实 canon,不可动摇
当前世界线事实 本局已确定的,覆盖 canon 的本地变体
已观察状态 角色实际看到的,限定认知范围
策划设定 用户/作者写的 —— 知识库没有时补位
未证实传闻 明确标为未证实线索 —— AI 编的有合法出处且不污染 canon
待确认提案 同上,且更低

1.4 knowledge_overlays:内置只读 + 用户改动分层

knowledge_overlays(
    stable_id TEXT PRIMARY KEY,   -- 指向内置记录
    base_id, operator_id, category, sub_id,
    content, raw_data, level,
    operation TEXT CHECK(operation IN ('upsert','delete')),
    updated_at)

读时「读内置 → 叠加 overlay」。升级内置资产不会冲掉用户改动。

1.5 两条注入通道(值得注意的对比)

通道 取 static_knowledge 的方式
memory_retriever(动态墙那条) ORDER BY level ASC LIMIT ? —— 按重要性取前 N 条,无相关性过滤
JourneyKnowledgeProvider(长剧情那条) hybrid(lexical + vector),有自己的 _tokens 分词,带 canon_policy: strict_canon

同一个人做了两条质量差很远的通道。 后者是认真做的——这也说明「按 level 取前 N 条」 是权宜之计,不是设计。


2. 与我们项目的对照(已核实落点)

app/systems/lorebook.py::LorebookEntry 现有字段:

uid, title, content
keywords: list[str]          # 主关键词+扩展词
weight: int = 50             # 优先级 0-100
level: int = 0               # 0=通用 1=职业 2=个人
restricted_to: list[str]     # 限定角色(空=全部)
constant: bool = False       # 常驻
scene: str = ""              # 绑定在场系统的场景键
sticky: int = 0              # 触发后持续 N 轮
cooldown: int = 0            # 触发后 N 轮内不再触发
enabled: bool = True
source: str = "system"       # system / user_import / user_custom
ZOOT! 我们 判断
key 关键词表 keywords ✅ 已有
level level ✅ 有(含义不同)
weight ✅ 我们更强
restricted_to ✅ 我们更强
constant/sticky/cooldown/scene ✅ 我们更强(对方完全没有调度维)
source(三值) source(同三值) ✅ 完全一致
authority + unverified 缺这一维
内置只读 + overlay 分层 ❌ 缺(我们有 source 区分来源,但没有"按 stable_id 覆盖同一条")
render() 的硬约束句 ❌ 缺

结论:我们的 lorebook 在触发/调度维度比它强,缺的是可信度这一维。


3. 建议怎么做(不是照搬)

3.1 只取一维:给条目加 authority,并在注入时显式标注

不引入它的六层命名(那是它长剧情模式的概念),而是按我们的语汇定三到四档:

我们的档位 含义 对应对方的
正典 官方设定 / 已确认的世界事实 官方事实
本局事实 这档剧情里已经发生并被确认的 当前世界线事实
待考 用户导入的、来源不明的、剧情需要但设定没写的 未证实传闻
提案 AI 提出、待确认的 待确认提案

关键动作是注入时的标注(这是对方的精髓,也是最便宜的一步):

【世界观依据】标注为「待考」的内容是未经确认的线索,不得作为事实引用,
             更不得覆盖「正典」设定。若需要确立新事实,请以「提案」形式提出。

对照我们已有的机制:CLAUDE.md 里那条「传闻会明确标记为未证实线索,避免覆盖确定设定」 已经写了这个意图,缺的只是把它落到数据字段 + prompt 句上。

3.2 覆盖层(可选,优先级低)

对方的 knowledge_overlays 解决的是「升级内置资产不冲掉用户改动」。 我们现在 source 已经区分 system / user_import / user_custom, 但没有「用 stable_id 覆盖内置同一条」的能力——用户想改一条内置设定, 只能新建一条,于是两条并存、可能矛盾。

⚠️ 但这项现在不必做:一测前不会有大量用户改内置资产。记在 backlog 即可。

3.3 ⚠️ 不要抄的:ORDER BY level ASC LIMIT ? 那条通道

对方动态墙那条通道是「按重要性取前 N 条,无相关性过滤」—— 这是它自己后来在长剧情里用 hybrid 取代掉的权宜之计。 我们 lorebook 的关键词触发比它这条,别退回去。


4. 如需实施,落点(已核实)

动作 落点
条目加 authority lorebook.py::LorebookEntry + parse_markdown_lorebook / parse_st_json
注入时标注 get_active_entries() 的出口(唯一出口,已核实)
硬约束句 注入模板(和 anti_drift / identity_guard 同级的位置)
守卫测试 新增 tests/test_lorebook_authority.py

⚠️ authority 应有缺省值(老条目没有这个字段 → 按「正典」或按 source 推导), 否则存量条目会全被判成最低档,反而让 AI 不敢引用真正可信的设定。 这个缺省口径必须先定,再写代码。