langchainrb 0.14.0 → 0.15.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 +8 -0
- data/lib/langchain/assistants/assistant.rb +175 -131
- data/lib/langchain/assistants/messages/ollama_message.rb +9 -21
- data/lib/langchain/contextual_logger.rb +2 -2
- data/lib/langchain/llm/google_gemini.rb +1 -1
- data/lib/langchain/llm/ollama.rb +23 -17
- data/lib/langchain/llm/openai.rb +1 -1
- data/lib/langchain/llm/response/ollama_response.rb +1 -15
- data/lib/langchain/llm/unified_parameters.rb +2 -2
- data/lib/langchain/tool/calculator.rb +38 -0
- data/lib/langchain/tool/{database/database.rb → database.rb} +24 -12
- data/lib/langchain/tool/file_system.rb +44 -0
- data/lib/langchain/tool/{google_search/google_search.rb → google_search.rb} +17 -23
- data/lib/langchain/tool/{news_retriever/news_retriever.rb → news_retriever.rb} +41 -14
- data/lib/langchain/tool/ruby_code_interpreter.rb +41 -0
- data/lib/langchain/tool/{tavily/tavily.rb → tavily.rb} +24 -10
- data/lib/langchain/tool/vectorsearch.rb +40 -0
- data/lib/langchain/tool/{weather/weather.rb → weather.rb} +21 -17
- data/lib/langchain/tool/{wikipedia/wikipedia.rb → wikipedia.rb} +17 -13
- data/lib/langchain/tool_definition.rb +212 -0
- data/lib/langchain/utils/hash_transformer.rb +9 -17
- data/lib/langchain/vectorsearch/chroma.rb +2 -2
- data/lib/langchain/vectorsearch/elasticsearch.rb +2 -2
- data/lib/langchain/vectorsearch/epsilla.rb +3 -3
- data/lib/langchain/vectorsearch/milvus.rb +2 -2
- data/lib/langchain/vectorsearch/pgvector.rb +2 -2
- data/lib/langchain/vectorsearch/pinecone.rb +2 -2
- data/lib/langchain/vectorsearch/qdrant.rb +2 -2
- data/lib/langchain/vectorsearch/weaviate.rb +4 -4
- data/lib/langchain/version.rb +1 -1
- metadata +13 -23
- data/lib/langchain/tool/base.rb +0 -107
- data/lib/langchain/tool/calculator/calculator.json +0 -19
- data/lib/langchain/tool/calculator/calculator.rb +0 -34
- data/lib/langchain/tool/database/database.json +0 -46
- data/lib/langchain/tool/file_system/file_system.json +0 -57
- data/lib/langchain/tool/file_system/file_system.rb +0 -32
- data/lib/langchain/tool/google_search/google_search.json +0 -19
- data/lib/langchain/tool/news_retriever/news_retriever.json +0 -122
- data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +0 -19
- data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.rb +0 -37
- data/lib/langchain/tool/tavily/tavily.json +0 -54
- data/lib/langchain/tool/vectorsearch/vectorsearch.json +0 -24
- data/lib/langchain/tool/vectorsearch/vectorsearch.rb +0 -36
- data/lib/langchain/tool/weather/weather.json +0 -19
- data/lib/langchain/tool/wikipedia/wikipedia.json +0 -19
@@ -1,16 +1,28 @@
|
|
1
1
|
module Langchain::Tool
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
#
|
3
|
+
# Connects to a database, executes SQL queries, and outputs DB schema for Agents to use
|
4
|
+
#
|
5
|
+
# Gem requirements:
|
6
|
+
# gem "sequel", "~> 5.68.0"
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# database = Langchain::Tool::Database.new(connection_string: "postgres://user:password@localhost:5432/db_name")
|
10
|
+
#
|
11
|
+
class Database
|
12
|
+
extend Langchain::ToolDefinition
|
13
|
+
include Langchain::DependencyHelper
|
14
|
+
|
15
|
+
define_function :list_tables, description: "Database Tool: Returns a list of tables in the database"
|
16
|
+
|
17
|
+
define_function :describe_tables, description: "Database Tool: Returns the schema for a list of tables" do
|
18
|
+
property :tables, type: "string", description: "The tables to describe", required: true
|
19
|
+
end
|
20
|
+
|
21
|
+
define_function :dump_schema, description: "Database Tool: Returns the database schema"
|
22
|
+
|
23
|
+
define_function :execute, description: "Database Tool: Executes a SQL query and returns the results" do
|
24
|
+
property :input, type: "string", description: "SQL query to be executed", required: true
|
25
|
+
end
|
14
26
|
|
15
27
|
attr_reader :db, :requested_tables, :excluded_tables
|
16
28
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain::Tool
|
4
|
+
#
|
5
|
+
# A tool that wraps the Ruby file system classes.
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
# file_system = Langchain::Tool::FileSystem.new
|
9
|
+
#
|
10
|
+
class FileSystem
|
11
|
+
extend Langchain::ToolDefinition
|
12
|
+
|
13
|
+
define_function :list_directory, description: "File System Tool: Lists out the content of a specified directory" do
|
14
|
+
property :directory_path, type: "string", description: "Directory path to list", required: true
|
15
|
+
end
|
16
|
+
|
17
|
+
define_function :read_file, description: "File System Tool: Reads the contents of a file" do
|
18
|
+
property :file_path, type: "string", description: "Path to the file to read from", required: true
|
19
|
+
end
|
20
|
+
|
21
|
+
define_function :write_to_file, description: "File System Tool: Write content to a file" do
|
22
|
+
property :file_path, type: "string", description: "Path to the file to write", required: true
|
23
|
+
property :content, type: "string", description: "Content to write to the file", required: true
|
24
|
+
end
|
25
|
+
|
26
|
+
def list_directory(directory_path:)
|
27
|
+
Dir.entries(directory_path)
|
28
|
+
rescue Errno::ENOENT
|
29
|
+
"No such directory: #{directory_path}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_file(file_path:)
|
33
|
+
File.read(file_path)
|
34
|
+
rescue Errno::ENOENT
|
35
|
+
"No such file: #{file_path}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_to_file(file_path:, content:)
|
39
|
+
File.write(file_path, content)
|
40
|
+
rescue Errno::EACCES
|
41
|
+
"Permission denied: #{file_path}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,19 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::Tool
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
#
|
5
|
+
# Wrapper around SerpApi's Google Search API
|
6
|
+
#
|
7
|
+
# Gem requirements:
|
8
|
+
# gem "google_search_results", "~> 2.0.0"
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
# search = Langchain::Tool::GoogleSearch.new(api_key: "YOUR_API_KEY")
|
12
|
+
# search.execute(input: "What is the capital of France?")
|
13
|
+
#
|
14
|
+
class GoogleSearch
|
15
|
+
extend Langchain::ToolDefinition
|
16
|
+
include Langchain::DependencyHelper
|
17
|
+
|
18
|
+
define_function :execute, description: "Executes Google Search and returns the result" do
|
19
|
+
property :input, type: "string", description: "Search query", required: true
|
20
|
+
end
|
17
21
|
|
18
22
|
attr_reader :api_key
|
19
23
|
|
@@ -29,16 +33,6 @@ module Langchain::Tool
|
|
29
33
|
@api_key = api_key
|
30
34
|
end
|
31
35
|
|
32
|
-
#
|
33
|
-
# Executes Google Search and returns hash_results JSON
|
34
|
-
#
|
35
|
-
# @param input [String] search query
|
36
|
-
# @return [Hash] hash_results JSON
|
37
|
-
#
|
38
|
-
def self.execute_search(input:)
|
39
|
-
new.execute_search(input: input)
|
40
|
-
end
|
41
|
-
|
42
36
|
# Executes Google Search and returns the result
|
43
37
|
#
|
44
38
|
# @param input [String] search query
|
@@ -1,16 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::Tool
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
#
|
5
|
+
# A tool that retrieves latest news from various sources via https://newsapi.org/.
|
6
|
+
# An API key needs to be obtained from https://newsapi.org/ to use this tool.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# news_retriever = Langchain::Tool::NewsRetriever.new(api_key: ENV["NEWS_API_KEY"])
|
10
|
+
#
|
11
|
+
class NewsRetriever
|
12
|
+
extend Langchain::ToolDefinition
|
13
|
+
|
14
|
+
define_function :get_everything, description: "News Retriever: Search through millions of articles from over 150,000 large and small news sources and blogs" do
|
15
|
+
property :q, type: "string", description: 'Keywords or phrases to search for in the article title and body. Surround phrases with quotes (") for exact match. Alternatively you can use the AND / OR / NOT keywords, and optionally group these with parenthesis. Must be URL-encoded'
|
16
|
+
property :search_in, type: "string", description: "The fields to restrict your q search to", enum: ["title", "description", "content"]
|
17
|
+
property :sources, type: "string", description: "A comma-separated string of identifiers (maximum 20) for the news sources or blogs you want headlines from. Use the /sources endpoint to locate these programmatically or look at the sources index"
|
18
|
+
property :domains, type: "string", description: "A comma-separated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to restrict the search to"
|
19
|
+
property :exclude_domains, type: "string", description: "A comma-separated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to remove from the results"
|
20
|
+
property :from, type: "string", description: "A date and optional time for the oldest article allowed. This should be in ISO 8601 format"
|
21
|
+
property :to, type: "string", description: "A date and optional time for the newest article allowed. This should be in ISO 8601 format"
|
22
|
+
property :language, type: "string", description: "The 2-letter ISO-639-1 code of the language you want to get headlines for", enum: ["ar", "de", "en", "es", "fr", "he", "it", "nl", "no", "pt", "ru", "sv", "ud", "zh"]
|
23
|
+
property :sort_by, type: "string", description: "The order to sort the articles in", enum: ["relevancy", "popularity", "publishedAt"]
|
24
|
+
property :page_size, type: "integer", description: "The number of results to return per page (request). 5 is the default, 100 is the maximum"
|
25
|
+
property :page, type: "integer", description: "Use this to page through the results if the total results found is greater than the page size"
|
26
|
+
end
|
27
|
+
|
28
|
+
define_function :get_top_headlines, description: "News Retriever: Provides live top and breaking headlines for a country, specific category in a country, single source, or multiple sources. You can also search with keywords. Articles are sorted by the earliest date published first" do
|
29
|
+
property :country, type: "string", description: "The 2-letter ISO 3166-1 code of the country you want to get headlines for", enum: ["ae", "ar", "at", "au", "be", "bg", "br", "ca", "ch", "cn", "co", "cu", "cz", "de", "eg", "fr", "gb", "gr", "hk", "hu", "id", "ie", "il", "in", "it", "jp", "kr", "lt", "lv", "ma", "mx", "my", "ng", "nl", "no", "nz", "ph", "pl", "pt", "ro", "rs", "ru", "sa", "se", "sg", "si", "sk", "th", "tr", "tw", "ua", "us", "ve", "za"]
|
30
|
+
property :category, type: "string", description: "The category you want to get headlines for", enum: ["business", "entertainment", "general", "health", "science", "sports", "technology"]
|
31
|
+
property :q, type: "string", description: "Keywords or a phrase to search for"
|
32
|
+
property :page_size, type: "integer", description: "The number of results to return per page (request). 5 is the default, 100 is the maximum"
|
33
|
+
property :page, type: "integer", description: "Use this to page through the results if the total results found is greater than the page size"
|
34
|
+
end
|
35
|
+
|
36
|
+
define_function :get_sources, description: "News Retriever: This endpoint returns the subset of news publishers that top headlines (/v2/top-headlines) are available from. It's mainly a convenience endpoint that you can use to keep track of the publishers available on the API, and you can pipe it straight through to your users" do
|
37
|
+
property :country, type: "string", description: "The 2-letter ISO 3166-1 code of the country you want to get headlines for. Default: all countries", enum: ["ae", "ar", "at", "au", "be", "bg", "br", "ca", "ch", "cn", "co", "cu", "cz", "de", "eg", "fr", "gb", "gr", "hk", "hu", "id", "ie", "il", "in", "it", "jp", "kr", "lt", "lv", "ma", "mx", "my", "ng", "nl", "no", "nz", "ph", "pl", "pt", "ro", "rs", "ru", "sa", "se", "sg", "si", "sk", "th", "tr", "tw", "ua", "us", "ve", "za"]
|
38
|
+
property :category, type: "string", description: "The category you want to get headlines for. Default: all categories", enum: ["business", "entertainment", "general", "health", "science", "sports", "technology"]
|
39
|
+
property :language, type: "string", description: "The 2-letter ISO-639-1 code of the language you want to get headlines for", enum: ["ar", "de", "en", "es", "fr", "he", "it", "nl", "no", "pt", "ru", "sv", "ud", "zh"]
|
40
|
+
end
|
14
41
|
|
15
42
|
def initialize(api_key: ENV["NEWS_API_KEY"])
|
16
43
|
@api_key = api_key
|
@@ -20,9 +47,9 @@ module Langchain::Tool
|
|
20
47
|
#
|
21
48
|
# @param q [String] Keywords or phrases to search for in the article title and body.
|
22
49
|
# @param search_in [String] The fields to restrict your q search to. The possible options are: title, description, content.
|
23
|
-
# @param sources [String] A comma-
|
24
|
-
# @param domains [String] A comma-
|
25
|
-
# @param exclude_domains [String] A comma-
|
50
|
+
# @param sources [String] A comma-separated string of identifiers (maximum 20) for the news sources or blogs you want headlines from. Use the /sources endpoint to locate these programmatically or look at the sources index.
|
51
|
+
# @param domains [String] A comma-separated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to restrict the search to.
|
52
|
+
# @param exclude_domains [String] A comma-separated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to remove from the results.
|
26
53
|
# @param from [String] A date and optional time for the oldest article allowed. This should be in ISO 8601 format.
|
27
54
|
# @param to [String] A date and optional time for the newest article allowed. This should be in ISO 8601 format.
|
28
55
|
# @param language [String] The 2-letter ISO-639-1 code of the language you want to get headlines for. Possible options: ar, de, en, es, fr, he, it, nl, no, pt, ru, se, ud, zh.
|
@@ -66,7 +93,7 @@ module Langchain::Tool
|
|
66
93
|
#
|
67
94
|
# @param country [String] The 2-letter ISO 3166-1 code of the country you want to get headlines for. Possible options: ae, ar, at, au, be, bg, br, ca, ch, cn, co, cu, cz, de, eg, fr, gb, gr, hk, hu, id, ie, il, in, it, jp, kr, lt, lv, ma, mx, my, ng, nl, no, nz, ph, pl, pt, ro, rs, ru, sa, se, sg, si, sk, th, tr, tw, ua, us, ve, za.
|
68
95
|
# @param category [String] The category you want to get headlines for. Possible options: business, entertainment, general, health, science, sports, technology.
|
69
|
-
# @param sources [String] A comma-
|
96
|
+
# @param sources [String] A comma-separated string of identifiers for the news sources or blogs you want headlines from. Use the /sources endpoint to locate these programmatically.
|
70
97
|
# @param q [String] Keywords or a phrase to search for.
|
71
98
|
# @param page_size [Integer] The number of results to return per page. 20 is the API's default, 100 is the maximum. Our default is 5.
|
72
99
|
# @param page [Integer] Use this to page through the results.
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain::Tool
|
4
|
+
#
|
5
|
+
# A tool that execute Ruby code in a sandboxed environment.
|
6
|
+
#
|
7
|
+
# Gem requirements:
|
8
|
+
# gem "safe_ruby", "~> 1.0.4"
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
# interpreter = Langchain::Tool::RubyCodeInterpreter.new
|
12
|
+
#
|
13
|
+
class RubyCodeInterpreter
|
14
|
+
extend Langchain::ToolDefinition
|
15
|
+
include Langchain::DependencyHelper
|
16
|
+
|
17
|
+
define_function :execute, description: "Executes Ruby code in a sandboxes environment" do
|
18
|
+
property :input, type: "string", description: "Ruby code expression", required: true
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(timeout: 30)
|
22
|
+
depends_on "safe_ruby"
|
23
|
+
|
24
|
+
@timeout = timeout
|
25
|
+
end
|
26
|
+
|
27
|
+
# Executes Ruby code in a sandboxes environment.
|
28
|
+
#
|
29
|
+
# @param input [String] ruby code expression
|
30
|
+
# @return [String] Answer
|
31
|
+
def execute(input:)
|
32
|
+
Langchain.logger.info("Executing \"#{input}\"", for: self.class)
|
33
|
+
|
34
|
+
safe_eval(input)
|
35
|
+
end
|
36
|
+
|
37
|
+
def safe_eval(code)
|
38
|
+
SafeRuby.eval(code, timeout: @timeout)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,16 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::Tool
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
#
|
5
|
+
# Tavily Search is a robust search API tailored specifically for LLM Agents.
|
6
|
+
# It seamlessly integrates with diverse data sources to ensure a superior, relevant search experience.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# tavily = Langchain::Tool::Tavily.new(api_key: ENV["TAVILY_API_KEY"])
|
10
|
+
#
|
11
|
+
class Tavily
|
12
|
+
extend Langchain::ToolDefinition
|
13
|
+
|
14
|
+
define_function :search, description: "Tavily Tool: Robust search API" do
|
15
|
+
property :query, type: "string", description: "The search query string", required: true
|
16
|
+
property :search_depth, type: "string", description: "The depth of the search: basic for quick results and advanced for indepth high quality results but longer response time", enum: ["basic", "advanced"]
|
17
|
+
property :include_images, type: "boolean", description: "Include a list of query related images in the response"
|
18
|
+
property :include_answer, type: "boolean", description: "Include answers in the search results"
|
19
|
+
property :include_raw_content, type: "boolean", description: "Include raw content in the search results"
|
20
|
+
property :max_results, type: "integer", description: "The number of maximum search results to return"
|
21
|
+
property :include_domains, type: "array", description: "A list of domains to specifically include in the search results" do
|
22
|
+
item type: "string"
|
23
|
+
end
|
24
|
+
property :exclude_domains, type: "array", description: "A list of domains to specifically exclude from the search results" do
|
25
|
+
item type: "string"
|
26
|
+
end
|
27
|
+
end
|
14
28
|
|
15
29
|
def initialize(api_key:)
|
16
30
|
@api_key = api_key
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain::Tool
|
4
|
+
#
|
5
|
+
# A tool wraps vectorsearch classes
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
# # Initialize the LLM that will be used to generate embeddings
|
9
|
+
# ollama = Langchain::LLM::Ollama.new(url: ENV["OLLAMA_URL"]
|
10
|
+
# chroma = Langchain::Vectorsearch::Chroma.new(url: ENV["CHROMA_URL"], index_name: "my_index", llm: ollama)
|
11
|
+
#
|
12
|
+
# # This tool can now be used by the Assistant
|
13
|
+
# vectorsearch_tool = Langchain::Tool::Vectorsearch.new(vectorsearch: chroma)
|
14
|
+
#
|
15
|
+
class Vectorsearch
|
16
|
+
extend Langchain::ToolDefinition
|
17
|
+
|
18
|
+
define_function :similarity_search, description: "Vectorsearch: Retrieves relevant document for the query" do
|
19
|
+
property :query, type: "string", description: "Query to find similar documents for", required: true
|
20
|
+
property :k, type: "integer", description: "Number of similar documents to retrieve. Default value: 4"
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :vectorsearch
|
24
|
+
|
25
|
+
# Initializes the Vectorsearch tool
|
26
|
+
#
|
27
|
+
# @param vectorsearch [Langchain::Vectorsearch::Base] Vectorsearch instance to use
|
28
|
+
def initialize(vectorsearch:)
|
29
|
+
@vectorsearch = vectorsearch
|
30
|
+
end
|
31
|
+
|
32
|
+
# Executes the vector search and returns the results
|
33
|
+
#
|
34
|
+
# @param query [String] The query to search for
|
35
|
+
# @param k [Integer] The number of results to return
|
36
|
+
def similarity_search(query:, k: 4)
|
37
|
+
vectorsearch.similarity_search(query:, k: 4)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,23 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::Tool
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
4
|
+
#
|
5
|
+
# A weather tool that gets current weather data
|
6
|
+
#
|
7
|
+
# Current weather data is free for 1000 calls per day (https://home.openweathermap.org/api_keys)
|
8
|
+
# Forecast and historical data require registration with credit card, so not supported yet.
|
9
|
+
#
|
10
|
+
# Gem requirements:
|
11
|
+
# gem "open-weather-ruby-client", "~> 0.3.0"
|
12
|
+
# api_key: https://home.openweathermap.org/api_keys
|
13
|
+
#
|
14
|
+
# Usage:
|
15
|
+
# weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
|
16
|
+
# weather.execute(input: "Boston, MA; imperial")
|
17
|
+
#
|
18
|
+
class Weather
|
19
|
+
extend Langchain::ToolDefinition
|
20
|
+
include Langchain::DependencyHelper
|
21
|
+
|
22
|
+
define_function :execute, description: "Returns current weather for a city" do
|
23
|
+
property :input, type: "string", description: "Comma separated city and unit (optional: imperial, metric, or standard)", required: true
|
24
|
+
end
|
21
25
|
|
22
26
|
attr_reader :client, :units
|
23
27
|
|
@@ -1,19 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::Tool
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
#
|
5
|
+
# Tool that adds the capability to search using the Wikipedia API
|
6
|
+
#
|
7
|
+
# Gem requirements:
|
8
|
+
# gem "wikipedia-client", "~> 1.17.0"
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
# wikipedia = Langchain::Tool::Wikipedia.new
|
12
|
+
# wikipedia.execute(input: "The Roman Empire")
|
13
|
+
#
|
14
|
+
class Wikipedia
|
15
|
+
extend Langchain::ToolDefinition
|
16
|
+
include Langchain::DependencyHelper
|
17
|
+
|
18
|
+
define_function :execute, description: "Executes Wikipedia API search and returns the answer" do
|
19
|
+
property :input, type: "string", description: "Search query", required: true
|
20
|
+
end
|
17
21
|
|
18
22
|
# Initializes the Wikipedia tool
|
19
23
|
def initialize
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
#
|
6
|
+
# Extends a class to be used as a tool in the assistant.
|
7
|
+
# A tool is a collection of functions (methods) used to perform specific tasks.
|
8
|
+
#
|
9
|
+
# == Usage
|
10
|
+
#
|
11
|
+
# 1. Extend your class with {Langchain::ToolDefinition}
|
12
|
+
# 2. Use {#define_function} to define each function of the tool
|
13
|
+
#
|
14
|
+
# == Key Concepts
|
15
|
+
#
|
16
|
+
# - {#define_function}: Defines a new function (method) for the tool
|
17
|
+
# - {ParameterBuilder#property}: Defines properties for the function parameters
|
18
|
+
# - {ParameterBuilder#item}: Alias for {ParameterBuilder#property}, used for array items
|
19
|
+
#
|
20
|
+
# These methods support various data types and nested structures, allowing for flexible and expressive tool definitions.
|
21
|
+
#
|
22
|
+
# @example Defining a tool with various property types and configurations
|
23
|
+
# define_function :sample_function, description: "Demonstrates various property types and configurations" do
|
24
|
+
# property :string_prop, type: "string", description: "A simple string property"
|
25
|
+
# property :number_prop, type: "number", description: "A number property"
|
26
|
+
# property :integer_prop, type: "integer", description: "An integer property"
|
27
|
+
# property :boolean_prop, type: "boolean", description: "A boolean property"
|
28
|
+
# property :enum_prop, type: "string", description: "An enum property", enum: ["option1", "option2", "option3"]
|
29
|
+
# property :required_prop, type: "string", description: "A required property", required: true
|
30
|
+
# property :array_prop, type: "array", description: "An array property" do
|
31
|
+
# item type: "string", description: "Array item"
|
32
|
+
# end
|
33
|
+
# property :object_prop, type: "object", description: "An object property" do
|
34
|
+
# property :nested_string, type: "string", description: "Nested string property"
|
35
|
+
# property :nested_number, type: "number", description: "Nested number property"
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
module Langchain::ToolDefinition
|
40
|
+
# Defines a function for the tool
|
41
|
+
#
|
42
|
+
# @param method_name [Symbol] Name of the method to define
|
43
|
+
# @param description [String] Description of the function
|
44
|
+
# @yield Block that defines the parameters for the function
|
45
|
+
def define_function(method_name, description:, &)
|
46
|
+
function_schemas.add_function(method_name:, description:, &)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the FunctionSchemas instance for this tool
|
50
|
+
#
|
51
|
+
# @return [FunctionSchemas] The FunctionSchemas instance
|
52
|
+
def function_schemas
|
53
|
+
@function_schemas ||= FunctionSchemas.new(tool_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the snake_case version of the class name as the tool's name
|
57
|
+
#
|
58
|
+
# @return [String] The snake_case version of the class name
|
59
|
+
def tool_name
|
60
|
+
@tool_name ||= name
|
61
|
+
.gsub("::", "_")
|
62
|
+
.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
|
63
|
+
.downcase
|
64
|
+
end
|
65
|
+
|
66
|
+
# Manages schemas for functions
|
67
|
+
class FunctionSchemas
|
68
|
+
def initialize(tool_name)
|
69
|
+
@schemas = {}
|
70
|
+
@tool_name = tool_name
|
71
|
+
end
|
72
|
+
|
73
|
+
# Adds a function to the schemas
|
74
|
+
#
|
75
|
+
# @param method_name [Symbol] Name of the method to add
|
76
|
+
# @param description [String] Description of the function
|
77
|
+
# @yield Block that defines the parameters for the function
|
78
|
+
# @raise [ArgumentError] If a block is defined and no parameters are specified for the function
|
79
|
+
def add_function(method_name:, description:, &)
|
80
|
+
name = "#{@tool_name}__#{method_name}"
|
81
|
+
|
82
|
+
if block_given?
|
83
|
+
parameters = ParameterBuilder.new(parent_type: "object").build(&)
|
84
|
+
|
85
|
+
if parameters[:properties].empty?
|
86
|
+
raise ArgumentError, "Function parameters must have at least one property defined within it, if a block is provided"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
@schemas[method_name] = {
|
91
|
+
type: "function",
|
92
|
+
function: {name:, description:, parameters:}.compact
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Converts schemas to OpenAI-compatible format
|
97
|
+
#
|
98
|
+
# @return [String] JSON string of schemas in OpenAI format
|
99
|
+
def to_openai_format
|
100
|
+
@schemas.values
|
101
|
+
end
|
102
|
+
|
103
|
+
# Converts schemas to Anthropic-compatible format
|
104
|
+
#
|
105
|
+
# @return [String] JSON string of schemas in Anthropic format
|
106
|
+
def to_anthropic_format
|
107
|
+
@schemas.values.map do |schema|
|
108
|
+
schema[:function].transform_keys(parameters: :input_schema)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Converts schemas to Google Gemini-compatible format
|
113
|
+
#
|
114
|
+
# @return [String] JSON string of schemas in Google Gemini format
|
115
|
+
def to_google_gemini_format
|
116
|
+
@schemas.values.map { |schema| schema[:function] }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Builds parameter schemas for functions
|
121
|
+
class ParameterBuilder
|
122
|
+
VALID_TYPES = %w[object array string number integer boolean].freeze
|
123
|
+
|
124
|
+
def initialize(parent_type:)
|
125
|
+
@schema = (parent_type == "object") ? {type: "object", properties: {}, required: []} : {}
|
126
|
+
@parent_type = parent_type
|
127
|
+
end
|
128
|
+
|
129
|
+
# Builds the parameter schema
|
130
|
+
#
|
131
|
+
# @yield Block that defines the properties of the schema
|
132
|
+
# @return [Hash] The built schema
|
133
|
+
def build(&)
|
134
|
+
instance_eval(&)
|
135
|
+
@schema
|
136
|
+
end
|
137
|
+
|
138
|
+
# Defines a property in the schema
|
139
|
+
#
|
140
|
+
# @param name [Symbol] Name of the property (required only for a parent of type object)
|
141
|
+
# @param type [String] Type of the property
|
142
|
+
# @param description [String] Description of the property
|
143
|
+
# @param enum [Array] Array of allowed values
|
144
|
+
# @param required [Boolean] Whether the property is required
|
145
|
+
# @yield [Block] Block for nested properties (only for object and array types)
|
146
|
+
# @raise [ArgumentError] If any parameter is invalid
|
147
|
+
def property(name = nil, type:, description: nil, enum: nil, required: false, &)
|
148
|
+
validate_parameters(name:, type:, enum:, required:)
|
149
|
+
|
150
|
+
prop = {type:, description:, enum:}.compact
|
151
|
+
|
152
|
+
if block_given?
|
153
|
+
nested_schema = ParameterBuilder.new(parent_type: type).build(&)
|
154
|
+
|
155
|
+
case type
|
156
|
+
when "object"
|
157
|
+
if nested_schema[:properties].empty?
|
158
|
+
raise ArgumentError, "Object properties must have at least one property defined within it"
|
159
|
+
end
|
160
|
+
prop = nested_schema
|
161
|
+
when "array"
|
162
|
+
if nested_schema.empty?
|
163
|
+
raise ArgumentError, "Array properties must have at least one item defined within it"
|
164
|
+
end
|
165
|
+
prop[:items] = nested_schema
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if @parent_type == "object"
|
170
|
+
@schema[:properties][name] = prop
|
171
|
+
@schema[:required] << name.to_s if required
|
172
|
+
else
|
173
|
+
@schema = prop
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Alias for property method, used for defining array items
|
178
|
+
alias_method :item, :property
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Validates the parameters for a property
|
183
|
+
#
|
184
|
+
# @param name [Symbol] Name of the property
|
185
|
+
# @param type [String] Type of the property
|
186
|
+
# @param enum [Array] Array of allowed values
|
187
|
+
# @param required [Boolean] Whether the property is required
|
188
|
+
# @raise [ArgumentError] If any parameter is invalid
|
189
|
+
def validate_parameters(name:, type:, enum:, required:)
|
190
|
+
if @parent_type == "object"
|
191
|
+
if name.nil?
|
192
|
+
raise ArgumentError, "Name must be provided for properties of an object"
|
193
|
+
end
|
194
|
+
unless name.is_a?(Symbol)
|
195
|
+
raise ArgumentError, "Invalid name '#{name}'. Name must be a symbol"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
unless VALID_TYPES.include?(type)
|
200
|
+
raise ArgumentError, "Invalid type '#{type}'. Valid types are: #{VALID_TYPES.join(", ")}"
|
201
|
+
end
|
202
|
+
|
203
|
+
unless enum.nil? || enum.is_a?(Array)
|
204
|
+
raise ArgumentError, "Invalid enum '#{enum}'. Enum must be nil or an array"
|
205
|
+
end
|
206
|
+
|
207
|
+
unless [true, false].include?(required)
|
208
|
+
raise ArgumentError, "Invalid required '#{required}'. Required must be a boolean"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|