1+ # frozen_string_literal: true
2+
3+ module LangchainrbRails
4+ module ActiveRecord
5+ # This module adds the following functionality to your ActiveRecord models:
6+ # * `vectorsearch` class method to set the vector search provider
7+ # * `similarity_search` class method to search for similar texts
8+ # * `upsert_to_vectorsearch` instance method to upsert the record to the vector search provider
9+ #
10+ # Usage:
11+ # class Recipe < ActiveRecord::Base
12+ # vectorsearch provider: Langchain::Vectorsearch::Weaviate.new(
13+ # api_key: ENV["WEAVIATE_API_KEY"],
14+ # url: ENV["WEAVIATE_URL"],
15+ # index_name: "Recipes",
16+ # llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
17+ # )
18+ #
19+ # after_save :upsert_to_vectorsearch
20+ #
21+ # # Overwriting how the model is serialized before it's indexed
22+ # def as_vector
23+ # [
24+ # "Title: #{title}",
25+ # "Description: #{description}",
26+ # ...
27+ # ]
28+ # .compact
29+ # .join("\n")
30+ # end
31+ # end
32+ #
33+ # Create the default schema
34+ # Recipe.class_variable_get(:@@provider).create_default_schema
35+ # Query the vector search provider
36+ # Recipe.similarity_search("carnivore dish")
37+ # Delete the default schema to start over
38+ # Recipe.class_variable_get(:@@provider).destroy_default_schema
39+ #
40+ module Hooks
41+ def self . included ( base )
42+ base . extend ClassMethods
43+ end
44+
45+ # Index the text to the vector search provider
46+ # You'd typically call this method in an ActiveRecord callback
47+ #
48+ # @return [Boolean] true
49+ # @raise [Error] Indexing to vector search DB failed
50+ def upsert_to_vectorsearch
51+ if previously_new_record?
52+ self . class . class_variable_get ( :@@provider ) . add_texts (
53+ texts : [ as_vector ] ,
54+ ids : [ id ]
55+ )
56+ else
57+ self . class . class_variable_get ( :@@provider ) . update_texts (
58+ texts : [ as_vector ] ,
59+ ids : [ id ]
60+ )
61+ end
62+ end
63+
64+ # Used to serialize the DB record to an indexable vector text
65+ # Overwrite this method in your model to customize
66+ #
67+ # @return [String] the text representation of the model
68+ def as_vector
69+ to_json
70+ end
71+
72+ module ClassMethods
73+ # Set the vector search provider
74+ #
75+ # @param provider [Object] The `Langchain::Vectorsearch::*` instance
76+ def vectorsearch ( provider :)
77+ class_variable_set ( :@@provider , provider )
78+ end
79+
80+ # Search for similar texts
81+ #
82+ # @param query [String] The query to search for
83+ # @param k [Integer] The number of results to return
84+ # @return [ActiveRecord::Relation] The ActiveRecord relation
85+ def similarity_search ( query , k : 1 )
86+ records = class_variable_get ( :@@provider ) . similarity_search (
87+ query : query ,
88+ k : k
89+ )
90+
91+ # We use "__id" when Weaviate is the provider
92+ ids = records . map { |record | record . dig ( "id" ) || record . dig ( "__id" ) }
93+ where ( id : ids )
94+ end
95+
96+ # Ask a question and return the answer
97+ #
98+ # @param question [String] The question to ask
99+ # @param k [Integer] The number of results to have in context
100+ # @yield [String] Stream responses back one String at a time
101+ # @return [String] The answer to the question
102+ def ask ( question :, k : 4 , &block )
103+ class_variable_get ( :@@provider ) . ask (
104+ question : question ,
105+ k : k ,
106+ &block
107+ )
108+ end
109+ end
110+ end
111+ end
112+ end
0 commit comments