AI / LLM Development

How to Create Embeddings With an LLM API 2026

0

Creating embeddings with an LLM API is a practical way to turn text into
searchable semantic representations for AI applications. Instead of
relying only on exact keyword matches, an embedding-based system can
compare the meaning of text and find related content.

In this tutorial, you will learn how to create embeddings with an LLM
API
using Python. You will generate an embedding for one piece of
text, process multiple documents, compare vectors with cosine
similarity, clean and chunk content, store useful metadata, and prepare
the pipeline for semantic search, vector databases, and RAG
applications.

The examples use OpenAI’s text-embedding-3-small model. Always verify
the current model name, limits, dimensions, and pricing in the provider
documentation before using an embedding model in production.

What You Will Build

You will build a small Python embedding pipeline that can:

  • Send text to an embedding API.
  • Receive a numerical vector.
  • Inspect the generated vector.
  • Generate embeddings for multiple documents.
  • Compare two pieces of text using cosine similarity.
  • Prepare clean content for indexing.
  • Split long documents into meaningful chunks.
  • Store metadata alongside vectors.
  • Prepare the architecture for a vector database or RAG system.

The goal is to understand the architecture instead of hiding the process
behind a framework. The same approach can later be connected to
PostgreSQL with pgvector, another vector database, or a complete
retrieval-augmented generation pipeline.

What Is an Embedding?

An embedding is a list of numbers produced by a machine-learning model.
These numbers are not intended to be read by a person. They represent
useful information about the meaning and relationships contained in the
input.

Related pieces of text can occupy nearby positions in an embedding
model’s vector space. For example:

  • “How can I make my website faster?”
  • “How do I improve page loading performance?”

The words are different, but the questions express a closely related
idea. A semantic retrieval system can compare their vectors and identify
that relationship.

Embeddings are different from generated answers. An embedding endpoint
produces a numerical representation of text. Your application then
decides how to use that representation for search, recommendations,
classification, clustering, or retrieval.

Choose an Embedding Model for an LLM API

Before you create embeddings with an LLM API, choose a model that fits
your application.

The example in this tutorial uses OpenAI’s text-embedding-3-small,
which is designed for embedding tasks such as search, clustering,
recommendations, anomaly detection, and classification.

A critical rule is consistency. Documents and queries used for retrieval
should be represented in a compatible vector space. Record the model
name and vector dimensions with your indexed data so you know how every
vector was generated.

If you later change embedding models, plan a re-indexing process. Do not
casually mix vectors generated by unrelated models or incompatible
vector spaces.

Create the Python Project

Create a virtual environment and install the official Python SDK.

python -m venv .venv

Activate the environment on Windows:

.venv\Scripts\activate

On macOS or Linux:

source .venv/bin/activate

Install the SDK:

pip install openai

Keep your API key outside your source code. Environment variables are
suitable for local development, while a managed secret store is better
for production.

Never publish an API key in a tutorial repository, browser JavaScript,
screenshots, or public Git history.

Create Your First Embedding With an LLM API

The first step to create embeddings with an LLM API is to send a text
string to the embedding endpoint.

from openai import OpenAI

client = OpenAI()

text = "How can I improve website performance?"

response = client.embeddings.create(
    model="text-embedding-3-small",
    input=text
)

vector = response.data[0].embedding

print("Vector length:", len(vector))
print("First five values:", vector[:5])

The SDK reads the API key from the environment. This keeps credentials
out of your Python source file.

For example, your application might receive the following input:

How can I improve website performance?

The embedding endpoint converts that text into a numerical vector. Your
application can then store the vector or compare it with other vectors.

Understand the Embedding Response

The response contains embedding data. The vector itself is a sequence of
floating-point values.

The vector length depends on the selected model and any supported
dimensions option. Do not hard-code a dimension simply because an
example on the internet uses that value.

Instead, inspect the actual vector during development:

print(len(vector))

If the vector will be stored in a vector database, the database column
dimension must match the vectors you generate.

It is also useful to store the model name with each indexed record. This
makes future migrations easier because you can identify which model
produced each vector.

Generate Multiple Embeddings With an LLM API

Real applications normally index many pieces of content. Instead of
sending a separate request for every short sentence, you can send
multiple input strings in a controlled batch.

texts = [
    "How to improve website loading speed",
    "How to create a PHP login system",
    "How to build a React application",
    "How to configure WordPress caching"
]

response = client.embeddings.create(
    model="text-embedding-3-small",
    input=texts
)

for item, text in zip(response.data, texts):
    print(text)
    print("Vector length:", len(item.embedding))

Batching is especially useful during a large initial indexing job.

However, batching does not mean sending unlimited content in one
request. Respect the provider’s token limits, rate limits, and request
limits. For large collections, process documents in controlled batches
and record successful and failed items.

Compare Two Texts With Cosine Similarity

After you create embeddings with an LLM API, a common next step is
comparing vectors.

Cosine similarity measures the orientation of two vectors. It is
commonly used to estimate how closely two embeddings are related.

from math import sqrt

def cosine_similarity(a, b):
    dot = sum(x * y for x, y in zip(a, b))

    na = sqrt(sum(x * x for x in a))
    nb = sqrt(sum(y * y for y in b))

    if na == 0 or nb == 0:
        return 0.0

    return dot / (na * nb)

You could create two embeddings and compare them:

text_a = "How can I improve website speed?"
text_b = "How do I make my web page load faster?"

response = client.embeddings.create(
    model="text-embedding-3-small",
    input=[text_a, text_b]
)

vector_a = response.data[0].embedding
vector_b = response.data[1].embedding

score = cosine_similarity(vector_a, vector_b)

print("Similarity:", score)

A similarity score is useful for ranking related content, but it should
not be treated as a universal confidence percentage. A high similarity
score does not guarantee that a document contains the correct answer.

Clean Text Before Creating Embeddings

Embedding quality depends partly on what you send to the model.

For website content, remove unnecessary material such as:

  • Navigation menus
  • Repeated footers
  • Cookie notices
  • Tracking fragments
  • Broken HTML
  • Repeated interface text
  • Unrelated boilerplate

Preserve useful context such as the article title and section heading.

For example, this is more useful:

Image Optimization

Convert large images to WebP and AVIF to reduce page weight and improve loading performance.

than a page fragment containing navigation menus, cookie notices,
unrelated buttons, and repeated footer content.

Clean input helps the embedding represent the subject you actually want
users to retrieve.

Chunk Long Documents

Do not automatically embed an entire 2,000-word article as one vector if
users may ask questions about individual sections.

Instead, split long documents into meaningful chunks.

A good chunk should:

  • Be understandable on its own.
  • Represent one coherent subject.
  • Preserve important context.
  • Avoid unnecessary repeated text.
  • Be connected to its source metadata.

For example, a tutorial can be divided into chunks such as:

Tutorial: Create a Python RAG Application
Section: Install Dependencies
Section: Generate Embeddings
Section: Store Vectors
Section: Search Documents
Section: Generate an Answer

Store information such as the document ID, title, section, chunk number,
URL, and original text alongside the vector.

When search finds a relevant chunk, your application can display the
correct source and send the relevant passage to an LLM.

Create a Reusable Embedding Function

Once you create embeddings with an LLM API in a real application, avoid
repeating the API call throughout your code.

Create a reusable function so validation, model selection, logging, and
error handling can live in one place.

def create_embedding(client, text, model="text-embedding-3-small"):
    if not isinstance(text, str):
        raise TypeError("text must be a string")

    text = text.strip()

    if not text:
        raise ValueError("text cannot be empty")

    response = client.embeddings.create(
        model=model,
        input=text
    )

    return response.data[0].embedding

You can then call it from different parts of your application:

vector = create_embedding(
    client,
    "How do I improve website performance?"
)

print("Vector length:", len(vector))

In production, add bounded retry behavior for transient network failures
and rate limits. Do not blindly retry invalid requests forever. Record
failures and place failed documents into a retry queue when appropriate.

Store Metadata With Vectors

A vector without its source is difficult to use.

When you create embeddings with an LLM API for a content library, store
useful metadata alongside every vector.

A record might contain:

tutorial_id
title
canonical_url
technology
difficulty
tutorial_series
section
chunk_number
content_version
language
embedding_model
vector

For example:

technology = PHP
difficulty = Intermediate
content_type = Tutorial

Metadata allows your application to filter results.

A user could search semantically while also applying filters such as:

Beginner JavaScript tutorials

or:

WordPress troubleshooting

This combination of semantic retrieval and metadata filtering can make
search results more useful.

Create Embeddings With an LLM API for Search

Once documents have embeddings, a semantic search workflow usually
follows this pattern:

User question
      ↓
Create query embedding
      ↓
Search stored vectors
      ↓
Rank similar chunks
      ↓
Apply metadata filters
      ↓
Return relevant sources

For example, a user might search:

How can I make my website load faster?

The application creates an embedding for the question. It then compares
that query vector with document vectors and retrieves the most relevant
chunks.

This approach can find content even when the query does not contain the
exact words used in the original article.

Prepare Embeddings for a Vector Database

For a small prototype, vectors can be inspected directly in Python. A
production system normally needs persistent storage.

After you create embeddings with an LLM API, you can store the vectors
in a vector-capable database.

PostgreSQL with pgvector is one practical option for developers who
already use PostgreSQL. Other vector databases can also be used
depending on your application requirements.

The basic architecture becomes:

Website content
      ↓
Clean and normalize
      ↓
Split into chunks
      ↓
Create embeddings
      ↓
Store vectors + metadata
      ↓
Create query embedding
      ↓
Vector search
      ↓
Retrieve relevant chunks

Once retrieval works, the same pipeline can become the retrieval layer
of a RAG application.

Cost and Performance

Embedding costs depend on input tokens and the selected model.

Before indexing a large content library, estimate the number of tokens
involved. Cache embeddings for unchanged content and regenerate vectors
only when the source text changes.

For example, keep a content hash:

document_id
content_hash
embedding_model
vector

When the content hash has not changed, the application can skip
unnecessary API requests.

Batch ingestion jobs can also reduce overhead. Monitor API usage and
measure embedding latency separately from vector-database latency.

This helps identify whether performance problems come from the embedding
API, database search, preprocessing, or another part of the pipeline.

Evaluate Embedding Search Before Scaling

Do not index thousands or millions of documents before checking whether
your retrieval system works.

Create a small evaluation set containing realistic questions.

For example:

How do I improve LCP?
How do I add a PHP login?
How can I upload files with React?
How do I configure WordPress caching?

Label which documents or sections should be relevant.

Include:

  • Paraphrased questions
  • Short questions
  • Technical phrases
  • Beginner wording
  • Long questions
  • Unrelated questions
  • Questions that should return no useful result

After changing your preprocessing, chunking strategy, embedding model,
database index, or ranking logic, run the same evaluation set again.

This gives you a measurable baseline instead of relying on one or two
examples.

Common Mistakes

Several mistakes can reduce the reliability of an embedding system.

Hard-Coding API Keys

Never place an API key directly in source code that may be published.

Use environment variables or a managed secret store.

Mixing Embedding Models

Documents and queries should use compatible embeddings. If you change
models, plan a migration and re-index the affected content.

Ignoring Vector Dimensions

The database vector dimension must match the vectors being stored.

Embedding Noisy Website Content

Navigation, repeated footers, cookie notices, and unrelated interface
text can make retrieval less useful.

Regenerating Every Vector

Do not regenerate vectors for unchanged content. Use content hashes or
versions to detect changes.

Treating Similarity as Truth

A similarity score indicates a relationship between representations. It
does not prove that the retrieved content is correct.

Ignoring Evaluation

Adding more documents does not automatically improve search. Retrieval
quality depends on source content, chunking, metadata, model choice,
ranking, and evaluation.

Build an Idempotent Indexing Pipeline

A production embedding pipeline should be idempotent.

Give every document and chunk a stable identifier. Store a content hash
and the embedding model name.

When a page has not changed:

Skip embedding

When a page has changed:

Regenerate affected chunks

This reduces API costs and makes indexing faster.

It also gives you a clean migration path if you later choose another
embedding model.

Use a Batch Worker for Large Collections

For a large content library, avoid embedding every page from a normal
web request.

A background worker can:

  1. Read pending documents.
  2. Clean and chunk the content.
  3. Create controlled embedding batches.
  4. Call the embedding API.
  5. Save successful vectors.
  6. Record failures.
  7. Retry temporary failures.
  8. Mark completed documents.

Keep retry logic bounded.

A temporary network failure can be retried. An invalid input should be
corrected rather than retried forever.

If one document fails, do not stop the entire indexing job. Put the
failed item into a retry queue and continue processing the remaining
documents.

Design the Pipeline as Separate Components

A reliable embedding system becomes easier to maintain when it
separates:

Content ingestion
       ↓
Preprocessing
       ↓
Chunking
       ↓
Embedding generation
       ↓
Vector storage
       ↓
Retrieval
       ↓
User interface

Each stage should have a clear input and output.

This modular design makes debugging easier because a failure can be
isolated to one stage. It also allows you to replace a component without
rewriting the entire application.

For example, you could change the vector database without changing your
content-cleaning logic.

Improve the User Experience

A technically correct retrieval system can still be frustrating if
results are difficult to scan.

Return useful information such as:

  • Tutorial title
  • Short excerpt
  • Category
  • Difficulty
  • Technology
  • Source URL

For a Q&A system, show the sources used to construct the answer.

Good retrieval should reduce the number of searches a visitor needs to
perform, not simply demonstrate that vectors are working.

Plan for Failure

An embedding provider can temporarily become unavailable. A database can
become slow. A document can fail during ingestion. A user can submit
unsupported content.

Use:

  • Timeouts
  • Bounded retries
  • Logging
  • Validation
  • Graceful fallback responses
  • Retry queues

If the AI layer fails, a normal website search or tutorial index should
remain available when possible.

If one document fails indexing, do not stop the entire indexing job.

A resilient architecture assumes that individual components can fail and
provides a controlled way to recover.

Document the Embedding System

AI systems evolve quickly, so document the important implementation
details.

Record:

  • Embedding model
  • Vector dimensions
  • Chunking rules
  • Database schema
  • Index type
  • Retrieval filters
  • Evaluation queries
  • Environment variables
  • Re-indexing procedure
  • Content versioning strategy

This documentation helps the next developer understand how the system
works and how to migrate it safely.

Related Resources

Continue with the CodexJunction guide on How to Create a Semantic
Search Feature With
Embeddings
.

For implementation reference, consult the official OpenAI Embeddings
documentation
,
NumPy documentation, and PostgreSQL
documentation
.

Conclusion

Learning how to create embeddings with an LLM API gives you the
foundation for building semantic search, recommendations, document
retrieval, classification systems, and RAG applications.

The basic workflow is straightforward:

Text
 ↓
Clean content
 ↓
Create embeddings
 ↓
Store vectors and metadata
 ↓
Create an embedding for the user query
 ↓
Search similar vectors
 ↓
Retrieve useful content
 ↓
Use the retrieved content in your application

The difficult part is not simply generating a vector. A reliable system
also requires clean source content, meaningful chunks, consistent
models, useful metadata, efficient storage, evaluation, error handling,
and a clear re-indexing strategy.

Start with a small evaluation dataset and a simple Python
implementation. Once retrieval quality is measurable, you can scale the
pipeline to a vector database and eventually connect it to a complete
RAG application.

How to Build a Simple RAG Application With Python

Previous article

How to Store Embeddings in a Vector Database

Next article

Comments

Leave a reply

Your email address will not be published. Required fields are marked *