diff --git a/enterprise/app/jobs/captain/contact_memories/extract_from_conversation_job.rb b/enterprise/app/jobs/captain/contact_memories/extract_from_conversation_job.rb index dc9623160..5492cd931 100644 --- a/enterprise/app/jobs/captain/contact_memories/extract_from_conversation_job.rb +++ b/enterprise/app/jobs/captain/contact_memories/extract_from_conversation_job.rb @@ -21,7 +21,9 @@ class Captain::ContactMemories::ExtractFromConversationJob < ApplicationJob facts = Captain::ContactMemories::ExtractionService.new(conversation: conversation).call return if facts.blank? - facts.each { |fact| persist_fact(fact, conversation) } + unit_id = resolve_unit_id(conversation) + created_memory_ids = persist_all(facts, conversation, unit_id) + enqueue_embedding_jobs(created_memory_ids) end private @@ -30,12 +32,23 @@ class Captain::ContactMemories::ExtractFromConversationJob < ApplicationJob Captain::ContactMemory.exists?(source_conversation_id: conversation.id) end - def persist_fact(fact, conversation) - memory = Captain::ContactMemory.create!(build_attributes(fact, conversation)) - Captain::ContactMemories::UpdateEmbeddingJob.perform_later(memory.id, run_contradiction_check: true) + def persist_all(facts, conversation, unit_id) + Captain::ContactMemory.transaction do + facts.map { |fact| persist_fact(fact, conversation, unit_id).id } + end end - def build_attributes(fact, conversation) + def enqueue_embedding_jobs(memory_ids) + memory_ids.each do |id| + Captain::ContactMemories::UpdateEmbeddingJob.perform_later(id, run_contradiction_check: true) + end + end + + def persist_fact(fact, conversation, unit_id) + Captain::ContactMemory.create!(build_attributes(fact, conversation, unit_id)) + end + + def build_attributes(fact, conversation, unit_id) ttl = TTL_BY_TYPE[fact[:memory_type]] { account_id: conversation.account_id, @@ -46,7 +59,7 @@ class Captain::ContactMemories::ExtractFromConversationJob < ApplicationJob confidence: fact[:confidence], scope: fact[:scope], source_conversation_id: conversation.id, - source_unit_id: resolve_unit_id(conversation), + source_unit_id: unit_id, source_inbox_id: conversation.inbox_id, last_verified_at: Time.current, expires_at: ttl&.from_now diff --git a/spec/enterprise/jobs/captain/contact_memories/extract_from_conversation_job_spec.rb b/spec/enterprise/jobs/captain/contact_memories/extract_from_conversation_job_spec.rb index 9d81fce43..5425b9b13 100644 --- a/spec/enterprise/jobs/captain/contact_memories/extract_from_conversation_job_spec.rb +++ b/spec/enterprise/jobs/captain/contact_memories/extract_from_conversation_job_spec.rb @@ -64,4 +64,22 @@ RSpec.describe Captain::ContactMemories::ExtractFromConversationJob do described_class.perform_now(conversation.id) expect(Captain::ContactMemory.last.expires_at).to be_nil end + + it 'rolls back all facts and does not enqueue jobs if any fact fails to persist' do + bad_facts = [ + { memory_type: 'preferencia', content: 'A', evidence: 'x', confidence: 0.9, scope: 'global' }, + { memory_type: 'invalid_type_not_in_enum', content: 'B', evidence: 'y', confidence: 0.9, scope: 'global' } + ] + allow_any_instance_of(Captain::ContactMemories::ExtractionService) # rubocop:disable RSpec/AnyInstance + .to receive(:call).and_return(bad_facts) + + expect do + expect { described_class.perform_now(conversation.id) }.to raise_error(ActiveRecord::RecordInvalid) + end.not_to change(Captain::ContactMemory, :count) + + embedding_jobs = ActiveJob::Base.queue_adapter.enqueued_jobs.select do |j| + j[:job] == Captain::ContactMemories::UpdateEmbeddingJob + end + expect(embedding_jobs).to be_empty + end end