Class: Llmemory::Retrieval::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/llmemory/retrieval/engine.rb

Constant Summary collapse

RELEVANCE_THRESHOLD =
0.7
FEEDBACK_CAP =
5

Instance Method Summary collapse

Constructor Details

#initialize(memory, llm: nil, feedback: nil) ⇒ Engine

Returns a new instance of Engine.



15
16
17
18
19
20
21
22
23
# File 'lib/llmemory/retrieval/engine.rb', line 15

def initialize(memory, llm: nil, feedback: nil)
  @memory = memory
  @llm = llm || Llmemory::LLM.client
  @ranker = TemporalRanker.new
  @assembler = ContextAssembler.new
  @bm25_scorer = Bm25Scorer.new
  @mmr_reranker = MmrReranker.new(lambda: Llmemory.configuration.mmr_lambda)
  @feedback = feedback || FeedbackStore.new
end

Instance Method Details

#iterative_retrieve(user_message, user_id: nil, max_tokens: nil, max_hops: 2, reasoner: nil) ⇒ Object

Multi-hop retrieval (CoALA: integrating retrieval and reasoning). After each hop, a reasoner inspects what has been retrieved and proposes a follow-up query for the missing piece, enabling multi-hop questions a single retrieval would miss. Candidates accumulate (deduped) across hops.

‘reasoner` is a callable (user_message, accumulated_candidates, hop) -> next query String, or “DONE”/blank to stop. Defaults to an LLM that proposes the next sub-query. Converges on `max_hops`, “DONE”, a blank query, or a repeated query.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/llmemory/retrieval/engine.rb', line 41

def iterative_retrieve(user_message, user_id: nil, max_tokens: nil, max_hops: 2, reasoner: nil)
  user_id ||= @memory.respond_to?(:user_id) ? @memory.user_id : nil
  reasoner ||= method(:default_followup_query)

  query = generate_query(user_message)
  seen = []
  accumulated = []
  hop = 0

  while hop < max_hops && live_query?(query) && !seen.include?(query)
    seen << query
    accumulated = merge_candidates(accumulated, ranked_candidates(query, user_id, query))
    hop += 1
    break if hop >= max_hops

    query = reasoner.call(user_message, accumulated, hop).to_s.strip
  end

  final = accumulated.sort_by { |c| -(c[:temporal_score] || c[:score] || 0) }
  @assembler.assemble(final, max_tokens: max_tokens)
end

#report_feedback(useful_ids: [], harmful_ids: [], user_id: nil) ⇒ Object

Records that previously-retrieved items were useful or harmful for the agent’s task. Repeatedly useful items rank higher in future retrievals; noisy ones are dampened. Item ids come from the candidates returned by the memory’s #read / #search_candidates.



67
68
69
70
71
72
# File 'lib/llmemory/retrieval/engine.rb', line 67

def report_feedback(useful_ids: [], harmful_ids: [], user_id: nil)
  user_id ||= @memory.respond_to?(:user_id) ? @memory.user_id : nil
  Array(useful_ids).each { |id| @feedback.record(user_id, id, 1) }
  Array(harmful_ids).each { |id| @feedback.record(user_id, id, -1) }
  true
end

#retrieve_for_inference(user_message, user_id: nil, max_tokens: nil) ⇒ Object



25
26
27
28
29
30
# File 'lib/llmemory/retrieval/engine.rb', line 25

def retrieve_for_inference(user_message, user_id: nil, max_tokens: nil)
  user_id ||= @memory.respond_to?(:user_id) ? @memory.user_id : nil
  search_query = generate_query(user_message)
  ranked = ranked_candidates(search_query, user_id, user_message)
  @assembler.assemble(ranked, max_tokens: max_tokens)
end