针对 Anthropic prompt playground 题目的完整解答:不可变版本模型、共享范围与撤销、超大 prompt、并发标签页、删除,以及导出为 API 调用。
作者:The Forward Deployed 编辑团队已审阅
本文属于 Anthropic 系统设计题库。该题目是这一轮面试的典型代表。分析与解答为本站原创。
问题描述
一位开发者打开 playground,编写系统 prompt 和几条消息,选择模型和设置,然后运行。他们反复调整并重新运行,保存自己喜欢的版本,并把链接发给同事。之后他们把最终胜出的版本导出为代码。面试官给出一个包含三个方框的图,并要求你不要再画更多的基础设施。
计费、模型服务和团队管理不在讨论范围内。
澄清问题
- 谁在使用它? 基于 API 进行开发的开发者。假设个人账户隶属于组织。
- 一个 prompt 能有多大? 大多数只有几 KB。有些包含整份文档或代码库,最大可达 10 MB。
- 需要哪些共享方式? 公开链接、任何已登录用户可访问的链接,以及邀请特定账户,每种都可以是查看者或编辑者。
- 是否支持实时协同编辑? 不支持。两个人编辑同一个 prompt 会被视为冲突,而不是逐键合并。
- 历史记录保留多久? 每个已保存的版本都会无限期保留,除非该 prompt 被删除。
- 运行记录会保存吗? 会,每次运行都会记录其版本、设置和输出,以便比较结果。
为什么 playground 有难度
流量很低。数据模型才是整个问题的核心。
一个 prompt 不是一个单一的值。它不断变化,人们想要回退,两个标签页同时编辑它,而指向它的链接会传给拥有不同权限的人。如果你把“这个 prompt”存储为一行可变数据,上述每一个需求都会变成特例:历史需要单独的审计表,撤销需要快照,共享某个特定状态需要复制一份,并发编辑会互相覆盖。
因此,核心矛盾是简单的可变模型与正确的历史、共享和并发之间的对立。答案是让版本不可变,只移动一个指针,这样上述大多数特例就变成了普通的读取操作。
flowchart LR B([Browser]):::user --> W[Web server]:::svc --> D[(Database)]:::store W --> O[(Object storage<br/>large bodies)]:::store W --> M[Model API]:::svc classDef user fill:#e6efec,stroke:#315e55,color:#171717; classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717; classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717;
关键思路。 面试官已经固定了架构,所以评分重点在于数据模型。不可变版本加上一个可移动的指针,用一个思路同时解决历史、撤销、共享快照和并发问题。
核心概念
不可变版本
每次保存都会创建一行永不改变的新版本记录。它记录其父版本,即它所编辑自的那个版本。提示词行保存一个指向其最新版本的指针。历史就是沿父版本向上回溯。撤销就是把指针移回去。从同一个父版本出发的两次编辑会创建两个子版本,这就是分支,无需加锁。
内容寻址
版本的正文按其哈希存储。两个正文相同的版本指向同一个存储对象。大正文只存一次,当用户只改设置时就能节省空间。
比较并设置
移动最新版本指针是一次条件写入:只有当它仍等于编辑者开始编辑时的版本,才把最新版本设为新版本。如果另一个标签页先移动了它,写入就会失败,客户端便知道要显示冲突。
范围与角色
范围规定谁可以打开链接:任何人、任何已登录用户,或列出的账户。角色规定他们可以做什么:查看、评论或编辑。把两者分开。同一个提示词可以同时拥有公开查看链接和编辑者邀请。
核心思想。 不可变版本、内容寻址的正文、比较并设置的指针,以及分离的范围与角色,是四个构建基石。
需求
继续阅读之前。 列出需求。然后决定你最想保护哪一个属性,以及在流量极小的前提下,真正的约束是什么。
1.1 功能需求
- 创建、编辑和保存提示词及其模型设置。
- 运行提示词并流式输出结果。保存每次运行。
- 查看历史、比较版本、恢复旧版本。
- 通过公开链接、登录链接或账户邀请进行分享,并附带角色。可撤销任何分享。
- 处理最大 10 MB 的提示词。
- 删除提示词,并可在保留期内恢复。
- 将一个版本导出为 curl 或 SDK 代码形式的 API 请求。
1.2 非功能需求
- 不丢失工作成果。 返回成功的保存必须持久化,且一个标签页永远不会悄悄覆盖另一个标签页的内容。
- 访问正确。 被撤销的用户在规定时限内失去访问权限。
- 编辑快速。 打开提示词和切换版本感觉即时,即使正文很大。
- 存储廉价。 一个 10 MB 提示词的多个版本不能让存储量随版本数成倍增长。
1.3 约束与属性
不丢失工作成果是要保护的属性。 开发者会连续迭代数小时;一次编辑因并发标签页或保存失败而丢失会摧毁信任。载荷大小是塑造设计的约束。 用户规模的扩展微不足道,但每个版本 10 MB 的正文决定了存储布局、上传路径和渲染路径。
核心思想。 保护已保存的工作。围绕正文大小进行设计,因为用户规模不是问题所在。
粗略估算
作为练习:一百万注册用户,10,000 日活跃用户,每人每天 20 次保存和 30 次运行。
2.1 请求速率
保存:每天 10,000 × 20 = 200,000 次,平均每秒约 2.3 次。运行:每天 300,000 次,每秒约 3.5 次。读取(如打开提示词和历史记录)的频率是它的几倍。一个关系型数据库可以轻松应对,绰绰有余。说明这一点,然后继续。
2.2 存储
假设平均正文为 4 KB,1% 的保存带有 5 MB 的正文。
小正文:198,000 × 4 KB ≈ 每天 0.8 GB。大正文:2,000 × 5 MB = 每天 10 GB。一年下来约 3.9 TB,主要由大正文构成。如果大编辑以平均 50 KB 的差异(diff)形式存储,大正文的成本降至每天 0.1 GB,年总量降至约 330 GB。
这就是为什么尽管请求速率微不足道,差异存储和去重在这里仍然重要。
2.3 病毒式传播的链接
发布到热门论坛的公开链接可能在几分钟内带来数千次打开。这是唯一需要规划的读取峰值,而且它只触及不可变数据。
核心观点。 请求速率极小。大正文的存储和偶尔的病毒式读取才是决定设计的仅有数字。
API 设计
继续阅读之前。 一个保存请求来自加载了版本 41 的标签页,但最新版本现在是 42。API 应该返回什么?
它应该拒绝移动指针,并返回一个包含当前最新版本的冲突。客户端随后展示两个版本,让用户选择。新版本仍可以作为分支存储,因此用户的工作永远不会丢失。
3.1 提示词与版本
POST /v1/prompts {title}
-> {prompt_id}
POST /v1/prompts/:id/versions
{parent_version_id, body_ref | body, model, settings}
-> 201 {version_id, latest: true}
-> 409 {version_id, latest: false, current_latest_id} // saved as a branch
GET /v1/prompts/:id -> {title, latest_version_id, owner, role}
GET /v1/prompts/:id/versions?cursor= -> page of {version_id, parent, author, created_at}
GET /v1/versions/:vid -> {model, settings, body_ref, body_hash, size}
POST /v1/prompts/:id/restore {version_id} // moves latest, creates no copy3.2 大正文
POST /v1/uploads {size, sha256}
-> {upload_id, part_urls[]} // presigned multipart URLs, or "exists": true
PUT <part_url> (browser to object storage directly)
POST /v1/uploads/:id/complete -> {body_ref}如果哈希已存在,服务器回答“已存在”,客户端跳过上传。
3.3 分享
POST /v1/prompts/:id/shares {scope: public|link|accounts, role, version_id?, expires_at?}
-> {token, url}
DELETE /v1/shares/:token
POST /v1/prompts/:id/acl {account_id, role}
DELETE /v1/prompts/:id/acl/:account_id
GET /s/:token -> the shared prompt or version, if allowed3.4 运行与导出
POST /v1/versions/:vid/runs -> stream of tokens, then {run_id, usage}
GET /v1/versions/:vid/export?format=curl|python|typescript核心观点。 保存携带其父版本,冲突作为分支保存,大正文通过哈希直接上传到存储,分享是带有自己令牌的独立对象。
数据模型
4.1 表
prompt (id, org_id, owner_id, title, latest_version_id,
created_at, updated_at, deleted_at)
version (id, prompt_id, parent_version_id, author_id, created_at,
body_ref, body_hash, body_size, body_kind, -- inline | blob | diff
base_version_id NULL, -- for diffs
model, settings_json)
run (id, version_id, user_id, created_at, output_ref, usage_json, status)
share_link (token, prompt_id, version_id NULL, scope, role,
created_by, created_at, expires_at, revoked_at)
acl_entry (prompt_id, principal_id, role, granted_by, created_at)
blob (hash, size, storage_key, ref_count, created_at)4.2 索引
prompt(owner_id, updated_at desc) “我的提示词”列表 prompt(org_id, updated_at desc) 组织库 version(prompt_id, created_at desc) 历史 share_link(token) 唯一,打开链接 acl_entry(principal_id, prompt_id) “与我共享” acl_entry(prompt_id) 谁有访问权限
4.3 为什么版本行保存设置
一个版本冻结所有影响输出的内容:模型名称、温度、最大令牌数、停止序列、工具和正文。一次运行指向一个版本,因此任何旧结果都可以被解释和复现。如果设置存储在提示词行上,后续的更改可能会改写旧运行看起来所使用的设置。
核心观点。 版本是真相的单位。它将正文和设置一起冻结,其他一切都指向它。
高层设计
5.1 单个可变行
最简单的设计是将每个提示词存储为一行,包含其文本和设置,并就地更新。
flowchart LR B([Browser]):::user --> W[Web server]:::svc --> P[(prompt row<br/>text, settings)]:::store classDef user fill:#e6efec,stroke:#315e55,color:#171717; classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717; classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717;
它在第一次真实使用中就失败了。两个标签页互相覆盖。没有历史记录。共享链接显示的是当前文本,而不是发送者当时看到的内容。一个 10 MB 的正文存放在数据库行中,每次打开都会传送到浏览器。
5.2 修复 1:不可变版本与指针
每次保存都会写入一个新版本,并通过比较并交换(compare-and-set)移动 prompt 的最新指针。
flowchart LR W[Web server]:::svc --> P[(prompt<br/>latest_version_id)]:::store W --> V[(version rows<br/>immutable, parent pointer)]:::new P -.points to.-> V classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717; classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717; classDef new fill:#ffffff,stroke:#c4492d,stroke-width:2px,stroke-dasharray:5 3,color:#171717;
历史、撤销和分支功能由此免费获得。并发的标签页无法互相覆盖,因为指针只会从编辑器开始时的那个版本移动。
5.3 修复 2:将正文移至对象存储
超过阈值(例如 64 KB)的正文存入对象存储,按哈希寻址。浏览器通过预签名 URL 直接上传。数据库只保存引用。
flowchart LR B([Browser]):::user -->|1. hash + size| W[Web server]:::svc W -->|2. presigned parts or exists| B B -->|3. upload parts| O[(Object storage<br/>by sha256)]:::new B -->|4. save version with body_ref| W --> V[(version)]:::store classDef user fill:#e6efec,stroke:#315e55,color:#171717; classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717; classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717; classDef new fill:#ffffff,stroke:#c4492d,stroke-width:2px,stroke-dasharray:5 3,color:#171717;
Web 服务器不再让 10 MB 的数据经过其内存,而且相同的正文只需上传一次。
5.4 修复 3:将共享作为独立对象
共享拥有自己的表,包含令牌、范围、角色、可选的固定版本以及撤销时间。直接的账户访问权限存放在 ACL 表中。对 prompt 的请求会依次检查所有权、ACL,然后是共享令牌。
flowchart LR
R[Request for prompt]:::svc --> O{Owner?}:::svc
O -->|no| A{ACL entry?}:::svc
A -->|no| T{Valid share token?<br/>scope, not revoked, not expired}:::new
T -->|no| X[403]:::bad
O -->|yes| OK[Allow with role]:::store
A -->|yes| OK
T -->|yes| OK
classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717;
classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717;
classDef bad fill:#fbe9e4,stroke:#c4492d,color:#171717;
classDef new fill:#ffffff,stroke:#c4492d,stroke-width:2px,stroke-dasharray:5 3,color:#171717;5.5 修复 4:缓存永不改变的内容
版本永不改变,因此任何以版本 ID 为键的内容都可以永久缓存:在浏览器中、CDN 上以及服务器中。权限检查则进行短期缓存,并在变更时失效。
5.6 组合后的设计
sequenceDiagram
autonumber
actor U as Developer
participant B as Browser
participant W as Web server
participant D as Database
participant O as Object storage
participant M as Model API
U->>B: edit and click Save
B->>W: POST versions {parent=41, body or body_ref}
W->>D: insert version 43 (parent 41)
W->>D: UPDATE prompt SET latest=43 WHERE latest=41
alt pointer moved
W-->>B: 201 latest
else someone saved 42 first
W-->>B: 409, branch saved, current latest=42
end
U->>B: click Run
B->>W: POST versions/43/runs
W->>O: fetch body (if blob)
W->>M: stream request
M-->>W: tokens
W-->>B: tokens
W->>D: insert run with usage核心思想。 每个修复都消除了可变行的一个缺陷:版本解决历史与冲突,对象存储解决体积问题,共享对象解决访问控制,缓存应对读取高峰。
深入探讨
6.1 撤销与过期访问
在继续阅读之前。 一位用户移除了某位同事的访问权限。这位同事还能继续读取该 prompt 多久?这样的时长是否可以接受?
有三个地方可能提供过期的访问权限。
权限缓存。 如果服务器将权限判定缓存 30 秒,被撤销权限的用户最多还能保留 30 秒的访问。写入时失效可以缩短这个时间:撤销时,删除该 prompt 对应的缓存条目。保留生存时间(TTL)作为兜底,以防失效消息丢失。
用于正文内容的签名 URL。 正文通过几分钟内过期的预签名 URL 提供。撤销会阻止签发新的 URL,但已经发出的 URL 在过期前仍然有效。保持较短的过期时间,5 到 15 分钟,并说明这就是边界。
浏览器副本。 团队成员已经加载的任何内容都会留在他们的屏幕上。任何服务器设计都无法将其收回。请直截了当地说明这一点。
将结果表述为一个边界:新请求在几秒内被拒绝,正文 URL 在 15 分钟内失效,而已经查看过的内容无法召回。对于大多数 prompt 内容来说这是可以接受的。对于粘贴进 prompt 的密钥,正确的做法是轮换该密钥。
区分答案优劣的点:撤销
弱回答假设撤销是即时的
只删除 ACL 行就结束,没有意识到缓存和签名 URL 的存在。
良好回答会指出各类缓存及其边界
解释权限缓存和签名 URL 的过期机制,并为每一项给出时间边界。
强回答会陈述完整的契约
给出新请求、正文 URL 以及已查看内容的边界,并说明哪些内容需要比撤销更强的应对措施。
6.2 10 MB 的路径
在继续阅读之前。 一个用户对 10 MB 的 prompt 编辑了 50 次。在你的设计下,这会消耗多少存储和带宽?
朴素做法下,50 × 10 MB = 500 MB 的存储,以及同样多的上传带宽。
使用差异(diff)时,每次保存只上传并存储相对于父版本发生变化的部分。每第十个版本,或当 diff 链超过大小限制时,存储一个完整快照,使得重建任何版本最多只需要几个 diff。版本行记录其正文是内联存储、完整 blob,还是相对于某个基础版本的 diff。
渲染也很重要。在浏览器文本区域中放置 10 MB 的字符串会导致输入卡顿。使用支持行虚拟化的编辑器,并按字节范围获取正文,这样在整份文件到达之前就能先显示第一屏。按版本 ID 将正文缓存在浏览器的 IndexedDB 中,这是永久安全的,因为版本内容从不改变。
在运行之前,根据模型的上下文窗口统计 token 数量,如果 prompt 放不下就发出警告。一份 10 MB 的正文可能超出所有模型的上下文。
flowchart LR V1[v1: full 10 MB]:::store --> V2[v2: diff 3 KB]:::svc --> V3[v3: diff 1 KB]:::svc --> V10[...]:::svc --> V11[v11: full snapshot]:::store --> V12[v12: diff 2 KB]:::svc classDef svc fill:#f4f1e8,stroke:#315e55,color:#171717; classDef store fill:#fdf3dc,stroke:#c4492d,color:#171717;
6.3 多标签页与冲突
在继续阅读之前。 一个用户在三个标签页中打开了同一个提示词,并在其中两个标签页里间隔几秒先后保存。每个标签页会看到什么?
每次保存都携带其标签页加载时的父版本。第一次保存将指针从 41 移动到 42。第二次保存同样基于 41,其比较并设置(compare-and-set)失败。服务器将其存储为版本 43,即 42 的兄弟版本,并返回 409 以及当前最新版本。第二个标签页显示一条横幅:"此提示词已在另一个标签页中更改。保留你的版本、保留对方的版本,或进行对比。" 保留对方的版本不会丢弃任何内容,因为版本 43 仍保留在历史记录中。
在同一浏览器的多个标签页之间,BroadcastChannel 可以在保存发生时通知其他标签页,使它们在用户编辑过时文本之前完成更新。这只是锦上添花。比较并设置才是真正的保证。
区分答案的关键:并发
弱:最后写入者胜
保存直接覆盖该行,较早标签页的工作成果消失。
良好:检测到冲突
使用版本检查,显示冲突而不是直接覆盖。
强:永不丢失工作成果
将落败的编辑保存为一个分支,解释指针上的比较并设置机制,并使用跨标签页消息传递来减少冲突,但不依赖它。
6.4 删除与恢复
通过设置
deleted_at
标志位实现软删除。所有读取路径都基于该标志位进行过滤,因此提示词立即消失。指向它的分享链接返回"此提示词已被删除",而不是通用的 404,这能减少支持工单。
清除任务在保留期(例如 30 天)之后运行。它删除版本行并递减 blob 的引用计数。只有当某个 blob 的计数降为零时才会被删除,因为另一个提示词可能通过哈希共享相同的正文。在保留期内恢复会清除
deleted_at
标志位,所有链接和 ACL 条目都重新生效,因为它们从未被改动过。
6.5 病毒式传播的公开链接
指向固定版本的公开链接是不可变内容。通过 CDN 提供渲染后的页面和正文,以版本 ID 作为键,并设置较长的缓存生存期。将作用域存储在分享行上,这样对公开链接的检查只需一次带索引的查询,无需 ACL 联表。如果链接被撤销,则清除该 token 对应的 CDN 条目。
6.6 导出
将版本渲染为一个真实的 API 请求:模型、设置、系统提示词和消息。提供 curl、Python 和 TypeScript。不要将 API 密钥包含在导出内容中,而是使用环境变量占位符。这个小功能是游乐场与生产环境的连接点,它表明你知道一个版本必须冻结哪些内容。
6.7 比较版本
在继续阅读之前。 一位开发者想查看一个 2 MB 提示词的版本 12 与版本 19 之间有什么变化,以及输出有何不同。你会如何构建这个功能?
有两种比较很重要:提示词本身和运行结果。
对于提示词,计算两个正文之间基于行的差异,并将设置更改显示为表格:模型、温度以及任何其他不同的字段。在浏览器中对 2 MB 的正文进行文本差异比较是可以的;请在 web worker 中进行,以保持页面响应。对于非常大的正文,在服务器端一次性计算差异,并按两个版本 ID 缓存,因为版本永远不会改变。
对于结果,开发者希望在相同的输入上运行两个版本。将其建模为一次对比运行:一组输入、两个版本,以及每个输入在每个版本下的一个输出,并排显示。由于每次运行都会记录其版本,因此即使两个版本后来都有了新的子版本,数周后结果仍然可以比较。
成本可能让人意外。在 20 个输入上运行一个 2 MB 提示词(约 50 万 token)的两个版本,就是 2000 万输入 token。在运行前显示预估成本,并在超过阈值时要求确认。当大正文是共享前缀时,提供商侧的提示词缓存会有所帮助。
6.8 数据库层面的访问控制
在继续阅读之前。 一位开发者添加了一个列出最近版本的新端点,却忘记检查权限。是什么阻止它泄露其他用户的提示词?
仅靠应用层检查依赖于每个端点都记得执行检查。请添加第二层。两种常见方案:
- 数据库中的行级安全。 每个查询都在会话中携带请求用户的 ID 运行,每张表上的策略将行过滤为该用户可访问的行:拥有的、通过 ACL 共享的,或通过有效分享令牌可达的。应用代码中被遗忘的检查只会返回空结果,而不是他人的数据。
- 单一数据访问层。 所有读取都经过一个模块,该模块接收调用者的身份并应用访问检查。在代码审查和 lint 规则中禁止处理器直接访问表。
无论采用哪种方式,都添加一个测试套件,以无权限用户的身份调用每个端点,并期望不返回任何内容。这就是权限测试集在 playground 中的版本。
答案的区分点:访问控制
弱:在每个处理器中检查
依赖于每个端点都记得检查权限。
良好:单一访问检查层
将访问检查集中在一个模块或中间件中,供所有读取使用。
强:纵深防御并配有测试
添加数据库层面的行策略或强制的数据访问层,并添加一个以未授权用户身份调用每个端点的测试。
变体
7.1 实时协同编辑
如果面试官提出实时协作,版本模型仍作为历史层。实时编辑在使用操作变换或 CRDT 的会话中进行,会话在检查点保存版本。请说明这是一项很大的新增功能,并在设计之前询问它是否在范围内。
7.2 评测运行
一个常见的扩展是让某个版本针对一组测试输入运行,并对输出进行评分。可以将其建模为一个评测集表、一次指向某个版本和某个集合的评测运行,以及每个用例的结果。不可变版本使评测结果可信,因为被测试的提示词事后无法更改。
7.3 组织库
团队希望拥有共享的、经过筛选的提示词。添加一个组织级别的范围和版本上的“已发布”标志。已发布的版本是其他团队引用的对象,而作者可以继续在草稿上迭代,而不会破坏它们。
7.4 当流量达到十倍时
设计几乎不需要改变,这一点值得说明。在每天 100,000 名用户的情况下,保存达到每秒约 25 次,运行达到每秒约 35 次:仍然是一个带读副本的主数据库。第一个压力点是病毒式传播的链接,CDN 已经处理了它;还有大正文的存储,它线性增长,并且已经放在对象存储中。第二个是针对模型 API 的运行吞吐量,这需要按组织设置速率限制。只有在远超这个规模之后,按组织对数据库进行分片才值得考虑。
可迁移的模式
这个 playground 是一个带有可移动头指针的不可变快照日志,与 git、文档历史和配置管理的形态相同。当问题涉及编辑、历史记录、共享特定状态和并发写入者时,首先考虑这种形态。这样一来,大多数棘手的情况都会变成读取操作。
回顾:30 秒答案
- 带有父指针的不可变版本。 免费获得历史记录、撤销和分支。
- 对最新指针进行比较并设置。 并发的标签页永远不会互相覆盖;落败的编辑成为一个分支。
- 正文按哈希存入对象存储。 直接上传、去重、结合定期快照的 diff。
- 共享作为带有范围和角色的对象。 撤销的延迟已针对缓存和签名 URL 说明。
- 按版本 ID 永久缓存。 它使病毒式传播的链接和标签页切换变得廉价。
测验
+为什么将版本存储为带有父指针的不可变行?
因为历史记录、撤销、共享特定状态和并发编辑都变得简单。历史记录是沿父指针向上遍历,撤销是移动一个指针,共享可以固定一个永不更改的版本,而来自同一父指针的两次编辑会成为一个分支,而不是覆盖。
+对最新指针进行比较并设置能防止什么?
丢失更新。只有当指针仍指向编辑者开始时的版本时,保存才会移动该指针。如果另一个标签页先移动了它,保存就会失败,客户端会显示冲突,同时该编辑被保留为一个分支。
+为什么撤销会有延迟,延迟有多长?
权限决策可能会被缓存,正文文件通过签名 URL 提供,签名 URL 在过期前一直有效。通过直写式失效,新请求会在几秒内被拒绝。已有的签名 URL 在到期前(例如 15 分钟)一直有效。已经显示在查看者屏幕上的内容无法撤回。
+为什么用差异加定期快照,而不是只用差异?
仅凭差异重建某个版本意味着要重放自第一个版本以来的所有差异,这会无限制地增长。每 N 个版本保存一次完整快照,可将重建限制在 N 个差异以内。
+提示词被清除后,blob 何时可以删除?
只有当不再有任何版本引用其哈希时。正文通过内容哈希在版本和提示词之间共享,因此清除操作会递减引用计数,并在计数为零时删除 blob。
+为什么要永久缓存两个版本之间的差异?
版本是不可变的,因此两个版本 ID 之间的差异永远不会改变。计算一次并按这对 ID 缓存始终是正确的。
+行级安全在应用层权限检查之上增加了什么?
它在数据库本身强制执行访问控制,因此即使某个端点忘记了自己的权限检查,其查询也只会返回用户有权查看的行。
参考资料与延伸阅读
- Git 内部原理:Git 对象讲解了内容寻址存储以及带有父指针的快照,这正是版本表背后的模型。
- Amazon S3 分段上传介绍了大对象的直接、可断点续传的上传方式。
- 提示词与结构化输出讨论了什么使提示词成为值得版本管理的契约。
- OpenAI 的资源池有一个共享较少、前端状态更多的近亲:模型 playground。