批处理、缓存与可靠性
本节目标
你将把一次 embed(text) 调用扩展成可恢复流水线:输入有稳定身份,批次遵守多重限制,响应逐项对账,失败有界重试,缓存绑定完整空间,写入前校验维度/有限值/范数/哈希,日志不泄露正文。
先在隔离环境试验
Windows 11 / PowerShell 7 的基础方式仍是 venv + pip。在 vault 外的练习目录执行,并确保 .venv/ 被忽略,不要提交虚拟环境:
python -m venv .venv # 在当前练习目录创建隔离虚拟环境,避免污染系统 Python。
.\.venv\Scripts\Activate.ps1 # 在 PowerShell 会话中激活刚创建的虚拟环境。
python -m pip install --upgrade pip # 用当前解释器对应的 pip 升级安装工具。
python -m pip install package-name # 将占位符换成已确认版本的官方 SDK 或本地运行库。最后一行的 package-name 只是占位,应替换为所选官方 SDK 或本地运行库,并固定经过验证的版本。掌握基础后可再使用 uv 管理环境;换工具不会替代锁定依赖、模型 revision 和复现实验。
本库离线项目不需要安装第三方包。
工作项而不是裸字符串
进入队列前,把每个输入变成稳定工作项:
{
"item_id": "chunk-018",
"source_revision": "rev-7",
"role": "document",
"input_sha256": "<hash of exact embedding input>",
"space_id": "candidate-2026-07",
"attempt": 0,
"state": "pending"
}JSON 不支持合法的行尾注释:item_id 关联 canonical 输入,source_revision 固定来源版本,role 选择官方 query/document 路径,input_sha256 绑定实际送入模型的文本,space_id 防止跨空间缓存命中,attempt 计数重试,state 驱动可恢复的状态机。保持代码块为纯 JSON,才能直接作为队列样例解析。
实际正文可从受控内容存储按 ID 读取,不要复制到普通队列日志。input_sha256 必须针对真正送入模型的文本,包括模型要求的标题、query/document prefix 或任务指令;只哈希展示正文会让缓存与向量来源不一致。
推荐状态:
pending → submitted → received → validated → indexed
失败分为:
retryable:网络中断、限流、明确的临时服务错误;permanent:认证、权限、非法角色、超长输入、坏 payload;unknown outcome:客户端超时但不确定服务端是否完成,需要 request ID/幂等策略和成本对账;quarantined:反复异常、需人工检查的单项。
批次同时受多种上限约束
只写“每批 100 条”不够。可能同时存在:
- 最大 item 数;
- 每项输入长度;
- 批次总 token/字符;
- HTTP payload bytes;
- 模型/设备显存;
- 响应体和数据库事务大小;
- 并发与每分钟/每日配额。
打包器应按官方限制和安全余量执行 first-fit 或顺序装箱,并在加入下一项前检查所有约束。一个超长 item 应单独拒绝/重切,不要让整批无限重试。
对于本地模型,还要记录实际 padding 浪费。把相近长度分桶可能提高吞吐,但排序后必须用 item_id 恢复对应关系。
不要盲目 zip 输入和响应
可靠 adapter 要定义并验证:
- 请求包含哪些稳定 ID;
- 服务是否保证响应顺序;
- 响应是否带 index/ID;
- 部分失败如何表达;
- usage 是按请求还是逐项返回;
- 重试后如何去重。
即使官方当前保证顺序,adapter 也应检查:
- 响应数量等于期望;
- 每个 index/ID 唯一且在范围内;
- 没有漏项或重复项;
- 每个向量通过空间校验;
- 再按
item_id写入。
不能只做 zip(items, vectors) 并假设网络、SDK 和批处理永远完整有序。
写入前的向量校验
对每项执行:
- 长度等于 contract.dimension;
- 每个值是数值而非 boolean;
- 没有 NaN 或 Inf;
- L2 norm 有限且大于 0;
- 若 contract 声明 normalized,则 norm 在约定容差内接近 1;
- role、model/revision、dimension、metric、dtype 与目标索引一致;
- 返回项对应当前 source/input hash;
- ACL、tenant 与 source metadata 不缺失。
“API 返回 200”只说明请求成功,不说明向量属于正确输入和空间。Norm 分布突然整体改变可能提示角色、预处理、模型 alias 或归一化变化,应触发告警与样本回归。
缓存键必须描述整个空间
一种可审计形式:
也可直接哈希规范化 JSON:
from hashlib import sha256 # 导入 SHA-256,用于生成固定长度且不可逆的缓存键。
import json # 导入标准 JSON 编码器,保证复合契约能稳定序列化。
from typing import Any # 导入 Any,表示 contract 的字段值可包含多种 JSON 类型。
def cache_key(contract: dict[str, Any], input_sha256: str) -> str: # 把完整空间契约和实际输入哈希压缩成一个缓存身份。
payload = { # 先显式构造需要参与身份计算的最小对象。
"contract": contract, # contract 内应含 provider、model、revision、role、维度和归一化等字段。
"input_sha256": input_sha256, # 只接受已针对真实 embedding 输入计算出的哈希。
} # 字典结束;任何缺失的契约字段都可能导致错误复用,调用方应先校验。
encoded = json.dumps( # 在本教学 Python 进程中稳定序列化;它不是跨语言的 JSON Canonicalization Scheme。
payload, # 编码上面构造的完整缓存身份对象。
ensure_ascii=False, # 保留非 ASCII 字符,避免不必要的转义差异。
allow_nan=False, # 拒绝 NaN/Infinity,防止生成不属于标准 JSON 的缓存键。
sort_keys=True, # 固定字典键顺序,避免同一 Python 对象的插入顺序改变缓存结果。
separators=(",", ":"), # 去除空白,确保格式变化不会改变同一对象的身份。
).encode("utf-8") # 将 JSON 文本转成明确的 UTF-8 字节,供哈希函数处理。
return sha256(encoded).hexdigest() # 返回十六进制摘要;它是键而不是原文或向量。这里的 sort_keys=True 只解决 Python dict 的键顺序;它没有规定跨语言的数字、Unicode 字符串或所有 JSON 值如何得到同一字节串。因此此示例适合受限字段、同一受控运行时的缓存键,不应被宣称为通用的“规范 JSON”或签名格式。若键要跨服务/语言共享,先冻结字段 schema 与序列化 revision,并使用经实现验证的跨语言 canonicalization(例如 RFC 8785 JSON Canonicalization Scheme)或直接把已版本化的字节协议作为输入;同一契约必须配跨实现测试向量。
模型别名、revision、query/document role、维度或归一化不同,都不能误命中旧向量。若 SDK 版本会改变默认预处理,也应纳入可追踪构建信息。
缓存向量是原文的派生产物,不应默认公开。它继承 tenant、ACL、保留与删除策略;备份和调试导出也要受控。
有界重试与总尝试预算
沿用 [[Knowledge/AI Agent Engineer/docs-CN/API/00-目录|API]]与 [[Knowledge/AI Agent Engineer/docs-CN/LLM API集成/00-目录|LLM API集成]]的模式:
- 显式 connect timeout、read timeout 与总 deadline;
- 对 429 和明确临时错误使用有界指数退避 + jitter;
- 尊重官方
Retry-After或等价 header; - 认证、权限和输入 4xx 修复后再试,不原样重放;
- SDK 内重试与外层队列重试合并计算总次数;
- 已验证成功项按 item/cache key no-op,不重复重算;
- 保存 provider request ID、attempt、错误类别、items/tokens、duration 和费用归属。
若一次批次中只有一项永久失败,可二分/隔离该项;不要重试整批并让成功项反复计费。是否支持服务端幂等 key 取决于当前 API,必须查提供商文档,不能假设。
本地模型的复现契约
至少记录:
- 模型仓库与不可变 revision;
- tokenizer revision;
- 库版本、Python、OS;
- pooling、prompt、normalize、dimension;
- device、dtype、batch size;
- 最大输入与截断策略;
- 是否
eval()、是否使用近似/量化 backend; - 一小组 canary 输入的输出 shape、norm 和检索排序。
不要声称跨 CPU/GPU、驱动或精度能 bitwise 一致。发布门禁应关注容差、排序与业务指标,而不只是逐浮点完全相等。
可观测性与隐私
记录:
| 类别 | 建议字段 |
|---|---|
| 身份 | space_id、job/batch/item ID、source revision |
| 性能 | queue wait、request duration、items/tokens、throughput |
| 可靠性 | attempt、HTTP/provider code、retry class、部分失败 |
| 质量 | dimension、norm 分布、zero/nonfinite/rejected count |
| 成本 | cache hit、billable units、估算/实际费用 |
| 追踪 | provider request ID、deployment/model revision |
默认不要记录 API key、完整正文、完整向量或用户查询。排障样本用授权 ID 回查,必要内容做最小化、脱敏和时限控制。
失败恢复流程
以 1,000 个 chunks 为例:
- 从 canonical source 生成 work items;
- 缓存命中且完整契约相同的项标记 validated;
- 其余按 item/token/payload 装批;
- 每批提交并保存 request ID;
- 校验响应,成功项逐项持久化;
- 临时失败项回队列并增加 attempt;
- 永久超长项回到 Chunking,其他永久错误隔离;
- 对
expected item IDs - validated IDs做差集; - 全量数量、ACL、dimension、norm、hash 对账后才发布。
恢复任务从状态表继续,不从头重跑整个语料。
常见错误与排查
- 吞吐突然下降:检查长度分布、padding、配额、重试放大和缓存命中。
- 向量与 ID 错位:检查响应顺序/索引、批次拆分和排序恢复。
- 缓存命中却质量漂移:缓存键缺 model revision、role 或 input hash。
- 某一坏项卡死队列:没有永久错误分类与隔离。
- 费用翻倍:SDK 与外层多层重试,或未知结果整批重放。
- 数据库正文与向量不一致:静默截断或只哈希原始正文。
- 日志泄露文本:错误对象直接序列化了请求 payload。
练习
- 为 1,000 个不同长度 chunks 设计同时限制 item 数、总 token 和 payload bytes 的装批器。
- 第 7 批第 3 项永久超长时,画出其他项如何继续、该项如何回到 Chunking。
- 列出一个完整缓存键;删除 role 后构造一次错误命中。
- 为“服务端完成但客户端超时”写出请求 ID、状态与成本对账方案。
- 设计 norm 分布 canary:正常范围、告警条件、回查信息和禁止记录的内容。
- 比较本地 CPU float32 与 GPU float16 时,定义比 bitwise equality 更合理的验收指标。
掌握检查
- 我在 vault 外用 venv + pip 建隔离环境,并不提交
.venv。 - 批次同时遵守 item、长度、payload、内存和配额上限。
- 每个输入/响应用稳定 ID 对账,不盲目 zip。
- 写入前校验空间、维度、有限值、norm、hash 与 ACL。
- 缓存键包含完整空间契约和实际输入哈希。
- 重试有总预算,永久错误会隔离,未知结果会对账。
- 日志保留可诊断 metadata,不记录密钥、全文或完整向量。
- 失败恢复从已验证状态继续,而不是全量重跑。
小结与下一步
可靠批处理保证“每个向量确实属于这个输入和空间”。下一步要保证检索配置与空间一致,并在模型升级时不混写、不停机、不丢删除:[[Knowledge/AI Agent Engineer/docs-CN/Embedding/04-相似度索引与版本迁移|相似度、索引与版本迁移]]。
参考资料
- Sentence Transformers Usage
- OpenAI: Vector embeddings
- Gemini API: Embeddings
- Cohere: Introduction to Embeddings
- RFC 8785: JSON Canonicalization Scheme
来源获取日期:2026-07-22。具体批次、配额、错误码与重试头属于动态 API 事实,应在接入当天查对应官方参考。返回 [[Knowledge/AI Agent Engineer/docs-CN/Embedding/00-目录|Embedding 目录]]。