fix(captain-memory): wrap ExtractFromConversationJob persistence in transaction + hoist unit lookup

This commit is contained in:
Rodribm10 2026-04-19 00:50:08 -03:00
parent 9d5e4c959f
commit 1646f66a97
2 changed files with 37 additions and 6 deletions

View File

@ -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

View File

@ -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