prompt
(slug + 最新指针)和 prompt_version(不可变历史)。新版本靠 INSERT 一行发布,绝
不去 UPDATE 已有行。回滚 = 把老版本的文本作为新版本再发布一次。可审计、无 race、
无聊 —— 这就是好特性。
前置条件
- 数据库已经迁移(
pnpm db:push),prompt/prompt_version表存在。 - 已登录的 admin 用户 —— 只有
adminActionClient应该有发布权限。 - 看一遍
src/db/ai.schema.ts了解列级真相。
两张表
(promptId, version) 上的唯一索引保证版本序列不乱。latestVersionId 是反规范化的
指针,方便一查到位。
步骤:注册一个新 prompt
三条写操作放进一个事务,避免落到中间状态。version: 2,更新 latestVersionId。老版本通过
(promptId, version) 唯一索引依然可查。
客户端渲染
usePrompt(slug) 拉最新版本,并暴露 render(vars):
{{name}} → 值)。缺失变量会变成空串 —— 客户端不强制 required,要在
服务端发起调用前自己校验。
服务端渲染
别 import hook,直接查:chat({ ... }, { promptId, promptVersionId, userId }),这样 ai_call 行就能记到
是哪一版 prompt 触发的。
回滚
没有 UPDATE。回滚 = 「把旧文本作为新版本发布」:- 拉出你想恢复那一版
promptVersion。 - 插入新行
version: max+1,模板照抄,note 写'rollback to v3'。 - 更新
latestVersionId到这条新行。
验证生效
- 按上面步骤插入一个 prompt。
psql跑SELECT slug, latest_version_id FROM prompt WHERE slug = 'summarize-article';—latest_version_id应该有值。- 在客户端组件里调
usePrompt('summarize-article').render({ article: 'hello' }),应该拿到替换后的 字符串。 - 用
{ promptId, promptVersionId }参数发一次 AI 调用,查ai_call:prompt_id和prompt_version_id列应该填上了。
常见坑
- insert 后忘了更新
latestVersionId。 版本创建出来了,但usePrompt返回null—— 指针没动。永远在同一个事务里更新。 - 去 UPDATE 已有的
prompt_version行。 别这样。唯一索引拦不住你,但这么做会 悄无声息地改写历史,让ai_call.promptVersionId的引用变成谎言。请插新版本。 - 服务端漏校 required 变量。
render会乐呵呵替成空串。调chat之前先Object.entries(variables).filter(([_, s]) => s.required)校一遍。 - slug 撞名。
prompt.slug有唯一索引 —— insert 直接抛错。换成summarize-article-v2这种命名空间式 slug,别复用旧的。 - prompt 来自磁盘文件。 如果你的模板是构建时读的
.txt,数据库根本不知道。请 只保留一个真相源。
官方文档
- Drizzle ORM:orm.drizzle.team
- Postgres 唯一索引:postgresql.org/docs/current/indexes-unique.html
- 源码:
src/db/ai.schema.ts、src/ai/hooks/use-prompt.ts