Class: Embedding::TextUnifier
- Inherits:
-
Object
- Object
- Embedding::TextUnifier
- Defined in:
- app/services/embedding/text_unifier.rb
Overview
Backfills Gemini Embedding 2 vectors into content_embeddings.unified_embedding
for text content, so text and images share one multimodal vector space.
It reads each source content_type = 'primary' text row, re-embeds its
content with Gemini (batched), and upserts a sibling content_type = 'unified' row tagged gemini-embedding-2. Chunked records are deliberately
excluded: only the normal EmbeddingWorker can preserve their complete
unified_chunk_<n> storage shape.
HISTORICAL — the migration this exists for is DONE. primary is the
pre-unification content_type; nothing writes it any more (the live path in
Models::Embeddable#generate_embedding! always sets unified /
unified_chunk_N), the OpenAI embedding column was dropped in PR #1055
(2026-06-07), and the read path cut over to unified_search long ago. The
legacy primary rows that remain are a fixed, non-growing set. This class is
kept only so the conversion is reproducible if a stray primary row ever
reappears. Always build input through TextUnifier.candidate_scope; it recognizes both
valid unified storage shapes and correlates them by locale.
INERT by default: nothing invokes this from a model callback or the live
search path. Run it explicitly via rake embeddings:backfill_unified_text
(count-first, gated) per the runbook.
Constant Summary collapse
- TEXT_TYPES =
Embeddable TEXT types eligible for unification — every embeddable type
except Image (images are embedded multimodally by the image pipeline).
Includes the sensitive internal types (CallRecord/Activity/Communication). %w[ Post Article Showcase Video Item ProductLine SiteMap ReviewsIo CallRecord Activity Communication AssistantBrainEntry ].freeze
- MODEL =
GA multimodal model written into unified_embedding.
ContentEmbedding::UNIFIED_MODEL
- DIMENSIONS =
MRL output width (HNSW-compatible; matches image unified embeddings).
1536- BATCH_SIZE =
Items per Gemini batchEmbedContents request.
Embedding::Gemini::MAX_BATCH_SIZE
- MAX_CONTENT_LENGTH =
Truncate to stay within the model's ~8k-token text window.
Models::Embeddable::MAX_CONTENT_LENGTH
Class Method Summary collapse
-
.backfill(primary_rows, dimensions: DIMENSIONS) ⇒ Hash
Backfill a set of source primary rows.
-
.candidate_scope(types: TEXT_TYPES) ⇒ ActiveRecord::Relation<ContentEmbedding>
Find legacy primary rows that can safely be converted to one unified row.
Instance Method Summary collapse
- #backfill(primary_rows) ⇒ Object
-
#initialize(dimensions: DIMENSIONS) ⇒ TextUnifier
constructor
A new instance of TextUnifier.
Constructor Details
#initialize(dimensions: DIMENSIONS) ⇒ TextUnifier
Returns a new instance of TextUnifier.
91 92 93 |
# File 'app/services/embedding/text_unifier.rb', line 91 def initialize(dimensions: DIMENSIONS) @dimensions = dimensions end |
Class Method Details
.backfill(primary_rows, dimensions: DIMENSIONS) ⇒ Hash
Backfill a set of source primary rows.
52 53 54 |
# File 'app/services/embedding/text_unifier.rb', line 52 def self.backfill(primary_rows, dimensions: DIMENSIONS) new(dimensions: dimensions).backfill(primary_rows) end |
.candidate_scope(types: TEXT_TYPES) ⇒ ActiveRecord::Relation<ContentEmbedding>
Find legacy primary rows that can safely be converted to one unified row.
A current single row completes the migration slot. Any chunk row also
excludes the source, regardless of model metadata: this legacy converter
must never add a single row beside a chunked shape. The normal embedding
refresh path owns validation and repair of chunk rows.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'app/services/embedding/text_unifier.rb', line 65 def self.candidate_scope(types: TEXT_TYPES) ContentEmbedding .where(embeddable_type: types, content_type: 'primary') .where( <<~SQL.squish, NOT EXISTS ( SELECT 1 FROM content_embeddings unified WHERE unified.embeddable_type = content_embeddings.embeddable_type AND unified.embeddable_id = content_embeddings.embeddable_id AND unified.locale IS NOT DISTINCT FROM content_embeddings.locale AND ( ( unified.content_type = 'unified' AND unified.embedding_model = ? AND unified.unified_embedding IS NOT NULL ) OR unified.content_type LIKE 'unified\\_chunk\\_%' ESCAPE '\\' ) ) SQL MODEL ) .preload(:embeddable) end |
Instance Method Details
#backfill(primary_rows) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'app/services/embedding/text_unifier.rb', line 95 def backfill(primary_rows) counts = { processed: 0, skipped: 0, failed: 0 } each_batch(primary_rows) do |rows| prepared = rows.filter_map do |row| content = content_for(row) if content.blank? counts[:skipped] += 1 nil else { row: row, content: content, content_hash: content_hash_for(row) } end end next if prepared.empty? vectors = Embedding::Gemini.(prepared.pluck(:content), dimensions: @dimensions) write_batch(prepared, vectors, counts) end counts end |