text-embedding-ada-002 is OpenAI's hosted embedding model released in December 2022. It returns a 1536-dimensional, unit-length vector for inputs of up to 8,191 tokens, and for a long time it was the default embedding choice in RAG tutorials and vector-database quick-starts. It has since been superseded by the text-embedding-3 models, but many production indexes still hold ada-002 vectors.
It is listed here alongside encoder-only embedding models because it plays the same role in a retrieval stack; OpenAI has not published its architecture. Compare with local models MiniLM (384 dims) and multi-qa-mpnet (768 dims).
You send text (a string or a list of strings) to the embeddings endpoint and receive one vector per input. Inputs are tokenized with the cl100k_base encoding; count with tiktoken and truncate or chunk beyond 8,191 tokens, otherwise the request is rejected. Although long inputs are accepted, embedding a whole document into one vector blurs its topics — retrieval usually works better on chunks of a few hundred tokens.
# pip install openai tiktoken numpy
import numpy as np, tiktoken
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY
enc = tiktoken.get_encoding("cl100k_base")
MAX_TOKENS = 8191
def embed(texts, model="text-embedding-ada-002", batch=256):
out = []
for i in range(0, len(texts), batch):
chunk = [enc.decode(enc.encode(t)[:MAX_TOKENS]) for t in texts[i:i + batch]]
resp = client.embeddings.create(model=model, input=chunk)
out.extend(d.embedding for d in resp.data) # order matches input
return np.array(out, dtype="float32")
docs = ["Refunds are processed within 5 business days.",
"Shipping is free on orders over $50."]
D = embed(docs)
q = embed(["how long does a refund take"])[0]
print(D.shape) # (2, 1536)
print(D @ q) # vectors are unit length -> cosine
Batch inputs to reduce request overhead, retry with exponential backoff on rate-limit errors, and cache embeddings keyed by a hash of (model, text) so re-runs do not pay twice.
Vectors are normalized, so cosine similarity, dot product and Euclidean distance all produce the same ranking. In practice ada-002 cosine scores cluster in a fairly narrow, high range even for unrelated text, so a fixed threshold like "0.8 means relevant" transfers poorly between corpora. Rank results, and if you need a cut-off, calibrate it on labelled pairs from your own data.
ada-002 replaced several first-generation task-specific models with one model. The text-embedding-3-small and -large models that followed support a dimensions parameter, so you can request shorter vectors (trained so that truncated prefixes remain useful) to trade quality for storage. Vectors from different models live in different spaces — never mix them in one index.
| Concern | ada-002 / hosted API | Local (MiniLM, mpnet) |
|---|---|---|
| Operations | No GPUs to run; scales with your quota | You host and scale the model |
| Data residency | Text is sent to a third party | Stays in your network |
| Cost model | Per token, recurring on re-embeds | Hardware; marginal cost near zero |
| Input length | 8,191 tokens | 256–512 wordpieces |
| Storage | 1536 x 4 B = 6 KB per vector | 1.5–3 KB per vector |
| Version control | Provider may deprecate models | Pinned weights |
For two vectors a and b, cosine similarity is a·b / (‖a‖‖b‖). Because ada-002 returns vectors with ‖a‖ = 1, the denominator is 1 and cosine equals the dot product. Squared Euclidean distance becomes ‖a − b‖2 = 2 − 2 a·b, a monotone function of the dot product, which is why all three metrics give the same ranking. In practice, choose inner product in the vector index: it is the cheapest to compute and equivalent here.
Consider a query scored against three chunks with similarities 0.86, 0.81 and 0.78. All three look "high" in absolute terms, but only the ranking is meaningful. A useful habit is to inspect score gaps rather than raw values: a clear margin between the first and second result suggests a confident match, whereas a flat sequence suggests the corpus has no strong answer and the application should say so or fall back to keyword search.
Anisotropy. The narrow score range is a known property of many learned embedding spaces: vectors occupy a cone rather than spreading evenly over the sphere, so even unrelated texts share a large common component. Techniques such as subtracting the corpus mean vector and re-normalizing can spread scores out; they do not change much for top-k ranking but can make thresholds and clustering behave better. Validate any such transform on your own retrieval labels.
Estimate cost and storage before indexing. Storage is predictable: 1536 dimensions × 4 bytes = 6,144 bytes per vector, so 1 million chunks need roughly 6 GB of raw float32 plus index overhead (an HNSW graph adds neighbor lists per node). API cost scales with total tokens embedded, so count tokens with tiktoken on a sample of documents, multiply out, and check current pricing on OpenAI's pricing page rather than relying on a figure from a tutorial. Remember that every change to chunking or to the model triggers a full re-embed, which is the recurring cost that local models avoid.
For throughput, send many inputs per request, run several requests concurrently within your rate limit, and handle HTTP 429 responses with exponential backoff and jitter. Keep a persistent cache keyed on a hash of model name plus exact text, so unchanged chunks are never re-sent.
Because vectors from different models are incompatible, a migration is a rebuild, not an in-place update. Build a second index with the new model (for example text-embedding-3-small, or a local model such as multi-qa-mpnet), run the same labeled query set against both, and compare recall@k and mean reciprocal rank. Store the model identifier alongside every vector and every index so a query can never be embedded with the wrong model. Switch traffic with an alias or configuration flag and keep the old index until the new one has proved itself on live traffic.
When choosing the target, weigh retrieval quality on your own data, vector size (text-embedding-3 models let you request shorter vectors), language coverage, data-residency requirements, and whether you want to own the serving infrastructure. Public benchmarks such as MTEB help shortlist candidates but do not replace an in-domain evaluation.
Further reading. OpenAI (December 2022), "New and improved embedding model" announcement; OpenAI (January 2024), announcement of the text-embedding-3 models; Neelakantan et al. (2022), "Text and Code Embeddings by Contrastive Pre-Training", arXiv:2201.10005; Ethayarajh (2019), "How Contextual are Contextualized Word Representations?" (anisotropy); Kusupati et al. (2022), "Matryoshka Representation Learning"; Muennighoff et al. (2022), "MTEB: Massive Text Embedding Benchmark".