Orux AI / Model notes
Embedding API Migration: Rebuild Vectors, Keep Rollback
Move between text-embedding-3-small and large without mixing vector spaces. Run six compatibility checks and compare rebuild costs, storage and cutover options.

On this page
An embedding model change needs a new search index
When migrating from text-embedding-3-small to text-embedding-3-large, build a separate index from the original document text and send queries through the matching embedding configuration. Do not make old vectors fit by adding zeros, or treat equal vector lengths as proof that two models share a coordinate system. Keep the old index available until the new document and query paths pass your own retrieval evaluation.
For a documentation team moving a product-help search service, this means budgeting a fresh corpus embedding pass, temporary storage for both indexes and a reversible configuration switch. The runnable Python example below demonstrates why a dimension check alone is insufficient, rejects six invalid fixtures and calculates a concrete rebuild budget. It runs entirely offline: its two-dimensional vectors are handwritten geometry, not outputs from either OpenAI model.

Handwritten vectors preserve the correct result inside either space. A mixed query and index return B despite identical vector lengths. Diagram derived from the executed local example.
Model dimensions and the current Orux boundary
OpenAI's embedding guide lists default vector lengths of 1,536 for text-embedding-3-small and 3,072 for text-embedding-3-large. It also documents a native dimensions parameter for shortening third-generation embeddings. Shortening a supported model's output is a configuration choice; it does not turn that model into another model.
Both exact aliases are currently listed on Orux. The text-embedding-3-small model page and text-embedding-3-large model page expose text input and float-format vectors. Their saved public examples report 1,536 and 3,072 dimensions respectively. Those examples support the reported output shape, not a general accuracy ranking between models.
The forms checked on November 3, 2026 do not expose a dimensions control. Therefore, do not plan a shortened Orux index solely because the native OpenAI documentation describes one. Confirm the exact integration contract before depending on that option. This article neither sends a paid embedding request nor demonstrates dimension forwarding through Orux. Consult the Orux API documentation for the current integration surface.
Record the model alias, returned dimension count, text preprocessing version and distance metric together. For example, a help article formed from title + body should have a different preprocessing version from one that embeds only body. Keep document revision hashes alongside chunk identifiers so that a later rebuild can identify stale text. A dimension match can catch an obvious shape error; the remaining contract fields catch configuration mistakes that shape cannot reveal.
A two-dimensional example that exposes the hidden mismatch
Imagine two document vectors in a toy old space: A is (1, 0) and B is (0, 1). The old query (1, 0) retrieves A. Now rotate the entire space by 90 degrees. A becomes (0, 1), B becomes (-1, 0) and the same conceptual query becomes (0, 1). Searching consistently within either space still retrieves A.
Mix the rotated query with the old documents, however, and B wins. Every vector still has two coordinates. The calculation executes successfully while the search meaning changes. This is a mathematical counterexample to the assumption that equal dimensions establish compatibility; it is not a simulation of either model's learned behavior or an estimate of real migration failure rates.
Padding an old vector with zeros changes its length but supplies no mapping into a new model's learned representation. Truncation also fails to establish compatibility across models. Even when shortening within one supported model, use a consistent dimension policy on documents and queries and evaluate the resulting retrieval behavior. Native shortening support is not permission to mix old and new representations indiscriminately.
The example's guard compares the full configuration before calculating cosine similarity. That comparison only works when metadata is honest: deliberately relabeling a new query as old bypasses the guard. The unsafe_mixed_top result below deliberately illustrates that limitation. Production provenance must come from the encoder configuration and storage path, not a caller's unverified claim.
Rebuild cost: price the corpus, not the number of vectors
Prices below were checked on November 3, 2026. Orux rates come from the two public model records. Direct OpenAI standard input rates come from its small model pricing and large model pricing. They are separate purchase options, not an assertion that account terms or operational behavior are identical.
The current configured conversion is 1 Credit = USD 0.005. USD values below are arithmetic equivalents; Orux billing uses Credits. All rates are per one million input tokens, not per million documents. The table excludes separate storage, index hosting and engineering costs, and does not assume Batch discounts or a free rebuild.
| Model | Orux input rate / 1M tokens | Direct OpenAI standard rate / 1M tokens | Orux planned 5M-token rebuild |
|---|---|---|---|
| text-embedding-3-small | 1.53336 Credits (USD 0.0076668) | 4 Credits equivalent (USD 0.02) | 7.6668 Credits (USD 0.038334) |
| text-embedding-3-large | 9.96672 Credits (USD 0.0498336) | 26 Credits equivalent (USD 0.13) | 49.8336 Credits (USD 0.249168) |
Swipe horizontally to view the full table.
For a planning corpus of 10,000 chunks averaging 500 input tokens, the total is five million tokens. Moving this entire corpus to large therefore requires budgeting the large rebuild, not just paying the difference between the two rows. Previous spend on small does not purchase large vectors. The large Orux rate is about 6.5 times the small rate in this snapshot; that ratio says nothing about the retrieval improvement on your documents.
These totals are estimates using an assumed token count and an aggregate calculation. Actual chunk lengths, query traffic, retries and billing granularity can change spend. Measure corpus tokens with the appropriate tokenizer before authorizing a real run. The executable code uses integer micro-Credits for the rate calculation and Decimal for display, avoiding binary floating-point money calculations.
Memory needs a separate estimate. At float32, 10,000 vectors require 61,440,000 bytes at 1,536 dimensions or 122,880,000 bytes at 3,072 dimensions: approximately 58.59 MiB and 117.19 MiB. Those are raw coordinate arrays only. Metadata, identifiers, search graph structures, replicas and serialization add overhead. During a reversible migration, retaining both raw arrays alone requires 184,320,000 bytes, before that overhead.
Run the compatibility and budget demonstration
Save the following as migration_demo.py and run python3 migration_demo.py with Python 3.9 or later. It uses only the standard library and makes no network requests. The rates are a dated snapshot so that the arithmetic is reproducible; replace them with verified current rates when planning a later migration.
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()The local run produced old_top: A, new_top: A, unsafe_mixed_top: B and rejected_fixtures: 6. The rejected cases are a different model contract, a wrong vector length, a zero vector, a non-finite coordinate, a boolean coordinate and a changed preprocessing version. The small and large planning totals are 7.6668 and 49.8336 Credits, matching the table.
This is an executable demonstration of the compatibility rule and cost arithmetic, not a database adapter. It does not fetch embeddings, build an approximate nearest-neighbor index, prove a model's retrieval quality or implement production access control. Its synthetic coordinate values are small; a production numeric library should also handle the full numeric range of its accepted inputs.

Deployment plan: keep each query encoder connected to its matching index; switch and roll back the pair. This diagram describes the proposed architecture, not a deployed migration.
Cut over the query encoder and index as one configuration
Create a new collection or namespace with the target contract. Read the original source text, apply the recorded chunking and preprocessing rules, and write target-model vectors under stable document/chunk identifiers. Keep the existing collection untouched. Track expected chunk IDs, completed IDs and source revisions rather than relying on a single processed-row counter.
If source documents change during the rebuild, choose a clear consistency policy. A simple maintenance-window snapshot avoids competing writes but delays updates. An online rebuild needs a replayable change log or equivalent reconciliation so that edits and deletions reach the new index before the switch. An equal vector count is insufficient when one deleted document and one missing document happen to cancel numerically.
Then bind each query encoder to its intended index. Do not update the application to generate large queries while it still reads the small collection. Treat the pair as one versioned application configuration, including preprocessing, dimensions and metric. Reject requests when the stored collection manifest and the query configuration disagree. In systems with multiple tenants, preserve the same tenant filtering and document permissions on both collections.
For rollback, retain the old query configuration and collection together. Merely keeping old vectors is not enough if the application can no longer generate compatible old queries. After switching, observe result quality, missing-document reports, latency and query costs. Retire the old pair only after your agreed evaluation and retention window, not simply when the new collection accepts writes.
Decide with labeled search questions, not vector size
Prepare a small, representative evaluation set before rebuilding everything. For a product-help service, include exact part numbers, vague troubleshooting questions, multilingual requests and documents with access restrictions. Record which document IDs are relevant to each question. Separate relevance judgments from whether the generated answer sounds fluent.
For each candidate configuration, retrieve the same number of results from the same document snapshot. Compare relevant-document presence in the top results and inspect important misses. Do not compare a small-model index built from short body fragments with a large-model index built from enriched title-and-body text and attribute the whole difference to the model. Chunking, preprocessing and access filtering are independent experimental variables.
This article provides no measured winner. Choose small when its own evaluation meets your requirements at the lower current input and storage cost. Consider large when its measured gains on your workload justify the rebuild, larger vectors and ongoing query cost. A successful local compatibility test establishes a guardrail, not a reason to buy a more expensive model.
FAQ
Must I regenerate every document vector when changing models?
Yes, regenerate the searchable corpus into the target model's space for the migration described here. Preserve the original text and stable chunk IDs, and encode future queries with the same target configuration. Existing vectors do not become compatible by renaming the model field.
Can I pad 1,536-dimensional vectors to 3,072 dimensions?
Padding can satisfy an array-length requirement but does not establish a mapping between models. It is unsuitable as the compatibility strategy for this migration. The toy example shows that a valid shape can still return the wrong document when spaces are mixed.
Does Orux expose OpenAI's native dimensions parameter?
The public forms checked for these two Orux aliases do not expose it. Native OpenAI documentation alone does not verify Orux behavior. Confirm the actual integration contract before relying on shortened outputs, then build and query a consistently configured index.
Is text-embedding-3-large automatically better for my search?
No workload-specific improvement is established by this article. Evaluate both configurations on your own labeled questions and source snapshot. Larger dimensions and higher prices are not substitutes for measuring whether relevant documents are retrieved.
What did the Python example actually test?
It tested handwritten-vector geometry, contract mismatch rejection, six invalid fixtures and dated budget/storage arithmetic. It made no embedding API calls, did not spend Orux Credits and did not benchmark model outputs or a live vector database.
Build with these models through one API
One OpenAI-compatible API for chat, image, video and audio models, billed in Credits.
Related articles
Browse all articles →
Veo 3.1 API Migration: Model IDs and Audio Costs
Separate retired Veo 3.1 Preview IDs from Orux GA aliases. Check 8-second sound costs, current form controls and a tested offline Credits calculator.

Seedance API Budget: Mini vs Fast for 1,500 Credits
Plan 4 Seedance shots within a 1,500-Credit allowance. Compare Mini and Fast rates, separate drafts from finals, and run an offline retake calculator.

Qwen3 API Costs: Embeddings and Reranking for RAG
Calculate Qwen3 embedding and reranking costs for 20, 50 or 100 candidates. Includes current Orux Credits, USD estimates and a tested offline Python calculator.



