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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +57 -10
- data/README.md +26 -5
- data/Rakefile +5 -0
- data/examples/create_and_manage_few_shot_prompt_templates.rb +3 -3
- data/examples/store_and_query_with_pinecone.rb +5 -2
- data/examples/store_and_query_with_qdrant.rb +4 -2
- data/examples/store_and_query_with_weaviate.rb +4 -1
- data/lib/agent/chain_of_thought_agent/chain_of_thought_agent.rb +13 -14
- data/lib/dependency_helper.rb +18 -0
- data/lib/langchain.rb +3 -0
- data/lib/llm/base.rb +3 -3
- data/lib/llm/cohere.rb +5 -5
- data/lib/llm/hugging_face.rb +32 -0
- data/lib/llm/openai.rb +4 -4
- data/lib/logging.rb +13 -0
- data/lib/prompt/base.rb +3 -4
- data/lib/prompt/loading.rb +3 -3
- data/lib/tool/base.rb +20 -8
- data/lib/tool/calculator.rb +11 -5
- data/lib/tool/serp_api.rb +25 -13
- data/lib/tool/wikipedia.rb +15 -8
- data/lib/vectorsearch/base.rb +5 -3
- data/lib/vectorsearch/milvus.rb +9 -24
- data/lib/vectorsearch/pinecone.rb +10 -20
- data/lib/vectorsearch/qdrant.rb +8 -15
- data/lib/vectorsearch/weaviate.rb +11 -26
- data/lib/version.rb +1 -1
- metadata +46 -29
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
|
-
|
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
|
-
|
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
|
data/lib/tool/calculator.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
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
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
21
|
-
hash_results =
|
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
|
-
|
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
|
-
|
48
|
+
.get_hash
|
37
49
|
end
|
38
50
|
end
|
39
51
|
end
|
data/lib/tool/wikipedia.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
data/lib/vectorsearch/base.rb
CHANGED
@@ -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"
|
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
|
-
{
|
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
|
data/lib/vectorsearch/milvus.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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: [
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
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: {
|
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
|
data/lib/vectorsearch/qdrant.rb
CHANGED
@@ -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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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({
|
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
|
-
|
15
|
-
|
16
|
-
|
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: {
|
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
|
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