DEMO · 五步走完闭环

不用接上游,也不用模型密钥,就能看它转一圈。

此前要理解这套机制,得同时具备上游接入、模型密钥、公网可达三个条件。 现在启动服务、打开一个页面、点五个按钮就够了。 本页的所有数字与产物取自一次真实运行,不是示意。

怎么跑

两条命令,一个页面。

# 设一个口令,本机演示用什么都行,正式部署务必用强随机值
export BIANMU_API_TOKEN=<你设的口令>
make serve

浏览器打开 http://127.0.0.1:8040/demo,用同一个口令登录, 依次点五个按钮即可。每点一步,页面显示该步的真实产物。

演示数据写在独立目录里,绝不碰真实埋点数据——后者是不可追补的。 页面顶部固定显示当前演示目录,「重置演示」只清这个目录。 有一条测试专门盯着这件事:演示跑完,真实目录不能多出任何日志文件。

五步分别在干什么

1 · 灌入数据

一个模拟上游的脚本假装成编目系统:产出 14 条内容的字段值(其中刻意混入错误), 模拟编目员校对,然后通过真实的 HTTP 接口把两条事件推过来。

走接口而不是直接写文件是刻意的——演示跑通就等于证明了上游接入这条路是通的, 这个脚本本身就是一份可运行的接入示例。

真实运行结果

23
修正事件 · 分子
14
完成事件 · 分母

一条修正事件长这样:

《山河故人令》 foreign_title
  「Shan He Gu Ren Ling」 → 「Legend of the Riverlands」   原因:来源错误

两条事件缺一不可。只有修正事件,你能算出「这个月改了 23 次」, 却算不出「修正率降了没有」——吞吐量一变就不可比。完成事件记录每条内容 各字段有没有值、有没有被改动,它才是分母。

2 · 差异分析

按字段聚合,看哪些字段错得最多、主要原因与来源是什么。全程不用模型。

真实运行结果 —— 三种注入的错误模式被如实排了出来

字段修正数主因主要来源
foreign_title10来源错误douban_snapshot
year9格式不符baike_snapshot
overseas_platform4漏抓补全douban_snapshot

三类数量刻意不同(10 / 9 / 4),排行才有真实区分度—— 如果样本设计成每类一样多,这一步就看不出它在工作。

3 · 提取规则

两步走:先由主要修正原因确定性地选定规则类型(这一步不用模型), 再让模型只填该类型 schema 内的几个参数。

把模型的自由度从「写一段话」收窄到「填几个值」,幻觉空间随之收窄, 而且结构化参数天然可校验——编出一个不存在的数据源名,入库时当场被拒。

真实运行结果 —— 三种错误模式各自提出对应类型的规则

类型字段回流层参数
source_priorityforeign_titleL1 order: [douban_api, baike_snapshot]
normalizeyearL3 mapping: {"2020年": "2020", …}
fallback_sourceoverseas_platformL4 when: empty, sources: [douban_api]

默认不调模型(dry-run),所以演示不依赖任何密钥。 配了密钥可以勾选真实模型,顺便看语义自测如何拦下不成立的规则。

4 · 审核放行

这一步刻意不能自动跳过。演示时最容易图省事把它自动化, 但那会传达完全错误的信息——错误规则一旦生效会被批量放大, 且因为它是「系统行为」而非个例错误,事后复核反而更难发现。

所以必须有人填写审核人姓名并点击。未署名时流转直接被拒。

放行后规则从「草案」走到「灰度 10%」——先放一成流量,验证有效再谈全量。

5 · 看效果

把生效规则渲染成上游可加载的规则包。这是整个系统对外的最终产物。

真实运行产出的规则包(节选)

{
  "schema": "bianmu-ruleset/1",
  "version": 1,
  "rules": [
    { "type": "source_priority", "field": "foreign_title", "layer": "L1",
      "canary_pct": 10,
      "params": { "order": ["douban_api", "baike_snapshot"] } },
    { "type": "fallback_source", "field": "overseas_platform", "layer": "L4",
      "canary_pct": 10,
      "params": { "when": "empty", "sources": ["douban_api"] } }
  ]
}

上游加载它,按类型执行、按灰度比例抽样。参考实现只依赖标准库,可直接抄。

演示之外的三页

五步走完之后,工作台另外三页承接的是日常使用。它们各自在开头说明「这一页回答什么问题」, 不需要先看文档:

页面回答什么
看板人工到底还要改多少、最费人力的是哪个字段、规则生效后有没有变好。 结论先行,再给明细与按占比的条形图。
修正录入手工补录一条修正。先选剧目,再对着 AI 的产出值逐字段改, 可疑项直接标出原因——而不是把一整套字段摊开让人从头填。
运行状态东西在不在跑、数据在哪有多少、配置是否齐全。只读。

运行状态页刻意不做启停按钮。工作台就是被控制的那个服务本身—— 「停止」点下去页面自己就没了,「启动」在服务没跑时根本打不开。 更要紧的是,让网页能杀进程等于给这个服务加一条可执行系统操作的攻击面, 换来的却只是省下切一次终端。启停命令以可复制文本给出,由人在终端执行。

这一页也只显示密钥「已配置 / 未配置」,绝不显示内容本身。

演示数据里埋了什么

14 条剧集,注入三种真实场景里会出现的错误,各自对应一类规则:

字段错误形态修正原因应提出的规则类型
foreign_title拼音转写而非官方英文名来源错误source_priority
year带「年」「月」等杂质格式不符normalize
overseas_platform留空漏抓补全fallback_source

样本是确定性的——同一条内容每次劣化结果一样, 所以每次演示看到的排行和规则都相同,不会「上次看到的和这次对不上」。

为什么不实时抓取

考虑过让演示实时从公开站点抓真实数据,但那是增加依赖而不是减少: 反爬、限流、页面结构变更三重不确定,演示时网络一抖就演不成。

离线样本零依赖、可复现,而且这条链路本来就在更早的版本里 「摒弃爬虫改走快照解析」——演示没有理由把已经绕开的问题重新引进来。

实时抓取留作后续的可选开关,不是默认路径。

也可以在命令行跑

模拟上游脚本可以单独运行,直接对着任意一个部署好的服务推数据:

python3 scripts/demo_upstream.py \
  --base-url http://127.0.0.1:8040 --token $BIANMU_API_TOKEN

它走的是真实的埋点接口,可当作接入参考实现。 注意这会写入真实数据目录,仅在确实想灌入数据时使用。

验证规则包真能被消费:

python3 examples/upstream_loader.py <演示目录>/rules/ruleset-latest.json

打印上游按类型分派后的配置——这就是编目链路实际会用到的东西。

← 看架构 · 建设现状