fix(captain-memory): strengthen RecallService logging context and document timeout tradeoff

This commit is contained in:
Rodribm10 2026-04-19 00:14:06 -03:00
parent 502c3d1698
commit 0fee1b3c2f
2 changed files with 31 additions and 10 deletions

View File

@ -12,21 +12,41 @@ class Captain::ContactMemories::RecallService
def call
return [] if @contact.blank? || @query_text.blank?
# NOTE: Timeout.timeout is used here as a hard cap on both embedding API + DB query.
# It has known hazards (async exception, can't cleanly interrupt C-level I/O, can
# corrupt connection state) — tradeoff accepted because this service is non-critical:
# any failure returns [] and the agent degrades gracefully without memory. Phase 6
# will consider refactoring the DB portion to Postgres statement_timeout for safer
# cancellation.
Timeout.timeout(TIMEOUT_SECONDS) do
query_embedding = Captain::Llm::EmbeddingService.new(account_id: @contact.account_id).get_embedding(@query_text)
return [] if query_embedding.blank?
Captain::ContactMemory
.active
.for_contact(@contact.id)
.scope_compatible(@unit_id)
.where.not(embedding: nil)
.nearest_neighbors(:embedding, query_embedding, distance: 'cosine')
.limit(@top_k)
.to_a
nearest_memories(query_embedding)
end
rescue StandardError => e
Rails.logger.warn("[ContactMemory::RecallService] #{e.class}: #{e.message}")
log_failure(e)
[]
end
private
def nearest_memories(query_embedding)
Captain::ContactMemory
.active
.for_contact(@contact.id)
.scope_compatible(@unit_id)
.where.not(embedding: nil)
.nearest_neighbors(:embedding, query_embedding, distance: 'cosine')
.limit(@top_k)
.to_a
end
def log_failure(error)
Rails.logger.warn(
"[ContactMemory::RecallService] #{error.class}: #{error.message} " \
"(contact_id=#{@contact&.id} account_id=#{@contact&.account_id})"
)
Rails.logger.warn(error.backtrace.first(5).join("\n")) unless error.is_a?(Timeout::Error)
end
end

View File

@ -62,8 +62,9 @@ RSpec.describe Captain::ContactMemories::RecallService do
it 'skips memories with NULL embedding' do
create(:captain_contact_memory, contact: contact, account: account, embedding: nil)
valid = create(:captain_contact_memory, contact: contact, account: account, embedding: Array.new(1536, 0.1))
result = described_class.new(contact: contact, query_text: 'x').call
expect(result).to eq([])
expect(result).to eq([valid])
end
end
end