Skip to content

Commit 467c7c6

Browse files
authored
Merge branch 'main' into java-sdk
2 parents ed0ac90 + 78affc8 commit 467c7c6

File tree

10 files changed

+145
-60
lines changed

10 files changed

+145
-60
lines changed

.github/workflows/test_on_deploy.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
python-version: ['3.8', '3.9','3.10', '3.11']
12+
python-version: ['3.10']
1313
steps:
1414
- name: Checkout code
1515
uses: actions/checkout@v2
@@ -28,4 +28,4 @@ jobs:
2828
env:
2929
PYTHONPATH: ./
3030
API_KEY: ${{ secrets.API_KEY }}
31-
BASE_URL: ${{ secrets.BASE_URL }}
31+
BASE_URL: 'https://mdb.ai'

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,9 @@ client.datasources.drop('my_datasource')
150150
>Note: The SDK currently does not support automatically removing a data source if it is no longer connected to any mind.
151151
152152
### Other SDKs
153-
#### Java SDK - [https://github.com/Better-Boy/minds-java-sdk](https://github.com/Better-Boy/minds-java-sdk)
153+
- [Java-SDK](https://github.com/Better-Boy/minds-java-sdk)
154+
- [Dart-SDK](https://github.com/ArnavK-09/mdb_dart)
155+
156+
#### Command Line Tools
157+
- [Minds CLI](https://github.com/Better-Boy/minds-cli-sdk)
158+

minds/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'minds_sdk'
22
__package_name__ = 'minds'
3-
__version__ = '1.0.7'
3+
__version__ = '1.0.9'
44
__description__ = 'An AI-Data Mind is an LLM with the built-in power to answer data questions for Agents'
55
__email__ = 'hello@mindsdb.com'
66
__author__ = 'MindsDB Inc'

minds/datasources/datasources.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ class DatabaseConfig(BaseModel):
1616
class Datasource(DatabaseConfig):
1717
...
1818

19+
1920
class Datasources:
2021
def __init__(self, client):
2122
self.api = client.api
2223

23-
def create(self, ds_config: DatabaseConfig, replace=False):
24+
def create(self, ds_config: DatabaseConfig, update=False):
2425
"""
2526
Create new datasource and return it
2627
@@ -30,19 +31,16 @@ def create(self, ds_config: DatabaseConfig, replace=False):
3031
- description: str, description of the database. Used by mind to know what data can be got from it.
3132
- connection_data: dict, optional, credentials to connect to database
3233
- tables: list of str, optional, list of allowed tables
34+
:param update: if true - to update datasourse if exists, default is false
3335
:return: datasource object
3436
"""
3537

3638
name = ds_config.name
3739

38-
if replace:
39-
try:
40-
self.get(name)
41-
self.drop(name, force=True)
42-
except exc.ObjectNotFound:
43-
...
44-
45-
self.api.post('/datasources', data=ds_config.model_dump())
40+
if update:
41+
self.api.put(f'/datasources/{name}', data=ds_config.model_dump())
42+
else:
43+
self.api.post('/datasources', data=ds_config.model_dump())
4644
return self.get(name)
4745

4846
def list(self) -> List[Datasource]:

minds/exceptions.py

+3
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ class Unauthorized(Exception):
1818
class UnknownError(Exception):
1919
...
2020

21+
22+
class MindNameInvalid(Exception):
23+
...

minds/minds.py

+26-28
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
from typing import List, Union, Iterable
2-
from urllib.parse import urlparse, urlunparse
3-
from datetime import datetime
4-
52
from openai import OpenAI
6-
3+
import minds.utils as utils
74
import minds.exceptions as exc
8-
95
from minds.datasources import Datasource, DatabaseConfig
106

117
DEFAULT_PROMPT_TEMPLATE = 'Use your database tools to answer the user\'s question: {{question}}'
128

13-
149
class Mind:
1510
def __init__(
1611
self, client, name,
@@ -25,7 +20,7 @@ def __init__(
2520
self.api = client.api
2621
self.client = client
2722
self.project = 'mindsdb'
28-
23+
2924
self.name = name
3025
self.model_name = model_name
3126
self.provider = provider
@@ -35,7 +30,11 @@ def __init__(
3530
self.parameters = parameters
3631
self.created_at = created_at
3732
self.updated_at = updated_at
38-
33+
base_url = utils.get_openai_base_url(self.api.base_url)
34+
self.openai_client = OpenAI(
35+
api_key=self.api.api_key,
36+
base_url=base_url
37+
)
3938
self.datasources = datasources
4039

4140
def __repr__(self):
@@ -74,6 +73,9 @@ def update(
7473
:param parameters, dict: alter other parameters of the mind, optional
7574
"""
7675
data = {}
76+
77+
if name is not None:
78+
utils.validate_mind_name(name)
7779

7880
if datasources is not None:
7981
ds_names = []
@@ -156,23 +158,7 @@ def completion(self, message: str, stream: bool = False) -> Union[str, Iterable[
156158
157159
:return: string if stream mode is off or iterator of ChoiceDelta objects (by openai)
158160
"""
159-
parsed = urlparse(self.api.base_url)
160-
161-
netloc = parsed.netloc
162-
if netloc == 'mdb.ai':
163-
llm_host = 'llm.mdb.ai'
164-
else:
165-
llm_host = 'ai.' + netloc
166-
167-
parsed = parsed._replace(path='', netloc=llm_host)
168-
169-
base_url = urlunparse(parsed)
170-
openai_client = OpenAI(
171-
api_key=self.api.api_key,
172-
base_url=base_url
173-
)
174-
175-
response = openai_client.chat.completions.create(
161+
response = self.openai_client.chat.completions.create(
176162
model=self.name,
177163
messages=[
178164
{'role': 'user', 'content': message}
@@ -216,7 +202,7 @@ def get(self, name: str) -> Mind:
216202
:param name: name of the mind
217203
:return: a mind object
218204
"""
219-
205+
220206
item = self.api.get(f'/projects/{self.project}/minds/{name}').json()
221207
return Mind(self.client, **item)
222208

@@ -243,6 +229,7 @@ def create(
243229
datasources=None,
244230
parameters=None,
245231
replace=False,
232+
update=False,
246233
) -> Mind:
247234
"""
248235
Create a new mind and return it
@@ -259,8 +246,12 @@ def create(
259246
:param datasources: list of datasources used by mind, optional
260247
:param parameters, dict: other parameters of the mind, optional
261248
:param replace: if true - to remove existing mind, default is false
249+
:param update: if true - to update mind if exists, default is false
262250
:return: created mind
263251
"""
252+
253+
if name is not None:
254+
utils.validate_mind_name(name)
264255

265256
if replace:
266257
try:
@@ -284,8 +275,15 @@ def create(
284275
if 'prompt_template' not in parameters:
285276
parameters['prompt_template'] = DEFAULT_PROMPT_TEMPLATE
286277

287-
self.api.post(
288-
f'/projects/{self.project}/minds',
278+
if update:
279+
method = self.api.put
280+
url = f'/projects/{self.project}/minds/{name}'
281+
else:
282+
method = self.api.post
283+
url = f'/projects/{self.project}/minds'
284+
285+
method(
286+
url,
289287
data={
290288
'name': name,
291289
'model_name': model_name,

minds/rest_api.py

+10
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ def post(self, url, data):
5757
_raise_for_status(resp)
5858
return resp
5959

60+
def put(self, url, data):
61+
resp = requests.put(
62+
self.base_url + url,
63+
headers=self._headers(),
64+
json=data,
65+
)
66+
67+
_raise_for_status(resp)
68+
return resp
69+
6070
def patch(self, url, data):
6171
resp = requests.patch(
6272
self.base_url + url,

minds/utils.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import re
2+
import minds.exceptions as exc
3+
from urllib.parse import urlparse, urlunparse
4+
5+
def get_openai_base_url(base_url: str) -> str:
6+
parsed = urlparse(base_url)
7+
8+
netloc = parsed.netloc
9+
if netloc == 'mdb.ai':
10+
llm_host = 'llm.mdb.ai'
11+
else:
12+
llm_host = 'ai.' + netloc
13+
14+
parsed = parsed._replace(path='', netloc=llm_host)
15+
16+
return urlunparse(parsed)
17+
18+
19+
def validate_mind_name(mind_name):
20+
"""
21+
Validate the Mind name.
22+
23+
A valid Mind name should:
24+
- Start with a letter
25+
- Contain only letters, numbers, or underscores
26+
- Have a maximum length of 32 characters
27+
- Not contain spaces
28+
29+
Parameters:
30+
mind_name (str): The Mind name to validate.
31+
32+
Returns:
33+
bool: True if valid, False otherwise.
34+
"""
35+
# Regular expression pattern
36+
pattern = r'^[A-Za-z][A-Za-z0-9_]{0,31}$'
37+
38+
# Check if the Mind name matches the pattern
39+
if not re.match(pattern, mind_name):
40+
raise exc.MindNameInvalid("Mind name should start with a letter and contain only letters, numbers or underscore, with a maximum of 32 characters. Spaces are not allowed.")

tests/integration/test_base_flow.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from minds.datasources.examples import example_ds
1111

12-
from minds.exceptions import ObjectNotFound
12+
from minds.exceptions import ObjectNotFound, MindNameInvalid
1313

1414

1515
def get_client():
@@ -37,7 +37,8 @@ def test_datasources():
3737

3838
# create
3939
ds = client.datasources.create(example_ds)
40-
ds = client.datasources.create(example_ds, replace=True)
40+
assert ds.name == example_ds.name
41+
ds = client.datasources.create(example_ds, update=True)
4142
assert ds.name == example_ds.name
4243

4344
# get
@@ -57,6 +58,7 @@ def test_minds():
5758
ds_name = 'test_datasource_'
5859
ds_name2 = 'test_datasource2_'
5960
mind_name = 'int_test_mind_'
61+
invalid_mind_name = 'mind-123'
6062
mind_name2 = 'int_test_mind2_'
6163
prompt1 = 'answer in german'
6264
prompt2 = 'answer in spanish'
@@ -71,14 +73,21 @@ def test_minds():
7173
# prepare datasources
7274
ds_cfg = copy.copy(example_ds)
7375
ds_cfg.name = ds_name
74-
ds = client.datasources.create(example_ds, replace=True)
76+
ds = client.datasources.create(example_ds, update=True)
7577

7678
# second datasource
7779
ds2_cfg = copy.copy(example_ds)
7880
ds2_cfg.name = ds_name2
7981
ds2_cfg.tables = ['home_rentals']
8082

8183
# create
84+
with pytest.raises(MindNameInvalid):
85+
client.minds.create(
86+
invalid_mind_name,
87+
datasources=[ds],
88+
provider='openai'
89+
)
90+
8291
mind = client.minds.create(
8392
mind_name,
8493
datasources=[ds],
@@ -90,6 +99,12 @@ def test_minds():
9099
datasources=[ds.name, ds2_cfg],
91100
prompt_template=prompt1
92101
)
102+
mind = client.minds.create(
103+
mind_name,
104+
update=True,
105+
datasources=[ds.name, ds2_cfg],
106+
prompt_template=prompt1
107+
)
93108

94109
# get
95110
mind = client.minds.get(mind_name)
@@ -106,6 +121,14 @@ def test_minds():
106121
datasources=[ds.name],
107122
prompt_template=prompt2
108123
)
124+
125+
with pytest.raises(MindNameInvalid):
126+
mind.update(
127+
name=invalid_mind_name,
128+
datasources=[ds.name],
129+
prompt_template=prompt2
130+
)
131+
109132
with pytest.raises(ObjectNotFound):
110133
# this name not exists
111134
client.minds.get(mind_name)
@@ -153,3 +176,4 @@ def test_minds():
153176
client.minds.drop(mind_name2)
154177
client.datasources.drop(ds.name)
155178
client.datasources.drop(ds2_cfg.name)
179+

0 commit comments

Comments
 (0)