通过将私密收藏数据(价格、来源)与公开叙事库完全隔离,配合 IPFS 哈希锚定任务需求,从根本上消除 LLM 幻觉问题,已实际支撑 296 枚钱币的博物馆级内容生成。
整个项目有两套代码库,彼此从不通信。
私有那半叫 Coinex:一套 Next.js 管理后台、一堆脚本、一个 GitHub Actions 任务。它知道我付了多少钱、从谁那里买的、下一步打算花多少。它从不响应任何公开请求。
公开那半叫 Numistoria:Vite、React、Cloudflare Workers。它只知道故事本身。
跨越这条鸿沟的唯一东西,是一个 JSON 文件。发布步骤会对它进行清洗,然后丢进 R2,公开站点再读取这个文件。没有实时连接、没有共享数据库、没有 CORS。私有引擎就算原地起火,博物馆也会若无其事地继续服务昨天的静态包。保持这道边界足够薄,就是整套安全模型——这意味着我在请求时永远不需要去推理"这个字段会不会泄漏"。要么进了清洗后的包里,要么对于公开站点来说,它根本不存在。

让模型写故事,但绝不碰事实
叙述内容来自 Claude,每个 world 调用一次而非每枚硬币一次。这是刻意为之。模型拿到主题、时代背景、每枚硬币的记录事实(按展示顺序排列),然后返回一段引言加每枚硬币的钩子与正文,写成一个连贯的叙事弧。每枚硬币的说明都会回顾前一枚、铺垫后一枚,这样一个"世界"读起来像一章而非一串图注清单。
我还没拥有的硬币,会作为叙事弧的延续来写——缺失的章节——而不是假装它们正躺在某个抽屉里。Prompt 可以对公认的历史大书特书,但严禁凭空编造这枚硬币的状况、出处或价格。输出在写入文件之前会经过 Zod schema 校验,所以格式错误的响应会在门口被拦截,而不是渲染到一半才崩溃。
真正让我骄傲的部分:诚实的引用
这里每个做过 LLM 功能的开发者都心知肚明的事。问模型要来源,它会以十足、耀眼的确信给你一串目录号和 URL,而其中一部分纯属虚构。
我没有试图靠 prompt 解决这个问题。我把机会直接拿掉了。
有一个手工策展的 sources.ts 文件,里面存放着约三十个真实的机构和数据库:Nomisma、OCRE、RPC Online、大英博物馆、Perseus 等等。每个都有一个简短标识符。模型在写一枚硬币的深度解读时,只允许返回这个列表里的 key 加上一行注释说明每个为什么相关。其他一概不行。然后一个 resolveReferences 函数把这些 key 映射到经过验证的标签和真实落地 URL,并对任何无法识别的东西一声不吭地丢弃。
// sources.ts: 白名单。约 30 个机构,每个都经过手工审核。
export const SOURCES = {
ocre: { label: "OCRE (RIC online)", url: "https://..." },
nomisma: { label: "Nomisma", url: "https://..." },
bm: { label: "The British Museum", url: "https://..." },
// ...
} as const
type SourceKey = keyof typeof SOURCES
// 模型可以为每枚硬币返回的全部内容:一个 key,加一行说明其相关性的注释。
type ModelReference = { key: string; note: string }
// 所有 URL 的所有权归代码所有。无法识别的 key 会被静默丢弃。
function resolveReferences(picked: ModelReference[]) {
return picked
.filter((r): r is { key: SourceKey; note: string } => r.key in SOURCES)
.map((r) => ({ ...SOURCES[r.key], note: r.note }))
}
所以站点上每一条链接的所有权都在代码手里。模型只能在我已经审核过的链接中做选择。它可以有十足的自信,但它无法凭空捏造一条引用,因为它从来不是那个执笔写 URL 的手。如果这篇文章你只记住一点,请记住这一个。它可以推广到任何需要机器生成文本但又要引用可信的场景。
每篇深度解读还带有一个 reviewed 标志,初始为 false,站点会如实说明,而不是假装机器写的页面具有权威性。

愿望清单也不是做无用功。策展模型为每个 world 推荐真实可收藏的硬币类型,并设了价格上限,同时必须包含一个"白鲸"——允许超出预算的那一个。
然后每周有一个 GitHub Actions 任务去购物。它按信任度从高到低搜索经销商(CNG、VCoins、MA-Shops,eBay API 排最后),在花掉一个 token 之前先通过标题排除明显的仿品,跳过价格远超上限的任何东西。剩下的交给 Claude,配上最多两张照片和一条关于该渠道可信度的备注,返回结构化的判断:是否 match、 BUY 还是 MAYBE 还是 SKIP、眼缘如何、以及真实性风险评级。Prompt 里直白地说,照片筛查不等于鉴定,便宜得离谱是红旗,未经认证的 eBay 硬币就算看起来再完美最高也只能给 MAYBE。我每周六下午收到邮件摘要。有时候好几周都找不到任何东西,这也很好。
站点上每个展厅都显示一个"已获得 X/N"的计量器,所以博物馆同时也是一个完成度追踪器,随着猎寻任务的工作逐渐填满。公开站点刻意做得很小:React、Vite、一个二十行的哈希路由器、一次同源 JSON 加载,运行时没有其他任何东西。
我和 AI 做了大量协作,这事我不避讳。判断是我的:边界在哪里、哪些事实是神圣的、哪些机构可以算作来源。模型做那些繁重的中间工作:写了又写、为 296 枚硬币生成叙事而不抱怨。这种分工——人类拥有品味和护栏,模型做量——是我现在构建大多数东西的方式。
站点已上线:numistoria.danmat.dev,公开站点开源:github.com/DanMat/Numistoria。去寻找白鲸吧。很想知道你会怎样划定私有到公开的边界。