langchainrb 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/langchain/assistants/assistant.rb +175 -131
  4. data/lib/langchain/assistants/messages/ollama_message.rb +9 -21
  5. data/lib/langchain/contextual_logger.rb +2 -2
  6. data/lib/langchain/llm/google_gemini.rb +1 -1
  7. data/lib/langchain/llm/ollama.rb +23 -17
  8. data/lib/langchain/llm/openai.rb +1 -1
  9. data/lib/langchain/llm/response/ollama_response.rb +1 -15
  10. data/lib/langchain/llm/unified_parameters.rb +2 -2
  11. data/lib/langchain/tool/calculator.rb +38 -0
  12. data/lib/langchain/tool/{database/database.rb → database.rb} +24 -12
  13. data/lib/langchain/tool/file_system.rb +44 -0
  14. data/lib/langchain/tool/{google_search/google_search.rb → google_search.rb} +17 -23
  15. data/lib/langchain/tool/{news_retriever/news_retriever.rb → news_retriever.rb} +41 -14
  16. data/lib/langchain/tool/ruby_code_interpreter.rb +41 -0
  17. data/lib/langchain/tool/{tavily/tavily.rb → tavily.rb} +24 -10
  18. data/lib/langchain/tool/vectorsearch.rb +40 -0
  19. data/lib/langchain/tool/{weather/weather.rb → weather.rb} +21 -17
  20. data/lib/langchain/tool/{wikipedia/wikipedia.rb → wikipedia.rb} +17 -13
  21. data/lib/langchain/tool_definition.rb +212 -0
  22. data/lib/langchain/utils/hash_transformer.rb +9 -17
  23. data/lib/langchain/vectorsearch/chroma.rb +2 -2
  24. data/lib/langchain/vectorsearch/elasticsearch.rb +2 -2
  25. data/lib/langchain/vectorsearch/epsilla.rb +3 -3
  26. data/lib/langchain/vectorsearch/milvus.rb +2 -2
  27. data/lib/langchain/vectorsearch/pgvector.rb +2 -2
  28. data/lib/langchain/vectorsearch/pinecone.rb +2 -2
  29. data/lib/langchain/vectorsearch/qdrant.rb +2 -2
  30. data/lib/langchain/vectorsearch/weaviate.rb +4 -4
  31. data/lib/langchain/version.rb +1 -1
  32. metadata +13 -23
  33. data/lib/langchain/tool/base.rb +0 -107
  34. data/lib/langchain/tool/calculator/calculator.json +0 -19
  35. data/lib/langchain/tool/calculator/calculator.rb +0 -34
  36. data/lib/langchain/tool/database/database.json +0 -46
  37. data/lib/langchain/tool/file_system/file_system.json +0 -57
  38. data/lib/langchain/tool/file_system/file_system.rb +0 -32
  39. data/lib/langchain/tool/google_search/google_search.json +0 -19
  40. data/lib/langchain/tool/news_retriever/news_retriever.json +0 -122
  41. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +0 -19
  42. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.rb +0 -37
  43. data/lib/langchain/tool/tavily/tavily.json +0 -54
  44. data/lib/langchain/tool/vectorsearch/vectorsearch.json +0 -24
  45. data/lib/langchain/tool/vectorsearch/vectorsearch.rb +0 -36
  46. data/lib/langchain/tool/weather/weather.json +0 -19
  47. data/lib/langchain/tool/wikipedia/wikipedia.json +0 -19
@@ -1,16 +1,28 @@
1
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
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
- class GoogleSearch < Base
5
- #
6
- # Wrapper around SerpApi's Google Search API
7
- #
8
- # Gem requirements:
9
- # gem "google_search_results", "~> 2.0.0"
10
- #
11
- # Usage:
12
- # search = Langchain::Tool::GoogleSearch.new(api_key: "YOUR_API_KEY")
13
- # search.execute(input: "What is the capital of France?")
14
- #
15
- NAME = "google_search"
16
- ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
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
- class NewsRetriever < Base
5
- #
6
- # A tool that retrieves latest news from various sources via https://newsapi.org/.
7
- # An API key needs to be obtained from https://newsapi.org/ to use this tool.
8
- #
9
- # Usage:
10
- # news_retriever = Langchain::Tool::NewsRetriever.new(api_key: ENV["NEWS_API_KEY"])
11
- #
12
- NAME = "news_retriever"
13
- ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
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-seperated 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.
24
- # @param domains [String] A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to restrict the search to.
25
- # @param exclude_domains [String] A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to remove from the results.
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-seperated string of identifiers for the news sources or blogs you want headlines from. Use the /sources endpoint to locate these programmatically.
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
- class Tavily < Base
5
- #
6
- # Tavily Search is a robust search API tailored specifically for LLM Agents.
7
- # It seamlessly integrates with diverse data sources to ensure a superior, relevant search experience.
8
- #
9
- # Usage:
10
- # tavily = Langchain::Tool::Tavily.new(api_key: ENV["TAVILY_API_KEY"])
11
- #
12
- NAME = "tavily"
13
- ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
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
- class Weather < Base
5
- #
6
- # A weather tool that gets current weather data
7
- #
8
- # Current weather data is free for 1000 calls per day (https://home.openweathermap.org/api_keys)
9
- # Forecast and historical data require registration with credit card, so not supported yet.
10
- #
11
- # Gem requirements:
12
- # gem "open-weather-ruby-client", "~> 0.3.0"
13
- # api_key: https://home.openweathermap.org/api_keys
14
- #
15
- # Usage:
16
- # weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
17
- # weather.execute(input: "Boston, MA; imperial")
18
- #
19
- NAME = "weather"
20
- ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
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
- class Wikipedia < Base
5
- #
6
- # Tool that adds the capability to search using the Wikipedia API
7
- #
8
- # Gem requirements:
9
- # gem "wikipedia-client", "~> 1.17.0"
10
- #
11
- # Usage:
12
- # wikipedia = Langchain::Tool::Wikipedia.new
13
- # wikipedia.execute(input: "The Roman Empire")
14
- #
15
- NAME = "wikipedia"
16
- ANNOTATIONS_PATH = Langchain.root.join("./langchain/tool/#{NAME}/#{NAME}.json").to_path
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