ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
生成/校验「骑士与无赖」(Knights and Knaves)谜题,并导出 puzzles.json。
|
||||
|
||||
每道谜题的每句话都用 csp_solver.py 里的结构化 DSL 表示(见该文件顶部说明),
|
||||
既能渲染成中文题面(给 LLM 看),也能直接翻译成 python-constraint 约束来求解。
|
||||
本脚本用 python-constraint 校验每题「解唯一」后才写出——这确保真值解无歧义,
|
||||
同时演示了实验 5-2 的核心:把谜题形式化为 CSP 并用求解器离线求解。
|
||||
|
||||
约定:骑士(knight)永远说真话,无赖(knave)永远说假话。t[name]=True 表示骑士。
|
||||
|
||||
用法:
|
||||
python build_puzzles.py # 导出内置的 12 道精选谜题(默认)
|
||||
python build_puzzles.py --generate 20 # 随机生成 20 道解唯一的谜题
|
||||
python build_puzzles.py --generate 20 --min-people 3 --max-people 5 --seed 7
|
||||
python build_puzzles.py --output my.json # 指定输出文件
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import random
|
||||
|
||||
from csp_solver import render_nl, solve, solve_labeled
|
||||
|
||||
# 每题:id, 名字列表, dict{name: 结构化陈述}。中文题面由结构化陈述自动渲染,
|
||||
# 但精选题保留手写的更自然的中文(见 STATEMENTS_NL 覆盖)。
|
||||
CURATED = [
|
||||
("kk01", ["A", "B"], {
|
||||
"A": ["is", "B", "knave"],
|
||||
"B": ["and", ["is", "A", "knave"], ["is", "B", "knave"]]}),
|
||||
("kk02", ["A", "B"], {
|
||||
"A": ["same", "A", "B"],
|
||||
"B": ["diff", "A", "B"]}),
|
||||
("kk03", ["A", "B"], {
|
||||
"A": ["count", "knight", ">=", 1],
|
||||
"B": ["is", "A", "knave"]}),
|
||||
("kk04", ["A", "B", "C"], {
|
||||
"A": ["is", "B", "knave"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["and", ["is", "A", "knave"], ["is", "B", "knave"]]}),
|
||||
("kk05", ["A", "B", "C"], {
|
||||
"A": ["is", "B", "knight"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["same", "A", "B"]}),
|
||||
("kk06", ["A", "B", "C"], {
|
||||
"A": ["same", "B", "C"],
|
||||
"B": ["is", "A", "knave"],
|
||||
"C": ["same", "C", "A"]}),
|
||||
("kk07", ["A", "B", "C"], {
|
||||
"A": ["or", ["is", "A", "knave"], ["is", "B", "knight"]],
|
||||
"B": ["is", "A", "knight"],
|
||||
"C": ["is", "B", "knave"]}),
|
||||
("kk08", ["A", "B", "C", "D"], {
|
||||
"A": ["same", "B", "D"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["is", "D", "knight"],
|
||||
"D": ["diff", "B", "C"]}),
|
||||
("kk09", ["A", "B", "C", "D"], {
|
||||
"A": ["is", "B", "knight"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["is", "D", "knight"],
|
||||
"D": ["diff", "A", "B"]}),
|
||||
("kk10", ["A", "B", "C", "D"], {
|
||||
"A": ["count", "knave", ">=", 3],
|
||||
"B": ["is", "A", "knave"],
|
||||
"C": ["is", "B", "knight"],
|
||||
"D": ["is", "C", "knave"]}),
|
||||
("kk11", ["A", "B", "C", "D", "E"], {
|
||||
"A": ["is", "B", "knight"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["is", "D", "knight"],
|
||||
"D": ["is", "E", "knave"],
|
||||
"E": ["count", "knight", ">=", 2]}),
|
||||
("kk12", ["A", "B", "C", "D", "E"], {
|
||||
"A": ["is", "B", "knight"],
|
||||
"B": ["is", "C", "knave"],
|
||||
"C": ["is", "D", "knave"],
|
||||
"D": ["is", "E", "knight"],
|
||||
"E": ["same", "A", "C"]}),
|
||||
]
|
||||
|
||||
# 精选题的手写中文题面(比自动渲染更自然)。未覆盖的句子回退到 render_nl。
|
||||
STATEMENTS_NL = {
|
||||
("kk01", "B"): "我们两人都不是骑士。",
|
||||
("kk02", "A"): "我和 B 是同一类人(要么都是骑士,要么都是无赖)。",
|
||||
("kk02", "B"): "我和 A 是不同类人。",
|
||||
("kk03", "A"): "我们当中至少有一个骑士。",
|
||||
("kk04", "C"): "A 和 B 都是无赖。",
|
||||
("kk06", "C"): "我和 A 是同一类人。",
|
||||
("kk07", "A"): "我是无赖,或者 B 是骑士。",
|
||||
("kk09", "D"): "A 和 B 不是同一类人。",
|
||||
("kk10", "A"): "我们四人当中至少有三个无赖。",
|
||||
("kk11", "E"): "我们五人当中至少有两个骑士。",
|
||||
("kk12", "E"): "A 和 C 是同一类人。",
|
||||
}
|
||||
|
||||
|
||||
def build_puzzle(pid, names, structs, nl_overrides=None):
|
||||
"""求解校验(要求解唯一)并组装成写入 puzzles.json 的一条记录。"""
|
||||
sols = solve_labeled(names, structs)
|
||||
if len(sols) != 1:
|
||||
raise ValueError(f"{pid} 解不唯一: {len(sols)} 个解 -> {sols}")
|
||||
solution = sols[0]
|
||||
|
||||
nl_overrides = nl_overrides or {}
|
||||
statements = {n: nl_overrides.get(n, render_nl(structs[n])) for n in names}
|
||||
lines = [f"{n}: 「{statements[n]}」" for n in names]
|
||||
desc = (
|
||||
f"这座岛上有 {len(names)} 位居民:{', '.join(names)}。"
|
||||
"每位居民要么是永远说真话的骑士(knight),要么是永远说假话的无赖(knave)。"
|
||||
"他们各自说了如下的话:\n" + "\n".join(lines)
|
||||
)
|
||||
return dict(id=pid, num_people=len(names), names=names,
|
||||
statements=statements, statements_struct=structs,
|
||||
description=desc, solution=solution)
|
||||
|
||||
|
||||
# ---------------- 随机生成器 ----------------
|
||||
def _random_stmt(speaker, names, rng):
|
||||
"""为 speaker 随机生成一句合法的结构化陈述。"""
|
||||
others = [n for n in names if n != speaker]
|
||||
# Solo resident: only count statements are valid (no "others" to name).
|
||||
if not others:
|
||||
role = rng.choice(["knight", "knave"])
|
||||
op = rng.choice([">=", "<=", "=="])
|
||||
return ["count", role, op, rng.randint(1, len(names))]
|
||||
kind = rng.choice(["is", "is", "same", "diff", "count"])
|
||||
if kind == "is":
|
||||
return ["is", rng.choice(others), rng.choice(["knight", "knave"])]
|
||||
if kind == "same":
|
||||
return ["same", speaker, rng.choice(others)]
|
||||
if kind == "diff":
|
||||
return ["diff", speaker, rng.choice(others)]
|
||||
# count:全体中某角色的人数满足某比较
|
||||
role = rng.choice(["knight", "knave"])
|
||||
op = rng.choice([">=", "<=", "=="])
|
||||
k = rng.randint(1, len(names))
|
||||
return ["count", role, op, k]
|
||||
|
||||
|
||||
def generate(count, min_people, max_people, seed):
|
||||
"""随机生成 count 道「解唯一」的谜题(用 python-constraint 过滤)。"""
|
||||
rng = random.Random(seed)
|
||||
names_pool = ["A", "B", "C", "D", "E", "F", "G"]
|
||||
puzzles = []
|
||||
attempts = 0
|
||||
while len(puzzles) < count and attempts < count * 2000:
|
||||
attempts += 1
|
||||
n = rng.randint(min_people, max_people)
|
||||
names = names_pool[:n]
|
||||
structs = {sp: _random_stmt(sp, names, rng) for sp in names}
|
||||
if len(solve(names, structs)) != 1: # 只保留解唯一的谜题
|
||||
continue
|
||||
pid = f"gen{len(puzzles) + 1:03d}"
|
||||
puzzles.append(build_puzzle(pid, names, structs))
|
||||
if len(puzzles) < count:
|
||||
print(f"警告:{attempts} 次尝试只生成了 {len(puzzles)}/{count} 道解唯一的谜题。")
|
||||
return puzzles
|
||||
|
||||
|
||||
def build_curated():
|
||||
out = []
|
||||
for pid, names, structs in CURATED:
|
||||
nl = {n: STATEMENTS_NL[(pid, n)]
|
||||
for n in names if (pid, n) in STATEMENTS_NL}
|
||||
out.append(build_puzzle(pid, names, structs, nl))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="生成/校验骑士与无赖谜题并导出 puzzles.json"
|
||||
"(用 python-constraint 离线求解,校验每题解唯一)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__)
|
||||
ap.add_argument("--generate", type=int, metavar="N", default=0,
|
||||
help="随机生成 N 道解唯一的谜题(默认 0=导出内置 12 道精选题)")
|
||||
ap.add_argument("--min-people", type=int, default=2,
|
||||
help="随机生成时每题最少居民数(默认 2)")
|
||||
ap.add_argument("--max-people", type=int, default=5,
|
||||
help="随机生成时每题最多居民数(难度上限,默认 5)")
|
||||
ap.add_argument("--seed", type=int, default=42,
|
||||
help="随机种子,保证可复现(默认 42)")
|
||||
ap.add_argument("--output", default="puzzles.json",
|
||||
help="输出文件路径(默认 puzzles.json)")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.generate > 0:
|
||||
print(f"随机生成 {args.generate} 道谜题"
|
||||
f"({args.min_people}~{args.max_people} 人,seed={args.seed})...")
|
||||
out = generate(args.generate, args.min_people, args.max_people, args.seed)
|
||||
else:
|
||||
out = build_curated()
|
||||
|
||||
for p in out:
|
||||
print(f"{p['id']}: OK 唯一解 = {p['solution']}")
|
||||
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
json.dump(out, f, ensure_ascii=False, indent=2)
|
||||
print(f"\n已写出 {len(out)} 题到 {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user