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 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