# Embeddings
{: .no_toc }

Transform text into numerical vectors for semantic search, recommendations, and content similarity
{: .fs-6 .fw-300 }

## Table of contents
{: .no_toc .text-delta }

1. TOC
{:toc}

---

After reading this guide, you will know:

*   How to generate embeddings for single or multiple texts.
*   How to choose specific embedding models.
*   How to use the results, including calculating similarity.
*   How to handle errors during embedding generation.
*   Best practices for performance and large datasets.
*   How to integrate embeddings in a Rails application.

## Basic Embedding Generation

The simplest way to create an embedding is with the global `RubyLLM.embed` method:

```ruby
embedding = RubyLLM.embed("Ruby is a programmer's best friend")

vector = embedding.vectors
puts "Vector dimension: #{vector.length}" # e.g., 1536 for text-embedding-3-small

puts "Model used: #{embedding.model}"
puts "Input tokens: #{embedding.input_tokens}"
```

## Embedding Multiple Texts

You can efficiently embed multiple texts in a single API call:

```ruby
texts = ["Ruby", "Python", "JavaScript"]
embeddings = RubyLLM.embed(texts)

puts "Number of vectors: #{embeddings.vectors.length}" # => 3
puts "First vector dimensions: #{embeddings.vectors.first.length}"
puts "Model used: #{embeddings.model}"
puts "Total input tokens: #{embeddings.input_tokens}"
```

> Batching multiple texts is generally more performant and cost-effective than making individual requests for each text.
{: .note }

## Choosing Models

By default, RubyLLM uses a capable default embedding model (like OpenAI's `text-embedding-3-small`), but you can specify a different one using the `model:` argument.

```ruby
embedding_large = RubyLLM.embed(
  "This is a test sentence",
  model: "text-embedding-3-large"
)

embedding_google = RubyLLM.embed(
  "This is another test sentence",
  model: "text-embedding-004" # Google's model
)

# Use a model not in the registry (useful for custom endpoints)
embedding_custom = RubyLLM.embed(
  "Custom model test",
  model: "my-custom-embedding-model",
  provider: :openai,
  assume_model_exists: true
)
```

You can configure the default embedding model globally:

```ruby
RubyLLM.configure do |config|
  config.default_embedding_model = "text-embedding-3-large"
end
```

Refer to the [Working with Models Guide](/next/models/) for details on finding available embedding models and their capabilities, and to [Model Resolution](/next/model-resolution/) for how `model:`, `provider:`, and `assume_model_exists:` resolve.

## Choosing Dimensions

Each embedding model has its own default output dimensions. For example, OpenAI's `text-embedding-3-small` outputs 1536 dimensions by default, while `text-embedding-3-large` outputs 3072 dimensions. RubyLLM allows you to specify these dimensions per request:

```ruby
embedding = RubyLLM.embed(
  "This is a test sentence",
  model: "text-embedding-3-small",
  dimensions: 512
)
```

This is particularly useful when:
- Working with vector databases that have specific dimension requirements
- Ensuring consistent dimensionality across different requests
- Optimizing storage and query performance in your vector database

Note that not all models support custom dimensions. If you specify dimensions that aren't supported by the chosen model, RubyLLM will use the model's default dimensions.

## Task Types

Some providers tune embeddings for a specific task, such as indexing a document versus matching a search query. Pass `task_type:` with a value in the provider's own vocabulary and RubyLLM places it on the right request field for you.

Vertex AI and Gemini accept values like `RETRIEVAL_QUERY`, `RETRIEVAL_DOCUMENT`, `SEMANTIC_SIMILARITY`, and `CLASSIFICATION`. On these providers you can also pass `title:` to label the document being embedded:

```ruby
embedding = RubyLLM.embed(
  "RubyLLM makes provider APIs feel native to Ruby.",
  model: "text-embedding-004",
  provider: :vertexai,
  task_type: "RETRIEVAL_DOCUMENT",
  title: "RubyLLM docs"
)
```

Bedrock's Cohere models map `task_type:` to their `input_type` field, so pass values like `search_document`, `search_query`, or `classification`. Cohere has no title concept, so `title:` is ignored there.

Providers that have no task concept, such as OpenAI, ignore both `task_type:` and `title:`.

## Provider Options

Use `provider_options:` for request fields in the provider's own vocabulary that are not first-class RubyLLM options. RubyLLM merges them into the rendered request as-is. For example, Vertex AI accepts request-level `parameters:`:

```ruby
embedding = RubyLLM.embed(
  "RubyLLM makes provider APIs feel native to Ruby.",
  model: "text-embedding-004",
  provider: :vertexai,
  provider_options: { parameters: { autoTruncate: false } }
)
```

Keys you pass replace what RubyLLM rendered, so `provider_options:` can override any field RubyLLM sets, including the task type. Reach for it only when a field has no first-class keyword like `task_type:` or `title:`.

## Using Embedding Results

### Vector Properties

The embedding result contains useful information:

```ruby
embedding = RubyLLM.embed("Example text")

puts embedding.vectors.class  # => Array
puts embedding.vectors.first.class  # => Float

puts embedding.vectors.first.length # => 1536

puts embedding.model  # => "text-embedding-3-small"
```

## Using Embedding Results

A primary use case for embeddings is measuring the semantic similarity between texts. Cosine similarity is a common metric.

```ruby
require 'matrix' # Ruby's built-in Vector class requires 'matrix'

embedding1 = RubyLLM.embed("I love Ruby programming")
embedding2 = RubyLLM.embed("Ruby is my favorite language")

vector1 = Vector.elements(embedding1.vectors)
vector2 = Vector.elements(embedding2.vectors)

# Calculate cosine similarity (value between -1 and 1, closer to 1 means more similar)
similarity = vector1.inner_product(vector2) / (vector1.norm * vector2.norm)
puts "Similarity: #{similarity.round(4)}" # => e.g., 0.9123
```

## Error Handling

Embedding API calls can fail for various reasons. Handle errors gracefully:

```ruby
begin
  embedding = RubyLLM.embed("Your text here")
rescue RubyLLM::Error => e
  puts "Embedding failed: #{e.message}"
end
```

For comprehensive error handling patterns and retry strategies, see the [Error Handling Guide](/next/error-handling/).

## Performance and Best Practices

*   **Batching:** Always embed multiple texts in a single call when possible. `RubyLLM.embed(["text1", "text2"])` is much faster than calling `RubyLLM.embed` twice.
*   **Caching/Persistence:** Embeddings are generally static for a given text and model. Store generated embeddings in your database or cache instead of regenerating them frequently.
*   **Dimensionality:** Different models produce vectors of different lengths (dimensions). Ensure your storage and similarity calculation methods handle the correct dimensionality (e.g., `text-embedding-3-small` uses 1536 dimensions, `text-embedding-3-large` uses 3072).
*   **Normalization:** Some vector databases and similarity algorithms perform better if vectors are normalized (scaled to have a length/magnitude of 1). Check the documentation for your specific use case or database.

## Rails Integration Example

In a Rails application using PostgreSQL with the `pgvector` extension, you might store and search embeddings like this:

```ruby
# Migration:
# add_column :documents, :embedding, :vector, limit: 1536 # Match your model's dimensions

# app/models/document.rb
class Document < ApplicationRecord
  has_neighbors :embedding # From the neighbor gem for pgvector

  # Automatically generate embedding before saving if content changed
  before_save :generate_embedding, if: :content_changed?

  # Scope for nearest neighbor search
  scope :search_by_similarity, ->(query_text, limit: 5) {
    query_embedding = RubyLLM.embed(query_text).vectors
    nearest_neighbors(:embedding, query_embedding, distance: :cosine).limit(limit)
  }

  private

  def generate_embedding
    return if content.blank?
    puts "Generating embedding for Document #{id}..."
    begin
      embedding_result = RubyLLM.embed(content) # Uses default embedding model
      self.embedding = embedding_result.vectors
    rescue RubyLLM::Error => e
      errors.add(:base, "Failed to generate embedding: #{e.message}")
      # Prevent saving if embedding fails (optional, depending on requirements)
      throw :abort
    end
  end
end

# Document.create(title: "Intro to Ruby", content: "Ruby is a dynamic language...")
# results = Document.search_by_similarity("What is Ruby?")
# results.each { |doc| puts "- #{doc.title}" }
```

> This Rails example assumes you have the `pgvector` extension enabled in PostgreSQL and are using a gem like `neighbor` for ActiveRecord integration.
{: .note }

This covers storing and searching embeddings. To turn that store into a retrieval-augmented chat - a retrieval tool the model calls, plus an answering agent that cites its sources - see [Retrieval-Augmented Generation (RAG)](/next/rag/).

## Next Steps

Now that you understand embeddings, you might want to explore:

*   [Retrieval-Augmented Generation (RAG)](/next/rag/) to ground answers in your own documents.
*   [Chatting with AI Models](/next/chat/) for interactive conversations.
*   [Using Tools](/next/tools/) to extend AI capabilities.
*   [Error Handling](/next/error-handling/) for building robust applications.
