langchainrb 0.7.5 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/README.md +113 -56
- data/lib/langchain/assistants/assistant.rb +213 -0
- data/lib/langchain/assistants/message.rb +58 -0
- data/lib/langchain/assistants/thread.rb +34 -0
- data/lib/langchain/chunker/markdown.rb +37 -0
- data/lib/langchain/chunker/recursive_text.rb +0 -2
- data/lib/langchain/chunker/semantic.rb +1 -3
- data/lib/langchain/chunker/sentence.rb +0 -2
- data/lib/langchain/chunker/text.rb +0 -2
- data/lib/langchain/contextual_logger.rb +1 -1
- data/lib/langchain/data.rb +4 -3
- data/lib/langchain/llm/ai21.rb +1 -1
- data/lib/langchain/llm/anthropic.rb +86 -11
- data/lib/langchain/llm/aws_bedrock.rb +52 -0
- data/lib/langchain/llm/azure.rb +10 -97
- data/lib/langchain/llm/base.rb +3 -2
- data/lib/langchain/llm/cohere.rb +5 -7
- data/lib/langchain/llm/google_palm.rb +4 -2
- data/lib/langchain/llm/google_vertex_ai.rb +151 -0
- data/lib/langchain/llm/hugging_face.rb +1 -1
- data/lib/langchain/llm/llama_cpp.rb +18 -16
- data/lib/langchain/llm/mistral_ai.rb +68 -0
- data/lib/langchain/llm/ollama.rb +209 -27
- data/lib/langchain/llm/openai.rb +138 -170
- data/lib/langchain/llm/prompts/ollama/summarize_template.yaml +9 -0
- data/lib/langchain/llm/replicate.rb +1 -7
- data/lib/langchain/llm/response/anthropic_response.rb +20 -0
- data/lib/langchain/llm/response/base_response.rb +7 -0
- data/lib/langchain/llm/response/google_palm_response.rb +4 -0
- data/lib/langchain/llm/response/google_vertex_ai_response.rb +33 -0
- data/lib/langchain/llm/response/llama_cpp_response.rb +13 -0
- data/lib/langchain/llm/response/mistral_ai_response.rb +39 -0
- data/lib/langchain/llm/response/ollama_response.rb +27 -1
- data/lib/langchain/llm/response/openai_response.rb +8 -0
- data/lib/langchain/loader.rb +3 -2
- data/lib/langchain/output_parsers/base.rb +0 -4
- data/lib/langchain/output_parsers/output_fixing_parser.rb +7 -14
- data/lib/langchain/output_parsers/structured_output_parser.rb +0 -10
- data/lib/langchain/processors/csv.rb +37 -3
- data/lib/langchain/processors/eml.rb +64 -0
- data/lib/langchain/processors/markdown.rb +17 -0
- data/lib/langchain/processors/pptx.rb +29 -0
- data/lib/langchain/prompt/loading.rb +1 -1
- data/lib/langchain/tool/base.rb +21 -53
- data/lib/langchain/tool/calculator/calculator.json +19 -0
- data/lib/langchain/tool/{calculator.rb → calculator/calculator.rb} +8 -16
- data/lib/langchain/tool/database/database.json +46 -0
- data/lib/langchain/tool/database/database.rb +99 -0
- data/lib/langchain/tool/file_system/file_system.json +57 -0
- data/lib/langchain/tool/file_system/file_system.rb +32 -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 -15
- 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} +8 -4
- data/lib/langchain/tool/vectorsearch/vectorsearch.json +24 -0
- data/lib/langchain/tool/vectorsearch/vectorsearch.rb +36 -0
- data/lib/langchain/tool/weather/weather.json +19 -0
- data/lib/langchain/tool/{weather.rb → weather/weather.rb} +3 -15
- data/lib/langchain/tool/wikipedia/wikipedia.json +19 -0
- data/lib/langchain/tool/{wikipedia.rb → wikipedia/wikipedia.rb} +9 -9
- data/lib/langchain/utils/token_length/ai21_validator.rb +6 -2
- data/lib/langchain/utils/token_length/base_validator.rb +1 -1
- data/lib/langchain/utils/token_length/cohere_validator.rb +6 -2
- data/lib/langchain/utils/token_length/google_palm_validator.rb +5 -1
- data/lib/langchain/utils/token_length/openai_validator.rb +55 -1
- data/lib/langchain/utils/token_length/token_limit_exceeded.rb +1 -1
- data/lib/langchain/vectorsearch/base.rb +11 -4
- data/lib/langchain/vectorsearch/chroma.rb +10 -1
- data/lib/langchain/vectorsearch/elasticsearch.rb +53 -4
- data/lib/langchain/vectorsearch/epsilla.rb +149 -0
- data/lib/langchain/vectorsearch/hnswlib.rb +5 -1
- data/lib/langchain/vectorsearch/milvus.rb +4 -2
- data/lib/langchain/vectorsearch/pgvector.rb +14 -4
- data/lib/langchain/vectorsearch/pinecone.rb +8 -5
- data/lib/langchain/vectorsearch/qdrant.rb +16 -4
- data/lib/langchain/vectorsearch/weaviate.rb +20 -2
- data/lib/langchain/version.rb +1 -1
- data/lib/langchain.rb +20 -5
- metadata +182 -45
- data/lib/langchain/agent/agents.md +0 -54
- data/lib/langchain/agent/base.rb +0 -20
- data/lib/langchain/agent/react_agent/react_agent_prompt.yaml +0 -26
- data/lib/langchain/agent/react_agent.rb +0 -131
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.yaml +0 -11
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.yaml +0 -21
- data/lib/langchain/agent/sql_query_agent.rb +0 -82
- data/lib/langchain/conversation/context.rb +0 -8
- data/lib/langchain/conversation/memory.rb +0 -86
- data/lib/langchain/conversation/message.rb +0 -48
- data/lib/langchain/conversation/prompt.rb +0 -8
- data/lib/langchain/conversation/response.rb +0 -8
- data/lib/langchain/conversation.rb +0 -93
- data/lib/langchain/tool/database.rb +0 -90
@@ -6,13 +6,11 @@ module Langchain::OutputParsers
|
|
6
6
|
class OutputFixingParser < Base
|
7
7
|
attr_reader :llm, :parser, :prompt
|
8
8
|
|
9
|
-
#
|
10
9
|
# Initializes a new instance of the class.
|
11
10
|
#
|
12
11
|
# @param llm [Langchain::LLM] The LLM used in the fixing process
|
13
12
|
# @param parser [Langchain::OutputParsers] The parser originally used which resulted in parsing error
|
14
13
|
# @param prompt [Langchain::Prompt::PromptTemplate]
|
15
|
-
#
|
16
14
|
def initialize(llm:, parser:, prompt:)
|
17
15
|
raise ArgumentError.new("llm must be an instance of Langchain::LLM got: #{llm.class}") unless llm.is_a?(Langchain::LLM::Base)
|
18
16
|
raise ArgumentError.new("parser must be an instance of Langchain::OutputParsers got #{parser.class}") unless parser.is_a?(Langchain::OutputParsers::Base)
|
@@ -30,17 +28,14 @@ module Langchain::OutputParsers
|
|
30
28
|
}
|
31
29
|
end
|
32
30
|
|
33
|
-
#
|
34
31
|
# calls get_format_instructions on the @parser
|
35
32
|
#
|
36
33
|
# @return [String] Instructions for how the output of a language model should be formatted
|
37
34
|
# according to the @schema.
|
38
|
-
#
|
39
35
|
def get_format_instructions
|
40
36
|
parser.get_format_instructions
|
41
37
|
end
|
42
38
|
|
43
|
-
#
|
44
39
|
# Parse the output of an LLM call, if fails with OutputParserException
|
45
40
|
# then call the LLM with a fix prompt in an attempt to get the correctly
|
46
41
|
# formatted response
|
@@ -48,21 +43,20 @@ module Langchain::OutputParsers
|
|
48
43
|
# @param completion [String] Text output from the LLM call
|
49
44
|
#
|
50
45
|
# @return [Object] object that is succesfully parsed by @parser.parse
|
51
|
-
#
|
52
46
|
def parse(completion)
|
53
47
|
parser.parse(completion)
|
54
48
|
rescue OutputParserException => e
|
55
49
|
new_completion = llm.chat(
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
messages: [{role: "user",
|
51
|
+
content: prompt.format(
|
52
|
+
instructions: parser.get_format_instructions,
|
53
|
+
completion: completion,
|
54
|
+
error: e
|
55
|
+
)}]
|
56
|
+
).completion
|
62
57
|
parser.parse(new_completion)
|
63
58
|
end
|
64
59
|
|
65
|
-
#
|
66
60
|
# Creates a new instance of the class using the given JSON::Schema.
|
67
61
|
#
|
68
62
|
# @param llm [Langchain::LLM] The LLM used in the fixing process
|
@@ -70,7 +64,6 @@ module Langchain::OutputParsers
|
|
70
64
|
# @param prompt [Langchain::Prompt::PromptTemplate]
|
71
65
|
#
|
72
66
|
# @return [Object] A new instance of the class
|
73
|
-
#
|
74
67
|
def self.from_llm(llm:, parser:, prompt: nil)
|
75
68
|
new(llm: llm, parser: parser, prompt: prompt || naive_fix_prompt)
|
76
69
|
end
|
@@ -5,15 +5,12 @@ require "json-schema"
|
|
5
5
|
|
6
6
|
module Langchain::OutputParsers
|
7
7
|
# = Structured Output Parser
|
8
|
-
#
|
9
8
|
class StructuredOutputParser < Base
|
10
9
|
attr_reader :schema
|
11
10
|
|
12
|
-
#
|
13
11
|
# Initializes a new instance of the class.
|
14
12
|
#
|
15
13
|
# @param schema [JSON::Schema] The json schema
|
16
|
-
#
|
17
14
|
def initialize(schema:)
|
18
15
|
@schema = validate_schema!(schema)
|
19
16
|
end
|
@@ -25,24 +22,20 @@ module Langchain::OutputParsers
|
|
25
22
|
}
|
26
23
|
end
|
27
24
|
|
28
|
-
#
|
29
25
|
# Creates a new instance of the class using the given JSON::Schema.
|
30
26
|
#
|
31
27
|
# @param schema [JSON::Schema] The JSON::Schema to use
|
32
28
|
#
|
33
29
|
# @return [Object] A new instance of the class
|
34
|
-
#
|
35
30
|
def self.from_json_schema(schema)
|
36
31
|
new(schema: schema)
|
37
32
|
end
|
38
33
|
|
39
|
-
#
|
40
34
|
# Returns a string containing instructions for how the output of a language model should be formatted
|
41
35
|
# according to the @schema.
|
42
36
|
#
|
43
37
|
# @return [String] Instructions for how the output of a language model should be formatted
|
44
38
|
# according to the @schema.
|
45
|
-
#
|
46
39
|
def get_format_instructions
|
47
40
|
<<~INSTRUCTIONS
|
48
41
|
You must format your output as a JSON value that adheres to a given "JSON Schema" instance.
|
@@ -62,13 +55,10 @@ module Langchain::OutputParsers
|
|
62
55
|
INSTRUCTIONS
|
63
56
|
end
|
64
57
|
|
65
|
-
#
|
66
58
|
# Parse the output of an LLM call extracting an object that abides by the @schema
|
67
59
|
#
|
68
60
|
# @param text [String] Text output from the LLM call
|
69
|
-
#
|
70
61
|
# @return [Object] object that abides by the @schema
|
71
|
-
#
|
72
62
|
def parse(text)
|
73
63
|
json = text.include?("```") ? text.strip.split(/```(?:json)?/)[1] : text.strip
|
74
64
|
parsed = JSON.parse(json)
|
@@ -5,15 +5,26 @@ require "csv"
|
|
5
5
|
module Langchain
|
6
6
|
module Processors
|
7
7
|
class CSV < Base
|
8
|
+
class InvalidChunkMode < StandardError; end
|
9
|
+
|
8
10
|
EXTENSIONS = [".csv"]
|
9
11
|
CONTENT_TYPES = ["text/csv"]
|
12
|
+
CHUNK_MODE = {
|
13
|
+
row: "row",
|
14
|
+
file: "file"
|
15
|
+
}
|
10
16
|
|
11
17
|
# Parse the document and return the text
|
12
18
|
# @param [File] data
|
13
|
-
# @return [
|
19
|
+
# @return [String]
|
14
20
|
def parse(data)
|
15
|
-
|
16
|
-
|
21
|
+
case chunk_mode
|
22
|
+
when CHUNK_MODE[:row]
|
23
|
+
chunk_row(data)
|
24
|
+
when CHUNK_MODE[:file]
|
25
|
+
chunk_file(data)
|
26
|
+
else
|
27
|
+
raise InvalidChunkMode
|
17
28
|
end
|
18
29
|
end
|
19
30
|
|
@@ -22,6 +33,29 @@ module Langchain
|
|
22
33
|
def separator
|
23
34
|
@options[:col_sep] || ","
|
24
35
|
end
|
36
|
+
|
37
|
+
def chunk_mode
|
38
|
+
if @options[:chunk_mode].to_s.empty?
|
39
|
+
CHUNK_MODE[:row]
|
40
|
+
else
|
41
|
+
raise InvalidChunkMode unless CHUNK_MODE.value?(@options[:chunk_mode])
|
42
|
+
|
43
|
+
@options[:chunk_mode]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def chunk_row(data)
|
48
|
+
::CSV.new(data.read, col_sep: separator).map do |row|
|
49
|
+
row
|
50
|
+
.compact
|
51
|
+
.map(&:strip)
|
52
|
+
.join(separator)
|
53
|
+
end.join("\n\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def chunk_file(data)
|
57
|
+
data.read
|
58
|
+
end
|
25
59
|
end
|
26
60
|
end
|
27
61
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
module Processors
|
5
|
+
class Eml < Base
|
6
|
+
EXTENSIONS = [".eml"]
|
7
|
+
CONTENT_TYPES = ["message/rfc822"]
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
depends_on "mail"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parse the document and return the cleaned text
|
14
|
+
# @param [File] data
|
15
|
+
# @return [String]
|
16
|
+
def parse(data)
|
17
|
+
mail = Mail.read(data.path)
|
18
|
+
text_content = extract_text_content(mail)
|
19
|
+
clean_content(text_content)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Extract text content from the email, preferring plaintext over HTML
|
25
|
+
def extract_text_content(mail)
|
26
|
+
text_content = ""
|
27
|
+
text_content += "From: #{mail.from}\n" \
|
28
|
+
"To: #{mail.to}\n" \
|
29
|
+
"Cc: #{mail.cc}\n" \
|
30
|
+
"Bcc: #{mail.bcc}\n" \
|
31
|
+
"Subject: #{mail.subject}\n" \
|
32
|
+
"Date: #{mail.date}\n\n"
|
33
|
+
if mail.multipart?
|
34
|
+
mail.parts.each do |part|
|
35
|
+
if part.content_type.start_with?("text/plain")
|
36
|
+
text_content += part.body.decoded.force_encoding("UTF-8").strip + "\n"
|
37
|
+
elsif part.content_type.start_with?("multipart/alternative", "multipart/mixed")
|
38
|
+
text_content += extract_text_content(part) + "\n" # Recursively extract from multipart
|
39
|
+
elsif part.content_type.start_with?("message/rfc822")
|
40
|
+
# Handle embedded .eml parts as separate emails
|
41
|
+
embedded_mail = Mail.read_from_string(part.body.decoded)
|
42
|
+
text_content += "--- Begin Embedded Email ---\n"
|
43
|
+
text_content += extract_text_content(embedded_mail) + "\n"
|
44
|
+
text_content += "--- End Embedded Email ---\n"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
elsif mail.content_type.start_with?("text/plain")
|
48
|
+
text_content = mail.body.decoded.force_encoding("UTF-8").strip
|
49
|
+
end
|
50
|
+
text_content
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clean and format the extracted content
|
54
|
+
def clean_content(content)
|
55
|
+
content
|
56
|
+
.gsub(/\[cid:[^\]]+\]/, "") # Remove embedded image references
|
57
|
+
.gsub(URI::DEFAULT_PARSER.make_regexp(%w[http https])) { |match| "<#{match}>" } # Format URLs
|
58
|
+
.gsub(/\r\n?/, "\n") # Normalize line endings to Unix style
|
59
|
+
.gsub(/[\u200B-\u200D\uFEFF]/, "") # Remove zero width spaces and similar characters
|
60
|
+
.gsub(/<\/?[^>]+>/, "") # Remove any HTML tags that might have sneaked in
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
module Processors
|
5
|
+
class Markdown < Base
|
6
|
+
EXTENSIONS = [".markdown", ".md"]
|
7
|
+
CONTENT_TYPES = ["text/markdown"]
|
8
|
+
|
9
|
+
# Parse the document and return the text
|
10
|
+
# @param [File] data
|
11
|
+
# @return [String]
|
12
|
+
def parse(data)
|
13
|
+
data.read
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
module Processors
|
5
|
+
class Pptx < Base
|
6
|
+
EXTENSIONS = [".pptx"]
|
7
|
+
CONTENT_TYPES = ["application/vnd.openxmlformats-officedocument.presentationml.presentation"]
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
depends_on "power_point_pptx"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parse the document and return the text
|
14
|
+
# @param [File] data
|
15
|
+
# @return [String]
|
16
|
+
def parse(data)
|
17
|
+
presentation = PowerPointPptx::Document.open(data)
|
18
|
+
|
19
|
+
slides = presentation.slides
|
20
|
+
contents = slides.map(&:content)
|
21
|
+
text = contents.map do |sections|
|
22
|
+
sections.map(&:strip).join(" ")
|
23
|
+
end
|
24
|
+
|
25
|
+
text.join("\n\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -33,7 +33,7 @@ module Langchain::Prompt
|
|
33
33
|
when ".json"
|
34
34
|
config = JSON.parse(File.read(file_path))
|
35
35
|
when ".yaml", ".yml"
|
36
|
-
config = YAML.
|
36
|
+
config = YAML.safe_load_file(file_path)
|
37
37
|
else
|
38
38
|
raise ArgumentError, "Got unsupported file type #{file_path.extname}"
|
39
39
|
end
|
data/lib/langchain/tool/base.rb
CHANGED
@@ -9,6 +9,7 @@ module Langchain::Tool
|
|
9
9
|
#
|
10
10
|
# - {Langchain::Tool::Calculator}: calculate the result of a math expression
|
11
11
|
# - {Langchain::Tool::Database}: executes SQL queries
|
12
|
+
# - {Langchain::Tool::FileSystem}: interacts with files
|
12
13
|
# - {Langchain::Tool::GoogleSearch}: search on Google (via SerpAPI)
|
13
14
|
# - {Langchain::Tool::RubyCodeInterpreter}: runs ruby code
|
14
15
|
# - {Langchain::Tool::Weather}: gets current weather data
|
@@ -29,8 +30,9 @@ module Langchain::Tool
|
|
29
30
|
#
|
30
31
|
# 3. Pass the tools when Agent is instantiated.
|
31
32
|
#
|
32
|
-
# agent = Langchain::
|
33
|
-
# llm: Langchain::LLM::OpenAI.new(api_key: "YOUR_API_KEY"), # or other
|
33
|
+
# agent = Langchain::Assistant.new(
|
34
|
+
# llm: Langchain::LLM::OpenAI.new(api_key: "YOUR_API_KEY"), # or other LLM that supports function calling (coming soon)
|
35
|
+
# thread: Langchain::Thread.new,
|
34
36
|
# tools: [
|
35
37
|
# Langchain::Tool::GoogleSearch.new(api_key: "YOUR_API_KEY"),
|
36
38
|
# Langchain::Tool::Calculator.new,
|
@@ -42,17 +44,16 @@ module Langchain::Tool
|
|
42
44
|
#
|
43
45
|
# 1. Create a new file in lib/langchain/tool/your_tool_name.rb
|
44
46
|
# 2. Create a class in the file that inherits from {Langchain::Tool::Base}
|
45
|
-
# 3. Add `NAME=` and `
|
46
|
-
# 4. Implement
|
47
|
-
# 5.
|
47
|
+
# 3. Add `NAME=` and `ANNOTATIONS_PATH=` constants in your Tool class
|
48
|
+
# 4. Implement various methods in your tool class
|
49
|
+
# 5. Create a sidecar .json file in the same directory as your tool file annotating the methods in the Open API format
|
50
|
+
# 6. Add your tool to the {file:README.md}
|
48
51
|
class Base
|
49
52
|
include Langchain::DependencyHelper
|
50
53
|
|
51
|
-
#
|
52
54
|
# Returns the NAME constant of the tool
|
53
55
|
#
|
54
56
|
# @return [String] tool name
|
55
|
-
#
|
56
57
|
def name
|
57
58
|
self.class.const_get(:NAME)
|
58
59
|
end
|
@@ -63,55 +64,22 @@ module Langchain::Tool
|
|
63
64
|
}
|
64
65
|
end
|
65
66
|
|
67
|
+
# Returns the tool as a list of OpenAI formatted functions
|
66
68
|
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
#
|
71
|
-
def description
|
72
|
-
self.class.const_get(:DESCRIPTION)
|
69
|
+
# @return [Hash] tool as an OpenAI tool
|
70
|
+
def to_openai_tools
|
71
|
+
method_annotations
|
73
72
|
end
|
74
73
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
# Instantiates and executes the tool and returns the answer
|
86
|
-
#
|
87
|
-
# @param input [String] input to the tool
|
88
|
-
# @return [String] answer
|
89
|
-
#
|
90
|
-
def self.execute(input:)
|
91
|
-
new.execute(input: input)
|
92
|
-
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# Executes the tool and returns the answer
|
96
|
-
#
|
97
|
-
# @param input [String] input to the tool
|
98
|
-
# @return [String] answer
|
99
|
-
# @raise NotImplementedError when not implemented
|
100
|
-
def execute(input:)
|
101
|
-
raise NotImplementedError, "Your tool must implement the `#execute(input:)` method that returns a string"
|
102
|
-
end
|
103
|
-
|
104
|
-
#
|
105
|
-
# Validates the list of tools or raises an error
|
106
|
-
# @param tools [Array<Langchain::Tool>] list of tools to be used
|
107
|
-
#
|
108
|
-
# @raise [ArgumentError] If any of the tools are not supported
|
109
|
-
#
|
110
|
-
def self.validate_tools!(tools:)
|
111
|
-
# Check if the tool count is equal to unique tool count
|
112
|
-
if tools.count != tools.map(&:name).uniq.count
|
113
|
-
raise ArgumentError, "Either tools are not unique or are conflicting with each other"
|
114
|
-
end
|
74
|
+
# Return tool's method annotations as JSON
|
75
|
+
#
|
76
|
+
# @return [Hash] Tool's method annotations
|
77
|
+
def method_annotations
|
78
|
+
JSON.parse(
|
79
|
+
File.read(
|
80
|
+
self.class.const_get(:ANNOTATIONS_PATH)
|
81
|
+
)
|
82
|
+
)
|
115
83
|
end
|
116
84
|
end
|
117
85
|
end
|
@@ -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,29 +6,21 @@ 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"
|
14
|
-
|
15
|
-
description <<~DESC
|
16
|
-
Useful for getting the result of a math expression.
|
17
|
-
|
18
|
-
The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.
|
19
|
-
Usage:
|
20
|
-
Action Input: 1 + 1
|
21
|
-
Action Input: 3 * 2 / 4
|
22
|
-
Action Input: 9 - 7
|
23
|
-
Action Input: (4.1 + 2.3) / (2.0 - 5.6) * 3
|
24
|
-
DESC
|
16
|
+
ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
|
25
17
|
|
26
18
|
def initialize
|
27
19
|
depends_on "eqn"
|
28
20
|
end
|
29
21
|
|
30
|
-
# Evaluates a pure math expression or if equation contains non-math characters (e.g.: "12F in Celsius") then
|
31
|
-
#
|
22
|
+
# 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
|
23
|
+
#
|
32
24
|
# @param input [String] math expression
|
33
25
|
# @return [String] Answer
|
34
26
|
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,99 @@
|
|
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
|
+
attr_reader :db, :requested_tables, :excluded_tables
|
16
|
+
|
17
|
+
# Establish a database connection
|
18
|
+
#
|
19
|
+
# @param connection_string [String] Database connection info, e.g. 'postgres://user:password@localhost:5432/db_name'
|
20
|
+
# @param tables [Array<Symbol>] The tables to use. Will use all if empty.
|
21
|
+
# @param except_tables [Array<Symbol>] The tables to exclude. Will exclude none if empty.
|
22
|
+
# @return [Database] Database object
|
23
|
+
def initialize(connection_string:, tables: [], exclude_tables: [])
|
24
|
+
depends_on "sequel"
|
25
|
+
|
26
|
+
raise StandardError, "connection_string parameter cannot be blank" if connection_string.empty?
|
27
|
+
|
28
|
+
@db = Sequel.connect(connection_string)
|
29
|
+
@requested_tables = tables
|
30
|
+
@excluded_tables = exclude_tables
|
31
|
+
end
|
32
|
+
|
33
|
+
# Database Tool: Returns a list of tables in the database
|
34
|
+
def list_tables
|
35
|
+
db.tables
|
36
|
+
end
|
37
|
+
|
38
|
+
# Database Tool: Returns the schema for a list of tables
|
39
|
+
#
|
40
|
+
# @param tables [String] The tables to describe.
|
41
|
+
# @return [String] Database schema for the tables
|
42
|
+
def describe_tables(tables:)
|
43
|
+
schema = ""
|
44
|
+
tables.split(",").each do |table|
|
45
|
+
describe_table(table, schema)
|
46
|
+
end
|
47
|
+
schema
|
48
|
+
end
|
49
|
+
|
50
|
+
# Database Tool: Returns the database schema
|
51
|
+
#
|
52
|
+
# @return [String] Database schema
|
53
|
+
def dump_schema
|
54
|
+
Langchain.logger.info("Dumping schema tables and keys", for: self.class)
|
55
|
+
schema = ""
|
56
|
+
db.tables.each do |table|
|
57
|
+
describe_table(table, schema)
|
58
|
+
end
|
59
|
+
schema
|
60
|
+
end
|
61
|
+
|
62
|
+
def describe_table(table, schema)
|
63
|
+
primary_key_columns = []
|
64
|
+
primary_key_column_count = db.schema(table).count { |column| column[1][:primary_key] == true }
|
65
|
+
|
66
|
+
schema << "CREATE TABLE #{table}(\n"
|
67
|
+
db.schema(table).each do |column|
|
68
|
+
schema << "#{column[0]} #{column[1][:type]}"
|
69
|
+
if column[1][:primary_key] == true
|
70
|
+
schema << " PRIMARY KEY" if primary_key_column_count == 1
|
71
|
+
else
|
72
|
+
primary_key_columns << column[0]
|
73
|
+
end
|
74
|
+
schema << ",\n" unless column == db.schema(table).last && primary_key_column_count == 1
|
75
|
+
end
|
76
|
+
if primary_key_column_count > 1
|
77
|
+
schema << "PRIMARY KEY (#{primary_key_columns.join(",")})"
|
78
|
+
end
|
79
|
+
db.foreign_key_list(table).each do |fk|
|
80
|
+
schema << ",\n" if fk == db.foreign_key_list(table).first
|
81
|
+
schema << "FOREIGN KEY (#{fk[:columns][0]}) REFERENCES #{fk[:table]}(#{fk[:key][0]})"
|
82
|
+
schema << ",\n" unless fk == db.foreign_key_list(table).last
|
83
|
+
end
|
84
|
+
schema << ");\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Database Tool: Executes a SQL query and returns the results
|
88
|
+
#
|
89
|
+
# @param input [String] SQL query to be executed
|
90
|
+
# @return [Array] Results from the SQL query
|
91
|
+
def execute(input:)
|
92
|
+
Langchain.logger.info("Executing \"#{input}\"", for: self.class)
|
93
|
+
|
94
|
+
db[input].to_a
|
95
|
+
rescue Sequel::DatabaseError => e
|
96
|
+
Langchain.logger.error(e.message, for: self.class)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"type": "function",
|
4
|
+
"function": {
|
5
|
+
"name": "file_system-list_directory",
|
6
|
+
"description": "File System Tool: Lists out the content of a specified directory",
|
7
|
+
"parameters": {
|
8
|
+
"type": "object",
|
9
|
+
"properties": {
|
10
|
+
"directory_path": {
|
11
|
+
"type": "string",
|
12
|
+
"description": "Directory path to list"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"required": ["directory_path"]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
},
|
19
|
+
{
|
20
|
+
"type": "function",
|
21
|
+
"function": {
|
22
|
+
"name": "file_system-read_file",
|
23
|
+
"description": "File System Tool: Reads the contents of a file",
|
24
|
+
"parameters": {
|
25
|
+
"type": "object",
|
26
|
+
"properties": {
|
27
|
+
"file_path": {
|
28
|
+
"type": "string",
|
29
|
+
"description": "Path to the file to read from"
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"required": ["file_path"]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"type": "function",
|
38
|
+
"function": {
|
39
|
+
"name": "file_system-write_to_file",
|
40
|
+
"description": "File System Tool: Write content to a file",
|
41
|
+
"parameters": {
|
42
|
+
"type": "object",
|
43
|
+
"properties": {
|
44
|
+
"file_path": {
|
45
|
+
"type": "string",
|
46
|
+
"description": "Path to the file to write"
|
47
|
+
},
|
48
|
+
"content": {
|
49
|
+
"type": "string",
|
50
|
+
"description": "Content to write to the file"
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"required": ["file_path", "content"]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
]
|