langchainrb 0.9.1 → 0.9.3

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +1 -1
  4. data/lib/langchain/assistants/assistant.rb +5 -3
  5. data/lib/langchain/output_parsers/output_fixing_parser.rb +6 -5
  6. data/lib/langchain/processors/eml.rb +65 -0
  7. data/lib/langchain/tool/base.rb +17 -32
  8. data/lib/langchain/tool/calculator/calculator.json +19 -0
  9. data/lib/langchain/tool/{calculator.rb → calculator/calculator.rb} +8 -5
  10. data/lib/langchain/tool/database/database.json +46 -0
  11. data/lib/langchain/tool/database/database.rb +105 -0
  12. data/lib/langchain/tool/google_search/google_search.json +19 -0
  13. data/lib/langchain/tool/{google_search.rb → google_search/google_search.rb} +5 -6
  14. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +19 -0
  15. data/lib/langchain/tool/{ruby_code_interpreter.rb → ruby_code_interpreter/ruby_code_interpreter.rb} +9 -1
  16. data/lib/langchain/tool/weather/weather.json +19 -0
  17. data/lib/langchain/tool/{weather.rb → weather/weather.rb} +3 -4
  18. data/lib/langchain/tool/wikipedia/wikipedia.json +19 -0
  19. data/lib/langchain/tool/{wikipedia.rb → wikipedia/wikipedia.rb} +10 -1
  20. data/lib/langchain/vectorsearch/chroma.rb +3 -1
  21. data/lib/langchain/vectorsearch/elasticsearch.rb +3 -1
  22. data/lib/langchain/vectorsearch/epsilla.rb +3 -1
  23. data/lib/langchain/vectorsearch/milvus.rb +3 -1
  24. data/lib/langchain/vectorsearch/pgvector.rb +3 -1
  25. data/lib/langchain/vectorsearch/pinecone.rb +3 -1
  26. data/lib/langchain/vectorsearch/qdrant.rb +3 -1
  27. data/lib/langchain/vectorsearch/weaviate.rb +3 -1
  28. data/lib/langchain/version.rb +1 -1
  29. data/lib/langchain.rb +7 -0
  30. metadata +37 -16
  31. data/lib/langchain/tool/database.rb +0 -90
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c9d0655d58ddff57b9c9163065908dd17d91c6bffc5b146bf7fc01b4c9fb96d
4
- data.tar.gz: ee82c644b7e38503fa0587ade2af0447819863303e9fa3755dce1676d68ad5f7
3
+ metadata.gz: b31725a5fdb7c09d25e97b3b8ecbd78eba1eeece6ef2db82f009aa121e1f4956
4
+ data.tar.gz: 1f9116daf6780682d8c32021212bba702a053af9b4293e488ca605cc298c8e12
5
5
  SHA512:
6
- metadata.gz: 05faddd31c819e6d351ed99e05353e462341ab1744769a1b3a9932c37de4c68907b54f79bcd65f6b652d954d5301e80055c5ee8c57b66a3917256918c51cc61f
7
- data.tar.gz: c2fed05da349fdc9ebd9990ea5c2d5c70a68241c491c903c631eb0584bce01da17bab7b04c59fa9fded8282547798219b2a0b951c1c5a8d1062d08f2a930062c
6
+ metadata.gz: 6f50889ce152ac93567951c2a854c5589f5306c8a54c852febc9d5884b3d924904beabfd076e87fefb95354dda99fbb77d179045274ff25bf9515ecee3b2d6bb
7
+ data.tar.gz: aee9ed10fe48eeef9dc5ba1433145823d20c55042bea7bc359ce5cad5dd4783eb68f1e84bdad79349efea17d77455b9dd6ba918a6a6d0d991e7c350887feac6d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.9.3]
4
+ - Add EML processor
5
+ - Tools can support multiple-methods
6
+ - Bump gems and bug fixes
7
+
8
+ ## [0.9.2]
9
+ - Fix vectorsearch#ask methods
10
+ - Bump cohere-ruby gem
11
+
3
12
  ## [0.9.1]
4
13
  - Add support for new OpenAI models
5
14
  - Add Ollama#chat method
data/README.md CHANGED
@@ -373,7 +373,7 @@ my_docx = Langchain.root.join("path/to/my.docx")
373
373
 
374
374
  client.add_data(paths: [my_pdf, my_text, my_docx])
375
375
  ```
376
- Supported file formats: docx, html, pdf, text, json, jsonl, csv, xlsx.
376
+ Supported file formats: docx, html, pdf, text, json, jsonl, csv, xlsx, eml.
377
377
 
378
378
  Retrieve similar documents based on the query string passed in:
379
379
  ```ruby
@@ -127,7 +127,7 @@ module Langchain
127
127
  params = {messages: thread.openai_messages}
128
128
 
129
129
  if tools.any?
130
- params[:tools] = tools.map(&:to_openai_tool)
130
+ params[:tools] = tools.map(&:to_openai_tools).flatten
131
131
  # TODO: Not sure that tool_choice should always be "auto"; Maybe we can let the user toggle it.
132
132
  params[:tool_choice] = "auto"
133
133
  end
@@ -142,14 +142,16 @@ module Langchain
142
142
  # Iterate over each function invocation and submit tool output
143
143
  tool_calls.each do |tool_call|
144
144
  tool_call_id = tool_call.dig("id")
145
- tool_name = tool_call.dig("function", "name")
145
+
146
+ function_name = tool_call.dig("function", "name")
147
+ tool_name, method_name = function_name.split("-")
146
148
  tool_arguments = JSON.parse(tool_call.dig("function", "arguments"), symbolize_names: true)
147
149
 
148
150
  tool_instance = tools.find do |t|
149
151
  t.name == tool_name
150
152
  end or raise ArgumentError, "Tool not found in assistant.tools"
151
153
 
152
- output = tool_instance.execute(**tool_arguments)
154
+ output = tool_instance.send(method_name, **tool_arguments)
153
155
 
154
156
  submit_tool_output(tool_call_id: tool_call_id, output: output)
155
157
  end
@@ -53,11 +53,12 @@ module Langchain::OutputParsers
53
53
  parser.parse(completion)
54
54
  rescue OutputParserException => e
55
55
  new_completion = llm.chat(
56
- prompt: prompt.format(
57
- instructions: parser.get_format_instructions,
58
- completion: completion,
59
- error: e
60
- )
56
+ messages: [{role: "user",
57
+ content: prompt.format(
58
+ instructions: parser.get_format_instructions,
59
+ completion: completion,
60
+ error: e
61
+ )}]
61
62
  ).completion
62
63
  parser.parse(new_completion)
63
64
  end
@@ -0,0 +1,65 @@
1
+ require "mail"
2
+ require "uri"
3
+
4
+ module Langchain
5
+ module Processors
6
+ class Eml < Base
7
+ EXTENSIONS = [".eml"]
8
+ CONTENT_TYPES = ["message/rfc822"]
9
+
10
+ def initialize(*)
11
+ depends_on "mail"
12
+ end
13
+
14
+ # Parse the document and return the cleaned text
15
+ # @param [File] data
16
+ # @return [String]
17
+ def parse(data)
18
+ mail = Mail.read(data.path)
19
+ text_content = extract_text_content(mail)
20
+ clean_content(text_content)
21
+ end
22
+
23
+ private
24
+
25
+ # Extract text content from the email, preferring plaintext over HTML
26
+ def extract_text_content(mail)
27
+ text_content = ""
28
+ text_content += "From: #{mail.from}\n" \
29
+ "To: #{mail.to}\n" \
30
+ "Cc: #{mail.cc}\n" \
31
+ "Bcc: #{mail.bcc}\n" \
32
+ "Subject: #{mail.subject}\n" \
33
+ "Date: #{mail.date}\n\n"
34
+ if mail.multipart?
35
+ mail.parts.each do |part|
36
+ if part.content_type.start_with?("text/plain")
37
+ text_content += part.body.decoded.force_encoding("UTF-8").strip + "\n"
38
+ elsif part.content_type.start_with?("multipart/alternative", "multipart/mixed")
39
+ text_content += extract_text_content(part) + "\n" # Recursively extract from multipart
40
+ elsif part.content_type.start_with?("message/rfc822")
41
+ # Handle embedded .eml parts as separate emails
42
+ embedded_mail = Mail.read_from_string(part.body.decoded)
43
+ text_content += "--- Begin Embedded Email ---\n"
44
+ text_content += extract_text_content(embedded_mail) + "\n"
45
+ text_content += "--- End Embedded Email ---\n"
46
+ end
47
+ end
48
+ elsif mail.content_type.start_with?("text/plain")
49
+ text_content = mail.body.decoded.force_encoding("UTF-8").strip
50
+ end
51
+ text_content
52
+ end
53
+
54
+ # Clean and format the extracted content
55
+ def clean_content(content)
56
+ content
57
+ .gsub(/\[cid:[^\]]+\]/, "") # Remove embedded image references
58
+ .gsub(URI::DEFAULT_PARSER.make_regexp(%w[http https])) { |match| "<#{match}>" } # Format URLs
59
+ .gsub(/\r\n?/, "\n") # Normalize line endings to Unix style
60
+ .gsub(/[\u200B-\u200D\uFEFF]/, "") # Remove zero width spaces and similar characters
61
+ .gsub(/<\/?[^>]+>/, "") # Remove any HTML tags that might have sneaked in
62
+ end
63
+ end
64
+ end
65
+ end
@@ -48,11 +48,9 @@ module Langchain::Tool
48
48
  class Base
49
49
  include Langchain::DependencyHelper
50
50
 
51
- #
52
51
  # Returns the NAME constant of the tool
53
52
  #
54
53
  # @return [String] tool name
55
- #
56
54
  def name
57
55
  self.class.const_get(:NAME)
58
56
  end
@@ -63,59 +61,37 @@ module Langchain::Tool
63
61
  }
64
62
  end
65
63
 
66
- #
67
64
  # Returns the DESCRIPTION constant of the tool
68
65
  #
69
66
  # @return [String] tool description
70
- #
71
67
  def description
72
68
  self.class.const_get(:DESCRIPTION)
73
69
  end
74
70
 
75
- #
76
71
  # Sets the DESCRIPTION constant of the tool
77
72
  #
78
73
  # @param value [String] tool description
79
- #
80
74
  def self.description(value)
81
75
  const_set(:DESCRIPTION, value.tr("\n", " ").strip)
82
76
  end
83
77
 
84
- #
85
78
  # Instantiates and executes the tool and returns the answer
86
79
  #
87
80
  # @param input [String] input to the tool
88
81
  # @return [String] answer
89
- #
90
82
  def self.execute(input:)
83
+ warn "DEPRECATED: `#{self}.execute` is deprecated, and will be removed in the next major version."
84
+
91
85
  new.execute(input: input)
92
86
  end
93
87
 
94
- # Returns the tool as an OpenAI tool
88
+ # Returns the tool as a list of OpenAI formatted functions
95
89
  #
96
90
  # @return [Hash] tool as an OpenAI tool
97
- def to_openai_tool
98
- # TODO: This is hardcoded to def execute(input:) found in each tool, needs to be dynamic.
99
- {
100
- type: "function",
101
- function: {
102
- name: name,
103
- description: description,
104
- parameters: {
105
- type: "object",
106
- properties: {
107
- input: {
108
- type: "string",
109
- description: "Input to the tool"
110
- }
111
- },
112
- required: ["input"]
113
- }
114
- }
115
- }
91
+ def to_openai_tools
92
+ method_annotations
116
93
  end
117
94
 
118
- #
119
95
  # Executes the tool and returns the answer
120
96
  #
121
97
  # @param input [String] input to the tool
@@ -125,12 +101,21 @@ module Langchain::Tool
125
101
  raise NotImplementedError, "Your tool must implement the `#execute(input:)` method that returns a string"
126
102
  end
127
103
 
128
- #
104
+ # Return tool's method annotations as JSON
105
+ #
106
+ # @return [Hash] Tool's method annotations
107
+ def method_annotations
108
+ JSON.parse(
109
+ File.read(
110
+ self.class.const_get(:ANNOTATIONS_PATH)
111
+ )
112
+ )
113
+ end
114
+
129
115
  # Validates the list of tools or raises an error
130
- # @param tools [Array<Langchain::Tool>] list of tools to be used
131
116
  #
117
+ # @param tools [Array<Langchain::Tool>] list of tools to be used
132
118
  # @raise [ArgumentError] If any of the tools are not supported
133
- #
134
119
  def self.validate_tools!(tools:)
135
120
  # Check if the tool count is equal to unique tool count
136
121
  if tools.count != tools.map(&:name).uniq.count
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "calculator-execute",
6
+ "description": "Evaluates a pure math expression or if equation contains non-math characters (e.g.: \"12F in Celsius\") then it uses the google search calculator to evaluate the expression",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "input": {
11
+ "type": "string",
12
+ "description": "math expression"
13
+ }
14
+ },
15
+ "required": ["input"]
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -6,11 +6,14 @@ module Langchain::Tool
6
6
  # A calculator tool that falls back to the Google calculator widget
7
7
  #
8
8
  # Gem requirements:
9
- # gem "eqn", "~> 1.6.5"
10
- # gem "google_search_results", "~> 2.0.0"
9
+ # gem "eqn", "~> 1.6.5"
10
+ # gem "google_search_results", "~> 2.0.0"
11
+ #
12
+ # Usage:
13
+ # calculator = Langchain::Tool::Calculator.new
11
14
  #
12
-
13
15
  NAME = "calculator"
16
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
14
17
 
15
18
  description <<~DESC
16
19
  Useful for getting the result of a math expression.
@@ -27,8 +30,8 @@ module Langchain::Tool
27
30
  depends_on "eqn"
28
31
  end
29
32
 
30
- # Evaluates a pure math expression or if equation contains non-math characters (e.g.: "12F in Celsius") then
31
- # it uses the google search calculator to evaluate the expression
33
+ # Evaluates a pure math expression or if equation contains non-math characters (e.g.: "12F in Celsius") then it uses the google search calculator to evaluate the expression
34
+ #
32
35
  # @param input [String] math expression
33
36
  # @return [String] Answer
34
37
  def execute(input:)
@@ -0,0 +1,46 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "database-describe_tables",
6
+ "description": "Database Tool: Returns the schema for a list of tables",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "tables": {
11
+ "type": "string",
12
+ "description": "The tables to describe."
13
+ }
14
+ },
15
+ "required": ["tables"]
16
+ }
17
+ }
18
+ }, {
19
+ "type": "function",
20
+ "function": {
21
+ "name": "database-list_tables",
22
+ "description": "Database Tool: Returns a list of tables in the database",
23
+ "parameters": {
24
+ "type": "object",
25
+ "properties": {},
26
+ "required": []
27
+ }
28
+ }
29
+ }, {
30
+ "type": "function",
31
+ "function": {
32
+ "name": "database-execute",
33
+ "description": "Database Tool: Executes a SQL query and returns the results",
34
+ "parameters": {
35
+ "type": "object",
36
+ "properties": {
37
+ "input": {
38
+ "type": "string",
39
+ "description": "SQL query to be executed"
40
+ }
41
+ },
42
+ "required": ["input"]
43
+ }
44
+ }
45
+ }
46
+ ]
@@ -0,0 +1,105 @@
1
+ module Langchain::Tool
2
+ class Database < Base
3
+ #
4
+ # Connects to a database, executes SQL queries, and outputs DB schema for Agents to use
5
+ #
6
+ # Gem requirements:
7
+ # gem "sequel", "~> 5.68.0"
8
+ #
9
+ # Usage:
10
+ # database = Langchain::Tool::Database.new(connection_string: "postgres://user:password@localhost:5432/db_name")
11
+ #
12
+ NAME = "database"
13
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
14
+
15
+ description <<~DESC
16
+ Useful for getting the result of a database query.
17
+
18
+ The input to this tool should be valid SQL.
19
+ DESC
20
+
21
+ attr_reader :db, :requested_tables, :excluded_tables
22
+
23
+ # Establish a database connection
24
+ #
25
+ # @param connection_string [String] Database connection info, e.g. 'postgres://user:password@localhost:5432/db_name'
26
+ # @param tables [Array<Symbol>] The tables to use. Will use all if empty.
27
+ # @param except_tables [Array<Symbol>] The tables to exclude. Will exclude none if empty.
28
+ # @return [Database] Database object
29
+ def initialize(connection_string:, tables: [], exclude_tables: [])
30
+ depends_on "sequel"
31
+
32
+ raise StandardError, "connection_string parameter cannot be blank" if connection_string.empty?
33
+
34
+ @db = Sequel.connect(connection_string)
35
+ @requested_tables = tables
36
+ @excluded_tables = exclude_tables
37
+ end
38
+
39
+ # Database Tool: Returns a list of tables in the database
40
+ def list_tables
41
+ db.tables
42
+ end
43
+
44
+ # Database Tool: Returns the schema for a list of tables
45
+ #
46
+ # @param tables [String] The tables to describe.
47
+ # @return [String] Database schema for the tables
48
+ def describe_tables(tables:)
49
+ schema = ""
50
+ tables.split(",").each do |table|
51
+ describe_table(table, schema)
52
+ end
53
+ schema
54
+ end
55
+
56
+ # Database Tool: Returns the database schema
57
+ #
58
+ # @return [String] Database schema
59
+ def dump_schema
60
+ Langchain.logger.info("Dumping schema tables and keys", for: self.class)
61
+ schema = ""
62
+ db.tables.each do |table|
63
+ describe_table(table, schema)
64
+ end
65
+ schema
66
+ end
67
+
68
+ def describe_table(table, schema)
69
+ primary_key_columns = []
70
+ primary_key_column_count = db.schema(table).count { |column| column[1][:primary_key] == true }
71
+
72
+ schema << "CREATE TABLE #{table}(\n"
73
+ db.schema(table).each do |column|
74
+ schema << "#{column[0]} #{column[1][:type]}"
75
+ if column[1][:primary_key] == true
76
+ schema << " PRIMARY KEY" if primary_key_column_count == 1
77
+ else
78
+ primary_key_columns << column[0]
79
+ end
80
+ schema << ",\n" unless column == db.schema(table).last && primary_key_column_count == 1
81
+ end
82
+ if primary_key_column_count > 1
83
+ schema << "PRIMARY KEY (#{primary_key_columns.join(",")})"
84
+ end
85
+ db.foreign_key_list(table).each do |fk|
86
+ schema << ",\n" if fk == db.foreign_key_list(table).first
87
+ schema << "FOREIGN KEY (#{fk[:columns][0]}) REFERENCES #{fk[:table]}(#{fk[:key][0]})"
88
+ schema << ",\n" unless fk == db.foreign_key_list(table).last
89
+ end
90
+ schema << ");\n"
91
+ end
92
+
93
+ # Database Tool: Executes a SQL query and returns the results
94
+ #
95
+ # @param input [String] SQL query to be executed
96
+ # @return [Array] Results from the SQL query
97
+ def execute(input:)
98
+ Langchain.logger.info("Executing \"#{input}\"", for: self.class)
99
+
100
+ db[input].to_a
101
+ rescue Sequel::DatabaseError => e
102
+ Langchain.logger.error(e.message, for: self.class)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "google_search-execute",
6
+ "description": "Executes Google Search and returns the result",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "input": {
11
+ "type": "string",
12
+ "description": "search query"
13
+ }
14
+ },
15
+ "required": ["input"]
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -5,14 +5,15 @@ module Langchain::Tool
5
5
  #
6
6
  # Wrapper around SerpApi's Google Search API
7
7
  #
8
- # Gem requirements: gem "google_search_results", "~> 2.0.0"
8
+ # Gem requirements:
9
+ # gem "google_search_results", "~> 2.0.0"
9
10
  #
10
11
  # Usage:
11
- # search = Langchain::Tool::GoogleSearch.new(api_key: "YOUR_API_KEY")
12
- # search.execute(input: "What is the capital of France?")
12
+ # search = Langchain::Tool::GoogleSearch.new(api_key: "YOUR_API_KEY")
13
+ # search.execute(input: "What is the capital of France?")
13
14
  #
14
-
15
15
  NAME = "google_search"
16
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
16
17
 
17
18
  description <<~DESC
18
19
  A wrapper around SerpApi's Google Search API.
@@ -44,12 +45,10 @@ module Langchain::Tool
44
45
  new.execute_search(input: input)
45
46
  end
46
47
 
47
- #
48
48
  # Executes Google Search and returns the result
49
49
  #
50
50
  # @param input [String] search query
51
51
  # @return [String] Answer
52
- #
53
52
  def execute(input:)
54
53
  Langchain.logger.info("Executing \"#{input}\"", for: self.class)
55
54
 
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "ruby_code_interpreter-execute",
6
+ "description": "Executes Ruby code in a sandboxes environment.",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "input": {
11
+ "type": "string",
12
+ "description": "ruby code expression"
13
+ }
14
+ },
15
+ "required": ["input"]
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -5,9 +5,15 @@ module Langchain::Tool
5
5
  #
6
6
  # A tool that execute Ruby code in a sandboxed environment.
7
7
  #
8
- # Gem requirements: gem "safe_ruby", "~> 1.0.4"
8
+ # Gem requirements:
9
+ # gem "safe_ruby", "~> 1.0.4"
10
+ #
11
+ # Usage:
12
+ # interpreter = Langchain::Tool::RubyCodeInterpreter.new
9
13
  #
10
14
  NAME = "ruby_code_interpreter"
15
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
16
+
11
17
  description <<~DESC
12
18
  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
19
  DESC
@@ -18,6 +24,8 @@ module Langchain::Tool
18
24
  @timeout = timeout
19
25
  end
20
26
 
27
+ # Executes Ruby code in a sandboxes environment.
28
+ #
21
29
  # @param input [String] ruby code expression
22
30
  # @return [String] Answer
23
31
  def execute(input:)
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "weather-execute",
6
+ "description": "Returns current weather for a city",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "input": {
11
+ "type": "string",
12
+ "description": "comma separated city and unit (optional: imperial, metric, or standard)"
13
+ }
14
+ },
15
+ "required": ["input"]
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -13,11 +13,11 @@ module Langchain::Tool
13
13
  # api_key: https://home.openweathermap.org/api_keys
14
14
  #
15
15
  # Usage:
16
- # weather = Langchain::Tool::Weather.new(api_key: "YOUR_API_KEY")
16
+ # weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
17
17
  # weather.execute(input: "Boston, MA; imperial")
18
18
  #
19
-
20
19
  NAME = "weather"
20
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
21
21
 
22
22
  description <<~DESC
23
23
  Useful for getting current weather data
@@ -32,12 +32,10 @@ module Langchain::Tool
32
32
 
33
33
  attr_reader :client, :units
34
34
 
35
- #
36
35
  # Initializes the Weather tool
37
36
  #
38
37
  # @param api_key [String] Open Weather API key
39
38
  # @return [Langchain::Tool::Weather] Weather tool
40
- #
41
39
  def initialize(api_key:, units: "metric")
42
40
  depends_on "open-weather-ruby-client"
43
41
  require "open-weather-ruby-client"
@@ -51,6 +49,7 @@ module Langchain::Tool
51
49
  end
52
50
 
53
51
  # Returns current weather for a city
52
+ #
54
53
  # @param input [String] comma separated city and unit (optional: imperial, metric, or standard)
55
54
  # @return [String] Answer
56
55
  def execute(input:)
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "wikipedia-execute",
6
+ "description": "Executes Wikipedia API search and returns the answer",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "input": {
11
+ "type": "string",
12
+ "description": "search query"
13
+ }
14
+ },
15
+ "required": ["input"]
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -5,9 +5,16 @@ module Langchain::Tool
5
5
  #
6
6
  # Tool that adds the capability to search using the Wikipedia API
7
7
  #
8
- # Gem requirements: gem "wikipedia-client", "~> 1.17.0"
8
+ # Gem requirements:
9
+ # gem "wikipedia-client", "~> 1.17.0"
10
+ #
11
+ # Usage:
12
+ # weather = Langchain::Tool::Wikipedia.new
13
+ # weather.execute(input: "The Roman Empire")
9
14
  #
10
15
  NAME = "wikipedia"
16
+ ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
17
+
11
18
  description <<~DESC
12
19
  A wrapper around Wikipedia.
13
20
 
@@ -17,11 +24,13 @@ module Langchain::Tool
17
24
  Input should be a search query.
18
25
  DESC
19
26
 
27
+ # Initializes the Wikipedia tool
20
28
  def initialize
21
29
  depends_on "wikipedia-client", req: "wikipedia"
22
30
  end
23
31
 
24
32
  # Executes Wikipedia API search and returns the answer
33
+ #
25
34
  # @param input [String] search query
26
35
  # @return [String] Answer
27
36
  def execute(input:)
@@ -126,7 +126,9 @@ module Langchain::Vectorsearch
126
126
 
127
127
  prompt = generate_rag_prompt(question: question, context: context)
128
128
 
129
- response = llm.chat(prompt: prompt, &block)
129
+ messages = [{role: "user", content: prompt}]
130
+ response = llm.chat(messages: messages, &block)
131
+
130
132
  response.context = context
131
133
  response
132
134
  end
@@ -141,7 +141,9 @@ module Langchain::Vectorsearch
141
141
 
142
142
  prompt = generate_rag_prompt(question: question, context: context)
143
143
 
144
- response = llm.chat(prompt: prompt, &block)
144
+ messages = [{role: "user", content: prompt}]
145
+ response = llm.chat(messages: messages, &block)
146
+
145
147
  response.context = context
146
148
  response
147
149
  end
@@ -139,7 +139,9 @@ module Langchain::Vectorsearch
139
139
 
140
140
  prompt = generate_rag_prompt(question: question, context: context)
141
141
 
142
- response = llm.chat(prompt: prompt, &block)
142
+ messages = [{role: "user", content: prompt}]
143
+ response = llm.chat(messages: messages, &block)
144
+
143
145
  response.context = context
144
146
  response
145
147
  end
@@ -151,7 +151,9 @@ module Langchain::Vectorsearch
151
151
 
152
152
  prompt = generate_rag_prompt(question: question, context: context)
153
153
 
154
- response = llm.chat(prompt: prompt, &block)
154
+ messages = [{role: "user", content: prompt}]
155
+ response = llm.chat(messages: messages, &block)
156
+
155
157
  response.context = context
156
158
  response
157
159
  end
@@ -148,7 +148,9 @@ module Langchain::Vectorsearch
148
148
 
149
149
  prompt = generate_rag_prompt(question: question, context: context)
150
150
 
151
- response = llm.chat(prompt: prompt, &block)
151
+ messages = [{role: "user", content: prompt}]
152
+ response = llm.chat(messages: messages, &block)
153
+
152
154
  response.context = context
153
155
  response
154
156
  end
@@ -181,7 +181,9 @@ module Langchain::Vectorsearch
181
181
 
182
182
  prompt = generate_rag_prompt(question: question, context: context)
183
183
 
184
- response = llm.chat(prompt: prompt, &block)
184
+ messages = [{role: "user", content: prompt}]
185
+ response = llm.chat(messages: messages, &block)
186
+
185
187
  response.context = context
186
188
  response
187
189
  end
@@ -137,7 +137,9 @@ module Langchain::Vectorsearch
137
137
 
138
138
  prompt = generate_rag_prompt(question: question, context: context)
139
139
 
140
- response = llm.chat(prompt: prompt, &block)
140
+ messages = [{role: "user", content: prompt}]
141
+ response = llm.chat(messages: messages, &block)
142
+
141
143
  response.context = context
142
144
  response
143
145
  end
@@ -137,7 +137,9 @@ module Langchain::Vectorsearch
137
137
 
138
138
  prompt = generate_rag_prompt(question: question, context: context)
139
139
 
140
- response = llm.chat(prompt: prompt, &block)
140
+ messages = [{role: "user", content: prompt}]
141
+ response = llm.chat(messages: messages, &block)
142
+
141
143
  response.context = context
142
144
  response
143
145
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.9.1"
4
+ VERSION = "0.9.3"
5
5
  end
data/lib/langchain.rb CHANGED
@@ -25,6 +25,13 @@ loader.inflector.inflect(
25
25
  )
26
26
  loader.collapse("#{__dir__}/langchain/llm/response")
27
27
  loader.collapse("#{__dir__}/langchain/assistants")
28
+
29
+ loader.collapse("#{__dir__}/langchain/tool/calculator")
30
+ loader.collapse("#{__dir__}/langchain/tool/database")
31
+ loader.collapse("#{__dir__}/langchain/tool/google_search")
32
+ loader.collapse("#{__dir__}/langchain/tool/ruby_code_interpreter")
33
+ loader.collapse("#{__dir__}/langchain/tool/weather")
34
+ loader.collapse("#{__dir__}/langchain/tool/wikipedia")
28
35
  loader.setup
29
36
 
30
37
  # Langchain.rb a is library for building LLM-backed Ruby applications. It is an abstraction layer that sits on top of the emerging AI-related tools that makes it easy for developers to consume and string those services together.
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.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Bondarev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-06 00:00:00.000000000 Z
11
+ date: 2024-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: baran
@@ -30,42 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.1
33
+ version: 1.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.8.1
40
+ version: 1.1.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: tiktoken_ruby
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.0.5
47
+ version: 0.0.7
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.0.5
54
+ version: 0.0.7
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json-schema
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 4.0.0
61
+ version: '4'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 4.0.0
68
+ version: '4'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: zeitwerk
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -240,14 +240,14 @@ dependencies:
240
240
  requirements:
241
241
  - - "~>"
242
242
  - !ruby/object:Gem::Version
243
- version: 0.9.7
243
+ version: 0.9.8
244
244
  type: :development
245
245
  prerelease: false
246
246
  version_requirements: !ruby/object:Gem::Requirement
247
247
  requirements:
248
248
  - - "~>"
249
249
  - !ruby/object:Gem::Version
250
- version: 0.9.7
250
+ version: 0.9.8
251
251
  - !ruby/object:Gem::Dependency
252
252
  name: docx
253
253
  requirement: !ruby/object:Gem::Requirement
@@ -416,6 +416,20 @@ dependencies:
416
416
  - - "~>"
417
417
  - !ruby/object:Gem::Version
418
418
  version: '1.13'
419
+ - !ruby/object:Gem::Dependency
420
+ name: mail
421
+ requirement: !ruby/object:Gem::Requirement
422
+ requirements:
423
+ - - "~>"
424
+ - !ruby/object:Gem::Version
425
+ version: '2.8'
426
+ type: :development
427
+ prerelease: false
428
+ version_requirements: !ruby/object:Gem::Requirement
429
+ requirements:
430
+ - - "~>"
431
+ - !ruby/object:Gem::Version
432
+ version: '2.8'
419
433
  - !ruby/object:Gem::Dependency
420
434
  name: open-weather-ruby-client
421
435
  requirement: !ruby/object:Gem::Requirement
@@ -692,6 +706,7 @@ files:
692
706
  - lib/langchain/processors/base.rb
693
707
  - lib/langchain/processors/csv.rb
694
708
  - lib/langchain/processors/docx.rb
709
+ - lib/langchain/processors/eml.rb
695
710
  - lib/langchain/processors/html.rb
696
711
  - lib/langchain/processors/json.rb
697
712
  - lib/langchain/processors/jsonl.rb
@@ -705,12 +720,18 @@ files:
705
720
  - lib/langchain/prompt/loading.rb
706
721
  - lib/langchain/prompt/prompt_template.rb
707
722
  - lib/langchain/tool/base.rb
708
- - lib/langchain/tool/calculator.rb
709
- - lib/langchain/tool/database.rb
710
- - lib/langchain/tool/google_search.rb
711
- - lib/langchain/tool/ruby_code_interpreter.rb
712
- - lib/langchain/tool/weather.rb
713
- - lib/langchain/tool/wikipedia.rb
723
+ - lib/langchain/tool/calculator/calculator.json
724
+ - lib/langchain/tool/calculator/calculator.rb
725
+ - lib/langchain/tool/database/database.json
726
+ - lib/langchain/tool/database/database.rb
727
+ - lib/langchain/tool/google_search/google_search.json
728
+ - lib/langchain/tool/google_search/google_search.rb
729
+ - lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json
730
+ - lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.rb
731
+ - lib/langchain/tool/weather/weather.json
732
+ - lib/langchain/tool/weather/weather.rb
733
+ - lib/langchain/tool/wikipedia/wikipedia.json
734
+ - lib/langchain/tool/wikipedia/wikipedia.rb
714
735
  - lib/langchain/utils/cosine_similarity.rb
715
736
  - lib/langchain/utils/token_length/ai21_validator.rb
716
737
  - lib/langchain/utils/token_length/base_validator.rb
@@ -1,90 +0,0 @@
1
- module Langchain::Tool
2
- class Database < Base
3
- #
4
- # Connects to a database, executes SQL queries, and outputs DB schema for Agents to use
5
- #
6
- # Gem requirements: gem "sequel", "~> 5.68.0"
7
- #
8
-
9
- NAME = "database"
10
-
11
- description <<~DESC
12
- Useful for getting the result of a database query.
13
-
14
- The input to this tool should be valid SQL.
15
- DESC
16
-
17
- attr_reader :db, :requested_tables, :excluded_tables
18
-
19
- #
20
- # Establish a database connection
21
- #
22
- # @param connection_string [String] Database connection info, e.g. 'postgres://user:password@localhost:5432/db_name'
23
- # @param tables [Array<Symbol>] The tables to use. Will use all if empty.
24
- # @param except_tables [Array<Symbol>] The tables to exclude. Will exclude none if empty.
25
-
26
- # @return [Database] Database object
27
- #
28
- def initialize(connection_string:, tables: [], exclude_tables: [])
29
- depends_on "sequel"
30
-
31
- raise StandardError, "connection_string parameter cannot be blank" if connection_string.empty?
32
-
33
- @db = Sequel.connect(connection_string)
34
- @requested_tables = tables
35
- @excluded_tables = exclude_tables
36
- end
37
-
38
- #
39
- # Returns the database schema
40
- #
41
- # @return [String] schema
42
- #
43
- def dump_schema
44
- Langchain.logger.info("Dumping schema tables and keys", for: self.class)
45
- schema = ""
46
- db.tables.each do |table|
47
- next if excluded_tables.include?(table)
48
- next unless requested_tables.empty? || requested_tables.include?(table)
49
-
50
- primary_key_columns = []
51
- primary_key_column_count = db.schema(table).count { |column| column[1][:primary_key] == true }
52
-
53
- schema << "CREATE TABLE #{table}(\n"
54
- db.schema(table).each do |column|
55
- schema << "#{column[0]} #{column[1][:type]}"
56
- if column[1][:primary_key] == true
57
- schema << " PRIMARY KEY" if primary_key_column_count == 1
58
- else
59
- primary_key_columns << column[0]
60
- end
61
- schema << ",\n" unless column == db.schema(table).last && primary_key_column_count == 1
62
- end
63
- if primary_key_column_count > 1
64
- schema << "PRIMARY KEY (#{primary_key_columns.join(",")})"
65
- end
66
- db.foreign_key_list(table).each do |fk|
67
- schema << ",\n" if fk == db.foreign_key_list(table).first
68
- schema << "FOREIGN KEY (#{fk[:columns][0]}) REFERENCES #{fk[:table]}(#{fk[:key][0]})"
69
- schema << ",\n" unless fk == db.foreign_key_list(table).last
70
- end
71
- schema << ");\n"
72
- end
73
- schema
74
- end
75
-
76
- #
77
- # Evaluates a sql expression
78
- #
79
- # @param input [String] sql expression
80
- # @return [Array] results
81
- #
82
- def execute(input:)
83
- Langchain.logger.info("Executing \"#{input}\"", for: self.class)
84
-
85
- db[input].to_a
86
- rescue Sequel::DatabaseError => e
87
- Langchain.logger.error(e.message, for: self.class)
88
- end
89
- end
90
- end