Class: SpreeMenuChat::Retriever
- Inherits:
-
Object
- Object
- SpreeMenuChat::Retriever
- Defined in:
- app/services/spree_menu_chat/retriever.rb
Overview
Embeds a customer's question and returns the top-K most similar SpreeMenuChat::Embedding rows for the store, via pgvector cosine distance — the retrieval half of RAG. SpreeMenuChat::AnswerGenerator is the only caller; kept separate so retrieval and generation each have one job.
Uses input_type: 'query' (not 'document') on the embedding call —
Voyage's asymmetric-embedding hint for the search-query side of a
document/query pair, per the same rationale already documented on
SpreeMenuChat::EmbeddingClient#embed.
Class Method Summary collapse
Instance Method Summary collapse
-
#call ⇒ Object
Returns a plain Array of SpreeMenuChat::Embedding (each with a real #neighbor_distance, populated by the
nearest_neighborsscope) — empty if the question is blank, embedding it fails, or nothing clears the similarity floor. -
#initialize(question, store:) ⇒ Retriever
constructor
A new instance of Retriever.
Constructor Details
#initialize(question, store:) ⇒ Retriever
Returns a new instance of Retriever.
17 18 19 20 |
# File 'app/services/spree_menu_chat/retriever.rb', line 17 def initialize(question, store:) @question = question @store = store end |
Class Method Details
.call(question, store: Spree::Store.default) ⇒ Object
13 14 15 |
# File 'app/services/spree_menu_chat/retriever.rb', line 13 def self.call(question, store: Spree::Store.default) new(question, store: store).call end |
Instance Method Details
#call ⇒ Object
Returns a plain Array of SpreeMenuChat::Embedding (each with a real
#neighbor_distance, populated by the nearest_neighbors scope) —
empty if the question is blank, embedding it fails, or nothing clears
the similarity floor. Never raises SpreeMenuChat::RequestError itself;
SpreeMenuChat::AnswerGenerator treats "nothing retrieved" as the
trigger for its no-context fallback regardless of why nothing came
back, so a Voyage outage degrades to the same honest "I don't have
that" response as a genuinely unanswerable question, not a 500.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'app/services/spree_menu_chat/retriever.rb', line 30 def call return [] if @question.blank? query_vector = return [] if query_vector.nil? # `nearest_neighbors` is chainable (a plain `scope`, see the neighbor # gem) but its `neighbor_distance` is a SELECT-list alias — Postgres # doesn't allow referencing that in a WHERE clause on the same query, # so the similarity floor is applied in Ruby after fetching, not as # SQL. `retrieval_top_k` keeps that fetch small regardless. SpreeMenuChat::Embedding .where(store: @store) .nearest_neighbors(:embedding, query_vector, distance: 'cosine') .limit(SpreeMenuChat::Config.retrieval_top_k) .select { |record| record.neighbor_distance <= max_distance } end |