omniai-tools 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbc1dae9ab7fe238fadd4c48d660f342e851b1803e3e33e22c487ceabd3bf32b
4
- data.tar.gz: cf37310371a1711adaedea7609ce340552c7858faab4d9b60c6d26919864490a
3
+ metadata.gz: d27871f61c1d70be893cce085bf33c0b3d9280686ff1a0ba64f35835b7bf10d4
4
+ data.tar.gz: 6dfddb639a3cbb99069cbb890bb5d8529bf152e5c246bcc90aaf344505758ea9
5
5
  SHA512:
6
- metadata.gz: 766b80d9ab2b3bf459eeee00473c69f352fd4e61b70a4be9531a76b164d1e1fbc7373fe634033f19c519dc56098b77e536431d53a46bd91bde017887ed91f61a
7
- data.tar.gz: 1fc747fda798976e4c13e4c0fd208a83253b61d8f1b473029e085308f7603f7d20e616f32c9b0e564952e6f48cf4e1eb10cc5570d8d6525c6c4a9728449e5b08
6
+ metadata.gz: ff02138674bc5176e869badb4268530d025e5543ca993eaea8aa74ee2ad114592bd5ac3e8ecd8ad8a882ec0af6dd57b9d74448b04669d3fe8295db3c9176e7b8
7
+ data.tar.gz: c8a79aa2542b81475148b878ab7785d8bea1b32e4649718bd2a5d4684ba897e010da3d392ce9f3da38374f51e10ea2fc074a3a07a2392172a6c68e93f9298aa5
data/Gemfile CHANGED
@@ -6,6 +6,11 @@ gemspec
6
6
 
7
7
  gem "factory_bot"
8
8
  gem "irb"
9
+ gem "nokogiri"
10
+ gem "omniai-anthropic"
11
+ gem "omniai-google"
12
+ gem "omniai-mistral"
13
+ gem "omniai-openai"
9
14
  gem "rake"
10
15
  gem "redcarpet"
11
16
  gem "rspec"
@@ -16,4 +21,6 @@ gem "rubocop-factory_bot"
16
21
  gem "rubocop-rake"
17
22
  gem "rubocop-rspec"
18
23
  gem "simplecov"
24
+ gem "sqlite3"
25
+ gem "watir"
19
26
  gem "yard"
data/README.md CHANGED
@@ -8,6 +8,108 @@
8
8
 
9
9
  `OmniAI::Tools` is a library of pre-built tools to simplify integrating common tasks with [OmniAI](https://github.com/ksylvest/omniai).
10
10
 
11
+ ## Browser
12
+
13
+ Database tools are focused on running SQL statements:
14
+
15
+ ```ruby
16
+ require "omniai/openai"
17
+ require "omniai/tools"
18
+
19
+ require "watir"
20
+
21
+ browser = Watir::Browser.new(:chrome)
22
+
23
+ client = OmniAI::OpenAI::Client.new
24
+ logger = Logger.new($stdout)
25
+
26
+ tools = [
27
+ OmniAI::Tools::Browser::VisitTool,
28
+ OmniAI::Tools::Browser::InspectTool,
29
+ OmniAI::Tools::Browser::ButtonClickTool,
30
+ OmniAI::Tools::Browser::LinkClickTool,
31
+ OmniAI::Tools::Browser::TextFieldAreaSetTool,
32
+ ].map { |klass| klass.new(browser:, logger:) }
33
+
34
+ puts "Type 'exit' or 'quit' to leave."
35
+
36
+ prompt = OmniAI::Chat::Prompt.build do |builder|
37
+ builder.system <<~TEXT
38
+ You are tasked with assisting a user in browsing the web.
39
+ TEXT
40
+ end
41
+
42
+ loop do
43
+ print "# "
44
+ text = gets.strip
45
+ break if %w[exit quit].include?(text)
46
+
47
+ prompt.user(text)
48
+ response = client.chat(prompt, stream: $stdout, tools:)
49
+ prompt.assistant(response.text)
50
+ end
51
+ ```
52
+
53
+ ```
54
+ Type 'exit' or 'quit' to leave.
55
+ # Visit news.ycombinator.com and list the top 5 posts.
56
+
57
+ [browser] OmniAI::Tools::Browser::VisitTool#execute url="https://news.ycombinator.com"
58
+ [browser] OmniAI::Tools::Browser::InspectTool#execute
59
+
60
+ Here are the top 5 posts on Hacker News right now:
61
+
62
+ ...
63
+ ```
64
+
65
+ ## Database
66
+
67
+ Database tools are focused on running SQL statements:
68
+
69
+ ```ruby
70
+ require "omniai/openai"
71
+ require "omniai/tools"
72
+
73
+ require "sqlite3"
74
+
75
+ db = SQLite3::Database.new(":memory:")
76
+
77
+ client = OmniAI::OpenAI::Client.new
78
+ logger = Logger.new($stdout)
79
+
80
+ tools = [
81
+ OmniAI::Tools::Database::SqliteTool.new(logger:, db:),
82
+ ]
83
+
84
+ puts "Type 'exit' or 'quit' to leave."
85
+
86
+ prompt = OmniAI::Chat::Prompt.build do |builder|
87
+ builder.system "Use tools to manage a database as requested."
88
+ end
89
+
90
+ loop do
91
+ print "# "
92
+ text = gets.strip
93
+ break if %w[exit quit].include?(text)
94
+
95
+ prompt.user(text)
96
+ response = client.chat(prompt, stream: $stdout, tools:)
97
+ prompt.assistant(response.text)
98
+ end
99
+ ```
100
+
101
+ ```
102
+ Type 'exit' or 'quit' to leave.
103
+ # Generate tables for artists, albums, songs, bands, members and populate it with data surrounding the Beatles.
104
+
105
+ CREATE TABLE artists (id INTEGER PRIMARY KEY, name TEXT NOT NULL)
106
+ CREATE TABLE bands (id INTEGER PRIMARY KEY, name TEXT NOT NULL)
107
+ CREATE TABLE members (id INTEGER PRIMARY KEY, artist_id INTEGER, band_id INTEGER, FOREIGN KEY(artist_id) REFERENCES artists(id), FOREIGNKEY(band_id) REFERENCES bands(id))"
108
+ CREATE TABLE albums (id INTEGER PRIMARY KEY, title TEXT NOT NULL, band_id INTEGER, FOREIGN KEY(band_id) REFERENCES bands(id))
109
+ CREATE TABLE songs (id INTEGER PRIMARY KEY, title TEXT NOT NULL, album_id INTEGER, FOREIGN KEY(album_id) REFERENCES albums(id))
110
+ ...
111
+ ```
112
+
11
113
  ## Disk
12
114
 
13
115
  Disk tools are focused on creating, updating, and deleting files and directories within a "root":
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "watir"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # class SeleniumTool < BaseTool
10
+ # # ...
11
+ # end
12
+ class BaseTool < OmniAI::Tool
13
+ # @param logger [IO] An optional logger for debugging executed commands.
14
+ # @param browser [Watir::Browser]
15
+ def initialize(browser:, logger: Logger.new(IO::NULL))
16
+ super()
17
+ @logger = logger
18
+ @browser = browser
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # browser = Watir::Browser.new(:chrome)
10
+ # tool = OmniAI::Tools::Browser::VisitTool.new(browser:)
11
+ # tool.click_link(selector: "link_id")
12
+ class ButtonClickTool < BaseTool
13
+ description "A browser automation tool for clicking a specific button."
14
+
15
+ parameter :selector, :string, description: "The ID or text of the button to interact with."
16
+
17
+ required %i[selector]
18
+
19
+ # @param to [String] The URL to navigate to.
20
+ def execute(selector:)
21
+ @logger.info("#{self.class.name}##{__method__} selector=#{selector.inspect}")
22
+
23
+ element = find(text: selector) || find(id: selector)
24
+
25
+ return { error: "unknown selector=#{selector}" } if element.nil?
26
+
27
+ element.click
28
+ end
29
+
30
+ protected
31
+
32
+ # @return [Watir::Anchor, nil]
33
+ def find(selector)
34
+ element = @browser.button(selector)
35
+ element if element.exists?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # browser = Watir::Browser.new(:chrome)
10
+ # tool = OmniAI::Tools::Browser::InspectTool.new(browser:)
11
+ # tool.execute
12
+ class InspectTool < BaseTool
13
+ description "A browser automation tool for viewing the HTML for the browser."
14
+
15
+ # @return [String]
16
+ def execute
17
+ @logger.info("#{self.class.name}##{__method__}")
18
+
19
+ html = @browser.html
20
+ doc = Nokogiri::HTML(html)
21
+
22
+ doc.css("link").each(&:remove)
23
+ doc.css("style").each(&:remove)
24
+ doc.css("script").each(&:remove)
25
+
26
+ doc.to_html
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # browser = Watir::Browser.new(:chrome)
10
+ # tool = OmniAI::Tools::Browser::VisitTool.new(browser:)
11
+ # tool.click_link(selector: "link_id")
12
+ class LinkClickTool < BaseTool
13
+ description "A browser automation tool for clicking a specific link."
14
+
15
+ parameter :selector, :string, description: "The ID or text of the link to interact with."
16
+
17
+ # @param to [String] The ID or text of the link to interact with.
18
+ def execute(selector:)
19
+ @logger.info("#{self.class.name}##{__method__} selector=#{selector.inspect}")
20
+
21
+ element = find(text: selector) || find(value: selector) || find(id: selector)
22
+
23
+ return { error: "unknown selector=#{selector}" } if element.nil?
24
+
25
+ element.click
26
+ end
27
+
28
+ protected
29
+
30
+ # @return [Watir::Anchor, nil]
31
+ def find(selector)
32
+ element = @browser.a(selector)
33
+ element if element.exists?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # browser = Watir::Browser.new(:chrome)
10
+ # tool = OmniAI::Tools::Browser::TextFieldSetTool.new(browser:)
11
+ # tool.execute(selector: "...", text: "...")
12
+ class TextFieldAreaSetTool < BaseTool
13
+ description "A browser automation tool for clicking a specific link."
14
+
15
+ parameter :selector, :string, description: "The ID / name of the text field / area to interact with."
16
+ parameter :text, :string, description: "The text to set."
17
+
18
+ required %i[selector]
19
+
20
+ # @param selector [String] The ID / name of the text field / text area to interact with.
21
+ # @param text [String] The text to set.
22
+ def execute(selector:, text:)
23
+ @logger.info("#{self.class.name}##{__method__} selector=#{selector.inspect} text=#{text.inspect}")
24
+
25
+ element = find(id: selector) || find(name: selector)
26
+
27
+ return { error: "unknown selector=#{selector}" } if element.nil?
28
+
29
+ element.set(text)
30
+ end
31
+
32
+ protected
33
+
34
+ # @param selector [Hash]
35
+ #
36
+ # @return [Watir::TextArea, Watir::TextField, nil]
37
+ def find(selector)
38
+ find_text_area(selector) || find_text_field(selector)
39
+ end
40
+
41
+ # @param selector [Hash]
42
+ #
43
+ # @return [Watir::TextArea, nil]
44
+ def find_text_area(selector)
45
+ element = @browser.textarea(selector)
46
+ element if element.exists?
47
+ end
48
+
49
+ # @param selector [Hash]
50
+ #
51
+ # @return [Watir::TextField, nil]
52
+ def find_text_field(selector)
53
+ element = @browser.text_field(selector)
54
+ element if element.exists?
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Browser
8
+ # @example
9
+ # driver = Selenium::WebDriver.for :chrome
10
+ # tool = OmniAI::Tools::Browser::VisitTool.new(driver:)
11
+ # tool.execute(to: "https://news.ycombinator.com")
12
+ class VisitTool < BaseTool
13
+ description "A browser automation tool for navigating to a specific URL."
14
+
15
+ parameter :url, :string, description: "A URL (e.g. https://news.ycombinator.com)."
16
+
17
+ required %i[url]
18
+
19
+ # @param url [String] A URL (e.g. https://news.ycombinator.com).
20
+ def execute(url:)
21
+ @logger.info("#{self.class.name}##{__method__} url=#{url.inspect}")
22
+
23
+ @browser.goto(url)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Database
8
+ # @example
9
+ # tool = OmniAI::Tools::Database::SqliteTool.new
10
+ # tool.execute(path: "./foo/bar")
11
+ class BaseTool < OmniAI::Tool
12
+ # @param logger [IO] An optional logger for debugging executed commands.
13
+ def initialize(logger: Logger.new(IO::NULL))
14
+ super()
15
+ @logger = logger
16
+ end
17
+
18
+ # @example
19
+ # tool = OmniAI::Tools::Database::BaseTool.new
20
+ # tool.execute(statements: ["SELECT * FROM people"])
21
+ #
22
+ # @param statements [Array<String>]
23
+ #
24
+ # @return [Array<Hash>]
25
+ def execute(statements:)
26
+ [].tap do |executions|
27
+ statements.map do |statement|
28
+ execution = perform(statement:)
29
+ executions << execution
30
+ break unless execution[:status].eql?(:ok)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module OmniAI
6
+ module Tools
7
+ module Database
8
+ # @example
9
+ # tool = OmniAI::Tools::Database::SqliteTool.new
10
+ # tool.execute(path: "./foo/bar")
11
+ class SqliteTool < BaseTool
12
+ description <<~TEXT
13
+ Executes SQL commands (INSERT / UPDATE / SELECT / etc) on a database.
14
+
15
+ Example:
16
+
17
+ STATEMENTS:
18
+
19
+ [
20
+ 'CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT NOT NULL)',
21
+ 'INSERT INTO people (name) VALUES ('John')',
22
+ 'INSERT INTO people (name) VALUES ('Paul')',
23
+ 'SELECT * FROM people',
24
+ 'DROP TABLE people'
25
+ ]
26
+
27
+ RESULT:
28
+
29
+ [
30
+ {
31
+ "status": "OK",
32
+ "statement": "CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
33
+ "result": "..."
34
+ },
35
+ {
36
+ "status": "OK",
37
+ "statement": "INSERT INTO people (name) VALUES ('John')"
38
+ "result": "..."
39
+ },
40
+ {
41
+ "status": "OK",
42
+ "statement": "INSERT INTO people (name) VALUES ('Paul')",
43
+ "result": "..."
44
+ },
45
+ {
46
+ "status": "OK",
47
+ "statement": "SELECT * FROM people",
48
+ "result": "..."
49
+ },
50
+ {
51
+ "status": "OK",
52
+ "statement": "DROP TABLE people",
53
+ "result": "..."
54
+ }
55
+ ]
56
+ TEXT
57
+
58
+ parameter(
59
+ :statements,
60
+ :array,
61
+ description: "A list of SQL statements to run sequentially.",
62
+ items: OmniAI::Schema.string(description: 'A SQL statement to run (e.g. "SELECT * FROM ...").')
63
+ )
64
+
65
+ required %i[statements]
66
+
67
+ # @param logger [IO] An optional logger for debugging executed commands.
68
+ # @param db [SQLite3::Database] A sqlite database.
69
+ def initialize(db:, logger: Logger.new(IO::NULL))
70
+ super(logger:)
71
+ @db = db
72
+ end
73
+
74
+ # @example
75
+ # tool = OmniAI::Tools::Database::BaseTool.new
76
+ # tool.execute(statements: ["SELECT * FROM people"])
77
+ #
78
+ # @param statements [Array<String>]
79
+ #
80
+ # @return [Array<Hash>]
81
+ def execute(statements:)
82
+ @logger.info("#{self.class.name}#{__method__} statements=#{statements.inspect}")
83
+
84
+ [].tap do |executions|
85
+ statements.map do |statement|
86
+ execution = perform(statement:)
87
+ executions << execution
88
+ break unless execution[:status].eql?(:ok)
89
+ end
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ # @param statement [String]
96
+ #
97
+ # @return [Hash]
98
+ def perform(statement:)
99
+ result = @db.execute2(statement)
100
+
101
+ { status: :ok, statement:, result: }
102
+ rescue ::SQLite3::Exception => e
103
+ @logger.warn("ERROR: #{e.message}")
104
+
105
+ { status: :error, statement:, result: e.message }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ module Tools
5
+ module Disk
6
+ # @example
7
+ # tool = OmniAI::Tools::Disk::DirectoryCreateTool.new(root: "./project")
8
+ # tool.execute(path: "./foo/bar")
9
+ class DirectoryCreateTool < BaseTool
10
+ description "Creates a directory."
11
+
12
+ parameter :old_path, :string, description: "a path (e.g. `./old`)"
13
+ parameter :new_path, :string, description: "a path (e.g. `./new`)"
14
+
15
+ required %i[old_path new_path]
16
+
17
+ # @param old_path [String]
18
+ # @param new_path [String]
19
+ #
20
+ # @return [String]
21
+ def execute(old_path:, new_path:)
22
+ @logger.info("#{self.class.name}#execute path=#{path.inspect}")
23
+
24
+ FileUtils.mv(old_path, new_path)
25
+ rescue SecurityError => e
26
+ @logger.error(e.message)
27
+ raise e
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -14,7 +14,7 @@ module OmniAI
14
14
  parameter :service, :string, description: "The service to run the command on (e.g. `app`)."
15
15
  parameter :command, :string, description: "The command to run (e.g. `rspec`)."
16
16
  parameter :args, :array, description: "The arguments for the command.",
17
- items: OmniAI::Tool::Property.string(description: "An argument for the command (e.g. `spec/main_spec.rb`).")
17
+ items: OmniAI::Schema.string(description: "An argument for the command (e.g. `spec/main_spec.rb`).")
18
18
 
19
19
  # @param service [String]
20
20
  # @param command [String]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module Tools
5
- VERSION = "0.3.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-23 00:00:00.000000000 Z
10
+ date: 2025-05-15 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: omniai
@@ -49,9 +49,18 @@ files:
49
49
  - bin/console
50
50
  - bin/setup
51
51
  - lib/omniai/tools.rb
52
+ - lib/omniai/tools/browser/base_tool.rb
53
+ - lib/omniai/tools/browser/button_click_tool.rb
54
+ - lib/omniai/tools/browser/inspect_tool.rb
55
+ - lib/omniai/tools/browser/link_click_tool.rb
56
+ - lib/omniai/tools/browser/text_field_area_set_tool.rb
57
+ - lib/omniai/tools/browser/visit_tool.rb
58
+ - lib/omniai/tools/database/base_tool.rb
59
+ - lib/omniai/tools/database/sqlite_tool.rb
52
60
  - lib/omniai/tools/disk/base_tool.rb
53
61
  - lib/omniai/tools/disk/directory_create_tool.rb
54
62
  - lib/omniai/tools/disk/directory_delete_tool.rb
63
+ - lib/omniai/tools/disk/directory_move_tool.rb
55
64
  - lib/omniai/tools/disk/file_create_tool.rb
56
65
  - lib/omniai/tools/disk/file_delete_tool.rb
57
66
  - lib/omniai/tools/disk/file_move_tool.rb