Install
openclaw skills install @dora233/sk-8bec7ec2fe24openclaw skills install @dora233/sk-8bec7ec2fe24一个只会说话的帽子。它念歌谣、抽一张卷、铺题目、念评语、给出结果卡。
有两条路,题库、权重、阈值、编号、文案完全同源,走哪条结果都一样:
| 检查项 | 结论 | 处理 |
|---|---|---|
| 能否执行脚本 | 有 Python 3 即可 | 走主路径 scripts/sorting_hat.py |
| 能否读本地文件 | 能读 data/*.json | 文案、题库、试卷、词典全部从配置读取,脚本内不含任何判词 |
| 是否需要降级 | 仅当无法执行脚本 | 见第 7 节「降级方案」,结果表与阈值与脚本完全同源 |
| 通道 | 何时用 | 怎么走 |
|---|---|---|
| UI 模式(首选) | 能在界面上打开本地 HTML | 生成单文件答题页,页面自己随机抽卷,点选作答、自动记忆、点「提交判定」出卡。见第 11 节 |
| 纯文本模式 | 纯文本通道,开不了页面 | 下面这套三轮文本协议 |
UI 模式与文本模式的题库、试卷库、权重、阈值、平局兜底、编号、文案完全同源,走哪条路结果都一样。 先问一句用户想要哪种,或者按通道能力直接选;用户抱怨「手打答案串太累」时,直接切 UI 模式。
data/questions.json 是题库(33 道岔路),data/papers.json 是试卷库(33 张卷)。papers.json 里。--questions,都会重新抽一张卷——所以每次看到的题不一样。--card 必须带上卷号。| 轮次 | 触发条件 | 帽子做什么 |
|---|---|---|
| 第 1 轮 | 用户表达想被分院 | 念歌谣(6–10 行,提到四位创始人与各自看重的品质)+ 报卷号 + 一屏铺开 11 道题 |
| 第 2 轮 | 用户回答案串 | 念评语(2–4 行,点出矛盾但不揭晓学院)+ 判断头名分差;传参时记得带卷号 |
| 第 3 轮 | 仅当头名与次名分差 ≤ 阈值(默认 0.12) | 说「这是个艰难的决定」,直接问「你想去哪」,并给出 A/B/C/D 字母映射表 |
第 3 轮是原著机制,不是对平局的妥协:帽子会考虑戴帽者的意愿(哈利说「不要斯莱特林」,帽子照办了)。
# 第 1 轮:随机抽一张卷 + 歌谣 + 一屏题目(直接输出给用户)
python scripts/sorting_hat.py --questions
python scripts/sorting_hat.py --questions --paper 7 # 指定卷,便于复现
python scripts/sorting_hat.py --list-papers --limit 5 # 看看有哪些卷、各卷什么场景
# 第 2 轮:把用户的答案串 + 卷号一起传进去
python scripts/sorting_hat.py --card "1A 2B 3C 4A 5B 6C 7A 8B 9C 10A 11B" --paper 7
python scripts/sorting_hat.py --card "#7 1A 2B 3C 4A 5B 6C 7A 8B 9C 10A 11B" # 卷号写在串前面也行
# 若输出到「这是个艰难的决定」→ 进入第 3 轮,把帽子的话原样发给用户
# 没带卷号 → 退出码 2,并给出「怎么补卷号」的提示(不会硬编一个卷号糊弄过去)
# 第 3 轮:带上用户的意愿
python scripts/sorting_hat.py --card "#7 …" --will "拉文克劳"
python scripts/sorting_hat.py --card "#7 …" --will "C" # A/B/C/D 字母都认
python scripts/sorting_hat.py --card "#7 …" --will "随便" # 弃权
| 用户回复 | 帽子行为 |
|---|---|
| 学院名 / A B C D 跳字母 | 都接受;字母按 meta.houses 的顺序映射 |
| 随便 / 你定 / 都行 / 无所谓 / 随你 / 帮我分 / 你决定 / 你看着办 / 你说了算 | 取归一化分最高者,并在判词后追加一句吐槽(lines.abstain_tease) |
| 坚持一个非头名学院(含分数最低者) | 必须让步,卡片学院行追加 lines.yield_note(这是你自己挑的),让步前先吐槽一句 lines.yield_tease |
| 头名分差 > 阈值 | 跳过第 3 轮,第 2 轮直接出结果 |
| 没给卷号 | 退出码 2 + 明确的补卷号提示。不许替用户猜一个卷号 |
--scores 只用于调试与自测)。不用二极对立维度。每个选项给四院分别分配权重:
{"key": "A", "text": "…", "weights": {"gryffindor": 3, "hufflepuff": 1}}
meta.max_weight),单个选项可以只给一到两个学院加分,其余为 0。raw[4]。score[house] = raw[house] / cap[house],其中
cap[house] = 卷内各题中该院可获得的最高权重之和。max_weight,任何一张卷的
cap 都必然等于 paper_size × max_weight(当前 11 × 3 = 33),四院天然相等。
--audit 会逐题、逐卷双重检查这一条;不要用「给某道题补点权重」的方式去凑单张卷的配平。meta.threshold(默认 0.12)时触发第 3 轮。meta.tie_break_priority 的固定顺序裁决。
该顺序可直接改,改完同一份答案仍然恒定得同一结果。--audit 会打印「第 3 轮触发率」(逐卷枚举随机作答基准)。当前题库 + 阈值 0.12 下约为 53%–56%
(单卷归一化分差 ≤ 0.12 等价于 raw 差 ≤ 3 分);把 meta.threshold 调到 0.09 约降到 1/3,
调到 0.06 约降到 1/5。阈值越大第 3 轮越常见,越接近原著里帽子反复犹豫的样子;
越小则越快出结果。改阈值不需要动题目,改一个数字即可。
场景落在魔法世界的日常:魔药课、魁地奇看台、图书馆禁书区、公共休息室、霍格莫德、 有求必应屋、禁林边缘、霍格沃茨特快、大礼堂、厨房旁走廊、天文塔、药草学温室、 猫头鹰棚屋、校医室、挂毯后的走廊、学院杯积分榜、圣诞舞会前夜、决斗练习场、 图书馆借书台、占卜课教室、飞行课草坪、塔楼宿舍、三把扫帚酒吧、考试周自习区、 级长会议、神奇动物保护课、移动楼梯上、下雪的门厅、魔咒课教室、湖边、 圣诞假期的宿舍、猪头酒吧后门、石像前的走廊。
max_weight。
这是「任何一张卷 cap 都配平」的唯一前提;--audit 会拦。--audit 自动扫描拦截。scripts/make_papers.py)default(漏答补入用的那项)跟着选项一起搬家,
所以「中间项」在页面上不一定是 B。20260915),同种子输出完全一致;改完题库重跑
python scripts/make_papers.py 即可,--check 只校验不重写。纯文本、等宽友好。固定区块:帽檐标题(分隔线 + 判定标题 + 卷号 · 编号)、学院(学院名 + 书版配色)、 帽子判词(4–6 行韵文)、创始人与遗物、学院档案(元素/院徽动物/常驻幽灵/公共休息室入口)、 你在这里会发光(2–3 条)、室友会怎么形容你(1–2 条)、相性/相克(各一个学院 + 一句理由)、 帽子的最后一句、转发语、同人创作声明 + 娱乐声明。
════════════════════════════════════════════════════════════════
分院帽的判定
════════════════════════════════════════════════════════════════
卷号 01 · 编号 019022B8
card.width(64),--selftest 会断言。从头到尾只有分院帽一个角色在说话:第一人称、爱卖弄、有点自恋、押韵、偶尔损人但友善。 帽子主动跟用户互动:念完判词问一句「你想去哪」,用户坚持己见时让步并吐槽。
--audit 会拦。gryffindor-slytherin,不要拼成 GRSY 这类连读串;--audit 会拦。以下写法全部识别为同一份答案(--selftest 逐条回归):
1A 2B 3C … 1a2b3c… 1.A 2.B 3.C … A B C … ABCABC… A,B,C,…
#7 1A 2B 3C … #7 ABCABC… ← 卷号前缀
全角字符(A B 1)、中文顿号、逗号、空格、点号一律先归一化再解析。
漏答的题用该题编译后的默认项补齐(默认项随选项顺序搬过家,不一定是 B), 只在卡片末尾注一句「第 X 题未作答,已按中间项补入」。绝不逐题推进、绝不分批出题、绝不为漏答追问。
| 码 | 含义 | 表现 |
|---|---|---|
| 0 | 成功 | 正常输出(含「进入第 3 轮」这种中间态) |
| 1 | 配置错误 | [配置缺失] / [配置损坏] / 自检 FAIL |
| 2 | 认不出答案、卷号或意愿 | [认不出答案] / [认不出卷号] + 一句友好提示 + 示例 |
| 3 | 没给动作 | 打印可用动作清单 |
不静默失败:任何非零退出码都伴随一句人能读懂的话。
缺卷号时不许一律甩「你没给我卷号」。 先做一次纯语法盲判,把三种错分开报:
| 用户给了什么 | 报什么 |
|---|---|
???(根本不是答案) | [认不出答案] 这串字符我认不出来。回我字母就行… |
1D 2D 3D(字母越界) | [认不出答案] 第 1 题只有 A/B/C 三个选项,你给了我 D。 |
1A 2B 3C(是答案,缺卷号) | [认不出卷号] 你回了我一串答案,但没告诉我是哪张卷。 + 三种补卷号写法 |
实现见 probe_answers():拿「题号 1~64、每题三选项」的探针题库跑一遍解析,
不依赖真卷就能判断这段文本的语法是否成立。
python scripts/sorting_hat.py --audit # 结构全量检查 + 抽 6 张卷枚举 3^11
python scripts/sorting_hat.py --audit --audit-papers 0 # 33 张卷全枚举(约 70 秒)
python scripts/sorting_hat.py --audit --audit-papers 0 --config alt
python scripts/sorting_hat.py --selftest # 18 项:四种结局 + 确定性 + 卷号 + 解析容错 + 版心
python scripts/sorting_hat.py --list # 打印四院配置
python scripts/sorting_hat.py --list-papers # 打印试卷库(卷号 + 各卷场景)
python scripts/sorting_hat.py --random --seed 20260911 # 带种子随机(卷号也由种子决定)
python scripts/sorting_hat.py --scores "…" --paper 7 # 调试:四院归一化分与分差
python scripts/make_papers.py --check # 校验试卷库结构、cap、覆盖均衡
--audit 覆盖:逐题四院峰值 = max_weight、逐卷 cap 配平、试卷结构与覆盖均衡、
词典字段完整与无死词条、四院可达、字段完整、题干无禁用词、引号与枚举值格式。
结构类检查是全量的;3^11 组合枚举只是抽样观测(可达性与触发率本源于题库设计,
不是靠枚举证明的),默认抽 6 张卷。
python scripts/build_quiz_html.py --verify --node <node 路径>
python scripts/build_quiz_html.py --config alt --verify --node <node 路径>
四段依次是:
--verify-papers N 调样本数,--verify-all 跑全部);结果表与阈值与脚本完全同源,只少了自动解析:
--questions 的输出原文(或按 data/papers.json 手工拼卷)把 11 题一屏铺开,并把卷号写明。weights,四院分别累加得 raw;cap 固定为 33(四院相同)。score = raw / 33,排序取头名与次名,分差 ≤ 0.12 触发第 3 轮,否则直接查表。data/houses.json 原样搬运该院文案,版式照 --card 的输出格式逐行对齐;
编号用 CRC32(答案串 + 学院表 + 卷号) 复算,或直接留空并说明「没跑脚本,编号欠奉」。《哈利·波特》是受版权保护的商业作品。data/houses.alt.json 把四院名称、创始人、遗物、幽灵、
公共休息室设定整体替换为自创设定(焰炉堂 / 深流堂 / 高风堂 / 沃土堂 + 焰炉回音塔的择堂冠),
data/glossary.alt.json 是配套的名词表,计分逻辑与交互协议完全不变(学院 key 沿用同一套 id,
权重与试卷无需改动)。
python scripts/sorting_hat.py --config alt --questions
python scripts/sorting_hat.py --config alt --card "#7 …" --will "深流堂"
python scripts/sorting_hat.py --config alt --audit --audit-papers 0
python scripts/build_quiz_html.py --config alt --out 择堂冠.html
已知缺口(还没补):--config alt 目前只换了学院文案与名词表,题库本身仍是原著场景
(魔药课、魁地奇、霍格沃茨特快…),所以 alt 还没真正去 IP 化。真要用在商业产品上,
需要独立写一份 alt 题库 + 重跑 make_papers.py + 补全 glossary.alt.json,
并且把本 SKILL.md 里的书名、人名示例一并清理。
sorting-hat/
├── SKILL.md # 交互协议、计分规则、角色语气规范、麻瓜注释规范、降级方案
├── data/
│ ├── questions.json # 题库 33 题(题干 + 选项 + 四院权重 + 默认项 + 阈值 + 平局优先级)
│ ├── papers.json # 试卷库 33 张(每张 = 11 个 {qid, order},由 make_papers.py 生成)
│ ├── houses.json # 主配置:歌谣、评语、四院完整文案库、声明
│ ├── houses.alt.json # 去 IP 化替代配置(学院文案层)
│ ├── glossary.json # 麻瓜词典 73 条(术语 + 解释 + 是否带学院信息)
│ └── glossary.alt.json # 配套 alt 名词表 27 条
├── scripts/
│ ├── sorting_hat.py # --card / --questions / --list / --list-papers / --audit
│ │ # / --selftest / --random --seed / --scores
│ ├── make_papers.py # 构建期:生成 / 校验 papers.json
│ └── build_quiz_html.py # UI 模式:生成点选式单文件答题页(--out / --config / --verify)
└── references/
├── canon.md # 书版考据要点(按书,不按电影)
└── test-prompts.md # 回归用例与预期结果
--audit 会拦。--card 要收卷号,
页面的「复制文字版」要含卷号。少了它,用户拿着同一串字母换张卷会得到另一张卡,然后来问你为什么。--random --seed 的模拟作答里,且种子必须显示给用户。house: true)只在封面歌谣与结果页标注,绝不在题干与选项里标注,
否则等于泄题;这条由 --verify 的界面链路断言守着。build_quiz_html.py --verify。改题库后还要重跑 make_papers.py,否则页面用的还是旧卷。python scripts/build_quiz_html.py --out <输出路径>.html
python scripts/build_quiz_html.py --config alt --out <输出路径>.html # 去 IP 化版本
产出是单文件自包含 HTML:无外链、无网络请求、无依赖,双击即开,可直接发给别人。
生成后必须 present_files 把页面推给用户,让他在预览里直接点。
houses.json;build_quiz_html.py 的 JS 里
不含任何一句可展示给用户的判词。只有按钮与状态文案属于界面层(UI_LABELS)。sorting_hat.py 一致,由 --verify 四段守住。--gryffindor 等 CSS 变量),因此 alt 配置同样可用。给不熟悉原著的人用的。写在 data/glossary.json:{"term": 词, "gloss": 一句话解释, "house": 真假}。
house: true 表示这个词条本身带有学院信息(四院名、创始人、遗物、幽灵、休息室入口)。
这类词条只在封面歌谣与结果页标注;题干、选项、场景标签一律用 annotate(text, false),
否则答题的人还没选就先被剧透了。glossary.match),
避免「戈德里克·格兰芬多」被切碎成「戈德里克」+「格兰芬多」;匹配位置不重叠。--audit 会拦死词条
(语料含词典自己的标题与说明,所以「麻瓜」这种只出现在说明里的词不会被误判)。glossary.json 里;不要往 houses.json 里塞解释,
那个文件的每一句都是帽子在说话。