Orux AI / 模型观察
Embedding API 迁移:重建向量索引与可回退切换
从 text-embedding-3-small 迁移到 large,不能只补齐向量维度。用离线 Python 验证六类错误,核对重建费用、存储占用与编码器和索引成对切换方案。

文章目录
换嵌入模型,就要为检索重建索引
从 text-embedding-3-small 迁移到 text-embedding-3-large 时,应当用原始文档文本建立独立的新索引,查询也要使用与它匹配的嵌入配置。不要给旧向量补零来凑长度,也不要因为两个向量维度相同,就认为它们使用相同的坐标系。在新文档和查询链路通过自己的检索评估之前,保留旧索引。
对于准备迁移产品帮助中心搜索的文档团队,这意味着需要预算一次完整语料的重新嵌入、两套索引并存的临时存储,以及可以回退的配置切换。下方可运行的 Python 示例演示了为什么只检查维度还不够,并拒绝六种无效输入,算出一份具体的重建预算。程序完全离线运行:其中的二维向量是手写几何数据,不是这两个 OpenAI 模型的输出。

手写向量在各自坐标系内保持正确结果。查询与索引混用时,即使向量长度相同,也会返回 B。示意图依据已执行的本地示例绘制。
模型维度与当前 Orux 的功能边界
OpenAI 嵌入指南列出的默认向量长度为:text-embedding-3-small 是 1,536,text-embedding-3-large 是 3,072。文档还说明第三代嵌入模型原生支持通过 dimensions 参数缩短向量。缩短受支持模型的输出是一种配置选择,不会把这个模型变成另一个模型。
这两个准确的 alias 当前都已在 Orux 上架。text-embedding-3-small 模型页和 text-embedding-3-large 模型页提供文本输入与 float 向量格式。其公开保存的示例分别记录了 1,536 和 3,072 维。这些示例可以支持输出形状的说明,不能证明哪个模型在一般任务上准确率更高。
2026 年 11 月 3 日核对的表单没有提供维度控制项。因此,不能仅凭 OpenAI 原生文档就规划一个缩短维度的 Orux 索引。依赖该选项之前,应确认具体接入约定。本文既未发起付费嵌入请求,也未验证 Orux 对维度参数的转发行为。当前接入方式请查看 Orux API 文档。
应当把模型 alias、实际返回的维度数、文本预处理版本和距离度量一起记录。例如,用 title + body 构造帮助文档文本,与只嵌入 body 的预处理版本应当不同。还要在分块标识旁保存文档修订哈希,便于后续重建时识别过期文本。维度检查能发现明显的形状错误;其余配置字段能发现形状检查识别不了的配置错误。
用二维例子揭示不易发现的不匹配
设旧坐标系中有两个文档向量:A 为 (1, 0),B 为 (0, 1)。旧查询 (1, 0) 会检索到 A。现在把整个坐标系旋转 90 度:A 变为 (0, 1),B 变为 (-1, 0),同一概念对应的查询变为 (0, 1)。只要在各自坐标系内保持一致,两边检索都会得到 A。
然而,把旋转后的查询与旧文档混用,胜出的就成了 B。所有向量仍然只有两个坐标。计算可以顺利完成,检索含义却变了。这是对“相同维度意味着兼容”这一假设的数学反例,不是对任何一个模型学习行为的模拟,也不是实际迁移失败率的估计。
给旧向量补零只改变长度,并未提供通往新模型所学表示空间的映射。截断同样不能建立跨模型兼容性。即使是在同一个受支持模型内缩短向量,文档与查询也应遵循一致的维度策略,并评估缩短后的检索表现。原生支持缩短向量,不代表可以随意混用新旧表示。
示例中的检查函数会先比较完整配置,再计算余弦相似度。这个检查有一个前提:元数据必须真实。故意把新查询标为旧查询,就能绕过它。下方的 unsafe_mixed_top 结果正是刻意展示这一限制。生产环境的来源信息应来自编码器配置和存储链路,不能只相信调用者未经核验的声明。
重建预算:按语料用量计算,而不是按向量个数
以下价格核对日期为 2026 年 11 月 3 日。Orux 售价来自两个公开模型记录;OpenAI 直连的标准输入价来自其 small 模型价格与 large 模型价格。这是不同的购买方式,不代表账户条款或运行行为完全一致。
当前配置的换算关系为 1 Credit = USD 0.005。表中的 USD 是算术换算值,Orux 使用 Credits 计费。所有单价的单位都是每百万输入 tokens,而非每百万篇文档。表格不包括单独的存储、索引托管和工程费用,也没有假设 Batch 折扣或免费重建。
| 模型 | Orux 输入单价/百万 tokens | OpenAI 直连标准单价/百万 tokens | Orux 计划重建 500 万 tokens |
|---|---|---|---|
| text-embedding-3-small | 1.53336 Credits(USD 0.0076668) | 等值 4 Credits(USD 0.02) | 7.6668 Credits(USD 0.038334) |
| text-embedding-3-large | 9.96672 Credits(USD 0.0498336) | 等值 26 Credits(USD 0.13) | 49.8336 Credits(USD 0.249168) |
左右滑动查看完整表格。
假设规划中的语料为 10,000 个分块,每块平均 500 输入 tokens,总量就是 500 万 tokens。将整份语料迁移到 large,需要预算 large 的完整重建费用,不能只支付两行价格的差额。此前在 small 上的花费并没有购买 large 向量。在这次快照中,Orux large 单价约为 small 的 6.5 倍;这个比值不能说明它在你的文档上能带来多少检索改善。
以上金额以假设的 token 数和汇总算法估算。实际分块长度、查询流量、重试和计费粒度都可能改变支出。授权真实运行前,应使用对应的 tokenizer 测量语料用量。可执行代码使用整数 micro-Credits 计算费用,再用 Decimal 展示,避免以二进制浮点处理金额。
内存需要单独估算。使用 float32 时,10,000 条向量在 1,536 维下需要 61,440,000 字节,在 3,072 维下需要 122,880,000 字节,约为 58.59 MiB 与 117.19 MiB。这些只是原始坐标数组。元数据、标识符、检索图结构、副本和序列化还会增加开销。为保证迁移可以回退,同时保留两套原始数组就需要 184,320,000 字节,尚未计算其他开销。
运行兼容性与预算示例
把下列内容保存为 migration_demo.py,使用 Python 3.9 或更高版本运行 python3 migration_demo.py。它只使用标准库,不发起网络请求。价格保留为注明日期的快照,便于复现计算;以后规划迁移时,应替换为核实过的当前单价。
import json
import math
from dataclasses import asdict, dataclass
from decimal import Decimal
@dataclass(frozen=True)
class Contract:
model: str
dimensions: int
preprocessing: str
metric: str = 'cosine'
def validate(vector, contract):
if len(vector) != contract.dimensions:
raise ValueError('dimension mismatch')
if any(isinstance(x, bool) or not isinstance(x, (int, float))
or not math.isfinite(x) for x in vector):
raise ValueError('non-finite or non-numeric vector')
if math.hypot(*vector) == 0:
raise ValueError('zero vector')
def search(query, query_contract, documents, index_contract):
if query_contract != index_contract:
raise ValueError('embedding contract mismatch')
validate(query, query_contract)
scores = []
for doc_id, vector in documents.items():
validate(vector, index_contract)
score = sum(a*b for a, b in zip(query, vector)) / (
math.hypot(*query) * math.hypot(*vector))
scores.append((doc_id, score))
return sorted(scores, key=lambda row: (-row[1], row[0]))
def budget(rate_micro_credits, total_tokens):
micro = (rate_micro_credits * total_tokens + 999999) // 1000000
credits = Decimal(micro) / 1000000
return {'credits': str(credits), 'usd': str(credits * Decimal('0.005'))}
def main():
# Handwritten two-dimensional geometry, NOT model outputs.
old = Contract('toy-old', 2, 'title-body-v1')
new = Contract('toy-new', 2, 'title-body-v1')
old_docs = {'A': [1, 0], 'B': [0, 1]}
new_docs = {'A': [0, 1], 'B': [-1, 0]}
old_top = search([1, 0], old, old_docs, old)[0][0]
new_top = search([0, 1], new, new_docs, new)[0][0]
unsafe_top = search([0, 1], old, old_docs, old)[0][0]
assert (old_top, new_top, unsafe_top) == ('A', 'A', 'B')
failures = [
lambda: search([0, 1], new, old_docs, old),
lambda: validate([1], old),
lambda: validate([0, 0], old),
lambda: validate([float('nan'), 1], old),
lambda: validate([True, 1], old),
lambda: search([1, 0], Contract('toy-old', 2, 'title-body-v2'), old_docs, old),
]
rejected = 0
for test in failures:
try:
test()
except ValueError:
rejected += 1
else:
raise AssertionError('invalid fixture was accepted')
chunks, tokens_per_chunk = 10000, 500
tokens = chunks * tokens_per_chunk
result = {
'toy_results': {'old_top': old_top, 'new_top': new_top,
'unsafe_mixed_top': unsafe_top},
'rejected_fixtures': rejected,
'planning_tokens': tokens,
'orux_small': budget(1533360, tokens),
'orux_large': budget(9966720, tokens),
'raw_float32_bytes': {'small_1536': chunks * 1536 * 4,
'large_3072': chunks * 3072 * 4},
'new_contract': asdict(new),
}
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()本地运行得到 old_top: A、new_top: A、unsafe_mixed_top: B,以及 rejected_fixtures: 6。被拒绝的情况分别是:模型配置不同、向量长度错误、零向量、非有限坐标、布尔坐标、预处理版本变化。small 和 large 的规划费用分别为 7.6668 与 49.8336 Credits,与表格一致。
这是兼容性规则和费用计算的可执行演示,不是数据库适配器。它不会获取嵌入、构建近似最近邻索引、证明模型的检索质量,也没有实现生产环境的访问控制。合成坐标的数值较小;生产数值库还应覆盖其允许输入的完整数值范围。

部署方案:每个查询编码器始终连接匹配的索引;切换和回退都以配对配置为单位。图中展示的是建议架构,不是已经部署的迁移结果。
查询编码器和索引要作为一套配置切换
建立使用目标配置的新集合或命名空间。读取原始文本,按记录的分块和预处理规则处理,再以稳定的文档/分块标识写入目标模型向量。保留现有集合不动。追踪预期分块 ID、已完成 ID 和来源修订,不要只依赖一个已处理行数计数器。
如果重建期间来源文档还在变化,应明确一致性策略。简单的维护窗口快照可以避免并发写入,但会延后更新。在线重建则需要可重放的变更日志或同等的对账机制,让编辑和删除在切换前同步到新索引。向量数量相同并不足够:一个该删除的文档与一个遗漏的文档,可能恰好在数量上抵消。
然后把每个查询编码器绑定到预定索引。不要在应用仍读取 small 集合时,先把查询改为 large。应将这一对作为带版本的应用配置,包含预处理、维度和度量方式。当集合中保存的配置清单与查询配置不一致时,拒绝请求。多租户系统还应在两个集合上保留相同的租户过滤与文档权限。
回退时,需要同时保留旧查询配置与旧集合。如果应用已经不能生成兼容的旧查询,仅保留旧向量也没有用。切换后,观察结果质量、文档缺失报告、延迟和查询费用。只有经过约定的评估和保留窗口,才能停用旧组合,而不是看到新集合接受写入就立即删除旧版。
用标注过的检索问题做决策,不看向量长度猜结果
在全量重建前,先准备一份规模适当、具有代表性的评估集。对于产品帮助搜索,应包含准确零件编号、模糊的故障描述、多语言请求,以及受访问权限限制的文档。为每个问题记录相关文档 ID。相关性判断要与最终生成的回答是否流畅分开。
每种候选配置都应从相同文档快照中检索相同数量的结果。比较相关文档是否进入前列,并检查重要的漏检。不要用短正文片段构建 small 索引,再用补充了标题的文本构建 large 索引,却把全部差异归因于模型。分块、预处理和访问过滤都是独立的实验变量。
本文没有给出实测赢家。当 small 在你自己的评估中满足要求时,可以选择当前输入与存储成本较低的方案。当 large 在你的工作负载上表现出的实际收益足以覆盖重建、更大的向量和持续查询费用时,再考虑它。本地兼容性检查通过,只说明建立了一道防护,不能成为购买更贵模型的理由。
常见问题
更换模型时,必须重新生成全部文档向量吗?
对于本文描述的迁移,是的,应将可搜索语料重新生成为目标模型空间中的向量。保留原始文本与稳定分块 ID,之后的查询也使用同一目标配置。修改模型字段名称不能让旧向量变得兼容。
能把 1,536 维向量补零到 3,072 维吗?
补零可以满足数组长度要求,却不能建立跨模型映射,因此不适合作为本次迁移的兼容方案。二维例子说明:形状合法,仍可能因为混用了坐标系而检索出错误文档。
Orux 是否提供 OpenAI 原生的 dimensions 参数?
核对的这两个 Orux alias 的公开表单没有提供这个参数。只有 OpenAI 原生文档不能验证 Orux 的行为。依赖缩短输出前,应确认实际接入约定,再用一致配置建立和查询索引。
text-embedding-3-large 一定更适合我的搜索吗?
本文没有证明它在你的工作负载上更好。应使用自己的标注问题和同一来源快照评估两套配置。维度更大、价格更高,都不能代替相关文档能否被检索到的测量。
Python 示例实际测试了什么?
它测试了手写向量几何、配置不匹配拒绝逻辑、六种无效输入,以及注明日期的预算与存储计算。它没有调用嵌入 API,没有消耗 Orux Credits,也没有对模型输出或在线向量数据库做基准测试。
用一个 API 调用这些模型
一个 OpenAI 兼容 API,覆盖对话、图像、视频与音频模型,按积分计费。
相关文章
浏览全部文章 →
DeepSeek API 教程:生成文本前先校验算术
DeepSeek API 工作流应在生成文本前校验算术。本教程用 Python 整数分检查金额并拦截不一致输入;证据来自本地测试,不是模型准确率评测。

低价视频 API 怎么选:Vidu、Wan、Kling 与 Seedance
按2026年10月29日候选快照比较 Vidu、Wan、Kling 和 Seedance 的每秒、整段费用及输入限制,区分图生与文生、音频和画质条件。附完整批量Python代码与真实案例,诚实列出 Orux 较贵之处,复核 fal 和官方价,并说明价格舍入、任务恢复与临时链接边界。

图像API价格比较:10款模型的1K、2K与4K费用
比较2026年10月29日所选10款Orux图像模型的每图与百图Credits费用,保留1K、2K、4K、参考图上限和历史耗时表。明确Google Standard与Batch差异、Alibaba地区价及Qwen Pro溢价,附统一Python示例,帮助按目标分辨率和可用成片成本选模型。


