langchainrb 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|