langchainrb 0.9.1 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +1 -1
- data/lib/langchain/assistants/assistant.rb +5 -3
- data/lib/langchain/output_parsers/output_fixing_parser.rb +6 -5
- data/lib/langchain/processors/eml.rb +65 -0
- data/lib/langchain/tool/base.rb +17 -32
- data/lib/langchain/tool/calculator/calculator.json +19 -0
- data/lib/langchain/tool/{calculator.rb → calculator/calculator.rb} +8 -5
- data/lib/langchain/tool/database/database.json +46 -0
- data/lib/langchain/tool/database/database.rb +105 -0
- data/lib/langchain/tool/google_search/google_search.json +19 -0
- data/lib/langchain/tool/{google_search.rb → google_search/google_search.rb} +5 -6
- data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +19 -0
- data/lib/langchain/tool/{ruby_code_interpreter.rb → ruby_code_interpreter/ruby_code_interpreter.rb} +9 -1
- data/lib/langchain/tool/weather/weather.json +19 -0
- data/lib/langchain/tool/{weather.rb → weather/weather.rb} +3 -4
- data/lib/langchain/tool/wikipedia/wikipedia.json +19 -0
- data/lib/langchain/tool/{wikipedia.rb → wikipedia/wikipedia.rb} +10 -1
- data/lib/langchain/vectorsearch/chroma.rb +3 -1
- data/lib/langchain/vectorsearch/elasticsearch.rb +3 -1
- data/lib/langchain/vectorsearch/epsilla.rb +3 -1
- data/lib/langchain/vectorsearch/milvus.rb +3 -1
- data/lib/langchain/vectorsearch/pgvector.rb +3 -1
- data/lib/langchain/vectorsearch/pinecone.rb +3 -1
- data/lib/langchain/vectorsearch/qdrant.rb +3 -1
- data/lib/langchain/vectorsearch/weaviate.rb +3 -1
- data/lib/langchain/version.rb +1 -1
- data/lib/langchain.rb +7 -0
- metadata +37 -16
- data/lib/langchain/tool/database.rb +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b31725a5fdb7c09d25e97b3b8ecbd78eba1eeece6ef2db82f009aa121e1f4956
|
4
|
+
data.tar.gz: 1f9116daf6780682d8c32021212bba702a053af9b4293e488ca605cc298c8e12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(&:
|
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
|
-
|
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.
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
data/lib/langchain/tool/base.rb
CHANGED
@@ -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
|
88
|
+
# Returns the tool as a list of OpenAI formatted functions
|
95
89
|
#
|
96
90
|
# @return [Hash] tool as an OpenAI tool
|
97
|
-
def
|
98
|
-
|
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
|
-
#
|
10
|
-
#
|
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
|
-
#
|
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:
|
8
|
+
# Gem requirements:
|
9
|
+
# gem "google_search_results", "~> 2.0.0"
|
9
10
|
#
|
10
11
|
# Usage:
|
11
|
-
#
|
12
|
-
#
|
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
|
+
]
|
data/lib/langchain/tool/{ruby_code_interpreter.rb → ruby_code_interpreter/ruby_code_interpreter.rb}
RENAMED
@@ -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:
|
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: "
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/langchain/version.rb
CHANGED
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.
|
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-
|
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:
|
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:
|
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.
|
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.
|
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
|
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
|
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.
|
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.
|
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.
|
709
|
-
- lib/langchain/tool/
|
710
|
-
- lib/langchain/tool/
|
711
|
-
- lib/langchain/tool/
|
712
|
-
- lib/langchain/tool/
|
713
|
-
- lib/langchain/tool/
|
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
|