# frozen_string_literal: true # Boxcars is a framework for running a series of tools to get an answer to a question. module Boxcars # For Boxcars that use an engine to do their work. class VectorSearch # initialize the vector search with the following parameters: # @param params [Hash] A Hash containing the initial configuration. # @option params [Hash] :vector_documents The vector documents to search. # example: # { # type: :in_memory, # vector_store: [ # Boxcars::VectorStore::Document.new( # content: "hello", # embedding: [0.1, 0.2, 0.3], # metadata: { a: 1 } # ) # ] # } def initialize(params) @vector_documents = params[:vector_documents] @embedding_tool = params[:embedding_tool] || :openai @vector_search_instance = vector_search_instance @openai_connection = params[:openai_connection] || default_connection(openai_access_token: params[:openai_access_token]) end # @param query [String] The query to search for. # @param count [Integer] The number of results to return. # @return [Array] array of hashes with :document and :distance keys # @example # [ # { # document: Boxcars::VectorStore::Document.new( # content: "hello", # embedding: [0.1, 0.2, 0.3], # metadata: { a: 1 } # ), # distance: 0.1 # } # ] def call(query:, count: 1) validate_query(query) query_vector = convert_query_to_vector(query) @vector_search_instance.call(query_vector: query_vector, count: count) end private attr_reader :vector_documents, :embedding_tool, :openai_connection def vector_search_instance case vector_documents[:type] when :hnswlib Boxcars::VectorStore::Hnswlib::Search.new( vector_documents: vector_documents ) when :in_memory Boxcars::VectorStore::InMemory::Search.new( vector_documents: vector_documents ) when :pgvector Boxcars::VectorStore::Pgvector::Search.new( vector_documents: vector_documents ) else raise_argument_error('Unsupported vector store provided') end end def default_connection(openai_access_token: nil) Openai.open_ai_client(openai_access_token: openai_access_token) end def validate_query(query) raise_argument_error('query must be a string') unless query.is_a?(String) raise_argument_error('query must not be empty') if query.empty? end def convert_query_to_vector(query) tool = embeddings_method(embedding_tool) res = tool[:klass].call( texts: [query], client: tool[:client] ).first res[:embedding] end def embeddings_method(embedding_tool) case embedding_tool when :openai { klass: Boxcars::VectorStore::EmbedViaOpenAI, client: openai_connection } when :tensorflow { klass: Boxcars::VectorStore::EmbedViaTensorflow, client: nil } end end def raise_argument_error(message) raise ::Boxcars::ArgumentError, message end end end require "boxcars/vector_store"