Class: Pikuri::VectorDb::Embedder
- Inherits:
-
Object
- Object
- Pikuri::VectorDb::Embedder
- Defined in:
- lib/pikuri/vector_db/embedder.rb
Overview
Thin wrapper over RubyLLM.embed. Holds an optional model kwarg, exposes a single #embed(texts) method that always takes an Array<String> and always returns an Array<Array<Float>> (parallel to the input).
Why a wrapper at all
RubyLLM.embed‘s shape isn’t fixed: a single-String input returns vectors as a flat Array<Float>; an array input returns Array<Array<Float>>. The Indexer (Phase 7) only ever wants the second shape, and would otherwise have to branch on input type at every call site. Pinning the contract here once means downstream code (Indexer, Search query path) stays simple.
The other reason is testability: stubbing RubyLLM.embed in every consumer’s spec via define_singleton_method is repetitive and fragile (the stub leaks across examples if after blocks aren’t disciplined). Routing through this class lets each consumer inject a fake #embed object via dependency injection — much cheaper than monkey-patching ruby_llm in dozens of tests.
Why no batching policy
RubyLLM.embed accepts the array verbatim and the provider decides what to do with it (OpenAI batches up to 2048 inputs; local llama.cpp processes one at a time but in a single HTTP call). Batching strategy is the Indexer‘s concern, not this wrapper’s — the Indexer slices a large corpus into batches and calls #embed once per batch.
Errors are loud
RubyLLM‘s exceptions (Faraday network failures, provider 4xx / 5xx, auth errors) propagate verbatim. This is internal pikuri code, not an LLM-facing tool, so no “Error: …” string return path.
Instance Attribute Summary collapse
-
#assume_model_exists ⇒ Boolean
readonly
Whether to bypass RubyLLM’s local model registry.
-
#model ⇒ String?
readonly
The embedding model name, or
nilto use whichever defaultRubyLLM.config.default_embedding_modelresolves to at call time. -
#provider ⇒ Symbol?
readonly
The explicit provider symbol (e.g.
:openai), ornilto let RubyLLM infer from the model name.
Instance Method Summary collapse
-
#embed(texts) ⇒ Array<Array<Float>>
Embed
textsas a single call to the underlying provider. - #initialize(model: nil, provider: nil, assume_model_exists: false) ⇒ Embedder constructor
Constructor Details
#initialize(model: nil, provider: nil, assume_model_exists: false) ⇒ Embedder
81 82 83 84 85 86 87 88 89 |
# File 'lib/pikuri/vector_db/embedder.rb', line 81 def initialize(model: nil, provider: nil, assume_model_exists: false) if assume_model_exists && provider.nil? raise ArgumentError, 'provider: must be specified when assume_model_exists is true' end @model = model @provider = provider @assume_model_exists = assume_model_exists end |
Instance Attribute Details
#assume_model_exists ⇒ Boolean (readonly)
Returns whether to bypass RubyLLM’s local model registry. true when pointing at a local llama.cpp endpoint whose model identifiers (router aliases, HF repo paths like “nomic-ai/nomic-embed-text-v1.5-GGUF:Q8_0”) are not in RubyLLM’s built-in catalog.
62 63 64 |
# File 'lib/pikuri/vector_db/embedder.rb', line 62 def assume_model_exists @assume_model_exists end |
#model ⇒ String? (readonly)
Returns the embedding model name, or nil to use whichever default RubyLLM.config.default_embedding_model resolves to at call time.
49 50 51 |
# File 'lib/pikuri/vector_db/embedder.rb', line 49 def model @model end |
#provider ⇒ Symbol? (readonly)
Returns the explicit provider symbol (e.g. :openai), or nil to let RubyLLM infer from the model name. Required by RubyLLM when assume_model_exists is true.
55 56 57 |
# File 'lib/pikuri/vector_db/embedder.rb', line 55 def provider @provider end |
Instance Method Details
#embed(texts) ⇒ Array<Array<Float>>
Embed texts as a single call to the underlying provider. Empty input short-circuits to [] — no HTTP round-trip.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/pikuri/vector_db/embedder.rb', line 100 def (texts) unless texts.is_a?(Array) raise ArgumentError, "expected Array<String>, got #{texts.class}" end return [] if texts.empty? unless texts.all? { |t| t.is_a?(String) } bad = texts.reject { |t| t.is_a?(String) }.first raise ArgumentError, "all elements must be String, got #{bad.class}" end kwargs = {} kwargs[:model] = @model if @model kwargs[:provider] = @provider if @provider kwargs[:assume_model_exists] = true if @assume_model_exists = kwargs.empty? ? RubyLLM.(texts) : RubyLLM.(texts, **kwargs) .vectors end |