langchainrb 0.5.0 → 0.5.1
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 +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -2
- data/lib/langchain/agent/chain_of_thought_agent/chain_of_thought_agent.rb +10 -5
- data/lib/langchain/agent/sql_query_agent/sql_query_agent.rb +7 -3
- data/lib/langchain/tool/base.rb +28 -16
- data/lib/langchain/tool/calculator.rb +6 -2
- data/lib/langchain/tool/database.rb +25 -10
- data/lib/langchain/tool/ruby_code_interpreter.rb +1 -1
- data/lib/langchain/tool/serp_api.rb +33 -10
- data/lib/langchain/tool/wikipedia.rb +1 -1
- data/lib/langchain/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2673339c5bbe874a8bdf1722a2556f26d9fe13394875af914b5203632714f2f0
|
4
|
+
data.tar.gz: 216ab880c2c6094b267cbf3efcaf19ce74bea7cc665442fbf2b23108a9cb087b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 408cf6194d85a4af076adbfd8be4a360d094200d127672f218d8914fbcd67d1a8a803645219532f66d4e79214571d61b629570df071de871b013e2d9d6c0d3a5
|
7
|
+
data.tar.gz: 123016bd42d1d2539c13f7d68074ddc19dc8a5880ae0b02b103e20bf7f058adfe2659beb90263a852bbf38b4b169622a1b7ac8a245c3791ab9b9ae8f8fc4e3cb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.1] - 2023-06-06
|
4
|
+
- 🛠️ Tools
|
5
|
+
- Modified Tool usage. Agents now accept Tools instances instead of Tool strings.
|
6
|
+
|
3
7
|
## [0.5.0] - 2023-06-05
|
4
8
|
- [BREAKING] LLMs are now passed as objects to Vectorsearch classes instead of `llm: :name, llm_api_key:` previously
|
5
9
|
- 📋 Prompts
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -256,7 +256,15 @@ Agents are semi-autonomous bots that can respond to user questions and use avail
|
|
256
256
|
Add `gem "ruby-openai"`, `gem "eqn"`, and `gem "google_search_results"` to your Gemfile
|
257
257
|
|
258
258
|
```ruby
|
259
|
-
|
259
|
+
search_tool = Langchain::Tool::SerpApi.new(api_key: ENV["SERPAPI_API_KEY"])
|
260
|
+
calculator = Langchain::Tool::Calculator.new
|
261
|
+
|
262
|
+
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
263
|
+
|
264
|
+
agent = Langchain::Agent::ChainOfThoughtAgent.new(
|
265
|
+
llm: openai,
|
266
|
+
tools: [search_tool, calculator]
|
267
|
+
)
|
260
268
|
|
261
269
|
agent.tools
|
262
270
|
# => ["search", "calculator"]
|
@@ -271,7 +279,9 @@ agent.run(question: "How many full soccer fields would be needed to cover the di
|
|
271
279
|
Add `gem "sequel"` to your Gemfile
|
272
280
|
|
273
281
|
```ruby
|
274
|
-
|
282
|
+
database = Langchain::Tool::Database.new(connection_string: "postgres://user:password@localhost:5432/db_name")
|
283
|
+
|
284
|
+
agent = Langchain::Agent::SQLQueryAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), tools: [database])
|
275
285
|
|
276
286
|
```
|
277
287
|
```ruby
|
@@ -55,10 +55,11 @@ module Langchain::Agent
|
|
55
55
|
# Find the input to the action in the "Action Input: [action_input]" format
|
56
56
|
action_input = response.match(/Action Input: "?(.*)"?/)&.send(:[], -1)
|
57
57
|
|
58
|
-
#
|
59
|
-
tool =
|
60
|
-
Langchain.logger.info("[#{self.class.name}]".red + ": Invoking \"#{tool}\" Tool with \"#{action_input}\"")
|
58
|
+
# Find the Tool and call `execute`` with action_input as the input
|
59
|
+
tool = tools.find { |tool| tool.tool_name == action.strip }
|
60
|
+
Langchain.logger.info("[#{self.class.name}]".red + ": Invoking \"#{tool.class}\" Tool with \"#{action_input}\"")
|
61
61
|
|
62
|
+
# Call `execute` with action_input as the input
|
62
63
|
result = tool.execute(input: action_input)
|
63
64
|
|
64
65
|
# Append the Observation to the prompt
|
@@ -81,12 +82,16 @@ module Langchain::Agent
|
|
81
82
|
# @param tools [Array] Tools to use
|
82
83
|
# @return [String] Prompt
|
83
84
|
def create_prompt(question:, tools:)
|
85
|
+
tool_list = tools.map(&:tool_name)
|
86
|
+
|
84
87
|
prompt_template.format(
|
85
88
|
date: Date.today.strftime("%B %d, %Y"),
|
86
89
|
question: question,
|
87
|
-
tool_names: "[#{
|
90
|
+
tool_names: "[#{tool_list.join(", ")}]",
|
88
91
|
tools: tools.map do |tool|
|
89
|
-
|
92
|
+
tool_name = tool.tool_name
|
93
|
+
tool_description = tool.class.const_get(:DESCRIPTION)
|
94
|
+
"#{tool_name}: #{tool_description}"
|
90
95
|
end.join("\n")
|
91
96
|
)
|
92
97
|
end
|
@@ -4,20 +4,24 @@ module Langchain::Agent
|
|
4
4
|
class SQLQueryAgent < Base
|
5
5
|
attr_reader :llm, :db, :schema
|
6
6
|
|
7
|
+
#
|
7
8
|
# Initializes the Agent
|
8
9
|
#
|
9
10
|
# @param llm [Object] The LLM client to use
|
10
|
-
# @param
|
11
|
-
|
11
|
+
# @param db [Object] Database connection info
|
12
|
+
#
|
13
|
+
def initialize(llm:, db:)
|
12
14
|
@llm = llm
|
13
|
-
@db =
|
15
|
+
@db = db
|
14
16
|
@schema = @db.schema
|
15
17
|
end
|
16
18
|
|
19
|
+
#
|
17
20
|
# Ask a question and get an answer
|
18
21
|
#
|
19
22
|
# @param question [String] Question to ask the LLM/Database
|
20
23
|
# @return [String] Answer to the question
|
24
|
+
#
|
21
25
|
def ask(question:)
|
22
26
|
prompt = create_prompt_for_sql(question: question)
|
23
27
|
|
data/lib/langchain/tool/base.rb
CHANGED
@@ -6,47 +6,59 @@ module Langchain::Tool
|
|
6
6
|
|
7
7
|
# How to add additional Tools?
|
8
8
|
# 1. Create a new file in lib/tool/your_tool_name.rb
|
9
|
-
# 2.
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
9
|
+
# 2. Create a class in the file that inherits from Langchain::Tool::Base
|
10
|
+
# 3. Add `NAME=` and `DESCRIPTION=` constants in your Tool class
|
11
|
+
# 4. Implement `execute(input:)` method in your tool class
|
12
|
+
# 5. Add your tool to the README.md
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
#
|
15
|
+
# Returns the NAME constant of the tool
|
16
|
+
#
|
17
|
+
# @return [String] tool name
|
18
|
+
#
|
19
|
+
def tool_name
|
20
|
+
self.class.const_get(:NAME)
|
21
|
+
end
|
20
22
|
|
23
|
+
#
|
24
|
+
# Sets the DESCRIPTION constant of the tool
|
25
|
+
#
|
26
|
+
# @param value [String] tool description
|
27
|
+
#
|
21
28
|
def self.description(value)
|
22
29
|
const_set(:DESCRIPTION, value.tr("\n", " ").strip)
|
23
30
|
end
|
24
31
|
|
32
|
+
#
|
25
33
|
# Instantiates and executes the tool and returns the answer
|
34
|
+
#
|
26
35
|
# @param input [String] input to the tool
|
27
36
|
# @return [String] answer
|
37
|
+
#
|
28
38
|
def self.execute(input:)
|
29
39
|
new.execute(input: input)
|
30
40
|
end
|
31
41
|
|
42
|
+
#
|
32
43
|
# Executes the tool and returns the answer
|
44
|
+
#
|
33
45
|
# @param input [String] input to the tool
|
34
46
|
# @return [String] answer
|
47
|
+
#
|
35
48
|
def execute(input:)
|
36
49
|
raise NotImplementedError, "Your tool must implement the `#execute(input:)` method that returns a string"
|
37
50
|
end
|
38
51
|
|
39
52
|
#
|
40
|
-
# Validates the list of
|
41
|
-
# @param tools [Array<
|
53
|
+
# Validates the list of tools or raises an error
|
54
|
+
# @param tools [Array<Langchain::Tool>] list of tools to be used
|
42
55
|
#
|
43
56
|
# @raise [ArgumentError] If any of the tools are not supported
|
44
57
|
#
|
45
58
|
def self.validate_tools!(tools:)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
raise ArgumentError, "Unrecognized Tools: #{unrecognized_tools}"
|
59
|
+
# Check if the tool count is equal to unique tool count
|
60
|
+
if tools.count != tools.map(&:tool_name).uniq.count
|
61
|
+
raise ArgumentError, "Either tools are not unique or are conflicting with each other"
|
50
62
|
end
|
51
63
|
end
|
52
64
|
end
|
@@ -8,9 +8,10 @@ module Langchain::Tool
|
|
8
8
|
# Gem requirements:
|
9
9
|
# gem "eqn", "~> 1.6.5"
|
10
10
|
# gem "google_search_results", "~> 2.0.0"
|
11
|
-
# ENV requirements: ENV["SERPAPI_API_KEY"]
|
12
11
|
#
|
13
12
|
|
13
|
+
NAME = "calculator"
|
14
|
+
|
14
15
|
description <<~DESC
|
15
16
|
Useful for getting the result of a math expression.
|
16
17
|
|
@@ -33,7 +34,10 @@ module Langchain::Tool
|
|
33
34
|
rescue Eqn::ParseError, Eqn::NoVariableValueError
|
34
35
|
# Sometimes the input is not a pure math expression, e.g: "12F in Celsius"
|
35
36
|
# We can use the google answer box to evaluate this expression
|
36
|
-
|
37
|
+
# TODO: Figure out to find a better way to evaluate these language expressions.
|
38
|
+
hash_results = Langchain::Tool::SerpApi
|
39
|
+
.new(api_key: ENV["SERPAPI_API_KEY"])
|
40
|
+
.execute_search(input: input)
|
37
41
|
hash_results.dig(:answer_box, :to)
|
38
42
|
end
|
39
43
|
end
|
@@ -6,40 +6,55 @@ module Langchain::Tool
|
|
6
6
|
# Gem requirements: gem "sequel", "~> 5.68.0"
|
7
7
|
#
|
8
8
|
|
9
|
+
NAME = "database"
|
10
|
+
|
9
11
|
description <<~DESC
|
10
12
|
Useful for getting the result of a database query.
|
11
13
|
|
12
14
|
The input to this tool should be valid SQL.
|
13
15
|
DESC
|
14
16
|
|
17
|
+
attr_reader :db
|
18
|
+
|
19
|
+
#
|
15
20
|
# Establish a database connection
|
16
|
-
#
|
17
|
-
|
21
|
+
#
|
22
|
+
# @param connection_string [String] Database connection info, e.g. 'postgres://user:password@localhost:5432/db_name'
|
23
|
+
# @return [Database] Database object
|
24
|
+
#
|
25
|
+
def initialize(connection_string:)
|
18
26
|
depends_on "sequel"
|
19
27
|
require "sequel"
|
20
28
|
require "sequel/extensions/schema_dumper"
|
21
29
|
|
22
|
-
raise StandardError, "
|
30
|
+
raise StandardError, "connection_string parameter cannot be blank" if connection_string.empty?
|
23
31
|
|
24
|
-
@db = Sequel.connect(
|
32
|
+
@db = Sequel.connect(connection_string)
|
25
33
|
@db.extension :schema_dumper
|
26
34
|
end
|
27
35
|
|
36
|
+
#
|
37
|
+
# Returns the database schema
|
38
|
+
#
|
39
|
+
# @return [String] schema
|
40
|
+
#
|
28
41
|
def schema
|
29
42
|
Langchain.logger.info("[#{self.class.name}]".light_blue + ": Dumping schema")
|
30
|
-
|
43
|
+
db.dump_schema_migration(same_db: true, indexes: false) unless db.adapter_scheme == :mock
|
31
44
|
end
|
32
45
|
|
46
|
+
#
|
33
47
|
# Evaluates a sql expression
|
48
|
+
#
|
34
49
|
# @param input [String] sql expression
|
35
50
|
# @return [Array] results
|
51
|
+
#
|
36
52
|
def execute(input:)
|
37
53
|
Langchain.logger.info("[#{self.class.name}]".light_blue + ": Executing \"#{input}\"")
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
54
|
+
|
55
|
+
db[input].to_a
|
56
|
+
rescue Sequel::DatabaseError => e
|
57
|
+
Langchain.logger.error("[#{self.class.name}]".light_red + ": #{e.message}")
|
43
58
|
end
|
44
59
|
end
|
45
60
|
end
|
@@ -7,7 +7,7 @@ module Langchain::Tool
|
|
7
7
|
#
|
8
8
|
# Gem requirements: gem "safe_ruby", "~> 1.0.4"
|
9
9
|
#
|
10
|
-
|
10
|
+
NAME = "ruby_code_interpreter"
|
11
11
|
description <<~DESC
|
12
12
|
A Ruby code interpreter. Use this to execute ruby expressions. Input should be a valid ruby expression. If you want to see the output of the tool, make sure to return a value.
|
13
13
|
DESC
|
@@ -6,8 +6,13 @@ module Langchain::Tool
|
|
6
6
|
# Wrapper around SerpAPI
|
7
7
|
#
|
8
8
|
# Gem requirements: gem "google_search_results", "~> 2.0.0"
|
9
|
-
# ENV requirements: ENV["SERPAPI_API_KEY"] # https://serpapi.com/manage-api-key)
|
10
9
|
#
|
10
|
+
# Usage:
|
11
|
+
# search = Langchain::Tool::SerpApi.new(api_key: "YOUR_API_KEY")
|
12
|
+
# search.execute(input: "What is the capital of France?")
|
13
|
+
#
|
14
|
+
|
15
|
+
NAME = "search"
|
11
16
|
|
12
17
|
description <<~DESC
|
13
18
|
A wrapper around Google Search.
|
@@ -18,39 +23,57 @@ module Langchain::Tool
|
|
18
23
|
Input should be a search query.
|
19
24
|
DESC
|
20
25
|
|
21
|
-
|
26
|
+
attr_reader :api_key
|
27
|
+
|
28
|
+
#
|
29
|
+
# Initializes the SerpAPI tool
|
30
|
+
#
|
31
|
+
# @param api_key [String] SerpAPI API key
|
32
|
+
# @return [Langchain::Tool::SerpApi] SerpAPI tool
|
33
|
+
#
|
34
|
+
def initialize(api_key:)
|
22
35
|
depends_on "google_search_results"
|
23
36
|
require "google_search_results"
|
37
|
+
@api_key = api_key
|
24
38
|
end
|
25
39
|
|
40
|
+
#
|
26
41
|
# Executes Google Search and returns hash_results JSON
|
42
|
+
#
|
27
43
|
# @param input [String] search query
|
28
44
|
# @return [Hash] hash_results JSON
|
29
|
-
|
45
|
+
#
|
30
46
|
def self.execute_search(input:)
|
31
47
|
new.execute_search(input: input)
|
32
48
|
end
|
33
49
|
|
34
|
-
#
|
50
|
+
#
|
51
|
+
# Executes Google Search and returns the result
|
52
|
+
#
|
35
53
|
# @param input [String] search query
|
36
54
|
# @return [String] Answer
|
37
|
-
#
|
38
|
-
# We may need to do the same thing here.
|
55
|
+
#
|
39
56
|
def execute(input:)
|
40
57
|
Langchain.logger.info("[#{self.class.name}]".light_blue + ": Executing \"#{input}\"")
|
41
58
|
|
42
59
|
hash_results = execute_search(input: input)
|
43
60
|
|
61
|
+
# 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
|
62
|
+
# We may need to do the same thing here.
|
44
63
|
hash_results.dig(:answer_box, :answer) ||
|
45
64
|
hash_results.dig(:answer_box, :snippet) ||
|
46
65
|
hash_results.dig(:organic_results, 0, :snippet)
|
47
66
|
end
|
48
67
|
|
68
|
+
#
|
69
|
+
# Executes Google Search and returns hash_results JSON
|
70
|
+
#
|
71
|
+
# @param input [String] search query
|
72
|
+
# @return [Hash] hash_results JSON
|
73
|
+
#
|
49
74
|
def execute_search(input:)
|
50
|
-
GoogleSearch
|
51
|
-
q: input,
|
52
|
-
serp_api_key: ENV["SERPAPI_API_KEY"]
|
53
|
-
)
|
75
|
+
GoogleSearch
|
76
|
+
.new(q: input, serp_api_key: api_key)
|
54
77
|
.get_hash
|
55
78
|
end
|
56
79
|
end
|
data/lib/langchain/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: langchainrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrei Bondarev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tiktoken_ruby
|