feat(captain-memory): add UpdateEmbeddingJob

This commit is contained in:
Rodribm10 2026-04-19 00:35:06 -03:00
parent 6723473fdc
commit dc366433bb
3 changed files with 69 additions and 0 deletions

View File

@ -0,0 +1,14 @@
class Captain::ContactMemories::ContradictionCheckerJob < ApplicationJob
queue_as :low
# TODO(phase3-task3.2): implement full contradiction detection logic.
# This skeleton exists so Captain::ContactMemories::UpdateEmbeddingJob can
# enqueue it when run_contradiction_check is true. Task 3.2 will replace
# the body with the real implementation.
def perform(memory_id)
memory = Captain::ContactMemory.find_by(id: memory_id)
return if memory.blank?
# no-op until task 3.2
end
end

View File

@ -0,0 +1,17 @@
class Captain::ContactMemories::UpdateEmbeddingJob < ApplicationJob
queue_as :low
retry_on Captain::Llm::EmbeddingService::EmbeddingsError, wait: :polynomially_longer, attempts: 3
def perform(memory_id, run_contradiction_check: false)
memory = Captain::ContactMemory.find_by(id: memory_id)
return if memory.blank?
embedding = Captain::Llm::EmbeddingService.new(account_id: memory.account_id).get_embedding(memory.content)
return if embedding.blank?
memory.update!(embedding: embedding)
Captain::ContactMemories::ContradictionCheckerJob.perform_later(memory.id) if run_contradiction_check
end
end

View File

@ -0,0 +1,38 @@
require 'rails_helper'
RSpec.describe Captain::ContactMemories::UpdateEmbeddingJob do
let(:memory) { create(:captain_contact_memory) }
let(:embedding_service) { instance_double(Captain::Llm::EmbeddingService) }
before do
allow(Captain::Llm::EmbeddingService).to receive(:new).and_return(embedding_service)
allow(embedding_service).to receive(:get_embedding).and_return(Array.new(1536, 0.5))
end
it 'sets embedding on the memory' do
described_class.perform_now(memory.id)
stored = memory.reload.embedding
# Model uses `has_neighbors :embedding, normalize: true`, so pgvector stores
# the L2-normalized vector rather than the raw stub. We assert the embedding
# is persisted with the correct dimensionality and reflects the stubbed
# uniform vector (all components equal after normalization).
expect(stored).to be_present
expect(stored.size).to eq(1536)
expect(stored.uniq.size).to eq(1)
expect(stored.first).to be_within(1e-6).of(1.0 / Math.sqrt(1536))
end
it 'does not enqueue ContradictionCheckerJob inside itself' do
expect { described_class.perform_now(memory.id) }
.not_to have_enqueued_job(Captain::ContactMemories::ContradictionCheckerJob)
end
it 'enqueues ContradictionCheckerJob after setting embedding when configured' do
expect { described_class.perform_now(memory.id, run_contradiction_check: true) }
.to have_enqueued_job(Captain::ContactMemories::ContradictionCheckerJob).with(memory.id)
end
it 'tolerates a missing memory silently' do
expect { described_class.perform_now(99_999_999) }.not_to raise_error
end
end