minds_sdk 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +26 -2
- data/lib/minds/client.rb +25 -17
- data/lib/minds/config/base.rb +7 -3
- data/lib/minds/datasources.rb +89 -0
- data/lib/minds/errors.rb +6 -5
- data/lib/minds/minds.rb +222 -0
- data/lib/minds/rest_client.rb +44 -0
- data/lib/minds/version.rb +1 -1
- data/lib/minds.rb +31 -2
- metadata +5 -5
- data/lib/minds/resources/base.rb +0 -26
- data/lib/minds/resources/datasources.rb +0 -96
- data/lib/minds/resources/minds.rb +0 -237
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7d338a8c7a60751a9ff07a8e0a80b88d5a41c8d725913d20b9a48e8101ac862
|
4
|
+
data.tar.gz: 5f6df32b49f25319e67d1ac7b2992bd6dd89922176f46ad5819b08aad119e698
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f481f5d03353c3199685763970b6c0b78c2787819d8222bb9ad8b18e71812c9de90736400ae8ace9c21ca94b529c5267845250b595e4199735e7493af358c935
|
7
|
+
data.tar.gz: 93b0f55f6be94511d2172324804f6ecc7a8c2f457beb0fc17804970596305f16e1be3afdb1622996f4149938a9db5b7d38caeb401ff284a9b310c590d6cb627d
|
data/README.md
CHANGED
@@ -24,6 +24,11 @@ Or install it yourself as:
|
|
24
24
|
```bash
|
25
25
|
$ gem install minds_sdk
|
26
26
|
```
|
27
|
+
and require with:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'minds'
|
31
|
+
```
|
27
32
|
|
28
33
|
## Getting Started
|
29
34
|
|
@@ -66,6 +71,20 @@ client = Minds::Client.new(api_key: "YOUR_API_KEY", base_url: "https://<custom_c
|
|
66
71
|
```
|
67
72
|
> Get your minds api key [here](https://mdb.ai/apiKeys)
|
68
73
|
|
74
|
+
### Logging
|
75
|
+
|
76
|
+
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:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# Global configuration
|
80
|
+
Minds::Client.configure do |config|
|
81
|
+
config.log_errors = true
|
82
|
+
end
|
83
|
+
|
84
|
+
# Or instance configuration
|
85
|
+
client = Minds::Client.new(log_errors: true)
|
86
|
+
```
|
87
|
+
|
69
88
|
## Resources
|
70
89
|
|
71
90
|
### Creating a Data Source
|
@@ -73,7 +92,7 @@ client = Minds::Client.new(api_key: "YOUR_API_KEY", base_url: "https://<custom_c
|
|
73
92
|
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.
|
74
93
|
|
75
94
|
```ruby
|
76
|
-
postgres_config = Minds::
|
95
|
+
postgres_config = Minds::DatabaseConfig.new(
|
77
96
|
name: 'my_datasource',
|
78
97
|
description: '<DESCRIPTION-OF-YOUR-DATA>',
|
79
98
|
engine: 'postgres',
|
@@ -192,7 +211,12 @@ response = mind.completion(message: "Hello, how are you?")
|
|
192
211
|
puts response
|
193
212
|
|
194
213
|
# For streaming responses
|
195
|
-
mind.completion(message: "Tell me a story", stream: true)
|
214
|
+
mind.completion(message: "Tell me a story", stream: true) do |chunk|
|
215
|
+
puts chunk
|
216
|
+
end
|
217
|
+
|
218
|
+
# => {"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}
|
219
|
+
# => ...
|
196
220
|
```
|
197
221
|
|
198
222
|
## Development
|
data/lib/minds/client.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
require "faraday"
|
2
|
-
require "json"
|
3
|
-
require_relative "config/base"
|
4
|
-
require_relative "resources/base"
|
5
|
-
require_relative "resources/minds"
|
6
|
-
require_relative "resources/datasources"
|
7
|
-
|
8
1
|
module Minds
|
9
2
|
class Client
|
3
|
+
include Minds::RestClient
|
4
|
+
|
5
|
+
SENSITIVE_ATTRIBUTES = %i[@base_url @api_key].freeze
|
6
|
+
CONFIG_KEYS = %i[base_url api_key log_errors api_version].freeze
|
7
|
+
attr_reader(*CONFIG_KEYS)
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
# if key not present. Fall back to global config
|
11
|
+
CONFIG_KEYS.each do |key|
|
12
|
+
instance_variable_set "@#{key}", options[key] || Client.config.send(key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
10
16
|
class << self
|
11
17
|
def config
|
12
18
|
@config ||= Config::Base.new
|
@@ -17,20 +23,22 @@ module Minds
|
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
attr_accessor :base_url, :api_key
|
21
|
-
|
22
|
-
def initialize(api_key: nil, base_url: nil)
|
23
|
-
# if api_key & base_url not present. Fall back to global config
|
24
|
-
@base_url = base_url.nil? ? Minds::Client.config.send(:base_url) : base_url
|
25
|
-
@api_key = api_key.nil? ? Minds::Client.config.send(:api_key) : api_key
|
26
|
-
end
|
27
|
-
|
28
26
|
def datasources
|
29
|
-
@datasources ||=
|
27
|
+
@datasources ||= Datasources.new(client: self)
|
30
28
|
end
|
31
29
|
|
32
30
|
def minds
|
33
|
-
@minds ||= Minds
|
31
|
+
@minds ||= Minds.new(client: self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
vars = instance_variables.map do |var|
|
36
|
+
value = instance_variable_get(var)
|
37
|
+
|
38
|
+
SENSITIVE_ATTRIBUTES.include?(var) ? "#{var}=[FILTERED]" : "#{var}=#{value.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} #{vars.join(', ')}>"
|
34
42
|
end
|
35
43
|
end
|
36
44
|
end
|
data/lib/minds/config/base.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
module Minds
|
2
2
|
module Config
|
3
3
|
class Base
|
4
|
-
attr_accessor :base_url, :api_key
|
5
|
-
DEFAULT_BASE_URL = "https://mdb.ai"
|
4
|
+
attr_accessor :base_url, :api_key, :log_errors, :api_version
|
5
|
+
DEFAULT_BASE_URL = "https://mdb.ai".freeze
|
6
|
+
DEFAULT_LOG_ERRORS = false
|
7
|
+
DEFAULT_API_VERSION = "api".freeze
|
6
8
|
|
7
9
|
def initialize
|
8
|
-
@base_url = DEFAULT_BASE_URL
|
9
10
|
@api_key = nil
|
11
|
+
@base_url = DEFAULT_BASE_URL
|
12
|
+
@log_errors = DEFAULT_LOG_ERRORS
|
13
|
+
@api_version = DEFAULT_API_VERSION
|
10
14
|
end
|
11
15
|
end
|
12
16
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Minds
|
2
|
+
class DatabaseConfig
|
3
|
+
attr_reader :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
|
+
# Create a new datasource and return it
|
32
|
+
#
|
33
|
+
# @param ds_config [DatabaseConfig] datasource configuration
|
34
|
+
# @option ds_config [String] :name Name of the datasource
|
35
|
+
# @option ds_config [String] :engine Type of database handler (e.g., 'postgres', 'mysql')
|
36
|
+
# @option ds_config [String] :description Description of the database. Used by mind to understand what data can be retrieved from it.
|
37
|
+
# @option ds_config [Hash] :connection_data (optional) Credentials to connect to the database
|
38
|
+
# @option ds_config [Array<String>] :tables (optional) List of allowed tables
|
39
|
+
# @param replace [Boolean] If true, replaces an existing datasource with the same name
|
40
|
+
# @return [Datasource] The created datasource object
|
41
|
+
def create(ds_config, replace = false)
|
42
|
+
name = ds_config.name
|
43
|
+
if replace
|
44
|
+
begin
|
45
|
+
find(name)
|
46
|
+
destroy(name, force: true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@client.post(path: "datasources", parameters: ds_config.to_h)
|
50
|
+
find(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a list of all datasources
|
54
|
+
#
|
55
|
+
# @return [Array<Datasource>] An array of Datasource objects
|
56
|
+
def all
|
57
|
+
data = @client.get(path: "datasources")
|
58
|
+
return [] if data.empty?
|
59
|
+
|
60
|
+
data.each_with_object([]) do |item, ds_list|
|
61
|
+
next if item["engine"].nil?
|
62
|
+
|
63
|
+
ds_list << Datasource.new(**item.transform_keys(&:to_sym))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get a datasource by name
|
68
|
+
#
|
69
|
+
# @param name [String] The name of the datasource to find
|
70
|
+
# @return [Datasource] The found datasource object
|
71
|
+
# @raise [ObjectNotSupported] If the datasource type is not supported
|
72
|
+
def find(name)
|
73
|
+
data = @client.get(path: "datasources/#{name}")
|
74
|
+
if data["engine"].nil?
|
75
|
+
raise ObjectNotSupported, "Wrong type of datasource: #{name}"
|
76
|
+
end
|
77
|
+
Datasource.new(**data.transform_keys(&:to_sym))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Destroy (delete) a datasource by name
|
81
|
+
#
|
82
|
+
# @param name [String] The name of the datasource to delete
|
83
|
+
# @param force: if true -remove from all minds, default: false
|
84
|
+
def destroy(name, force: false)
|
85
|
+
data = force ? { cascade: true } : nil
|
86
|
+
@client.delete(path: "datasources/#{name}", parameters: data)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/minds/errors.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class ValidationError < Error; end
|
4
|
-
class ObjectNotFound < Error; end
|
5
|
-
class ObjectNotSupported < Error; end
|
1
|
+
module Minds
|
2
|
+
class Error < StandardError; end
|
3
|
+
class ValidationError < Error; end
|
4
|
+
class ObjectNotFound < Error; end
|
5
|
+
class ObjectNotSupported < Error; end
|
6
|
+
end
|
data/lib/minds/minds.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
require "openai"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
module Minds
|
5
|
+
DEFAULT_PROMPT_TEMPLATE = "Use your database tools to answer the user's question: {{question}}"
|
6
|
+
|
7
|
+
class Mind
|
8
|
+
attr_accessor :name, :model_name, :provider, :parameters, :created_at, :updated_at, :datasources
|
9
|
+
|
10
|
+
def initialize(client, attributes = {})
|
11
|
+
@client = client
|
12
|
+
@project = "mindsdb"
|
13
|
+
@name = attributes["name"]
|
14
|
+
@model_name = attributes["model_name"]
|
15
|
+
@provider = attributes["provider"]
|
16
|
+
@parameters = attributes["parameters"] || {}
|
17
|
+
@prompt_template = @parameters.delete("prompt_template")
|
18
|
+
@created_at = attributes["created_at"]
|
19
|
+
@updated_at = attributes["updated_at"]
|
20
|
+
@datasources = attributes["datasources"]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Update the mind with new parameters
|
24
|
+
#
|
25
|
+
# @param name [String, nil] New name of the mind (optional)
|
26
|
+
# @param model_name [String, nil] New LLM model name (optional)
|
27
|
+
# @param provider [String, nil] New LLM provider (optional)
|
28
|
+
# @param prompt_template [String, nil] New prompt template (optional)
|
29
|
+
# @param datasources [Array<String, Datasource, DatabaseConfig>, nil] Alter list of datasources used by mind (optional)
|
30
|
+
# Datasource can be passed as:
|
31
|
+
# - String: name of the datasource
|
32
|
+
# - Datasource object (Minds::Resources::Datasource)
|
33
|
+
# - DatabaseConfig object (Minds::Resources::DatabaseConfig), in this case datasource will be created
|
34
|
+
# @param parameters [Hash, nil] Alter other parameters of the mind (optional)
|
35
|
+
# @return [void]
|
36
|
+
def update(name: nil, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil)
|
37
|
+
data = {}
|
38
|
+
|
39
|
+
ds_names = []
|
40
|
+
datasources.each do |ds|
|
41
|
+
ds_name = @client.minds.check_datasource(ds)
|
42
|
+
ds_names << ds_name
|
43
|
+
data["datasources"] = ds_names
|
44
|
+
end if datasources
|
45
|
+
|
46
|
+
data["name"] = name if name
|
47
|
+
data["model_name"] = model_name if model_name
|
48
|
+
data["provider"] = provider if provider
|
49
|
+
data["parameters"] = parameters.nil? ? {} : parameters
|
50
|
+
data["parameters"]["prompt_template"] = prompt_template if prompt_template
|
51
|
+
|
52
|
+
@client.patch(path: "projects/#{@project}/minds/#{@name}", parameters: data)
|
53
|
+
|
54
|
+
@name = name if name && name != @name
|
55
|
+
end
|
56
|
+
|
57
|
+
# Add a datasource to the mind
|
58
|
+
#
|
59
|
+
# @param datasource [String, Datasource, DatabaseConfig] The datasource to add
|
60
|
+
# Can be passed as:
|
61
|
+
# - String: name of the datasource
|
62
|
+
# - Datasource object (Minds::Resources::Datasource)
|
63
|
+
# - DatabaseConfig object (Minds::Resources::DatabaseConfig), in this case datasource will be created
|
64
|
+
# @return [void]
|
65
|
+
def add_datasources(datasource)
|
66
|
+
ds_name = @client.minds.check_datasource(datasource)
|
67
|
+
data = { name: ds_name }
|
68
|
+
@client.post(path: "projects/#{@project}/minds/#{@name}/datasources", parameters: data)
|
69
|
+
|
70
|
+
mind = @client.minds.find(@name)
|
71
|
+
@datasources = mind.datasources
|
72
|
+
end
|
73
|
+
|
74
|
+
# Delete a datasource from the mind
|
75
|
+
#
|
76
|
+
# @param datasource [String, Datasource] The datasource to delete
|
77
|
+
# Can be passed as:
|
78
|
+
# - String: name of the datasource
|
79
|
+
# - Datasource object (Minds::Resources::Datasource)
|
80
|
+
# @return [void]
|
81
|
+
def destroy_datasources(datasource)
|
82
|
+
if datasource.is_a?(Datasource)
|
83
|
+
datasource = datasource.name
|
84
|
+
elsif !datasource.is_a?(String)
|
85
|
+
raise ArgumentError, "Unknown type of datasource: #{datasource}"
|
86
|
+
end
|
87
|
+
@client.delete(path: "projects/#{@project}/minds/#{@name}/datasources/#{datasource}")
|
88
|
+
|
89
|
+
mind = @client.minds.find(@name)
|
90
|
+
@datasources = mind.datasources
|
91
|
+
end
|
92
|
+
|
93
|
+
# Call mind completion
|
94
|
+
#
|
95
|
+
# @param message [String] The input question or prompt
|
96
|
+
# @param stream [Boolean] Whether to enable stream mode (default: false)
|
97
|
+
# @return [String, Enumerator] If stream mode is off, returns a String.
|
98
|
+
# If stream mode is on, returns an Enumerator of ChoiceDelta objects (as defined by OpenAI)
|
99
|
+
def completion(message:, stream: false)
|
100
|
+
openai_client = OpenAI::Client.new(access_token: @client.api_key, uri_base: @client.base_url)
|
101
|
+
params = {
|
102
|
+
model: @name,
|
103
|
+
messages: [ { role: "user", content: message } ],
|
104
|
+
temperature: 0
|
105
|
+
}
|
106
|
+
|
107
|
+
if stream
|
108
|
+
openai_client.chat(
|
109
|
+
parameters: params.merge(
|
110
|
+
stream: proc do |chunk, _bytesize|
|
111
|
+
yield chunk if block_given?
|
112
|
+
end
|
113
|
+
)
|
114
|
+
)
|
115
|
+
else
|
116
|
+
response = openai_client.chat(parameters: params)
|
117
|
+
response.dig("choices", 0, "message", "content")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Minds
|
123
|
+
def initialize(client:)
|
124
|
+
@client = client
|
125
|
+
@project = "mindsdb"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a list of all minds
|
129
|
+
#
|
130
|
+
# @return [Array<Mind>] An array of Mind objects
|
131
|
+
def all
|
132
|
+
data = @client.get(path: "projects/#{@project}/minds")
|
133
|
+
return [] if data.empty?
|
134
|
+
|
135
|
+
data.map { |item| Mind.new(@client, item) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get a mind by name
|
139
|
+
#
|
140
|
+
# @param name [String] The name of the mind to find
|
141
|
+
# @return [Mind] The found mind object
|
142
|
+
def find(name)
|
143
|
+
data = @client.get(path: "projects/#{@project}/minds/#{name}")
|
144
|
+
Mind.new(@client, data)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Drop (delete) a mind by name
|
148
|
+
#
|
149
|
+
# @param name [String] The name of the mind to delete
|
150
|
+
# @return [void]
|
151
|
+
def destroy(name)
|
152
|
+
@client.delete(path: "projects/#{@project}/minds/#{name}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Create a new mind and return it
|
156
|
+
#
|
157
|
+
# @param name [String] The name of the mind
|
158
|
+
# @param model_name [String, nil] The LLM model name (optional)
|
159
|
+
# @param provider [String, nil] The LLM provider (optional)
|
160
|
+
# @param prompt_template [String, nil] Instructions to LLM (optional)
|
161
|
+
# @param datasources [Array<String, Datasource, DatabaseConfig>, nil] List of datasources used by mind (optional)
|
162
|
+
# Datasource can be passed as:
|
163
|
+
# - String: name of the datasource
|
164
|
+
# - Datasource object (Minds::Datasource)
|
165
|
+
# - DatabaseConfig object (Minds::DatabaseConfig), in this case datasource will be created
|
166
|
+
# @param parameters [Hash, nil] Other parameters of the mind (optional)
|
167
|
+
# @param replace [Boolean] If true, remove existing mind with the same name (default: false)
|
168
|
+
# @return [Mind] The created mind object
|
169
|
+
def create(name:, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil, replace: false)
|
170
|
+
if replace
|
171
|
+
begin
|
172
|
+
find(name)
|
173
|
+
destroy(name)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
ds_names = []
|
178
|
+
datasources.each do |ds|
|
179
|
+
ds_name = check_datasource(ds)
|
180
|
+
ds_names << ds_name
|
181
|
+
end if datasources
|
182
|
+
|
183
|
+
parameters = {} if parameters.nil?
|
184
|
+
|
185
|
+
parameters["prompt_template"] = prompt_template if prompt_template
|
186
|
+
parameters["prompt_template"] ||= DEFAULT_PROMPT_TEMPLATE
|
187
|
+
data = {
|
188
|
+
name: name,
|
189
|
+
model_name: model_name,
|
190
|
+
provider: provider,
|
191
|
+
parameters: parameters,
|
192
|
+
datasources: ds_names
|
193
|
+
}
|
194
|
+
|
195
|
+
@client.post(path: "projects/#{@project}/minds", parameters: data)
|
196
|
+
find(name)
|
197
|
+
end
|
198
|
+
|
199
|
+
def check_datasource(ds)
|
200
|
+
ds_name = extract_datasource_name(ds)
|
201
|
+
create_datasource_if_needed(ds)
|
202
|
+
ds_name
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
def extract_datasource_name(ds)
|
208
|
+
case ds
|
209
|
+
when Datasource, DatabaseConfig, String
|
210
|
+
ds.respond_to?(:name) ? ds.name : ds
|
211
|
+
else
|
212
|
+
raise ArgumentError, "Unknown type of datasource: #{ds.class}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def create_datasource_if_needed(ds)
|
217
|
+
return unless ds.is_a?(DatabaseConfig)
|
218
|
+
|
219
|
+
@client.datasources.find(ds.name)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Minds
|
2
|
+
module RestClient
|
3
|
+
def get(path:)
|
4
|
+
conn.get(uri(path: path))&.body
|
5
|
+
end
|
6
|
+
|
7
|
+
def post(path:, parameters:)
|
8
|
+
conn.post(uri(path: path)) do |req|
|
9
|
+
req.body = parameters.to_json
|
10
|
+
end&.body
|
11
|
+
end
|
12
|
+
|
13
|
+
def patch(path:, parameters:)
|
14
|
+
conn.patch(uri(path: path)) do |req|
|
15
|
+
req.body = parameters.to_json
|
16
|
+
end&.body
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(path:, parameters: nil)
|
20
|
+
conn.delete(uri(path: path)) do |req|
|
21
|
+
req.body = parameters.to_json
|
22
|
+
end&.body
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def uri(path:)
|
28
|
+
return path if @base_url.include?(@api_version)
|
29
|
+
|
30
|
+
"/#{@api_version}/#{path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def conn
|
34
|
+
Faraday.new(url: @base_url) do |builder|
|
35
|
+
builder.use MiddlewareErrors if @log_errors
|
36
|
+
builder.headers["Authorization"] = "Bearer #{@api_key}"
|
37
|
+
builder.headers["Content-Type"] = "application/json"
|
38
|
+
builder.request :json
|
39
|
+
builder.response :json
|
40
|
+
builder.response :raise_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/minds/version.rb
CHANGED
data/lib/minds.rb
CHANGED
@@ -1,9 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
3
5
|
require_relative "minds/version"
|
6
|
+
require_relative "minds/rest_client"
|
4
7
|
require_relative "minds/client"
|
5
8
|
require_relative "minds/errors"
|
6
9
|
require_relative "minds/config/base"
|
7
|
-
require_relative "minds/
|
10
|
+
require_relative "minds/datasources"
|
11
|
+
require_relative "minds/minds"
|
8
12
|
|
9
|
-
module Minds
|
13
|
+
module Minds
|
14
|
+
class MiddlewareErrors < Faraday::Middleware
|
15
|
+
def call(env)
|
16
|
+
@app.call(env)
|
17
|
+
rescue Faraday::Error => e
|
18
|
+
raise e unless e.response.is_a?(Hash)
|
19
|
+
|
20
|
+
logger = Logger.new($stdout)
|
21
|
+
logger.formatter = proc do |_severity, datetime, _progname, msg|
|
22
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%L")
|
23
|
+
|
24
|
+
error_prefix = "\033[31m[Minds #{VERSION}] #{timestamp} ERROR"
|
25
|
+
error_suffix = "\033[0m"
|
26
|
+
|
27
|
+
formatted_message = msg.split("\n").map do |line|
|
28
|
+
"#{' ' * 15}#{line}"
|
29
|
+
end.join("\n")
|
30
|
+
|
31
|
+
"#{error_prefix} Rest Client Error\n#{formatted_message}#{error_suffix}\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
logger.error(e.response[:body])
|
35
|
+
raise e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minds_sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tungnt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -55,10 +55,10 @@ files:
|
|
55
55
|
- lib/minds.rb
|
56
56
|
- lib/minds/client.rb
|
57
57
|
- lib/minds/config/base.rb
|
58
|
+
- lib/minds/datasources.rb
|
58
59
|
- lib/minds/errors.rb
|
59
|
-
- lib/minds/
|
60
|
-
- lib/minds/
|
61
|
-
- lib/minds/resources/minds.rb
|
60
|
+
- lib/minds/minds.rb
|
61
|
+
- lib/minds/rest_client.rb
|
62
62
|
- lib/minds/version.rb
|
63
63
|
- sig/minds.rbs
|
64
64
|
homepage: https://github.com/tungnt1203/minds_ruby_sdk
|
data/lib/minds/resources/base.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module Minds
|
2
|
-
module Resources
|
3
|
-
class Base
|
4
|
-
attr_accessor :api_key, :base_url, :api
|
5
|
-
|
6
|
-
def initialize(client)
|
7
|
-
@base_url = client.base_url
|
8
|
-
@api_key = client.api_key
|
9
|
-
@api = conn
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def conn
|
15
|
-
Faraday.new(url: @base_url) do |builder|
|
16
|
-
builder.request :json
|
17
|
-
builder.response :json
|
18
|
-
builder.response :raise_error
|
19
|
-
builder.adapter Faraday.default_adapter
|
20
|
-
builder.headers["Authorization"] = "Bearer #{@api_key}"
|
21
|
-
builder.headers["Content-Type"] = "application/json"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module Minds
|
2
|
-
module Resources
|
3
|
-
class DatabaseConfig
|
4
|
-
attr_reader :name, :engine, :description, :connection_data, :tables, :created_at
|
5
|
-
|
6
|
-
def initialize(name:, engine:, description:, connection_data: {}, tables: [], created_at: nil)
|
7
|
-
@name = name
|
8
|
-
@engine = engine
|
9
|
-
@description = description
|
10
|
-
@connection_data = connection_data
|
11
|
-
@tables = tables
|
12
|
-
@created_at = created_at
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_h
|
16
|
-
{
|
17
|
-
name: @name,
|
18
|
-
engine: @engine,
|
19
|
-
description: @description,
|
20
|
-
connection_data: @connection_data,
|
21
|
-
tables: @tables
|
22
|
-
}
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class Datasource < DatabaseConfig; end
|
27
|
-
|
28
|
-
class Datasources < Base
|
29
|
-
# Create a new datasource and return it
|
30
|
-
#
|
31
|
-
# @param ds_config [DatabaseConfig] datasource configuration
|
32
|
-
# @option ds_config [String] :name Name of the datasource
|
33
|
-
# @option ds_config [String] :engine Type of database handler (e.g., 'postgres', 'mysql')
|
34
|
-
# @option ds_config [String] :description Description of the database. Used by mind to understand what data can be retrieved from it.
|
35
|
-
# @option ds_config [Hash] :connection_data (optional) Credentials to connect to the database
|
36
|
-
# @option ds_config [Array<String>] :tables (optional) List of allowed tables
|
37
|
-
# @param replace [Boolean] If true, replaces an existing datasource with the same name
|
38
|
-
# @return [Datasource] The created datasource object
|
39
|
-
# @raise [ObjectNotFound] If the datasource to be replaced doesn't exist
|
40
|
-
def create(ds_config, replace = false)
|
41
|
-
name = ds_config.name
|
42
|
-
|
43
|
-
if replace
|
44
|
-
begin
|
45
|
-
find(name)
|
46
|
-
destroy(name)
|
47
|
-
rescue ObjectNotFound
|
48
|
-
# Do nothing
|
49
|
-
end
|
50
|
-
end
|
51
|
-
self.api.post("/api/datasources") do |req|
|
52
|
-
req.body = ds_config.to_h.to_json
|
53
|
-
end
|
54
|
-
find(name)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns a list of all datasources
|
58
|
-
#
|
59
|
-
# @return [Array<Datasource>] An array of Datasource objects
|
60
|
-
def all
|
61
|
-
data = self.api.get("/api/datasources").body
|
62
|
-
data.each_with_object([]) do |item, ds_list|
|
63
|
-
next if item["engine"].nil?
|
64
|
-
|
65
|
-
ds_list << Datasource.new(**item.transform_keys(&:to_sym))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Get a datasource by name
|
70
|
-
#
|
71
|
-
# @param name [String] The name of the datasource to find
|
72
|
-
# @return [Datasource] The found datasource object
|
73
|
-
# @raise [ObjectNotSupported] If the datasource type is not supported
|
74
|
-
def find(name)
|
75
|
-
data = self.api.get("api/datasources/#{name}").body
|
76
|
-
|
77
|
-
if data["engine"].nil?
|
78
|
-
raise ObjectNotSupported, "Wrong type of datasource: #{name}"
|
79
|
-
end
|
80
|
-
|
81
|
-
Datasource.new(**data.transform_keys(&:to_sym))
|
82
|
-
end
|
83
|
-
|
84
|
-
# Drop (delete) a datasource by name
|
85
|
-
#
|
86
|
-
# @param name [String] The name of the datasource to delete
|
87
|
-
# @return [void]
|
88
|
-
# @raise [Faraday::ResourceNotFound] If the datasource doesn't exist
|
89
|
-
# @raise [Faraday::ClientError] If there's a client-side error
|
90
|
-
# @raise [Faraday::ServerError] If there's a server-side error
|
91
|
-
def destroy(name)
|
92
|
-
self.api.delete("api/datasources/#{name}")
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,237 +0,0 @@
|
|
1
|
-
require "openai"
|
2
|
-
require "uri"
|
3
|
-
module Minds
|
4
|
-
module Resources
|
5
|
-
DEFAULT_PROMPT_TEMPLATE = "Use your database tools to answer the user's question: {{question}}"
|
6
|
-
|
7
|
-
class Mind < Base
|
8
|
-
attr_accessor :name, :model_name, :provider, :parameters, :created_at, :updated_at, :datasources
|
9
|
-
|
10
|
-
def initialize(client, attributes = {})
|
11
|
-
super(client)
|
12
|
-
@project = "mindsdb"
|
13
|
-
@name = attributes["name"]
|
14
|
-
@model_name = attributes["model_name"]
|
15
|
-
@provider = attributes["provider"]
|
16
|
-
@parameters = attributes["parameters"] || {}
|
17
|
-
@prompt_template = @parameters.delete("prompt_template")
|
18
|
-
@created_at = attributes["created_at"]
|
19
|
-
@updated_at = attributes["updated_at"]
|
20
|
-
@datasources = attributes["datasources"]
|
21
|
-
end
|
22
|
-
|
23
|
-
# Update the mind with new parameters
|
24
|
-
#
|
25
|
-
# @param name [String, nil] New name of the mind (optional)
|
26
|
-
# @param model_name [String, nil] New LLM model name (optional)
|
27
|
-
# @param provider [String, nil] New LLM provider (optional)
|
28
|
-
# @param prompt_template [String, nil] New prompt template (optional)
|
29
|
-
# @param datasources [Array<String, Datasource, DatabaseConfig>, nil] Alter list of datasources used by mind (optional)
|
30
|
-
# Datasource can be passed as:
|
31
|
-
# - String: name of the datasource
|
32
|
-
# - Datasource object (Minds::Resources::Datasource)
|
33
|
-
# - DatabaseConfig object (Minds::Resources::DatabaseConfig), in this case datasource will be created
|
34
|
-
# @param parameters [Hash, nil] Alter other parameters of the mind (optional)
|
35
|
-
# @return [void]
|
36
|
-
def update(name: nil, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil)
|
37
|
-
data = {}
|
38
|
-
|
39
|
-
ds_names = []
|
40
|
-
datasources.each do |ds|
|
41
|
-
minds = Minds.new(self)
|
42
|
-
ds_name = minds.check_datasource(ds)
|
43
|
-
ds_names << ds_name
|
44
|
-
data["datasources"] = ds_names
|
45
|
-
end if datasources
|
46
|
-
|
47
|
-
data["name"] = name if name
|
48
|
-
data["model_name"] = model_name if model_name
|
49
|
-
data["provider"] = provider if provider
|
50
|
-
data["parameters"] = parameters.nil? ? {} : parameters
|
51
|
-
data["parameters"]["prompt_template"] = prompt_template if prompt_template
|
52
|
-
|
53
|
-
self.api.patch("/api/projects/#{@project}/minds/#{@name}") do |req|
|
54
|
-
req.body = data.to_json
|
55
|
-
end
|
56
|
-
|
57
|
-
@name = name if name && name != @name
|
58
|
-
end
|
59
|
-
|
60
|
-
# Add a datasource to the mind
|
61
|
-
#
|
62
|
-
# @param datasource [String, Datasource, DatabaseConfig] The datasource to add
|
63
|
-
# Can be passed as:
|
64
|
-
# - String: name of the datasource
|
65
|
-
# - Datasource object (Minds::Resources::Datasource)
|
66
|
-
# - DatabaseConfig object (Minds::Resources::DatabaseConfig), in this case datasource will be created
|
67
|
-
# @return [void]
|
68
|
-
def add_datasources(datasource)
|
69
|
-
minds = Minds.new(self)
|
70
|
-
ds_name = minds.check_datasource(datasource)
|
71
|
-
self.api.post("/api/projects/#{project}/minds/#{@name}/datasources") do |req|
|
72
|
-
req.body = { name: ds_name }.to_json
|
73
|
-
end
|
74
|
-
|
75
|
-
mind = minds.find(@name)
|
76
|
-
|
77
|
-
@datasources = mind.datasources
|
78
|
-
end
|
79
|
-
|
80
|
-
# Delete a datasource from the mind
|
81
|
-
#
|
82
|
-
# @param datasource [String, Datasource] The datasource to delete
|
83
|
-
# Can be passed as:
|
84
|
-
# - String: name of the datasource
|
85
|
-
# - Datasource object (Minds::Resources::Datasource)
|
86
|
-
# @return [void]
|
87
|
-
def destroy_datasources(datasource)
|
88
|
-
if datasource.is_a?(Datasource)
|
89
|
-
datasource = datasource.name
|
90
|
-
elsif !datasource.is_a?(String)
|
91
|
-
raise ArgumentError, "Unknown type of datasource: #{datasource}"
|
92
|
-
end
|
93
|
-
self.api.delete("/api/projects/#{@project}/minds/#{@name}/datasources/#{datasource}")
|
94
|
-
|
95
|
-
minds = Minds.new(self)
|
96
|
-
mind = minds.find(@name)
|
97
|
-
@datasources = mind.datasources
|
98
|
-
end
|
99
|
-
|
100
|
-
# Call mind completion
|
101
|
-
#
|
102
|
-
# @param message [String] The input question or prompt
|
103
|
-
# @param stream [Boolean] Whether to enable stream mode (default: false)
|
104
|
-
# @return [String, Enumerator] If stream mode is off, returns a String.
|
105
|
-
# If stream mode is on, returns an Enumerator of ChoiceDelta objects (as defined by OpenAI)
|
106
|
-
def completion(message:, stream: false)
|
107
|
-
openai_client = OpenAI::Client.new(access_token: self.api_key, uri_base: self.base_url)
|
108
|
-
if stream
|
109
|
-
openai_client.chat(
|
110
|
-
parameters: {
|
111
|
-
model: @name,
|
112
|
-
messages: [ { role: "user", content: message } ], # Required.
|
113
|
-
temperature: 0,
|
114
|
-
stream: proc do |chunk, _bytesize|
|
115
|
-
print chunk.dig("choices", 0, "delta")
|
116
|
-
end
|
117
|
-
}
|
118
|
-
)
|
119
|
-
else
|
120
|
-
response = openai_client.chat(
|
121
|
-
parameters: {
|
122
|
-
model: @name,
|
123
|
-
messages: [ { role: "user", content: message } ],
|
124
|
-
temperature: 0
|
125
|
-
}
|
126
|
-
)
|
127
|
-
response.dig("choices", 0, "message", "content")
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
class Minds < Base
|
133
|
-
def initialize(client)
|
134
|
-
super
|
135
|
-
@project = "mindsdb"
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns a list of all minds
|
139
|
-
#
|
140
|
-
# @return [Array<Mind>] An array of Mind objects
|
141
|
-
def all
|
142
|
-
data = self.api.get("/api/projects/#{@project}/minds").body
|
143
|
-
return [] if data.empty?
|
144
|
-
|
145
|
-
data.map { |item| Mind.new(self, item) }
|
146
|
-
end
|
147
|
-
|
148
|
-
# Get a mind by name
|
149
|
-
#
|
150
|
-
# @param name [String] The name of the mind to find
|
151
|
-
# @return [Mind] The found mind object
|
152
|
-
def find(name)
|
153
|
-
resp = self.api.get("/api/projects/#{@project}/minds/#{name}")
|
154
|
-
Mind.new(self, resp.body)
|
155
|
-
end
|
156
|
-
|
157
|
-
# Drop (delete) a mind by name
|
158
|
-
#
|
159
|
-
# @param name [String] The name of the mind to delete
|
160
|
-
# @return [void]
|
161
|
-
def destroy(name)
|
162
|
-
self.api.delete("/api/projects/#{@project}/minds/#{name}")
|
163
|
-
end
|
164
|
-
|
165
|
-
# Create a new mind and return it
|
166
|
-
#
|
167
|
-
# @param name [String] The name of the mind
|
168
|
-
# @param model_name [String, nil] The LLM model name (optional)
|
169
|
-
# @param provider [String, nil] The LLM provider (optional)
|
170
|
-
# @param prompt_template [String, nil] Instructions to LLM (optional)
|
171
|
-
# @param datasources [Array<String, Datasource, DatabaseConfig>, nil] List of datasources used by mind (optional)
|
172
|
-
# Datasource can be passed as:
|
173
|
-
# - String: name of the datasource
|
174
|
-
# - Datasource object (Minds::Resources::Datasource)
|
175
|
-
# - DatabaseConfig object (Minds::Resources::DatabaseConfig), in this case datasource will be created
|
176
|
-
# @param parameters [Hash, nil] Other parameters of the mind (optional)
|
177
|
-
# @param replace [Boolean] If true, remove existing mind with the same name (default: false)
|
178
|
-
# @return [Mind] The created mind object
|
179
|
-
def create(name:, model_name: nil, provider: nil, prompt_template: nil, datasources: nil, parameters: nil, replace: false)
|
180
|
-
if replace
|
181
|
-
begin
|
182
|
-
find(name)
|
183
|
-
destroy(name)
|
184
|
-
rescue Faraday::ResourceNotFound
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
ds_names = []
|
189
|
-
datasources.each do |ds|
|
190
|
-
ds_name = check_datasource(ds)
|
191
|
-
ds_names << ds_name
|
192
|
-
end if datasources
|
193
|
-
|
194
|
-
parameters = {} if parameters.nil?
|
195
|
-
|
196
|
-
parameters["prompt_template"] = prompt_template if prompt_template
|
197
|
-
parameters["prompt_template"] ||= DEFAULT_PROMPT_TEMPLATE
|
198
|
-
self.api.post("api/projects/#{@project}/minds") do |req|
|
199
|
-
req.body = {
|
200
|
-
name: name,
|
201
|
-
model_name: model_name,
|
202
|
-
provider: provider,
|
203
|
-
parameters: parameters,
|
204
|
-
datasources: ds_names
|
205
|
-
}.to_json
|
206
|
-
end
|
207
|
-
find(name)
|
208
|
-
end
|
209
|
-
|
210
|
-
def check_datasource(ds)
|
211
|
-
ds_name = extract_datasource_name(ds)
|
212
|
-
create_datasource_if_needed(ds)
|
213
|
-
ds_name
|
214
|
-
end
|
215
|
-
|
216
|
-
private
|
217
|
-
|
218
|
-
def extract_datasource_name(ds)
|
219
|
-
case ds
|
220
|
-
when Datasource, DatabaseConfig, String
|
221
|
-
ds.respond_to?(:name) ? ds.name : ds
|
222
|
-
else
|
223
|
-
raise ArgumentError, "Unknown type of datasource: #{ds.class}"
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def create_datasource_if_needed(ds)
|
228
|
-
return unless ds.is_a?(DatabaseConfig)
|
229
|
-
|
230
|
-
datasources = Datasources.new(self)
|
231
|
-
datasources.find(ds.name)
|
232
|
-
rescue Faraday::ResourceNotFound
|
233
|
-
datasources.create(ds)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|