Model Library
Browse and deploy state-of-the-art AI models through the DEVUP Gateway.
Browse and deploy state-of-the-art AI models through the DEVUP Gateway.
Text Embedding 3 Small converts text into numeric vectors for search, retrieval, clustering, and classification. Its defining feature is that the vector length is yours to choose: the full output runs to 1,536 dimensions, and a single parameter shortens it to anything smaller without retraining and without the quality collapse that naive truncation would cause. Cutting to 512 dimensions leaves most of the retrieval quality intact while reducing storage and comparison cost by two thirds — a trade that matters once an index holds millions of vectors rather than thousands. It handles inputs up to 8,191 tokens and returns deterministic output, so unchanged text always produces an identical vector.

Turns text into vectors. Same text, same numbers — every time.
Embeddings are the foundation of semantic search and retrieval-augmented generation: two passages that mean similar things land near each other in vector space, whatever words they used to say it. This model is the efficient tier of OpenAI's current embedding generation.
Most embedding models return one fixed vector size. This one returns up to 1,536 dimensions and lets you ask for fewer.
{
"model": "openai/text-embedding-3-small",
"input": "How do I speed up a slow query?",
"dimensions": 512
}This is not truncation in the destructive sense. The model was trained so that the most important information concentrates in the earliest dimensions. Shortening the vector discards the least informative components first, which is why a 512-dimension vector retains most of the retrieval quality of the full 1,536 while occupying a third of the space.
An index of ten million vectors at 1,536 dimensions is roughly three times the storage and three times the comparison cost of the same index at 512. On a small corpus the difference is invisible; past a few million documents it is the difference between a vector database that fits comfortably and one that does not.
A workable approach. Build your evaluation set first — a few hundred query-and-expected-result pairs from your actual corpus. Embed at full dimension, measure retrieval accuracy, then repeat at 768 and 512. Take the smallest size that holds your accuracy. The answer depends on your data, and measuring it costs an afternoon.
One rule that admits no exceptions: every vector in an index must have the same dimension count. Mixing 1,536 and 512 vectors in one index produces results that are silently meaningless rather than an error.
| Model ID | openai/text-embedding-3-small |
| Max input | 8,191 tokens |
| Output dimensions | 1,536 by default, reducible |
| Output | Normalised float vector |
| Determinism | Identical input yields identical output |
| Endpoint | /v1/embeddings |
Specifications as published by OpenAI. Nothing is disclosed about the model itself — no parameter count, no architecture, no training method, no weights.
| Capability | Value |
|---|---|
input_types | text |
output_types | embedding |
context_window | 8191 |
output_dimensions | 1536 (reducible) |
endpoint | /v1/embeddings |
streaming | Not applicable |
tool_calling | Not applicable |
reasoning | Not applicable |
deterministic | Yes |
requires_prompt | Yes — input text required |
Worth stating, because this model sits in a catalogue mostly filled with generative models.
It does not generate text, answer questions, follow instructions, or hold a conversation. There is no system prompt, no temperature, no max tokens. You send text and receive a list of numbers.
In a retrieval pipeline it does the finding. A generative model does the answering. The two are complementary and neither substitutes for the other.
Endpoint: POST https://api.devupai.com/v1/embeddings
curl https://api.devupai.com/v1/embeddings \
-H "Authorization: Bearer $DEVUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/text-embedding-3-small",
"input": "How do I speed up a slow database query?"
}'Batching is the single largest performance difference available on an embedding endpoint. One request carrying a hundred texts is dramatically faster than a hundred requests carrying one each.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEVUP_API_KEY"],
base_url="https://api.devupai.com/v1",
)
passages = [
"Adding an index on the filtered column usually eliminates a sequential scan.",
"Connection pooling reduces the cost of opening a new database session.",
"Les paiements par carte CIB sont traites en dinar algerien.",
]
response = client.embeddings.create(
model="openai/text-embedding-3-small",
input=passages,
)
vectors = [item.embedding for item in response.data]
print(f"{len(vectors)} vectors of {len(vectors[0])} dimensions")
print(f"Tokens consumed: {response.usage.total_tokens}")Results come back in the order you sent them. Batches in the low hundreds are a sensible working size — large enough to amortise the request overhead, small enough that one failure does not cost much.
response = client.embeddings.create(
model="openai/text-embedding-3-small",
input=passages,
dimensions=512,
)
print(f"{len(response.data[0].embedding)} dimensions")Decide this before you build the index, not after. Changing the dimension means re-embedding everything, because vectors of different lengths cannot be compared.
import numpy as np
def embed(texts: list[str], dimensions: int = 512) -> np.ndarray:
"""Embed a list of texts and return them as a matrix, one row per input."""
response = client.embeddings.create(
model="openai/text-embedding-3-small",
input=texts,
dimensions=dimensions,
)
return np.array([item.embedding for item in response.data])
CORPUS = [
"Refunds are issued to the original payment method within 14 days.",
"Orders above 5000 DZD qualify for free delivery nationwide.",
"Support is available Sunday through Thursday, 9am to 5pm.",
]
corpus_vectors = embed(CORPUS)
query_vector = embed(["How long until I get my money back?"])[0]
# Output vectors are normalised, so the dot product is the cosine similarity.
scores = corpus_vectors @ query_vector
for rank, index in enumerate(np.argsort(scores)[::-1], start=1):
print(f"{rank}. [{scores[index]:.3f}] {CORPUS[index]}")The query and the matching passage share almost no vocabulary. That gap is precisely what embeddings close and keyword search does not.
A query in one language finds passages written in another, without a translation step.
CORPUS = [
"Les remboursements sont effectues sous 14 jours ouvrables.",
"La livraison est gratuite pour les commandes superieures a 5000 DZD.",
"Le service client est disponible du dimanche au jeudi.",
]
corpus_vectors = embed(CORPUS)
query_vector = embed(["When will I be refunded?"])[0]
print(CORPUS[int(np.argmax(corpus_vectors @ query_vector))])Output is deterministic, so re-embedding unchanged text produces a byte-identical vector and buys nothing.
import hashlib
import json
from pathlib import Path
CACHE = Path("embedding_cache.json")
DIMENSIONS = 512
def cached_embed(texts: list[str]) -> list[list[float]]:
"""Embed only the texts not already stored, keyed by content hash."""
store = json.loads(CACHE.read_text()) if CACHE.exists() else {}
keys = [hashlib.sha256(f"{DIMENSIONS}:{t}".encode()).hexdigest() for t in texts]
missing = [(k, t) for k, t in zip(keys, texts) if k not in store]
if missing:
response = client.embeddings.create(
model="openai/text-embedding-3-small",
input=[t for _, t in missing],
dimensions=DIMENSIONS,
)
for (key, _), item in zip(missing, response.data):
store[key] = item.embedding
CACHE.write_text(json.dumps(store))
return [store[k] for k in keys]The dimension count is part of the cache key on purpose. Two vectors for the same text at different lengths are different vectors, and treating them as interchangeable corrupts the index.
npm install devupaiimport DevupAI from "devupai";
const client = new DevupAI({
apiKey: process.env.DEVUP_API_KEY,
});
const response = await client.embeddings.create({
model: "openai/text-embedding-3-small",
input: [
"Adding an index usually eliminates a sequential scan.",
"Connection pooling reduces session setup cost.",
],
dimensions: 512,
});
const vectors = response.data.map((item) => item.embedding);
console.log(`${vectors.length} vectors of ${vectors[0].length} dimensions`);The input limit is a ceiling, not a target.
A vector is one point in space. The longer and more varied the text behind it, the more that point becomes an average of unrelated meanings — close to nothing in particular and retrieved for nothing well.
Split on meaning, not on length. Section headings, paragraph breaks, logical units. A chunk that ends mid-argument produces a vector representing half an idea.
A reasonable working range is 200 to 800 tokens per chunk for most documents. Long enough to carry context, short enough to stay about one thing.
Overlap neighbouring chunks by a sentence or two, so a passage that straddles a boundary is findable from either side.
Keep the heading with the body. A chunk that says "must be completed within 14 days" without naming what must be completed is retrievable and useless.
Query and corpus must use the same model and the same dimension count. Vectors from different models — or the same model at different lengths — are not comparable, and mixing them fails silently rather than loudly.
Vectors come back normalised. The dot product is already the cosine similarity; no extra normalisation step is needed.
Record the model and dimension alongside your index. Six months from now, knowing which vector space your data lives in is the difference between a migration and a rebuild.
Similarity is not relevance. Two passages can be semantically close and still be the wrong answer to a question. Retrieval quality depends on your chunking, your corpus, and your ranking at least as much as on the model.
Pair it with keyword search where exact strings matter. Product codes, version numbers, invoice references, and rare proper nouns are where dense retrieval is weakest and a lexical index is strongest. Running both and merging the results outperforms either alone.
Debounce anything that embeds on keystroke. A search box calling an embedding endpoint on every character typed is the most common way a cheap model produces an expensive bill.