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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0a2fe8026e861c9d97465bce7da08a0b077492d6f7cf8fb42c45dbfdfe6749f
4
- data.tar.gz: c04099c44a847bd9c05e8594859f92ca1f54d338c463ce59a375c2cb9731b1ad
3
+ metadata.gz: 2673339c5bbe874a8bdf1722a2556f26d9fe13394875af914b5203632714f2f0
4
+ data.tar.gz: 216ab880c2c6094b267cbf3efcaf19ce74bea7cc665442fbf2b23108a9cb087b
5
5
  SHA512:
6
- metadata.gz: dec375b2b7cae377cf31f3f8ed0a6ac9d79215c945e7c0da78ed1fbad3c502ecfcc5ce5318c55a9a634db88fea1f5fbbeed7a0f7dc6ab8096c909e0a3ff02154
7
- data.tar.gz: 558a0f6ddf90ad044f9e2cc7c6ca678958472748d2e430a3cbb4308290898b9b22a204a94a5b5943f06137f7da5238d038a65b8c79d96f8a3705499d95cfb597
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langchainrb (0.5.0)
4
+ langchainrb (0.5.1)
5
5
  colorize (~> 0.8.1)
6
6
  tiktoken_ruby (~> 0.0.5)
7
7
 
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
- agent = Langchain::Agent::ChainOfThoughtAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), tools: ['search', 'calculator'])
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
- agent = Langchain::Agent::SQLQueryAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), db_connection_string: "postgres://user:password@localhost:5432/db_name")
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
- # Retrieve the Tool::[ToolName] class and call `execute`` with action_input as the input
59
- tool = Langchain::Tool.const_get(Langchain::Tool::Base::TOOLS[action.strip])
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: "[#{tools.join(", ")}]",
90
+ tool_names: "[#{tool_list.join(", ")}]",
88
91
  tools: tools.map do |tool|
89
- "#{tool}: #{Langchain::Tool.const_get(Langchain::Tool::Base::TOOLS[tool]).const_get(:DESCRIPTION)}"
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 db_connection_string [String] Database connection info
11
- def initialize(llm:, db_connection_string:)
11
+ # @param db [Object] Database connection info
12
+ #
13
+ def initialize(llm:, db:)
12
14
  @llm = llm
13
- @db = Langchain::Tool::Database.new(db_connection_string)
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
 
@@ -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. Add your tool to the TOOLS hash below
10
- # "your_tool_name" => "Tool::YourToolName"
11
- # 3. Implement `self.execute(input:)` method in your tool class
12
- # 4. Add your tool to the README.md
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
- TOOLS = {
15
- "calculator" => "Langchain::Tool::Calculator",
16
- "search" => "Langchain::Tool::SerpApi",
17
- "wikipedia" => "Langchain::Tool::Wikipedia",
18
- "database" => "Langchain::Tool::Database"
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 strings (tools) are all supported or raises an error
41
- # @param tools [Array<String>] list of tools to be used
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
- unrecognized_tools = tools - Langchain::Tool::Base::TOOLS.keys
47
-
48
- if unrecognized_tools.any?
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
- hash_results = Langchain::Tool::SerpApi.execute_search(input: input)
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
- # @param db_connection_string [String] Database connection info, e.g. 'postgres://user:password@localhost:5432/db_name'
17
- def initialize(db_connection_string)
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, "db_connection_string parameter cannot be blank" if db_connection_string.empty?
30
+ raise StandardError, "connection_string parameter cannot be blank" if connection_string.empty?
23
31
 
24
- @db = Sequel.connect(db_connection_string)
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
- @db.dump_schema_migration(same_db: true, indexes: false) unless @db.adapter_scheme == :mock
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
- begin
39
- @db[input].to_a
40
- rescue Sequel::DatabaseError => e
41
- Langchain.logger.error("[#{self.class.name}]".light_red + ": #{e.message}")
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
- def initialize
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
- # Executes Google Search and returns hash_results JSON
50
+ #
51
+ # Executes Google Search and returns the result
52
+ #
35
53
  # @param input [String] search query
36
54
  # @return [String] Answer
37
- # 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
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.new(
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
@@ -7,7 +7,7 @@ module Langchain::Tool
7
7
  #
8
8
  # Gem requirements: gem "wikipedia-client", "~> 1.17.0"
9
9
  #
10
-
10
+ NAME = "wikipedia"
11
11
  description <<~DESC
12
12
  A wrapper around Wikipedia.
13
13
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
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.0
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-05 00:00:00.000000000 Z
11
+ date: 2023-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tiktoken_ruby