langchainrb 0.14.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -2
  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 +11 -5
  6. data/lib/langchain/evals/ragas/faithfulness.rb +5 -1
  7. data/lib/langchain/llm/google_gemini.rb +1 -1
  8. data/lib/langchain/llm/ollama.rb +23 -17
  9. data/lib/langchain/llm/openai.rb +1 -1
  10. data/lib/langchain/llm/response/ollama_response.rb +1 -15
  11. data/lib/langchain/llm/unified_parameters.rb +2 -2
  12. data/lib/langchain/tool/calculator.rb +38 -0
  13. data/lib/langchain/tool/{database/database.rb → database.rb} +24 -12
  14. data/lib/langchain/tool/file_system.rb +44 -0
  15. data/lib/langchain/tool/{google_search/google_search.rb → google_search.rb} +17 -23
  16. data/lib/langchain/tool/{news_retriever/news_retriever.rb → news_retriever.rb} +41 -14
  17. data/lib/langchain/tool/ruby_code_interpreter.rb +41 -0
  18. data/lib/langchain/tool/{tavily/tavily.rb → tavily.rb} +24 -10
  19. data/lib/langchain/tool/vectorsearch.rb +40 -0
  20. data/lib/langchain/tool/weather.rb +109 -0
  21. data/lib/langchain/tool/{wikipedia/wikipedia.rb → wikipedia.rb} +17 -13
  22. data/lib/langchain/tool_definition.rb +212 -0
  23. data/lib/langchain/utils/colorizer.rb +19 -0
  24. data/lib/langchain/utils/hash_transformer.rb +9 -17
  25. data/lib/langchain/utils/to_boolean.rb +27 -0
  26. data/lib/langchain/vectorsearch/chroma.rb +2 -2
  27. data/lib/langchain/vectorsearch/elasticsearch.rb +2 -2
  28. data/lib/langchain/vectorsearch/epsilla.rb +3 -3
  29. data/lib/langchain/vectorsearch/milvus.rb +2 -2
  30. data/lib/langchain/vectorsearch/pgvector.rb +2 -2
  31. data/lib/langchain/vectorsearch/pinecone.rb +2 -2
  32. data/lib/langchain/vectorsearch/qdrant.rb +2 -2
  33. data/lib/langchain/vectorsearch/weaviate.rb +4 -4
  34. data/lib/langchain/version.rb +1 -1
  35. data/lib/langchain.rb +1 -2
  36. metadata +18 -54
  37. data/lib/langchain/tool/base.rb +0 -107
  38. data/lib/langchain/tool/calculator/calculator.json +0 -19
  39. data/lib/langchain/tool/calculator/calculator.rb +0 -34
  40. data/lib/langchain/tool/database/database.json +0 -46
  41. data/lib/langchain/tool/file_system/file_system.json +0 -57
  42. data/lib/langchain/tool/file_system/file_system.rb +0 -32
  43. data/lib/langchain/tool/google_search/google_search.json +0 -19
  44. data/lib/langchain/tool/news_retriever/news_retriever.json +0 -122
  45. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +0 -19
  46. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.rb +0 -37
  47. data/lib/langchain/tool/tavily/tavily.json +0 -54
  48. data/lib/langchain/tool/vectorsearch/vectorsearch.json +0 -24
  49. data/lib/langchain/tool/vectorsearch/vectorsearch.rb +0 -36
  50. data/lib/langchain/tool/weather/weather.json +0 -19
  51. data/lib/langchain/tool/weather/weather.rb +0 -55
  52. data/lib/langchain/tool/wikipedia/wikipedia.json +0 -19
@@ -77,8 +77,8 @@ module Langchain::LLM
77
77
  @parameters.to_h
78
78
  end
79
79
 
80
- def each(&)
81
- to_params.each(&)
80
+ def each(&block)
81
+ to_params.each(&block)
82
82
  end
83
83
 
84
84
  def <=>(other)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain::Tool
4
+ #
5
+ # A calculator tool that falls back to the Google calculator widget
6
+ #
7
+ # Gem requirements:
8
+ # gem "eqn", "~> 1.6.5"
9
+ # gem "google_search_results", "~> 2.0.0"
10
+ #
11
+ # Usage:
12
+ # calculator = Langchain::Tool::Calculator.new
13
+ #
14
+ class Calculator
15
+ extend Langchain::ToolDefinition
16
+ include Langchain::DependencyHelper
17
+
18
+ define_function :execute, 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" do
19
+ property :input, type: "string", description: "Math expression", required: true
20
+ end
21
+
22
+ def initialize
23
+ depends_on "eqn"
24
+ end
25
+
26
+ # 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
27
+ #
28
+ # @param input [String] math expression
29
+ # @return [String] Answer
30
+ def execute(input:)
31
+ Langchain.logger.info("Executing \"#{input}\"", for: self.class)
32
+
33
+ Eqn::Calculator.calc(input)
34
+ rescue Eqn::ParseError, Eqn::NoVariableValueError
35
+ "\"#{input}\" is an invalid mathematical expression"
36
+ end
37
+ end
38
+ end
@@ -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
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain::Tool
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
+ # Usage:
11
+ # weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
12
+ # assistant = Langchain::Assistant.new(
13
+ # llm: llm,
14
+ # tools: [weather]
15
+ # )
16
+ #
17
+ class Weather
18
+ extend Langchain::ToolDefinition
19
+
20
+ define_function :get_current_weather, description: "Returns current weather for a city" do
21
+ property :city,
22
+ type: "string",
23
+ description: "City name",
24
+ required: true
25
+ property :state_code,
26
+ type: "string",
27
+ description: "State code",
28
+ required: true
29
+ property :country_code,
30
+ type: "string",
31
+ description: "Country code",
32
+ required: false
33
+ property :units,
34
+ type: "string",
35
+ description: "Units for temperature (imperial or metric). Default: \"imperial\"",
36
+ enum: ["imperial", "metric", "standard"],
37
+ required: false
38
+ end
39
+
40
+ def initialize(api_key:)
41
+ @api_key = api_key
42
+ end
43
+
44
+ def get_current_weather(city:, state_code:, country_code: nil, units: "imperial")
45
+ validate_input(city: city, state_code: state_code, country_code: country_code, units: units)
46
+
47
+ Langchain.logger.info("get_current_weather", for: self.class, city:, state_code:, country_code:, units:)
48
+
49
+ fetch_current_weather(city: city, state_code: state_code, country_code: country_code, units: units)
50
+ end
51
+
52
+ private
53
+
54
+ def fetch_current_weather(city:, state_code:, country_code:, units:)
55
+ params = {appid: @api_key, q: [city, state_code, country_code].compact.join(","), units: units}
56
+
57
+ location_response = send_request(path: "geo/1.0/direct", params: params.except(:units))
58
+ return location_response if location_response.is_a?(String) # Error occurred
59
+
60
+ location = location_response.first
61
+ return "Location not found" unless location
62
+
63
+ params = params.merge(lat: location["lat"], lon: location["lon"]).except(:q)
64
+ weather_data = send_request(path: "data/2.5/weather", params: params)
65
+
66
+ parse_weather_response(weather_data, units)
67
+ end
68
+
69
+ def send_request(path:, params:)
70
+ uri = URI.parse("https://api.openweathermap.org/#{path}?#{URI.encode_www_form(params)}")
71
+ http = Net::HTTP.new(uri.host, uri.port)
72
+ http.use_ssl = true
73
+
74
+ request = Net::HTTP::Get.new(uri.request_uri)
75
+ request["Content-Type"] = "application/json"
76
+
77
+ Langchain.logger.info("Sending request to OpenWeatherMap API", path: path, params: params.except(:appid))
78
+ response = http.request(request)
79
+ Langchain.logger.info("Received response from OpenWeatherMap API", status: response.code)
80
+
81
+ if response.code == "200"
82
+ JSON.parse(response.body)
83
+ else
84
+ "API request failed: #{response.code} - #{response.message}"
85
+ end
86
+ end
87
+
88
+ def validate_input(city:, state_code:, country_code:, units:)
89
+ raise ArgumentError, "City name cannot be empty" if city.to_s.strip.empty?
90
+ raise ArgumentError, "State code cannot be empty" if state_code.to_s.strip.empty?
91
+ raise ArgumentError, "Invalid units. Use \"imperial\", \"standard\" or \"metric\"" unless ["imperial", "metric", "standard"].include?(units)
92
+ end
93
+
94
+ def parse_weather_response(response, units)
95
+ temp_unit = case units
96
+ when "standard" then "K"
97
+ when "metric" then "°C"
98
+ when "imperial" then "°F"
99
+ end
100
+ speed_unit = (units == "imperial") ? "mph" : "m/s"
101
+ {
102
+ temperature: "#{response["main"]["temp"]} #{temp_unit}",
103
+ humidity: "#{response["main"]["humidity"]}%",
104
+ description: response["weather"][0]["description"],
105
+ wind_speed: "#{response["wind"]["speed"]} #{speed_unit}"
106
+ }
107
+ end
108
+ end
109
+ end
@@ -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