├── .rspec ├── .rubocop.yml ├── lib ├── minds │ ├── version.rb │ ├── errors.rb │ ├── config │ │ └── base.rb │ ├── client.rb │ ├── rest_client.rb │ ├── validators.rb │ ├── datasources.rb │ └── minds.rb └── minds.rb ├── sig └── minds.rbs ├── bin ├── setup ├── console └── release ├── .gitignore ├── Rakefile ├── Gemfile ├── .github └── workflows │ └── main.yml ├── spec ├── rest_client_spec.rb ├── spec_helper.rb ├── minds_spec.rb ├── minds │ └── integration │ │ ├── minds_spec.rb │ │ └── datasources_spec.rb └── fixtures │ └── vcr_cassettes │ ├── datasources │ ├── destroy_datasource.yml │ ├── create_datasource.yml │ └── all_datasource.yml │ └── minds │ ├── find_mind.yml │ ├── completion.yml │ └── create_mind.yml ├── LICENSE.txt ├── CHANGELOG.md ├── minds_sdk.gemspec ├── Gemfile.lock ├── CODE_OF_CONDUCT.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Omakase Ruby styling for Rails 2 | inherit_gem: 3 | rubocop-rails-omakase: rubocop.yml 4 | -------------------------------------------------------------------------------- /lib/minds/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Minds 4 | VERSION = "1.1.0" 5 | end 6 | -------------------------------------------------------------------------------- /sig/minds.rbs: -------------------------------------------------------------------------------- 1 | module Minds 2 | VERSION: String 3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides 4 | end 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | .tool-versions 13 | .env 14 | 15 | .idea/ 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require "rubocop/rake_task" 9 | 10 | RuboCop::RakeTask.new 11 | 12 | task default: %i[spec rubocop] 13 | -------------------------------------------------------------------------------- /lib/minds/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Minds 4 | class Error < StandardError; end 5 | class ObjectNotFound < Error; end 6 | class ObjectNotSupported < Error; end 7 | class MindNameInvalid < Error; end 8 | class DatasourceNameInvalid < Error; end 9 | end 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "minds" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in minds_sdk.gemspec 6 | gemspec 7 | 8 | gem "faraday", "~> 2.12" 9 | gem "ruby-openai", "~> 7.3", ">= 7.3.1" 10 | gem "rake", "~> 13.0" 11 | gem "rspec", "~> 3.0" 12 | gem "vcr", "~> 6.3", ">= 6.3.1" 13 | gem "webmock", "~> 3.24" 14 | gem "dotenv", "~> 3.1", ">= 3.1.4" 15 | 16 | gem "rubocop", "~> 1.21" 17 | 18 | gem "rubocop-rails-omakase", require: false, group: [ :development ] 19 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | if [ -z "$VERSION" ]; then 6 | echo "Usage: $0 " 7 | exit 1 8 | fi 9 | 10 | printf "# frozen_string_literal: true\n\nmodule Minds\n VERSION = \"$VERSION\"\nend\n" > ./lib/minds/version.rb 11 | bundle 12 | git add Gemfile.lock lib/minds/version.rb 13 | git commit -m "Bump version for $VERSION" 14 | git push 15 | git tag v$VERSION 16 | git push --tags 17 | gem build minds_sdk.gemspec 18 | gem push "minds_sdk-$VERSION.gem" --host https://rubygems.org 19 | rm "minds_sdk-$VERSION.gem" 20 | -------------------------------------------------------------------------------- /lib/minds/config/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Minds 4 | module Config 5 | class Base 6 | attr_accessor :base_url, :api_key, :log_errors, :api_version 7 | DEFAULT_BASE_URL = "https://mdb.ai".freeze 8 | DEFAULT_LOG_ERRORS = false 9 | DEFAULT_API_VERSION = "api".freeze 10 | 11 | def initialize 12 | @api_key = nil 13 | @base_url = DEFAULT_BASE_URL 14 | @log_errors = DEFAULT_LOG_ERRORS 15 | @api_version = DEFAULT_API_VERSION 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | name: Ruby ${{ matrix.ruby }} 14 | strategy: 15 | matrix: 16 | ruby: 17 | - '3.2.2' 18 | env: 19 | MINDS_API_KEY: ${{ secrets.MINDS_API_KEY }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby }} 26 | bundler-cache: true 27 | - name: Run the default task 28 | run: bundle exec rake 29 | -------------------------------------------------------------------------------- /spec/rest_client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Minds::Client do 4 | describe '#uri' do 5 | it 'prefixes api version when base url lacks version path but contains substring in host' do 6 | client = described_class.new(api_key: 'test', base_url: 'https://api.example.com') 7 | expect(client.send(:uri, path: 'datasources')).to eq('/api/datasources') 8 | end 9 | 10 | it 'does not prefix api version when base url already includes version path' do 11 | client = described_class.new(api_key: 'test', base_url: 'https://example.com/api') 12 | expect(client.send(:uri, path: 'datasources')).to eq('datasources') 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "minds" 4 | require "vcr" 5 | require "webmock/rspec" 6 | require "dotenv/load" 7 | 8 | VCR.configure do |config| 9 | config.cassette_library_dir = "spec/fixtures/vcr_cassettes" 10 | config.hook_into :webmock 11 | config.configure_rspec_metadata! 12 | config.filter_sensitive_data("") { ENV.fetch("MINDS_API_KEY") } 13 | config.filter_sensitive_data("") { Minds::Config::Base::DEFAULT_BASE_URL } 14 | config.allow_http_connections_when_no_cassette = true 15 | end 16 | 17 | RSpec.configure do |config| 18 | # Enable flags like --only-failures and --next-failure 19 | config.example_status_persistence_file_path = ".rspec_status" 20 | 21 | # Disable RSpec exposing methods globally on `Module` and `main` 22 | config.disable_monkey_patching! 23 | 24 | config.expect_with :rspec do |c| 25 | c.syntax = :expect 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/minds_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Minds do 4 | it "has a version number" do 5 | expect(Minds::VERSION).not_to be nil 6 | end 7 | 8 | it "does something useful" do 9 | client = Minds::Client.new(api_key: "test_api_key") 10 | expect(client).to be_a(Minds::Client) 11 | end 12 | 13 | describe Minds::Client do 14 | it "initializes with an API key" do 15 | client = Minds::Client.new(api_key: "test_api_key") 16 | expect(client.api_key).to eq("test_api_key") 17 | end 18 | 19 | it "uses default base URL if not provided" do 20 | client = Minds::Client.new(api_key: "test_api_key") 21 | expect(client.base_url).to eq(Minds::Config::Base::DEFAULT_BASE_URL) 22 | end 23 | 24 | it "allows setting a custom base URL" do 25 | custom_url = "https://custom.api.com" 26 | client = Minds::Client.new(api_key: "test_api_key", base_url: custom_url) 27 | expect(client.base_url).to eq(custom_url) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/minds/integration/minds_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Minds Integration", :vcr do 2 | let(:client) { Minds::Client.new(api_key: ENV.fetch("MINDS_API_KEY"), log_errors: true) } 3 | let(:minds) { Minds::Minds.new(client: client) } 4 | 5 | describe "#create" do 6 | it "create a new mind" do 7 | VCR.use_cassette("minds/create_mind") do 8 | mind = minds.create( 9 | name: "test_mind" 10 | ) 11 | 12 | expect(mind).to be_a(Minds::Mind) 13 | expect(mind.name).to eq("test_mind") 14 | end 15 | end 16 | end 17 | 18 | describe "#completion" do 19 | let(:mind) do 20 | VCR.use_cassette("minds/find_mind") do 21 | minds.find("test_recommend") 22 | end 23 | end 24 | 25 | it "gets completion response" do 26 | VCR.use_cassette("minds/completion") do 27 | response = mind.completion(message: "What is Mind") 28 | expect(response).to be_a(String) 29 | expect(response).not_to be_empty 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 tms-tungnt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [1.0.0] - 2024-10-14 4 | 5 | ### Added 6 | - Initial release of the Minds Ruby SDK 7 | - Implemented `Minds::Client` for configuring and initializing the SDK 8 | - Added `Minds::Datasources` for managing data sources: 9 | - `create`: Create a new datasource 10 | - `all`: List all datasources 11 | - `find`: Get a datasource by name 12 | - `destroy`: Delete a datasource 13 | - Added `Minds::Minds` for managing minds: 14 | - `all`: List all minds 15 | - `find`: Get a mind by name 16 | - `create`: Create a new mind 17 | - `destroy`: Delete a mind 18 | - Implemented `Minds::Mind` class with methods: 19 | - `update`: Update mind properties 20 | - `add_datasources`: Add a datasource to a mind 21 | - `destroy_datasources`: Remove a datasource from a mind 22 | - `completion`: Call mind completion (with streaming support) 23 | - Added support for various datasource types through `Minds::DatabaseConfig` class 24 | - Implemented error handling with custom error classes 25 | - Added YARD-style documentation for all public methods 26 | 27 | ### Changed 28 | - N/A 29 | 30 | ### Deprecated 31 | - N/A 32 | 33 | ### Removed 34 | - N/A 35 | 36 | ### Fixed 37 | - N/A 38 | 39 | ### Security 40 | - N/A 41 | -------------------------------------------------------------------------------- /lib/minds/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Minds 4 | class Client 5 | include Minds::RestClient 6 | 7 | SENSITIVE_ATTRIBUTES = %i[@base_url @api_key].freeze 8 | CONFIG_KEYS = %i[base_url api_key log_errors api_version].freeze 9 | attr_reader(*CONFIG_KEYS, :faraday_middleware) 10 | 11 | def initialize(options = {}, &faraday_middleware) 12 | # if key not present. Fall back to global config 13 | CONFIG_KEYS.each do |key| 14 | instance_variable_set "@#{key}", options[key] || Client.config.send(key) 15 | end 16 | 17 | @faraday_middleware = faraday_middleware 18 | end 19 | 20 | class << self 21 | def config 22 | @config ||= Config::Base.new 23 | end 24 | 25 | def configure 26 | yield(config) if block_given? 27 | end 28 | end 29 | 30 | def datasources 31 | @datasources ||= Datasources.new(client: self) 32 | end 33 | 34 | def minds 35 | @minds ||= Minds.new(client: self) 36 | end 37 | 38 | def inspect 39 | vars = instance_variables.map do |var| 40 | value = instance_variable_get(var) 41 | 42 | SENSITIVE_ATTRIBUTES.include?(var) ? "#{var}=[FILTERED]" : "#{var}=#{value.inspect}" 43 | end 44 | 45 | "#<#{self.class}:0x#{object_id.to_s(16)} #{vars.join(', ')}>" 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/minds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "faraday" 4 | require "json" 5 | require_relative "minds/version" 6 | require_relative "minds/rest_client" 7 | require_relative "minds/client" 8 | require_relative "minds/errors" 9 | require_relative "minds/config/base" 10 | require_relative "minds/datasources" 11 | require_relative "minds/minds" 12 | require_relative "minds/validators" 13 | 14 | module Minds 15 | class MiddlewareErrors < Faraday::Middleware 16 | ## 17 | # Handles API error responses and provides detailed logging 18 | # 19 | # @param env [Hash] The Faraday environment hash 20 | # @raise [Faraday::Error] Re-raises the original error after logging 21 | def call(env) 22 | @app.call(env) 23 | rescue Faraday::Error => e 24 | raise e unless e.response.is_a?(Hash) 25 | 26 | logger = Logger.new($stdout) 27 | logger.formatter = proc do |_severity, datetime, _progname, msg| 28 | timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%L") 29 | 30 | error_prefix = "\033[31m[Minds #{VERSION}] #{timestamp} ERROR" 31 | error_suffix = "\033[0m" 32 | 33 | formatted_message = msg.split("\n").map do |line| 34 | "#{' ' * 14}#{line}" 35 | end.join("\n") 36 | 37 | "#{error_prefix} Minds Client Error\n#{formatted_message}#{error_suffix}\n" 38 | end 39 | 40 | logger.error(e.response[:body]) 41 | raise e 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/minds/rest_client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "uri" 4 | 5 | module Minds 6 | module RestClient 7 | def get(path:) 8 | conn.get(uri(path: path))&.body 9 | end 10 | 11 | def post(path:, parameters:) 12 | conn.post(uri(path: path)) do |req| 13 | req.body = parameters.to_json 14 | end&.body 15 | end 16 | 17 | def patch(path:, parameters:) 18 | conn.patch(uri(path: path)) do |req| 19 | req.body = parameters.to_json 20 | end&.body 21 | end 22 | 23 | def put(path:, parameters:) 24 | conn.put(uri(path: path)) do |req| 25 | req.body = parameters.to_json 26 | end&.body 27 | end 28 | 29 | def delete(path:, parameters: nil) 30 | conn.delete(uri(path: path)) do |req| 31 | req.body = parameters.to_json unless parameters.nil? 32 | end&.body 33 | end 34 | 35 | private 36 | 37 | def uri(path:) 38 | base_path = URI(@base_url).path 39 | return path if base_path.split("/").include?(@api_version) 40 | 41 | "/#{@api_version}/#{path}" 42 | end 43 | 44 | def conn 45 | connection = Faraday.new(url: @base_url) do |builder| 46 | builder.use MiddlewareErrors if @log_errors 47 | builder.headers["Authorization"] = "Bearer #{@api_key}" 48 | builder.headers["Content-Type"] = "application/json" 49 | builder.request :json 50 | builder.response :json 51 | builder.response :raise_error 52 | end 53 | 54 | @faraday_middleware&.call(connection) 55 | connection 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/datasources/destroy_datasource.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: delete 5 | uri: "/api/datasources/my_datasource" 6 | body: 7 | encoding: UTF-8 8 | string: '{"cascade":true}' 9 | headers: 10 | Authorization: 11 | - Bearer 12 | Content-Type: 13 | - application/json 14 | User-Agent: 15 | - Faraday v2.12.0 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Mon, 28 Oct 2024 16:40:43 GMT 27 | Content-Type: 28 | - text/html; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - route=1730133644.442.2443.667652|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Wed, 35 | 30-Oct-24 16:40:43 GMT; Max-Age=172800; Path=/; HttpOnly 36 | Vary: 37 | - Accept-Encoding, Cookie 38 | Access-Control-Allow-Origin: 39 | - "*" 40 | Cf-Cache-Status: 41 | - DYNAMIC 42 | Report-To: 43 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=YIMFkgajiOTk7CEVZlo4%2FA9mjz5zIuR02Yl%2FjRZeBGOpFj94k%2BG3K%2FKNIpShKrD5IfAya%2BmEUeHOrCpVXNar0OUqlWGqwPkmtkGYxoSUZKkdy5ETykAmKA%3D%3D"}],"group":"cf-nel","max_age":604800}' 44 | Nel: 45 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 46 | Server: 47 | - cloudflare 48 | Cf-Ray: 49 | - 8d9c5e819fc84baa-SIN 50 | body: 51 | encoding: ASCII-8BIT 52 | string: '' 53 | recorded_at: Mon, 28 Oct 2024 16:40:43 GMT 54 | recorded_with: VCR 6.3.1 55 | -------------------------------------------------------------------------------- /spec/minds/integration/datasources_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Datasources Integration", :vcr do 2 | let(:client) { Minds::Client.new(api_key: ENV.fetch("MINDS_API_KEY")) } 3 | let(:datasources) { Minds::Datasources.new(client: client) } 4 | 5 | describe "#create" do 6 | let(:ds_config) do 7 | Minds::DatabaseConfig.new( 8 | name: 'my_datasource', 9 | description: '', 10 | engine: 'postgres', 11 | connection_data: { 12 | user: 'demo_user', 13 | password: 'demo_password', 14 | host: 'samples.mindsdb.com', 15 | port: 5432, 16 | database: 'demo', 17 | schema: 'demo_data' 18 | }, 19 | tables: [ '', '' ] 20 | ) 21 | end 22 | 23 | it "creates a new datasource" do 24 | VCR.use_cassette("datasources/create_datasource", record: :new_episodes) do 25 | result = datasources.create(ds_config) 26 | expect(result).to be_a(Minds::Datasource) 27 | expect(result.name).to eq("my_datasource") 28 | expect(result.engine).to eq("postgres") 29 | end 30 | end 31 | end 32 | 33 | describe "#all" do 34 | it "get all datasources" do 35 | VCR.use_cassette("datasources/all_datasource") do 36 | result = datasources.all 37 | expect(result).to be_an(Array) 38 | result.each do |ds| 39 | expect(ds).to be_a(Minds::Datasource) 40 | end 41 | end 42 | end 43 | end 44 | 45 | describe "#destroy" do 46 | it "delete datasources" do 47 | VCR.use_cassette("datasources/destroy_datasource") do 48 | result = datasources.destroy("my_datasource", force: true) 49 | expect(result).to eq("") 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /minds_sdk.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/minds/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "minds_sdk" 7 | spec.version = Minds::VERSION 8 | spec.authors = [ "tungnt" ] 9 | spec.email = [ "tungnguyen120301@gmail.com" ] 10 | 11 | spec.summary = 'Minds Ruby SDK provides an interface to interact with the Minds AI system API. It allows you to create and manage "minds" (artificial intelligences), create chat completions, and manage data sources.' 12 | spec.description = "Official Ruby SDK for Minds" 13 | spec.homepage = "https://github.com/tungnt1203/minds_ruby_sdk" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 3.0.0" 16 | 17 | spec.metadata["allowed_push_host"] = "https://rubygems.org" 18 | 19 | spec.metadata["homepage_uri"] = spec.homepage 20 | spec.metadata["source_code_uri"] = "https://github.com/tungnt1203/minds_ruby_sdk" 21 | spec.metadata["changelog_uri"] = "https://github.com/tungnt1203/minds_ruby_sdk/blob/main/CHANGELOG.md" 22 | 23 | # Specify which files should be added to the gem when it is released. 24 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 25 | gemspec = File.basename(__FILE__) 26 | spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| 27 | ls.readlines("\x0", chomp: true).reject do |f| 28 | (f == gemspec) || 29 | f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) 30 | end 31 | end 32 | spec.bindir = "exe" 33 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 34 | spec.require_paths = [ "lib" ] 35 | 36 | # Uncomment to register a new dependency of your gem 37 | spec.add_dependency "faraday", '~> 2.12' 38 | spec.add_dependency "ruby-openai", '~> 7.3', '>= 7.3.1' 39 | # For more information and examples about making a new gem, check out our 40 | # guide at: https://bundler.io/guides/creating_gem.html 41 | end 42 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/minds/find_mind.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "/api/projects/mindsdb/minds/test_recommend" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Bearer 12 | Content-Type: 13 | - application/json 14 | User-Agent: 15 | - Faraday v2.12.0 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Sat, 26 Oct 2024 17:43:10 GMT 27 | Content-Type: 28 | - application/json 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - route=1729964590.908.2175.806213|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Mon, 35 | 28-Oct-24 17:43:09 GMT; Max-Age=172800; Path=/; HttpOnly 36 | Vary: 37 | - Accept-Encoding, Cookie 38 | Access-Control-Allow-Origin: 39 | - "*" 40 | Cf-Cache-Status: 41 | - DYNAMIC 42 | Report-To: 43 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=ElsQm4qdv1RhsYBD2mIVEofzvl57kqIbt297RAncZJx5GHTsDK%2Fw5fgH1KjDaJFgmVSCvLQuxv46fg6WHaoSJRkONZZsmt7qBy8UC%2FjXzI4IdlTziY6dzg%3D%3D"}],"group":"cf-nel","max_age":604800}' 44 | Nel: 45 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 46 | Server: 47 | - cloudflare 48 | Cf-Ray: 49 | - 8d8c3f3cf98c4977-SIN 50 | body: 51 | encoding: ASCII-8BIT 52 | string: | 53 | { 54 | "created_at": "2024-10-21 03:11:38.918704", 55 | "datasources": [ 56 | "test_recommend" 57 | ], 58 | "model_name": "gpt-4o", 59 | "name": "test_recommend", 60 | "parameters": { 61 | "prompt_template": "Use your database tools to answer the user's question: {{question}}" 62 | }, 63 | "provider": "openai", 64 | "updated_at": "2024-10-24 15:43:20.858729" 65 | } 66 | recorded_at: Sat, 26 Oct 2024 17:43:10 GMT 67 | recorded_with: VCR 6.3.1 68 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/minds/completion.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: "/v1/chat/completions" 6 | body: 7 | encoding: UTF-8 8 | string: '{"model":"test_recommend","messages":[{"role":"user","content":"What 9 | is Mind"}],"temperature":0}' 10 | headers: 11 | Content-Type: 12 | - application/json 13 | Authorization: 14 | - Bearer 15 | Accept-Encoding: 16 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 17 | Accept: 18 | - "*/*" 19 | User-Agent: 20 | - Ruby 21 | response: 22 | status: 23 | code: 200 24 | message: OK 25 | headers: 26 | Date: 27 | - Sat, 26 Oct 2024 17:43:12 GMT 28 | Content-Type: 29 | - application/json 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - route=1729964591.415.2972.695640|03ad488504fbf9bd40778ecdfa18fd41; Expires=Mon, 36 | 28-Oct-24 17:43:10 GMT; Max-Age=172800; Path=/; HttpOnly 37 | Cf-Cache-Status: 38 | - DYNAMIC 39 | Report-To: 40 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=UWnPFm4eudDU4OaU3t5E3w8EUfP3qTLnWLOwXVFgHjPGiTHsXUw93ZXUMCQfKpRYP1f7xptAZj%2Bncx%2FKwmnFkMBqVdQ4uPY%2Fza0zSkqHivyDs5gJ9KmHaA%3D%3D"}],"group":"cf-nel","max_age":604800}' 41 | Nel: 42 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 43 | Server: 44 | - cloudflare 45 | Cf-Ray: 46 | - 8d8c3f413bf1ce0e-SIN 47 | body: 48 | encoding: ASCII-8BIT 49 | string: '{"id":"test_recommend-1729964592","choices":[{"finish_reason":"stop","index":0,"logprobs":null,"message":{"content":"To 50 | answer your question about \"Mind,\" it seems like you might be referring 51 | to MindsDB, which is a tool mentioned in the context of the database tools 52 | I have access to. MindsDB is an open-source machine learning platform that 53 | allows developers to integrate machine learning models into their applications 54 | easily. It is designed to work with databases and provides a way to make predictions 55 | using SQL queries.\n\nIf you have a specific question about MindsDB or need 56 | information on how it works, feel free to ask!","role":"assistant","function_call":null,"tool_calls":null}}],"created":1729964592,"model":"test_recommend","object":"chat.completion","system_fingerprint":null,"usage":{"completion_tokens":0,"prompt_tokens":0,"total_tokens":0}}' 57 | recorded_at: Sat, 26 Oct 2024 17:43:12 GMT 58 | recorded_with: VCR 6.3.1 59 | -------------------------------------------------------------------------------- /lib/minds/validators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Minds 4 | module Validators 5 | class << self 6 | # Validates a mind name according to naming rules 7 | # 8 | # @param name [String] The mind name to validate 9 | # @return [Boolean] Returns true if valid 10 | # @raise [MindNameInvalid] If the mind name is invalid 11 | # 12 | # @example Valid mind names 13 | # validate_mind_name!("my_mind_1") # => true 14 | # 15 | # @example Invalid mind names 16 | # validate_mind_name!("123_mind") # raises MindNameInvalid 17 | # validate_mind_name!("my mind") # raises MindNameInvalid 18 | # validate_mind_name!("very_very_long_mind_name_over_32_chars") # raises MindNameInvalid 19 | # 20 | # @note Mind name rules: 21 | # - Must start with a letter 22 | # - Can contain only letters, numbers, or underscores 23 | # - Maximum length of 32 characters 24 | # - Cannot contain spaces 25 | def validate_mind_name!(name) 26 | unless name.match?(/\A[a-zA-Z][a-zA-Z0-9_]{0,31}\z/) 27 | raise MindNameInvalid, "Mind name '#{name}' is invalid. It must start with a letter, contain only letters, numbers, or underscores, and be 32 characters or less." 28 | end 29 | end 30 | 31 | # Validates a datasource name according to naming rules 32 | # 33 | # @param name [String] The datasource name to validate 34 | # @return [Boolean] Returns true if valid 35 | # @raise [DatasourceNameInvalid] If the datasource name is invalid 36 | # 37 | # @example Valid datasource names 38 | # validate_datasource_name!("my_database") # => true 39 | # 40 | # @example Invalid datasource names 41 | # validate_datasource_name!("123_db") # raises DatasourceNameInvalid 42 | # validate_datasource_name!("my database") # raises DatasourceNameInvalid 43 | # validate_datasource_name!("very_very_long_database_name_over_62_characters_not_allowed") # raises DatasourceNameInvalid 44 | # 45 | # @note Datasource name rules: 46 | # - Must start with a letter 47 | # - Can contain only letters, numbers, or underscores 48 | # - Maximum length of 62 characters 49 | # - Cannot contain spaces 50 | def validate_datasource_name!(name) 51 | unless name.match?(/\A[a-zA-Z][a-zA-Z0-9_]{0,61}\z/) 52 | raise DatasourceNameInvalid, "Datasource name '#{name}' is invalid. It must start with a letter, contain only letters, numbers, or underscores, and be 62 characters or less." 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | minds_sdk (1.1.0) 5 | faraday (~> 2.12) 6 | ruby-openai (~> 7.3, >= 7.3.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activesupport (7.1.4) 12 | base64 13 | bigdecimal 14 | concurrent-ruby (~> 1.0, >= 1.0.2) 15 | connection_pool (>= 2.2.5) 16 | drb 17 | i18n (>= 1.6, < 2) 18 | minitest (>= 5.1) 19 | mutex_m 20 | tzinfo (~> 2.0) 21 | addressable (2.8.7) 22 | public_suffix (>= 2.0.2, < 7.0) 23 | ast (2.4.2) 24 | base64 (0.2.0) 25 | bigdecimal (3.1.8) 26 | concurrent-ruby (1.3.4) 27 | connection_pool (2.4.1) 28 | crack (1.0.0) 29 | bigdecimal 30 | rexml 31 | diff-lcs (1.5.1) 32 | dotenv (3.1.4) 33 | drb (2.2.1) 34 | event_stream_parser (1.0.0) 35 | faraday (2.12.0) 36 | faraday-net_http (>= 2.0, < 3.4) 37 | json 38 | logger 39 | faraday-multipart (1.0.4) 40 | multipart-post (~> 2) 41 | faraday-net_http (3.3.0) 42 | net-http 43 | hashdiff (1.1.1) 44 | i18n (1.14.6) 45 | concurrent-ruby (~> 1.0) 46 | json (2.7.2) 47 | language_server-protocol (3.17.0.3) 48 | logger (1.6.1) 49 | minitest (5.25.1) 50 | multipart-post (2.4.1) 51 | mutex_m (0.2.0) 52 | net-http (0.4.1) 53 | uri 54 | parallel (1.26.3) 55 | parser (3.3.5.0) 56 | ast (~> 2.4.1) 57 | racc 58 | public_suffix (6.0.1) 59 | racc (1.8.1) 60 | rack (3.1.7) 61 | rainbow (3.1.1) 62 | rake (13.2.1) 63 | regexp_parser (2.9.2) 64 | rexml (3.3.9) 65 | rspec (3.13.0) 66 | rspec-core (~> 3.13.0) 67 | rspec-expectations (~> 3.13.0) 68 | rspec-mocks (~> 3.13.0) 69 | rspec-core (3.13.1) 70 | rspec-support (~> 3.13.0) 71 | rspec-expectations (3.13.3) 72 | diff-lcs (>= 1.2.0, < 2.0) 73 | rspec-support (~> 3.13.0) 74 | rspec-mocks (3.13.2) 75 | diff-lcs (>= 1.2.0, < 2.0) 76 | rspec-support (~> 3.13.0) 77 | rspec-support (3.13.1) 78 | rubocop (1.66.1) 79 | json (~> 2.3) 80 | language_server-protocol (>= 3.17.0) 81 | parallel (~> 1.10) 82 | parser (>= 3.3.0.2) 83 | rainbow (>= 2.2.2, < 4.0) 84 | regexp_parser (>= 2.4, < 3.0) 85 | rubocop-ast (>= 1.32.2, < 2.0) 86 | ruby-progressbar (~> 1.7) 87 | unicode-display_width (>= 2.4.0, < 3.0) 88 | rubocop-ast (1.32.3) 89 | parser (>= 3.3.1.0) 90 | rubocop-minitest (0.36.0) 91 | rubocop (>= 1.61, < 2.0) 92 | rubocop-ast (>= 1.31.1, < 2.0) 93 | rubocop-performance (1.22.1) 94 | rubocop (>= 1.48.1, < 2.0) 95 | rubocop-ast (>= 1.31.1, < 2.0) 96 | rubocop-rails (2.26.2) 97 | activesupport (>= 4.2.0) 98 | rack (>= 1.1) 99 | rubocop (>= 1.52.0, < 2.0) 100 | rubocop-ast (>= 1.31.1, < 2.0) 101 | rubocop-rails-omakase (1.0.0) 102 | rubocop 103 | rubocop-minitest 104 | rubocop-performance 105 | rubocop-rails 106 | ruby-openai (7.3.1) 107 | event_stream_parser (>= 0.3.0, < 2.0.0) 108 | faraday (>= 1) 109 | faraday-multipart (>= 1) 110 | ruby-progressbar (1.13.0) 111 | tzinfo (2.0.6) 112 | concurrent-ruby (~> 1.0) 113 | unicode-display_width (2.6.0) 114 | uri (0.13.1) 115 | vcr (6.3.1) 116 | base64 117 | webmock (3.24.0) 118 | addressable (>= 2.8.0) 119 | crack (>= 0.3.2) 120 | hashdiff (>= 0.4.0, < 2.0.0) 121 | 122 | PLATFORMS 123 | arm64-darwin-21 124 | arm64-darwin-24 125 | x86_64-darwin-22 126 | x86_64-linux 127 | 128 | DEPENDENCIES 129 | dotenv (~> 3.1, >= 3.1.4) 130 | faraday (~> 2.12) 131 | minds_sdk! 132 | rake (~> 13.0) 133 | rspec (~> 3.0) 134 | rubocop (~> 1.21) 135 | rubocop-rails-omakase 136 | ruby-openai (~> 7.3, >= 7.3.1) 137 | vcr (~> 6.3, >= 6.3.1) 138 | webmock (~> 3.24) 139 | 140 | BUNDLED WITH 141 | 2.3.22 142 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/minds/create_mind.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: "/api/projects/mindsdb/minds" 6 | body: 7 | encoding: UTF-8 8 | string: '{"name":"test_mind","model_name":null,"provider":null,"parameters":{"prompt_template":"Use 9 | your database tools to answer the user''s question: {{question}}"},"datasources":[]}' 10 | headers: 11 | Authorization: 12 | - Bearer 13 | Content-Type: 14 | - application/json 15 | User-Agent: 16 | - Faraday v2.12.0 17 | Accept-Encoding: 18 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 19 | Accept: 20 | - "*/*" 21 | response: 22 | status: 23 | code: 200 24 | message: OK 25 | headers: 26 | Date: 27 | - Sat, 26 Oct 2024 17:40:58 GMT 28 | Content-Type: 29 | - text/html; charset=utf-8 30 | Transfer-Encoding: 31 | - chunked 32 | Connection: 33 | - keep-alive 34 | Set-Cookie: 35 | - route=1729964459.408.2178.575371|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Mon, 36 | 28-Oct-24 17:40:58 GMT; Max-Age=172800; Path=/; HttpOnly 37 | Vary: 38 | - Accept-Encoding, Cookie 39 | Access-Control-Allow-Origin: 40 | - "*" 41 | Cf-Cache-Status: 42 | - DYNAMIC 43 | Report-To: 44 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=jH8bM64Uac3ImugOf%2FhLTL%2Bw6HmZpaO4RzbxIXb3N26P3k64jrqzAco55mUqgyAFwJ87x3eqQpwMWbSV%2FaajVcOOS3u%2BcuflYH4EyS9wC7UtM9CyzntXcg%3D%3D"}],"group":"cf-nel","max_age":604800}' 45 | Nel: 46 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 47 | Server: 48 | - cloudflare 49 | Cf-Ray: 50 | - 8d8c3c028c499c44-SIN 51 | body: 52 | encoding: ASCII-8BIT 53 | string: '' 54 | recorded_at: Sat, 26 Oct 2024 17:40:58 GMT 55 | - request: 56 | method: get 57 | uri: "/api/projects/mindsdb/minds/test_mind" 58 | body: 59 | encoding: US-ASCII 60 | string: '' 61 | headers: 62 | Authorization: 63 | - Bearer 64 | Content-Type: 65 | - application/json 66 | User-Agent: 67 | - Faraday v2.12.0 68 | Accept-Encoding: 69 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 70 | Accept: 71 | - "*/*" 72 | response: 73 | status: 74 | code: 200 75 | message: OK 76 | headers: 77 | Date: 78 | - Sat, 26 Oct 2024 17:40:59 GMT 79 | Content-Type: 80 | - application/json 81 | Transfer-Encoding: 82 | - chunked 83 | Connection: 84 | - keep-alive 85 | Set-Cookie: 86 | - route=1729964460.607.2972.570948|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Mon, 87 | 28-Oct-24 17:40:59 GMT; Max-Age=172800; Path=/; HttpOnly 88 | Vary: 89 | - Accept-Encoding, Cookie 90 | Access-Control-Allow-Origin: 91 | - "*" 92 | Cf-Cache-Status: 93 | - DYNAMIC 94 | Report-To: 95 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=6AHCUKr68d7hcwpWb4h0xG4BHr3UhQwZdbx5G33MmLFE%2FPDyFFJCYrLVAhOGDg8%2B6P%2FwDxZunl%2FewXXakaprN5H0BOgSCfAKeNewbnnixAxaga2N4RahAw%3D%3D"}],"group":"cf-nel","max_age":604800}' 96 | Nel: 97 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 98 | Server: 99 | - cloudflare 100 | Cf-Ray: 101 | - 8d8c3c0b198fce3a-SIN 102 | body: 103 | encoding: ASCII-8BIT 104 | string: | 105 | { 106 | "created_at": "2024-10-26 17:40:58.448649", 107 | "datasources": [], 108 | "model_name": "gpt-4o", 109 | "name": "test_mind", 110 | "parameters": { 111 | "prompt_template": "Use your database tools to answer the user's question: {{question}}" 112 | }, 113 | "provider": "openai", 114 | "updated_at": "2024-10-26 17:40:58.448647" 115 | } 116 | recorded_at: Sat, 26 Oct 2024 17:40:59 GMT 117 | recorded_with: VCR 6.3.1 118 | -------------------------------------------------------------------------------- /lib/minds/datasources.rb: -------------------------------------------------------------------------------- 1 | module Minds 2 | class DatabaseConfig 3 | attr_accessor :name, :engine, :description, :connection_data, :tables, :created_at 4 | 5 | def initialize(name:, engine:, description:, connection_data: {}, tables: [], created_at: nil) 6 | @name = name 7 | @engine = engine 8 | @description = description 9 | @connection_data = connection_data 10 | @tables = tables 11 | @created_at = created_at 12 | end 13 | 14 | def to_h 15 | { 16 | name: @name, 17 | engine: @engine, 18 | description: @description, 19 | connection_data: @connection_data, 20 | tables: @tables 21 | } 22 | end 23 | end 24 | 25 | class Datasource < DatabaseConfig; end 26 | 27 | class Datasources 28 | def initialize(client:) 29 | @client = client 30 | end 31 | 32 | # Create a new datasource and return it 33 | # 34 | # @param ds_config [DatabaseConfig] datasource configuration 35 | # @option ds_config [String] :name Name of the datasource 36 | # @option ds_config [String] :engine Type of database handler (e.g., 'postgres', 'mysql') 37 | # @option ds_config [String] :description Description of the database. Used by mind to understand what data can be retrieved from it. 38 | # @option ds_config [Hash] :connection_data (optional) Credentials to connect to the database 39 | # @option ds_config [Array] :tables (optional) List of allowed tables 40 | # @param update [Boolean] If true, to update datasource if exists, default is false 41 | # @return [Datasource] The created datasource object 42 | # @raise [ObjectNotSupported] If datasource type is not supported 43 | # 44 | # @example 45 | # config = DatabaseConfig.new( 46 | # name: 'sales_db', 47 | # engine: 'postgres', 48 | # connection_data: { 49 | # host: 'localhost', 50 | # port: 5432, 51 | # user_name: "test" 52 | # password: "test" 53 | # } 54 | # ) 55 | # datasource = datasources.create(config) 56 | # 57 | def create(ds_config, update = false) 58 | name = ds_config.name 59 | 60 | Validators.validate_datasource_name!(name) 61 | 62 | path = "datasources" 63 | path += "/#{name}" if update 64 | 65 | @client.send(update ? :put : :post, path: path, parameters: ds_config.to_h) 66 | find(name) 67 | end 68 | 69 | # Return a list of datasources 70 | # 71 | # @return [Array] An array of Datasource objects 72 | # 73 | # @example 74 | # datasources = datasources.all 75 | # datasources.each { |ds| puts ds.name } 76 | # 77 | def all 78 | data = @client.get(path: "datasources") 79 | return [] if data.empty? 80 | 81 | data.each_with_object([]) do |item, ds_list| 82 | next if item["engine"].nil? 83 | 84 | ds_list << Datasource.new(**item.transform_keys(&:to_sym)) 85 | end 86 | end 87 | 88 | # Find a datasource by name 89 | # 90 | # @param name [String] The name of the datasource to find 91 | # @return [Datasource] The found datasource object 92 | # @raise [ObjectNotSupported] If the datasource type is not supported 93 | # 94 | # @example 95 | # datasource = datasources.find('sales_db') 96 | # puts datasource.engine 97 | # 98 | def find(name) 99 | data = @client.get(path: "datasources/#{name}") 100 | if data["engine"].nil? 101 | raise ObjectNotSupported, "Wrong type of datasource: #{name}" 102 | end 103 | Datasource.new(**data.transform_keys(&:to_sym)) 104 | end 105 | 106 | # Delete a datasource 107 | # 108 | # @param name [String] Datasource name to delete 109 | # @param force [Boolean] Whether to force delete from all minds 110 | # 111 | # @example 112 | # # Simple delete 113 | # datasources.destroy('old_db') 114 | # 115 | # # Force delete 116 | # datasources.destroy('old_db', force: true) 117 | # 118 | def destroy(name, force: false) 119 | data = force ? { cascade: true } : nil 120 | @client.delete(path: "datasources/#{name}", parameters: data) 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/datasources/create_datasource.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: "/api/datasources" 6 | body: 7 | encoding: UTF-8 8 | string: '{"name":"my_datasource","engine":"postgres","description":"","connection_data":{"user":"demo_user","password":"demo_password","host":"samples.mindsdb.com","port":5432,"database":"demo","schema":"demo_data"},"tables":["",""]}' 9 | headers: 10 | Authorization: 11 | - Bearer 12 | Content-Type: 13 | - application/json 14 | User-Agent: 15 | - Faraday v2.12.0 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Mon, 28 Oct 2024 16:40:40 GMT 27 | Content-Type: 28 | - text/html; charset=utf-8 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - route=1730133641.574.2969.89761|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Wed, 35 | 30-Oct-24 16:40:40 GMT; Max-Age=172800; Path=/; HttpOnly 36 | Vary: 37 | - Accept-Encoding, Cookie 38 | Access-Control-Allow-Origin: 39 | - "*" 40 | Cf-Cache-Status: 41 | - DYNAMIC 42 | Report-To: 43 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=VrVKuzU743Y6XUh3n3vDkvPzR23rY1cmgX1rnzZdRWOvk8pBNdbr7mIT1onrwK9Q35wbOawuKqixybzdf%2FcOeLebnI6tesW9s9QbcR4OXCxci2chedma8A%3D%3D"}],"group":"cf-nel","max_age":604800}' 44 | Nel: 45 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 46 | Server: 47 | - cloudflare 48 | Cf-Ray: 49 | - 8d9c5e6fdf5e7981-SIN 50 | body: 51 | encoding: ASCII-8BIT 52 | string: '' 53 | recorded_at: Mon, 28 Oct 2024 16:40:40 GMT 54 | - request: 55 | method: get 56 | uri: "/api/datasources/my_datasource" 57 | body: 58 | encoding: US-ASCII 59 | string: '' 60 | headers: 61 | Authorization: 62 | - Bearer 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - Faraday v2.12.0 67 | Accept-Encoding: 68 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 69 | Accept: 70 | - "*/*" 71 | response: 72 | status: 73 | code: 200 74 | message: OK 75 | headers: 76 | Date: 77 | - Mon, 28 Oct 2024 16:40:42 GMT 78 | Content-Type: 79 | - application/json 80 | Transfer-Encoding: 81 | - chunked 82 | Connection: 83 | - keep-alive 84 | Set-Cookie: 85 | - route=1730133643.038.2174.948954|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Wed, 86 | 30-Oct-24 16:40:42 GMT; Max-Age=172800; Path=/; HttpOnly 87 | Vary: 88 | - Accept-Encoding, Cookie 89 | Access-Control-Allow-Origin: 90 | - "*" 91 | Cf-Cache-Status: 92 | - DYNAMIC 93 | Report-To: 94 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=zdJb6KsCHDR9mwjpRP5ykjyKIsPWymSYyeSumVe%2FfJa2ZgYXCuxwwMjFj6b%2Fm1W%2F4j%2Bb2Fg2JD2fcKYS8Q%2B9MLhnwp1veZTBbKrrgasIiGDAp0cdzGdieg%3D%3D"}],"group":"cf-nel","max_age":604800}' 95 | Nel: 96 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 97 | Server: 98 | - cloudflare 99 | Cf-Ray: 100 | - 8d9c5e793fafcdde-SIN 101 | body: 102 | encoding: ASCII-8BIT 103 | string: | 104 | { 105 | "connection_data": { 106 | "database": "demo", 107 | "host": "samples.mindsdb.com", 108 | "password": "demo_password", 109 | "port": 5432, 110 | "schema": "demo_data", 111 | "user": "demo_user" 112 | }, 113 | "created_at": "2024-10-28 16:40:40.770670", 114 | "description": "", 115 | "engine": "postgres", 116 | "name": "my_datasource", 117 | "tables": [ 118 | "", 119 | "" 120 | ] 121 | } 122 | recorded_at: Mon, 28 Oct 2024 16:40:42 GMT 123 | recorded_with: VCR 6.3.1 124 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minds Ruby SDK 2 | [![Gem Version](https://img.shields.io/gem/v/minds_sdk)](https://rubygems.org/gems/minds_sdk) 3 | [![Ruby](https://img.shields.io/badge/ruby->=3.0.0-ruby.svg)](https://www.ruby-lang.org/en/) 4 | [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://www.rubydoc.info/gems/minds_sdk) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | 8 | Minds Ruby SDK provides an interface to interact with the Minds AI system API. It allows you to create and manage "minds" (artificial intelligences), create chat completions, and manage data sources. 9 | 10 | The best starting point is the [Mind Website](https://docs.mdb.ai/docs/data-mind), with its introduction and explanation. 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | ```ruby 17 | gem 'minds_sdk' 18 | ``` 19 | 20 | And then execute: 21 | 22 | ```bash 23 | $ bundle install 24 | ``` 25 | 26 | Or install it yourself as: 27 | 28 | ```bash 29 | $ gem install minds_sdk 30 | ``` 31 | and require with: 32 | 33 | ```ruby 34 | require 'minds' 35 | ``` 36 | 37 | ## Getting Started 38 | 39 | ### Configuration 40 | 41 | There are two ways to configure the Minds client: 42 | 43 | 1. Global Configuration: 44 | 45 | You can set up a global configuration that will be used by default for all client instances: 46 | 47 | ```ruby 48 | Minds::Client.configure do |config| 49 | config.base_url = "https://mdb.ai" # Optional: defaults to https://mdb.ai 50 | config.api_key = "YOUR_API_KEY" 51 | end 52 | ``` 53 | 54 | 2. Instance Configuration: 55 | 56 | Alternatively, you can configure each client instance individually: 57 | 58 | ```ruby 59 | client = Minds::Client.new(api_key: "YOUR_API_KEY", base_url: "https://mdb.ai") 60 | ``` 61 | 62 | ### Initialize the Client 63 | 64 | After configuration, you can initialize the client: 65 | 66 | ```ruby 67 | # Using global configuration 68 | client = Minds::Client.new 69 | 70 | # Or with instance-specific configuration 71 | client = Minds::Client.new(api_key: "YOUR_API_KEY", base_url: "https://mdb.ai") 72 | 73 | # For a self-hosted Minds Cloud instance 74 | client = Minds::Client.new(api_key: "YOUR_API_KEY", base_url: "https://.mdb.ai") 75 | ``` 76 | > Get your minds api key [here](https://mdb.ai/apiKeys) 77 | 78 | ### Logging 79 | 80 | By default, the Minds SDK does not log any Faraday::Errors encountered during network requests to prevent potential data leaks. To enable error logging, you can set `log_errors` to true when configuring the client: 81 | 82 | ```ruby 83 | # Global configuration 84 | Minds::Client.configure do |config| 85 | config.log_errors = true 86 | end 87 | 88 | # Or instance configuration 89 | client = Minds::Client.new(log_errors: true) 90 | ``` 91 | 92 | ## Resources 93 | 94 | ### Creating a Data Source 95 | 96 | You can connect to various databases, such as PostgreSQL, by configuring your data source. Use the DatabaseConfig to define the connection details for your data source. 97 | 98 | ```ruby 99 | postgres_config = Minds::DatabaseConfig.new( 100 | name: 'my_datasource', 101 | description: '', 102 | engine: 'postgres', 103 | connection_data: { 104 | user: 'demo_user', 105 | password: 'demo_password', 106 | host: 'samples.mindsdb.com', 107 | port: 5432, 108 | database: 'demo', 109 | schema: 'demo_data' 110 | }, 111 | tables: ['', ''] 112 | ) 113 | 114 | ``` 115 | 116 | > See supported [Data Sources](https://docs.mdb.ai/docs/data_sources) 117 | 118 | ### Creating a Mind 119 | 120 | You can create a mind and associate it with a data source. 121 | 122 | ```ruby 123 | # Create a mind with a data source 124 | mind = client.minds.create(name: 'mind_name', datasources: [postgres_config]) 125 | 126 | # Alternatively, create a data source separately and add it to a mind later 127 | datasource = client.datasources.create(postgres_config) 128 | mind2 = client.minds.create(name: 'mind_name', datasources: [datasource]) 129 | ``` 130 | 131 | You can also add a data source to an existing mind: 132 | 133 | ```ruby 134 | # Create a mind without a data source 135 | mind3 = client.minds.create(name: 'mind_name') 136 | 137 | # Add a data source to the mind 138 | mind3.add_datasources(postgres_config) # Using the config 139 | mind3.add_datasources(datasource) # Using the data source object 140 | ``` 141 | 142 | ### Managing Minds 143 | 144 | You can create a mind or replace an existing one with the same name. 145 | 146 | ```ruby 147 | mind = client.minds.create(name: 'mind_name', replace: true, datasources: [postgres_config]) 148 | ``` 149 | 150 | To update a mind, specify the new attributes: 151 | 152 | ```ruby 153 | mind.update( 154 | name: 'new_mind_name', 155 | datasources: [postgres_config] 156 | ) 157 | ``` 158 | 159 | ### List Minds 160 | 161 | You can list all the minds you've created: 162 | 163 | ```ruby 164 | client.minds.all 165 | ``` 166 | 167 | ### Get a Mind by Name 168 | 169 | You can fetch details of a mind by its name: 170 | 171 | ```ruby 172 | mind = client.minds.find('mind_name') 173 | ``` 174 | 175 | ### Remove a Mind 176 | 177 | To delete a mind, use the following command: 178 | 179 | ```ruby 180 | client.minds.destroy('mind_name') 181 | ``` 182 | 183 | ### Managing Data Sources 184 | 185 | To view all data sources: 186 | 187 | ```ruby 188 | client.datasources.all 189 | ``` 190 | 191 | ### Get a Data Source by Name 192 | 193 | You can fetch details of a specific data source by its name: 194 | 195 | ```ruby 196 | datasource = client.datasources.find('my_datasource') 197 | ``` 198 | 199 | ### Remove a Data Source 200 | 201 | To delete a data source, use the following command: 202 | 203 | ```ruby 204 | client.datasources.destroy('my_datasource') 205 | ``` 206 | 207 | Note: The SDK currently does not support automatically removing a data source if it is no longer connected to any mind. 208 | 209 | ## Chat Completion 210 | 211 | You can use a mind to generate chat completions: 212 | 213 | ```ruby 214 | response = mind.completion(message: "Hello, how are you?") 215 | puts response 216 | 217 | # For streaming responses 218 | mind.completion(message: "Tell me a story", stream: true) do |chunk| 219 | puts chunk 220 | end 221 | 222 | # => {"id"=>"ad2592865b844aadbb070b3fb5090869", "choices"=>[{"delta"=>{"content"=>"I understand your request. I'm working on a detailed response for you.", "function_call"=>nil, "role"=>"assistant", "tool_calls"=>nil}, "finish_reason"=>nil, "index"=>0, "logprobs"=>nil}], "created"=>1729085931, "model"=>"mind_house_sale", "object"=>"chat.completion.chunk", "system_fingerprint"=>nil, "usage"=>nil} 223 | # => ... 224 | ``` 225 | 226 | ## Development 227 | 228 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 229 | 230 | ## Contributing 231 | 232 | Bug reports and pull requests are welcome on GitHub at [here](https://github.com/tungnt1203/minds_ruby_sdk). 233 | 234 | ## Acknowledgments 235 | 236 | This SDK is built for integration with Minds, AI layer for existing databases. See more docs [here](https://docs.mdb.ai/docs/data-mind) 237 | 238 | We would like to express our gratitude to the MindsDB team for their innovative work in making AI more accessible. 239 | For more information about MindsDB, please visit their official website: [https://mindsdb.com/](https://mindsdb.com/) 240 | ## License 241 | 242 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 243 | -------------------------------------------------------------------------------- /lib/minds/minds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "openai" 4 | require "uri" 5 | 6 | module Minds 7 | DEFAULT_PROMPT_TEMPLATE = "Use your database tools to answer the user's question: {{question}}" 8 | DEFAULT_MODEL = "gpt-4o" 9 | class Mind 10 | attr_reader :name, :model_name, :provider, :parameters, :created_at, :updated_at, :datasources, :prompt_template 11 | 12 | def initialize(client, attributes = {}) 13 | @client = client 14 | @project = "mindsdb" 15 | @name = attributes["name"] 16 | @model_name = attributes["model_name"] 17 | @provider = attributes["provider"] 18 | @parameters = attributes["parameters"] || {} 19 | @prompt_template = @parameters.delete("prompt_template") 20 | @created_at = attributes["created_at"] 21 | @updated_at = attributes["updated_at"] 22 | @datasources = attributes["datasources"] 23 | end 24 | 25 | # Update mind 26 | # 27 | # @param name [String, nil] New name of the mind (optional) 28 | # @param model_name [String, nil] New LLM model name (optional) 29 | # @param provider [String, nil] New LLM provider (optional) 30 | # @param prompt_template [String, nil] New prompt template (optional) 31 | # @param datasources [Array, nil] Alter list of datasources used by mind (optional) 32 | # Datasource can be passed as: 33 | # - String: name of the datasource 34 | # - Datasource object (Minds::Datasource) 35 | # - DatabaseConfig object (Minds::DatabaseConfig), in this case datasource will be created 36 | # @param parameters [Hash, nil] Alter other parameters of the mind (optional) 37 | # @return [void] 38 | def update(name: nil, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil) 39 | Validators.validate_mind_name!(name) if !name.nil? 40 | data = {} 41 | ds_names = [] 42 | datasources.each do |ds| 43 | ds_name = @client.minds.check_datasource(ds) 44 | ds_names << ds_name 45 | end if datasources 46 | 47 | data["datasources"] = ds_names if !ds_names.empty? 48 | data["name"] = name if name 49 | data["model_name"] = model_name if model_name 50 | data["provider"] = provider if provider 51 | data["parameters"] = parameters.nil? ? {} : parameters 52 | data["parameters"]["prompt_template"] = prompt_template if prompt_template 53 | 54 | @client.patch(path: "projects/#{@project}/minds/#{@name}", parameters: data) 55 | 56 | @name = name if name && name != @name 57 | 58 | updated_mind = @client.minds.find(@name) 59 | 60 | @model_name = updated_mind.model_name 61 | @datasources = updated_mind.datasources 62 | @parameters = updated_mind.parameters 63 | @prompt_template = updated_mind.prompt_template 64 | @provider = updated_mind.provider 65 | @created_at = updated_mind.created_at 66 | @updated_at = updated_mind.updated_at 67 | true 68 | end 69 | 70 | # Add datasource to mind 71 | # 72 | # @param datasource [String, Datasource, DatabaseConfig] Datasource to add 73 | # Can be passed as: 74 | # - String: name of the datasource 75 | # - Datasource object (Minds::Datasource) 76 | # - DatabaseConfig object (Minds::DatabaseConfig), in this case datasource will be created 77 | # @return [void] 78 | def add_datasources(datasource) 79 | ds_name = @client.minds.check_datasource(datasource) 80 | data = { name: ds_name } 81 | @client.post(path: "projects/#{@project}/minds/#{@name}/datasources", parameters: data) 82 | 83 | mind = @client.minds.find(@name) 84 | @datasources = mind.datasources 85 | 86 | true 87 | end 88 | 89 | # Remove datasource from mind 90 | # 91 | # @param datasource [String, Datasource] Datasource to remove 92 | # Can be passed as: 93 | # - String: name of the datasource 94 | # - Datasource object (Minds::Datasource) 95 | # @return [void] 96 | # @raise [ArgumentError] If datasource type is invalid 97 | def destroy_datasources(datasource) 98 | if datasource.is_a?(Datasource) 99 | datasource = datasource.name 100 | elsif !datasource.is_a?(String) 101 | raise ArgumentError, "Unknown type of datasource: #{datasource}" 102 | end 103 | @client.delete(path: "projects/#{@project}/minds/#{@name}/datasources/#{datasource}") 104 | 105 | mind = @client.minds.find(@name) 106 | @datasources = mind.datasources 107 | true 108 | end 109 | 110 | # Call mind completion 111 | # 112 | # @param message [String] The input question or prompt 113 | # @param stream [Boolean] Whether to enable stream mode (default: false) 114 | # @return [String, Enumerator] If stream mode is off, returns a String. 115 | # If stream mode is on, returns an Enumerator of ChoiceDelta objects (as defined by OpenAI) 116 | def completion(message:, stream: false) 117 | openai_client = OpenAI::Client.new(access_token: @client.api_key, uri_base: @client.base_url) 118 | params = { 119 | model: @name, 120 | messages: [ { role: "user", content: message } ], 121 | temperature: 0 122 | } 123 | 124 | if stream 125 | openai_client.chat( 126 | parameters: params.merge( 127 | stream: proc do |chunk, _bytesize| 128 | yield chunk if block_given? 129 | end 130 | ) 131 | ) 132 | else 133 | response = openai_client.chat(parameters: params) 134 | response.dig("choices", 0, "message", "content") 135 | end 136 | end 137 | end 138 | 139 | class Minds 140 | def initialize(client:) 141 | @client = client 142 | @project = "mindsdb" 143 | end 144 | 145 | # Lists minds 146 | # 147 | # @return [Array] List of minds 148 | # 149 | # @example 150 | # minds = minds.all 151 | # minds.each { |mind| puts mind.name } 152 | # 153 | def all 154 | data = @client.get(path: "projects/#{@project}/minds") 155 | return [] if data.empty? 156 | 157 | data.map { |item| Mind.new(@client, item) } 158 | end 159 | 160 | # Find a mind by name 161 | # 162 | # @param name [String] The name of the mind to find 163 | # @return [Mind] The found mind object 164 | # 165 | # @example 166 | # mind = minds.find('sales_assistant') 167 | # puts mind.model_name 168 | # 169 | def find(name) 170 | data = @client.get(path: "projects/#{@project}/minds/#{name}") 171 | Mind.new(@client, data) 172 | end 173 | 174 | # Delete a mind 175 | # 176 | # @param name [String] The name of the mind to delete 177 | # @return [void] 178 | # 179 | # @example 180 | # minds.destroy('old_assistant') 181 | # 182 | def destroy(name) 183 | @client.delete(path: "projects/#{@project}/minds/#{name}") 184 | end 185 | 186 | # Create a new mind and return it 187 | # 188 | # @param name [String] The name of the mind 189 | # @param model_name [String, nil] The LLM model name (optional) 190 | # @param provider [String, nil] The LLM provider (optional) 191 | # @param prompt_template [String, nil] Instructions to LLM (optional) 192 | # @param datasources [Array, nil] List of datasources used by mind (optional) 193 | # Datasource can be passed as: 194 | # - String: name of the datasource 195 | # - Datasource object (Minds::Datasource) 196 | # - DatabaseConfig object (Minds::DatabaseConfig), in this case datasource will be created 197 | # @param parameters [Hash, nil] Other parameters of the mind (optional) 198 | # @param replace [Boolean] If true, remove existing mind with the same name (default: false) 199 | # @param update [Boolean] If true, to update mind if exists(default: false) 200 | # @return [Mind] The created mind object 201 | # 202 | # @example Simple creation 203 | # mind = minds.create(name: 'sales_assistant', model_name: 'gpt-4') 204 | # 205 | # @example Creation with datasources 206 | # mind = minds.create( 207 | # name: 'sales_assistant', 208 | # model_name: 'gpt-4', 209 | # datasources: ['sales_db'], 210 | # prompt_template: 'Analyze sales data: {{question}}' 211 | # ) 212 | # 213 | def create(name:, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil, replace: false, update: false) 214 | Validators.validate_mind_name!(name) if !name.nil? 215 | 216 | if replace 217 | find(name) 218 | destroy(name) 219 | end 220 | 221 | ds_names = [] 222 | datasources.each do |ds| 223 | ds_name = check_datasource(ds) 224 | ds_names << ds_name 225 | end if datasources 226 | 227 | parameters = {} if parameters.nil? 228 | 229 | parameters["prompt_template"] = prompt_template if prompt_template 230 | parameters["prompt_template"] ||= DEFAULT_PROMPT_TEMPLATE 231 | data = { 232 | name: name, 233 | model_name: model_name || DEFAULT_MODEL, 234 | provider: provider, 235 | parameters: parameters, 236 | datasources: ds_names 237 | } 238 | 239 | path = "projects/#{@project}/minds" 240 | path += "/#{name}" if update 241 | @client.send(update ? :put : :post, path: path, parameters: data) 242 | find(name) 243 | end 244 | 245 | def check_datasource(ds) 246 | ds_name = extract_datasource_name(ds) 247 | create_datasource_if_needed(ds) 248 | ds_name 249 | end 250 | 251 | private 252 | 253 | def extract_datasource_name(ds) 254 | case ds 255 | when Datasource, DatabaseConfig, String 256 | ds.respond_to?(:name) ? ds.name : ds 257 | else 258 | raise ArgumentError, "Unknown type of datasource: #{ds.class}" 259 | end 260 | end 261 | 262 | def create_datasource_if_needed(ds) 263 | return unless ds.is_a?(DatabaseConfig) 264 | 265 | @client.datasources.find(ds.name) 266 | rescue Faraday::ResourceNotFound 267 | @client.datasources.create(ds) 268 | end 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_cassettes/datasources/all_datasource.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: "/api/datasources" 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Authorization: 11 | - Bearer 12 | Content-Type: 13 | - application/json 14 | User-Agent: 15 | - Faraday v2.12.0 16 | Accept-Encoding: 17 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 18 | Accept: 19 | - "*/*" 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Date: 26 | - Sat, 26 Oct 2024 17:22:26 GMT 27 | Content-Type: 28 | - application/json 29 | Transfer-Encoding: 30 | - chunked 31 | Connection: 32 | - keep-alive 33 | Set-Cookie: 34 | - route=1729963346.962.2972.497593|9d990cb3ff1a8f5d1dd029aa4ab3886b; Expires=Mon, 35 | 28-Oct-24 17:22:25 GMT; Max-Age=172800; Path=/; HttpOnly 36 | Vary: 37 | - Accept-Encoding, Cookie 38 | Access-Control-Allow-Origin: 39 | - "*" 40 | Cf-Cache-Status: 41 | - DYNAMIC 42 | Report-To: 43 | - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=psydoJt0eKWJUTqv1SuZWu2t8WRYt2yr%2B53qAQbBmX%2BHrvA9MTuZeodt7u95PDI%2Bwm%2FPcU6RMH3HwhsAbufwPQyApLu6VihND2oFxu%2FsaD1Qv6OA8G91HQ%3D%3D"}],"group":"cf-nel","max_age":604800}' 44 | Nel: 45 | - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' 46 | Server: 47 | - cloudflare 48 | Cf-Ray: 49 | - 8d8c20dab8c0ce4e-SIN 50 | body: 51 | encoding: ASCII-8BIT 52 | string: | 53 | [ 54 | { 55 | "connection_data": { 56 | "database": "demo", 57 | "host": "samples.mindsdb.com", 58 | "password": "demo_password", 59 | "port": "5432", 60 | "schema": "demo_data", 61 | "user": "demo_user" 62 | }, 63 | "created_at": "2024-10-11 17:29:35.131488", 64 | "description": "House sales data", 65 | "engine": "postgres", 66 | "name": "datasource_name", 67 | "tables": [] 68 | }, 69 | { 70 | "connection_data": { 71 | "database": "demo", 72 | "host": "samples.mindsdb.com", 73 | "integrations_name": "test_recommend", 74 | "password": "demo_password", 75 | "port": 5432, 76 | "publish": true, 77 | "schema": "public", 78 | "test": true, 79 | "type": "postgres", 80 | "user": "demo_user" 81 | }, 82 | "created_at": "2024-10-21 03:11:31.038480", 83 | "description": "data job", 84 | "engine": "postgres", 85 | "name": "test_recommend", 86 | "tables": [ 87 | "models_1", 88 | "newstable", 89 | "newtable", 90 | "predictions_blackbox", 91 | "predictions_table", 92 | "regression100", 93 | "regression_mindsdb", 94 | "nodes", 95 | "regression_output", 96 | "policy", 97 | "airline_passenger_satisfaction", 98 | "FINAL_BUY_PREDICTION", 99 | "FINAL_STOCK_ACTION", 100 | "accs", 101 | "alembic_version", 102 | "amazon_reviews_with_sentiments", 103 | "chats", 104 | "bank_statement", 105 | "demo_data.home_rentals", 106 | "fired", 107 | "homesale", 108 | "conversations", 109 | "live_news", 110 | "house_sales", 111 | "rentals_2024-02-02 21:38:18", 112 | "rentals_2024-02-24 16:28:28", 113 | "rentals_2024-02-24 16:57:33", 114 | "rentals_history_test", 115 | "rentals_{{START_DATETIME}}", 116 | "stock_test", 117 | "sales_data", 118 | "stock_lives", 119 | "stocks", 120 | "stockusers", 121 | "subjects", 122 | "roadmaps", 123 | "t3", 124 | "table1", 125 | "tesla_sales", 126 | "utenti", 127 | "viaggi", 128 | "users", 129 | "bank_customer_transactions", 130 | "pomodoro_sessions", 131 | "gdp_test", 132 | "Olympics 2024", 133 | "olympics", 134 | "companies", 135 | "company_industries", 136 | "jobs" 137 | ] 138 | }, 139 | { 140 | "connection_data": { 141 | "database": "demo", 142 | "host": "samples.mindsdb.com", 143 | "password": "demo_password", 144 | "port": 5432, 145 | "schema": "demo_data", 146 | "user": "demo_user" 147 | }, 148 | "created_at": "2024-10-26 17:20:45.798512", 149 | "description": "", 150 | "engine": "postgres", 151 | "name": "my_datasource", 152 | "tables": [ 153 | "", 154 | "" 155 | ] 156 | }, 157 | { 158 | "connection_data": { 159 | "database": "demo", 160 | "host": "samples.mindsdb.com", 161 | "password": "demo_password", 162 | "port": "5432", 163 | "schema": "demo_data", 164 | "user": "demo_user" 165 | }, 166 | "created_at": null, 167 | "description": "House Sales", 168 | "engine": "postgres", 169 | "name": "house_sales_db_mind_20240820033107_datasource_ac5c443f6c1f436fb33667e9d5a98015_sql_skill_76a215ce-c9ff-46fc-bd47-54bcbee588d0", 170 | "tables": [ 171 | "house_sales" 172 | ] 173 | }, 174 | { 175 | "connection_data": { 176 | "database": "demo", 177 | "host": "samples.mindsdb.com", 178 | "password": "demo_password", 179 | "port": "5432", 180 | "schema": "demo_data", 181 | "user": "demo_user" 182 | }, 183 | "created_at": null, 184 | "description": "House Sales", 185 | "engine": "postgres", 186 | "name": "house_sales_db_mind_20240820132130_datasource_fe18513e697745389bf716d1e2e85314_sql_skill_e8934703-7ed8-41bb-98ea-a6f23a4ff516", 187 | "tables": [ 188 | "house_sales" 189 | ] 190 | }, 191 | { 192 | "connection_data": { 193 | "database": "demo", 194 | "host": "samples.mindsdb.com", 195 | "password": "demo_password", 196 | "port": "5432", 197 | "schema": "demo_data", 198 | "user": "demo_user" 199 | }, 200 | "created_at": null, 201 | "description": "House Sales", 202 | "engine": "postgres", 203 | "name": "house_sales_db_mind_20240820132143_datasource_ee5325c0d5c04f3cbd6d1c6918ecce65_sql_skill_dfae59a8-243b-42be-b8a4-084066531c01", 204 | "tables": [ 205 | "house_sales" 206 | ] 207 | }, 208 | { 209 | "connection_data": { 210 | "database": "demo", 211 | "host": "samples.mindsdb.com", 212 | "password": "demo_password", 213 | "port": "5432", 214 | "schema": "demo_data", 215 | "user": "demo_user" 216 | }, 217 | "created_at": null, 218 | "description": "House Sales", 219 | "engine": "postgres", 220 | "name": "house_sales_db_mind_20240820132458_datasource_db804d207da642d4a1823f41447c88b8_sql_skill_2064aa76-5b8e-4d06-a7d0-6e543c49f96c", 221 | "tables": [ 222 | "house_sales" 223 | ] 224 | }, 225 | { 226 | "connection_data": { 227 | "database": "demo", 228 | "host": "samples.mindsdb.com", 229 | "password": "demo_password", 230 | "port": "5432", 231 | "schema": "public", 232 | "user": "demo_user" 233 | }, 234 | "created_at": null, 235 | "description": "olympics 2024", 236 | "engine": "postgres", 237 | "name": "olympics_202420240820144425_datasource_2b09ed7d03334f60b4ab5ed9b440e196_sql_skill_13406a7e-dfa2-4c1a-9cdd-3ecac8d1f886", 238 | "tables": [ 239 | "olympics" 240 | ] 241 | }, 242 | { 243 | "connection_data": { 244 | "database": "demo", 245 | "host": "samples.mindsdb.com", 246 | "password": "demo_password", 247 | "port": "5432", 248 | "schema": "public", 249 | "user": "demo_user" 250 | }, 251 | "created_at": null, 252 | "description": "olympics 2024", 253 | "engine": "postgres", 254 | "name": "olympics_202420240820144704_datasource_47cf80387b46460ab7d8dd84298f8b24_sql_skill_1eed8870-2a6b-4b58-8b36-d7f5395d2e42", 255 | "tables": [ 256 | "olympics" 257 | ] 258 | }, 259 | { 260 | "connection_data": { 261 | "database": "demo", 262 | "host": "samples.mindsdb.com", 263 | "password": "demo_password", 264 | "port": "5432", 265 | "schema": "public", 266 | "user": "demo_user" 267 | }, 268 | "created_at": null, 269 | "description": "olympics 2024", 270 | "engine": "postgres", 271 | "name": "olympics_minds_datasource_c3d7e773475b4a24a72ecd67d4dc1ec1_sql_skill_dbf20e93-5690-476b-9a08-9c0c2e5811d2", 272 | "tables": [ 273 | "olympics" 274 | ] 275 | }, 276 | { 277 | "connection_data": { 278 | "database": "demo", 279 | "host": "samples.mindsdb.com", 280 | "password": "demo_password", 281 | "port": "5432", 282 | "schema": "public", 283 | "user": "demo_user" 284 | }, 285 | "created_at": "2024-08-21 06:26:13.065855", 286 | "description": "Companies Data", 287 | "engine": "postgres", 288 | "name": "companies_mind_datasource_8f745e1615954e2e98c4be02fa09b91e_sql_skill_0223a62d-6d4f-4d0f-8ee0-5dd10f7ea8c8", 289 | "tables": [ 290 | "companies", 291 | "company_industries" 292 | ] 293 | }, 294 | { 295 | "connection_data": { 296 | "database": "demo", 297 | "host": "samples.mindsdb.com", 298 | "password": "demo_password", 299 | "port": "5432", 300 | "schema": "public", 301 | "user": "demo_user" 302 | }, 303 | "created_at": "2024-09-06 10:00:26.157702", 304 | "description": "Companies Data", 305 | "engine": "postgres", 306 | "name": "companies_mind_datasource_721f0596d55f40d68eba5a1194989531_sql_skill_98f918f0-f0af-4b80-b4bc-7dceb1c854d2", 307 | "tables": [ 308 | "companies", 309 | "company_industries" 310 | ] 311 | }, 312 | { 313 | "connection_data": { 314 | "database": "demo", 315 | "host": "samples.mindsdb.com", 316 | "password": "demo_password", 317 | "port": "5432", 318 | "schema": "demo_data", 319 | "user": "demo_user" 320 | }, 321 | "created_at": "2024-09-10 08:32:30.800172", 322 | "description": "house sales", 323 | "engine": "postgres", 324 | "name": "test123_datasource_204da485b3cf4ce0875d0f1c881a90a4_sql_skill_b4d38a67-9562-4a46-a849-1d587f75d24a", 325 | "tables": [ 326 | "house_sales" 327 | ] 328 | }, 329 | { 330 | "connection_data": { 331 | "database": "demo", 332 | "host": "samples.mindsdb.com", 333 | "password": "demo_password", 334 | "port": "5432", 335 | "schema": "demo_data", 336 | "user": "demo_user" 337 | }, 338 | "created_at": "2024-09-13 07:50:14.704975", 339 | "description": "house sales", 340 | "engine": "postgres", 341 | "name": "my mind_datasource_bbd1d07358e14683925000076dc535f1_sql_skill_86ba47e0-bdd8-4c01-b1fb-7fcd1bcf2179", 342 | "tables": [ 343 | "house_sales" 344 | ] 345 | }, 346 | { 347 | "connection_data": { 348 | "database": "demo", 349 | "host": "samples.mindsdb.com", 350 | "password": "demo_password", 351 | "port": 5432, 352 | "schema": "demo_data", 353 | "user": "demo_user" 354 | }, 355 | "created_at": "2024-09-13 07:56:24.969951", 356 | "description": "Main database", 357 | "engine": "postgres", 358 | "name": "test_mind_datasource_6c31c5ef82f7437c89f0f3e3ed0be0c8_sql_skill_a6023ac1-5ec6-42f8-af30-5dbf734cbf4c", 359 | "tables": [ 360 | "house_sales" 361 | ] 362 | }, 363 | { 364 | "connection_data": { 365 | "database": "demo", 366 | "host": "samples.mindsdb.com", 367 | "password": "demo_password", 368 | "port": 5432, 369 | "schema": "demo_data", 370 | "user": "demo_user" 371 | }, 372 | "created_at": "2024-10-07 08:58:08.878075", 373 | "description": "Main application database", 374 | "engine": "postgres", 375 | "name": "dropdb", 376 | "tables": null 377 | } 378 | ] 379 | recorded_at: Sat, 26 Oct 2024 17:22:26 GMT 380 | recorded_with: VCR 6.3.1 381 | --------------------------------------------------------------------------------