langchainrb 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/tool/base.rb CHANGED
@@ -12,27 +12,39 @@ module Tool
12
12
  TOOLS = {
13
13
  "calculator" => "Tool::Calculator",
14
14
  "search" => "Tool::SerpApi",
15
- "wikipedia" => "Tool::Wikipedia"
15
+ "wikipedia" => "Tool::Wikipedia",
16
+ "news" => "Tool::News"
16
17
  }
17
18
 
18
- # Executes the tool and returns the answer
19
+ def self.description(value)
20
+ const_set(:DESCRIPTION, value.tr("\n", " ").strip)
21
+ end
22
+
23
+ # Instantiates and executes the tool and returns the answer
19
24
  # @param input [String] input to the tool
20
25
  # @return [String] answer
21
26
  def self.execute(input:)
22
- raise NotImplementedError, "Your tool must implement the `self.execute(input:)` method that returns a string"
27
+ new.execute(input: input)
28
+ end
29
+
30
+ # Executes the tool and returns the answer
31
+ # @param input [String] input to the tool
32
+ # @return [String] answer
33
+ def execute(input:)
34
+ raise NotImplementedError, "Your tool must implement the `#execute(input:)` method that returns a string"
23
35
  end
24
36
 
25
- #
37
+ #
26
38
  # Validates the list of strings (tools) are all supported or raises an error
27
39
  # @param tools [Array<String>] list of tools to be used
28
- #
40
+ #
29
41
  # @raise [ArgumentError] If any of the tools are not supported
30
- #
42
+ #
31
43
  def self.validate_tools!(tools:)
32
- unrecognized_tools = tools - Tool::Base::TOOLS.keys
44
+ unrecognized_tools = tools - Tool::Base::TOOLS.keys
33
45
 
34
46
  if unrecognized_tools.any?
35
- raise ArgumentError, "Unrecognized Tools: #{unrecognized_tools}"
47
+ raise ArgumentError, "Unrecognized Tools: #{unrecognized_tools}"
36
48
  end
37
49
  end
38
50
  end
@@ -1,17 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "eqn"
4
-
5
3
  module Tool
6
4
  class Calculator < Base
7
- DESCRIPTION = "Useful for getting the result of a math expression. " +
8
- "The input to this tool should be a valid mathematical expression that could be executed by a simple calculator."
5
+ description <<~DESC
6
+ Useful for getting the result of a math expression.
7
+
8
+ The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.
9
+ DESC
10
+
11
+ def initialize
12
+ depends_on "eqn"
13
+ require "eqn"
14
+ end
9
15
 
10
16
  # Evaluates a pure math expression or if equation contains non-math characters (e.g.: "12F in Celsius") then
11
17
  # it uses the google search calculator to evaluate the expression
12
18
  # @param input [String] math expression
13
19
  # @return [String] Answer
14
- def self.execute(input:)
20
+ def execute(input:)
15
21
  Eqn::Calculator.calc(input)
16
22
  rescue Eqn::ParseError, Eqn::NoVariableValueError
17
23
  # Sometimes the input is not a pure math expression, e.g: "12F in Celsius"
data/lib/tool/serp_api.rb CHANGED
@@ -1,39 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "google_search_results"
4
-
5
3
  module Tool
6
4
  class SerpApi < Base
7
5
  # Wrapper around SerpAPI
8
6
  # Set ENV["SERPAPI_API_KEY"] to use it
9
7
 
10
- DESCRIPTION = "A wrapper around Google Search. " +
11
- "Useful for when you need to answer questions about current events. " +
12
- "Always one of the first options when you need to find information on internet. " +
13
- "Input should be a search query."
8
+ description <<~DESC
9
+ A wrapper around Google Search.
10
+
11
+ Useful for when you need to answer questions about current events.
12
+ Always one of the first options when you need to find information on internet.
13
+
14
+ Input should be a search query.
15
+ DESC
16
+
17
+ def initialize
18
+ depends_on "google_search_results"
19
+ require "google_search_results"
20
+ end
21
+
22
+ # Executes Google Search and returns hash_results JSON
23
+ # @param input [String] search query
24
+ # @return [Hash] hash_results JSON
25
+
26
+ def self.execute_search(input:)
27
+ new.execute_search(input: input)
28
+ end
14
29
 
15
30
  # Executes Google Search and returns hash_results JSON
16
31
  # @param input [String] search query
17
32
  # @return [String] Answer
18
33
  # TODO: Glance at all of the fields that langchain Python looks through: https://github.com/hwchase17/langchain/blob/v0.0.166/langchain/utilities/serpapi.py#L128-L156
19
34
  # We may need to do the same thing here.
20
- def self.execute(input:)
21
- hash_results = self.execute_search(input: input)
35
+ def execute(input:)
36
+ hash_results = execute_search(input: input)
22
37
 
23
38
  hash_results.dig(:answer_box, :answer) ||
24
39
  hash_results.dig(:answer_box, :snippet) ||
25
40
  hash_results.dig(:organic_results, 0, :snippet)
26
41
  end
27
42
 
28
- # Executes Google Search and returns hash_results JSON
29
- # @param input [String] search query
30
- # @return [Hash] hash_results JSON
31
- def self.execute_search(input:)
43
+ def execute_search(input:)
32
44
  GoogleSearch.new(
33
45
  q: input,
34
46
  serp_api_key: ENV["SERPAPI_API_KEY"]
35
47
  )
36
- .get_hash
48
+ .get_hash
37
49
  end
38
50
  end
39
51
  end
@@ -1,23 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'wikipedia'
4
-
5
3
  module Tool
6
4
  class Wikipedia < Base
7
5
  # Tool that adds the capability to search using the Wikipedia API
8
6
 
9
- DESCRIPTION = "A wrapper around Wikipedia. " +
10
- "Useful for when you need to answer general questions about " +
11
- "people, places, companies, facts, historical events, or other subjects. " +
12
- "Input should be a search query."
7
+ description <<~DESC
8
+ A wrapper around Wikipedia.
9
+
10
+ Useful for when you need to answer general questions about
11
+ people, places, companies, facts, historical events, or other subjects.
12
+
13
+ Input should be a search query.
14
+ DESC
15
+
16
+ def initialize
17
+ depends_on "wikipedia-client"
18
+ require "wikipedia"
19
+ end
13
20
 
14
21
  # Executes Wikipedia API search and returns the answer
15
22
  # @param input [String] search query
16
23
  # @return [String] Answer
17
- def self.execute(input:)
24
+ def execute(input:)
18
25
  page = ::Wikipedia.find(input)
19
26
  # It would be nice to figure out a way to provide page.content but the LLM token limit is an issue
20
- page.summary
27
+ page.summary
21
28
  end
22
29
  end
23
30
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Vectorsearch
4
6
  class Base
5
7
  extend Forwardable
6
8
 
7
9
  attr_reader :client, :index_name, :llm, :llm_api_key, :llm_client
8
10
 
9
- DEFAULT_METRIC = "cosine".freeze
11
+ DEFAULT_METRIC = "cosine"
10
12
 
11
13
  # @param llm [Symbol] The LLM to use
12
14
  # @param llm_api_key [String] The API key for the LLM
@@ -46,7 +48,7 @@ module Vectorsearch
46
48
  input_variables: ["context"]
47
49
  ),
48
50
  examples: [
49
- { context: context }
51
+ {context: context}
50
52
  ],
51
53
  input_variables: ["question"],
52
54
  example_separator: "\n"
@@ -55,4 +57,4 @@ module Vectorsearch
55
57
  prompt_template.format(question: question)
56
58
  end
57
59
  end
58
- end
60
+ end
@@ -1,27 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "milvus"
4
-
5
3
  module Vectorsearch
6
4
  class Milvus < Base
7
- def initialize(
8
- url:,
9
- api_key: nil,
10
- index_name:,
11
- llm:,
12
- llm_api_key:
13
- )
14
- @client = ::Milvus::Client.new(
15
- url: url
16
- )
5
+ def initialize(url:, index_name:, llm:, llm_api_key:, api_key: nil)
6
+ depends_on "milvus"
7
+ require "milvus"
8
+
9
+ @client = ::Milvus::Client.new(url: url)
17
10
  @index_name = index_name
18
11
 
19
12
  super(llm: llm, llm_api_key: llm_api_key)
20
13
  end
21
14
 
22
- def add_texts(
23
- texts:
24
- )
15
+ def add_texts(texts:)
25
16
  client.entities.insert(
26
17
  collection_name: index_name,
27
18
  num_rows: texts.count,
@@ -77,10 +68,7 @@ module Vectorsearch
77
68
  )
78
69
  end
79
70
 
80
- def similarity_search(
81
- query:,
82
- k: 4
83
- )
71
+ def similarity_search(query:, k: 4)
84
72
  embedding = generate_embedding(text: query)
85
73
 
86
74
  similarity_search_by_vector(
@@ -89,14 +77,11 @@ module Vectorsearch
89
77
  )
90
78
  end
91
79
 
92
- def similarity_search_by_vector(
93
- embedding:,
94
- k: 4
95
- )
80
+ def similarity_search_by_vector(embedding:, k: 4)
96
81
  client.search(
97
82
  collection_name: index_name,
98
83
  top_k: k.to_s,
99
- vectors: [ embedding ],
84
+ vectors: [embedding],
100
85
  dsl_type: 1,
101
86
  params: "{\"nprobe\": 10}",
102
87
  anns_field: "content",
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pinecone"
4
-
5
3
  module Vectorsearch
6
4
  class Pinecone < Base
7
5
  # Initialize the Pinecone client
@@ -10,18 +8,15 @@ module Vectorsearch
10
8
  # @param index_name [String] The name of the index to use
11
9
  # @param llm [Symbol] The LLM to use
12
10
  # @param llm_api_key [String] The API key for the LLM
13
- def initialize(
14
- environment:,
15
- api_key:,
16
- index_name:,
17
- llm:,
18
- llm_api_key:
19
- )
11
+ def initialize(environment:, api_key:, index_name:, llm:, llm_api_key:)
12
+ depends_on "pinecone"
13
+ require "pinecone"
14
+
20
15
  ::Pinecone.configure do |config|
21
- config.api_key = api_key
16
+ config.api_key = api_key
22
17
  config.environment = environment
23
18
  end
24
-
19
+
25
20
  @client = ::Pinecone::Client.new
26
21
  @index_name = index_name
27
22
 
@@ -31,14 +26,12 @@ module Vectorsearch
31
26
  # Add a list of texts to the index
32
27
  # @param texts [Array] The list of texts to add
33
28
  # @return [Hash] The response from the server
34
- def add_texts(
35
- texts:
36
- )
29
+ def add_texts(texts:)
37
30
  vectors = texts.map do |text|
38
31
  {
39
32
  # TODO: Allows passing in your own IDs
40
33
  id: SecureRandom.uuid,
41
- metadata: { content: text },
34
+ metadata: {content: text},
42
35
  values: generate_embedding(text: text)
43
36
  }
44
37
  end
@@ -78,10 +71,7 @@ module Vectorsearch
78
71
  # @param embedding [Array] The embedding to search for
79
72
  # @param k [Integer] The number of results to return
80
73
  # @return [Array] The list of results
81
- def similarity_search_by_vector(
82
- embedding:,
83
- k: 4
84
- )
74
+ def similarity_search_by_vector(embedding:, k: 4)
85
75
  index = client.index(index_name)
86
76
 
87
77
  response = index.query(
@@ -109,4 +99,4 @@ module Vectorsearch
109
99
  generate_completion(prompt: prompt)
110
100
  end
111
101
  end
112
- end
102
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "qdrant"
4
-
5
3
  module Vectorsearch
6
4
  class Qdrant < Base
7
5
  # Initialize the Qdrant client
@@ -10,13 +8,10 @@ module Vectorsearch
10
8
  # @param index_name [String] The name of the index to use
11
9
  # @param llm [Symbol] The LLM to use
12
10
  # @param llm_api_key [String] The API key for the LLM
13
- def initialize(
14
- url:,
15
- api_key:,
16
- index_name:,
17
- llm:,
18
- llm_api_key:
19
- )
11
+ def initialize(url:, api_key:, index_name:, llm:, llm_api_key:)
12
+ depends_on "qdrant-ruby"
13
+ require "qdrant"
14
+
20
15
  @client = ::Qdrant::Client.new(
21
16
  url: url,
22
17
  api_key: api_key
@@ -29,15 +24,13 @@ module Vectorsearch
29
24
  # Add a list of texts to the index
30
25
  # @param texts [Array] The list of texts to add
31
26
  # @return [Hash] The response from the server
32
- def add_texts(
33
- texts:
34
- )
35
- batch = { ids: [], vectors: [], payloads: [] }
27
+ def add_texts(texts:)
28
+ batch = {ids: [], vectors: [], payloads: []}
36
29
 
37
30
  texts.each do |text|
38
31
  batch[:ids].push(SecureRandom.uuid)
39
32
  batch[:vectors].push(generate_embedding(text: text))
40
- batch[:payloads].push({ content: text })
33
+ batch[:payloads].push({content: text})
41
34
  end
42
35
 
43
36
  client.points.upsert(
@@ -106,4 +99,4 @@ module Vectorsearch
106
99
  generate_completion(prompt: prompt)
107
100
  end
108
101
  end
109
- end
102
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "weaviate"
4
-
5
3
  module Vectorsearch
6
4
  class Weaviate < Base
7
5
  # Initialize the Weaviate adapter
@@ -10,13 +8,10 @@ module Vectorsearch
10
8
  # @param index_name [String] The name of the index to use
11
9
  # @param llm [Symbol] The LLM to use
12
10
  # @param llm_api_key [String] The API key for the LLM
13
- def initialize(
14
- url:,
15
- api_key:,
16
- index_name:,
17
- llm:,
18
- llm_api_key:
19
- )
11
+ def initialize(url:, api_key:, index_name:, llm:, llm_api_key:)
12
+ depends_on "weaviate-ruby"
13
+ require "weaviate"
14
+
20
15
  @client = ::Weaviate::Client.new(
21
16
  url: url,
22
17
  api_key: api_key,
@@ -31,13 +26,11 @@ module Vectorsearch
31
26
  # Add a list of texts to the index
32
27
  # @param texts [Array] The list of texts to add
33
28
  # @return [Hash] The response from the server
34
- def add_texts(
35
- texts:
36
- )
29
+ def add_texts(texts:)
37
30
  objects = texts.map do |text|
38
31
  {
39
32
  class: index_name,
40
- properties: { content: text }
33
+ properties: {content: text}
41
34
  }
42
35
  end
43
36
 
@@ -50,7 +43,7 @@ module Vectorsearch
50
43
  def create_default_schema
51
44
  client.schema.create(
52
45
  class_name: index_name,
53
- vectorizer: "text2vec-#{llm.to_s}",
46
+ vectorizer: "text2vec-#{llm}",
54
47
  # TODO: Figure out a way to optionally enable it
55
48
  # "module_config": {
56
49
  # "qna-openai": {}
@@ -69,10 +62,7 @@ module Vectorsearch
69
62
  # @param query [String] The query to search for
70
63
  # @param k [Integer|String] The number of results to return
71
64
  # @return [Hash] The search results
72
- def similarity_search(
73
- query:,
74
- k: 4
75
- )
65
+ def similarity_search(query:, k: 4)
76
66
  near_text = "{ concepts: [\"#{query}\"] }"
77
67
 
78
68
  client.query.get(
@@ -87,10 +77,7 @@ module Vectorsearch
87
77
  # @param embedding [Array] The vector to search for
88
78
  # @param k [Integer|String] The number of results to return
89
79
  # @return [Hash] The search results
90
- def similarity_search_by_vector(
91
- embedding:,
92
- k: 4
93
- )
80
+ def similarity_search_by_vector(embedding:, k: 4)
94
81
  near_vector = "{ vector: #{embedding} }"
95
82
 
96
83
  client.query.get(
@@ -104,9 +91,7 @@ module Vectorsearch
104
91
  # Ask a question and return the answer
105
92
  # @param question [String] The question to ask
106
93
  # @return [Hash] The answer
107
- def ask(
108
- question:
109
- )
94
+ def ask(question:)
110
95
  # Weaviate currently supports the `ask:` parameter only for the OpenAI LLM (with `qna-openai` module enabled).
111
96
  # The Cohere support is on the way: https://github.com/weaviate/weaviate/pull/2600
112
97
  if llm == :openai
@@ -132,4 +117,4 @@ module Vectorsearch
132
117
  end
133
118
  end
134
119
  end
135
- end
120
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.4"
5
5
  end