Skip to content

Commit 9d07d68

Browse files
authored
Merge pull request #100 from mindsdb/staging
Release 2.2.1
2 parents 14ca3cc + fe6b7cd commit 9d07d68

File tree

18 files changed

+1501
-55
lines changed

18 files changed

+1501
-55
lines changed

.github/workflows/mindsdb_python_sdk.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ jobs:
2222
- name: Install dependencies
2323
run: |
2424
python -m pip install --upgrade pip==22.0.4
25-
pip install -r requirements.txt
25+
pip install -r requirements.txt
26+
pip install -r requirements_test.txt
2627
pip install --no-cache-dir .
2728
- name: Run tests
2829
run: |
@@ -54,8 +55,9 @@ jobs:
5455
- name: Install dependencies
5556
run: |
5657
python -m pip install --upgrade pip
57-
pip install flake8 pytest pytest-cov
58-
pip install -r requirements.txt
58+
pip install flake8
59+
pip install -r requirements.txt
60+
pip install -r requirements_test.txt
5961
6062
- name: Build coverage file
6163
run: |

examples/using_agents.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import mindsdb_sdk
2+
3+
con = mindsdb_sdk.connect()
4+
5+
# We currently support Langchain as a backend.
6+
print('Creating underlying langchain model for the agent to use...')
7+
try:
8+
langchain_engine = con.ml_engines.get('langchain')
9+
except Exception:
10+
# Create the engine if it doesn't exist.
11+
langchain_engine = con.ml_engines.create('langchain', handler='langchain')
12+
13+
# Actually create the underlying model the agent will use.
14+
langchain_model = con.models.create(
15+
'agent_model',
16+
predict='answer',
17+
engine='langchain',
18+
prompt_template='You are a spicy, cheeky assistant. Add some personality and flare when responding to the user question: {{question}}',
19+
model_name='gpt-4-0125-preview' # This is the underlying LLM. Can use OpenAI, Claude, local Ollama, etc
20+
# Can optionally set LLM args here. For example:
21+
# temperature=0.0,
22+
# max_tokens=1000,
23+
# top_p=1.0,
24+
# top_k=0,
25+
# ...
26+
)
27+
print('Agent ready to use.')
28+
29+
# Now create an agent that will use the model we just created.
30+
agent = con.agents.create('new_agent', langchain_model)
31+
print('Ask a question: ')
32+
question = input()
33+
answer = agent.completion([{'question': question, 'answer': None}])
34+
print(answer.content)

mindsdb_sdk/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'mindsdb_sdk'
22
__package_name__ = 'mindsdb_sdk'
3-
__version__ = '2.1.1'
3+
__version__ = '2.2.1'
44
__description__ = "MindsDB Python SDK, provides an SDK to use a remote mindsdb instance"
55
__email__ = "jorge@mindsdb.com"
66
__author__ = 'MindsDB Inc'

mindsdb_sdk/agents.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
from requests.exceptions import HTTPError
2+
from typing import List, Union
3+
from uuid import uuid4
4+
import datetime
5+
import pandas as pd
6+
7+
from mindsdb_sdk.databases import Databases
8+
from mindsdb_sdk.knowledge_bases import KnowledgeBases
9+
from mindsdb_sdk.models import Model
10+
from mindsdb_sdk.skills import Skill, Skills
11+
from mindsdb_sdk.utils.objects_collection import CollectionBase
12+
13+
14+
class AgentCompletion:
15+
"""Represents a full MindsDB agent completion"""
16+
def __init__(self, content: str):
17+
self.content = content
18+
19+
def __repr__(self):
20+
return self.content
21+
22+
23+
class Agent:
24+
"""Represents a MindsDB agent.
25+
26+
Working with agents:
27+
28+
Get an agent by name:
29+
30+
>>> agent = agents.get('my_agent')
31+
32+
Query an agent:
33+
34+
>>> completion = agent.completion([{'question': 'What is your name?', 'answer': None}])
35+
>>> print(completion.content)
36+
37+
List all agents:
38+
39+
>>> agents = agents.list()
40+
41+
Create a new agent:
42+
43+
>>> model = models.get('my_model') # Or use models.create(...)
44+
>>> # Connect your agent to a MindsDB table.
45+
>>> text_to_sql_skill = skills.create('text_to_sql', 'sql', { 'tables': ['my_table'], 'database': 'my_database' })
46+
>>> agent = agents.create('my_agent', model, [text_to_sql_skill])
47+
48+
Update an agent:
49+
50+
>>> new_model = models.get('new_model')
51+
>>> agent.model_name = new_model.name
52+
>>> new_skill = skills.create('new_skill', 'sql', { 'tables': ['new_table'], 'database': 'new_database' })
53+
>>> updated_agent.skills.append(new_skill)
54+
>>> updated_agent = agents.update('my_agent', agent)
55+
56+
Delete an agent by name:
57+
58+
>>> agents.delete('my_agent')
59+
"""
60+
def __init__(
61+
self,
62+
name: str,
63+
model_name: str,
64+
skills: List[Skill],
65+
params: dict,
66+
created_at: datetime.datetime,
67+
updated_at: datetime.datetime,
68+
collection: CollectionBase = None
69+
):
70+
self.name = name
71+
self.model_name = model_name
72+
self.skills = skills
73+
self.params = params
74+
self.created_at = created_at
75+
self.updated_at = updated_at
76+
self.collection = collection
77+
78+
def completion(self, messages: List[dict]) -> AgentCompletion:
79+
return self.collection.completion(self.name, messages)
80+
81+
def add_file(self, file_path: str, description: str, knowledge_base: str = None):
82+
"""
83+
Add a file to the agent for retrieval.
84+
85+
:param file_path: Path to the file to be added.
86+
"""
87+
self.collection.add_file(self.name, file_path, description, knowledge_base)
88+
89+
def __repr__(self):
90+
return f'{self.__class__.__name__}(name: {self.name})'
91+
92+
def __eq__(self, other):
93+
if self.name != other.name:
94+
return False
95+
if self.model_name != other.model_name:
96+
return False
97+
if self.skills != other.skills:
98+
return False
99+
if self.params != other.params:
100+
return False
101+
if self.created_at != other.created_at:
102+
return False
103+
return self.updated_at == other.updated_at
104+
105+
@classmethod
106+
def from_json(cls, json: dict, collection: CollectionBase):
107+
return cls(
108+
json['name'],
109+
json['model_name'],
110+
[Skill.from_json(skill) for skill in json['skills']],
111+
json['params'],
112+
json['created_at'],
113+
json['updated_at'],
114+
collection
115+
)
116+
117+
118+
class Agents(CollectionBase):
119+
"""Collection for agents"""
120+
def __init__(self, api, project: str, knowledge_bases: KnowledgeBases, databases: Databases, skills: Skills = None):
121+
self.api = api
122+
self.project = project
123+
self.skills = skills or Skills(self.api, project)
124+
self.databases = databases
125+
self.knowledge_bases = knowledge_bases
126+
127+
def list(self) -> List[Agent]:
128+
"""
129+
List available agents.
130+
131+
:return: list of agents
132+
"""
133+
data = self.api.agents(self.project)
134+
return [Agent.from_json(agent, self) for agent in data]
135+
136+
def get(self, name: str) -> Agent:
137+
"""
138+
Gets an agent by name.
139+
140+
:param name: Name of the agent
141+
142+
:return: agent with given name
143+
"""
144+
data = self.api.agent(self.project, name)
145+
return Agent.from_json(data, self)
146+
147+
def completion(self, name: str, messages: List[dict]) -> AgentCompletion:
148+
"""
149+
Queries the agent for a completion.
150+
151+
:param name: Name of the agent
152+
:param messages: List of messages to be sent to the agent
153+
154+
:return: completion from querying the agent
155+
"""
156+
data = self.api.agent_completion(self.project, name, messages)
157+
return AgentCompletion(data['message']['content'])
158+
159+
def add_file(self, name: str, file_path: str, description: str, knowledge_base: str = None):
160+
"""
161+
Add a file to the agent for retrieval.
162+
163+
:param name: Name of the agent
164+
:param file_path: Path to the file to be added, or name of existing file.
165+
:param description: Description of the file. Used by agent to know when to do retrieval
166+
:param knowledge_base: Name of an existing knowledge base to be used. Will create a default knowledge base if not given.
167+
"""
168+
filename = file_path.split('/')[-1]
169+
filename_no_extension = filename.split('.')[0]
170+
try:
171+
_ = self.api.get_file_metadata(filename_no_extension)
172+
except HTTPError as e:
173+
if e.response.status_code >= 400 and e.response.status_code != 404:
174+
raise e
175+
# Upload file if it doesn't exist.
176+
with open(file_path, 'rb') as file:
177+
content = file.read()
178+
df = pd.DataFrame.from_records([{'content': content}])
179+
self.api.upload_file(filename_no_extension, df)
180+
181+
# Insert uploaded file into new knowledge base.
182+
if knowledge_base is not None:
183+
kb = self.knowledge_bases.get(knowledge_base)
184+
else:
185+
kb_name = f'{name}_{filename_no_extension}_kb'
186+
try:
187+
kb = self.knowledge_bases.get(kb_name)
188+
except AttributeError as e:
189+
# Create KB if it doesn't exist.
190+
kb = self.knowledge_bases.create(kb_name)
191+
# Wait for underlying embedding model to finish training.
192+
kb.model.wait_complete()
193+
194+
# Insert the entire file.
195+
kb.insert(self.databases.files.tables.get(filename_no_extension))
196+
197+
# Make sure skill name is unique.
198+
skill_name = f'{filename_no_extension}_retrieval_skill_{uuid4()}'
199+
retrieval_params = {
200+
'source': kb.name,
201+
'description': description,
202+
}
203+
file_retrieval_skill = self.skills.create(skill_name, 'knowledge_base', retrieval_params)
204+
agent = self.get(name)
205+
agent.skills.append(file_retrieval_skill)
206+
self.update(agent.name, agent)
207+
208+
def create(
209+
self,
210+
name: str,
211+
model: Model,
212+
skills: List[Union[Skill, str]] = None,
213+
params: dict = None) -> Agent:
214+
"""
215+
Create new agent and return it
216+
217+
:param name: Name of the agent to be created
218+
:param model: Model to be used by the agent
219+
:param skills: List of skills to be used by the agent. Currently only 'sql' is supported.
220+
:param params: Parameters for the agent
221+
222+
:return: created agent object
223+
"""
224+
skills = skills or []
225+
skill_names = []
226+
for skill in skills:
227+
if isinstance(skill, str):
228+
# Check if skill exists.
229+
_ = self.skills.get(skill)
230+
skill_names.append(skill)
231+
continue
232+
# Create the skill if it doesn't exist.
233+
_ = self.skills.create(skill.name, skill.type, skill.params)
234+
skill_names.append(skill.name)
235+
236+
data = self.api.create_agent(self.project, name, model.name, skill_names, params)
237+
return Agent.from_json(data, self)
238+
239+
def update(self, name: str, updated_agent: Agent):
240+
"""
241+
Update an agent by name.
242+
243+
:param name: Name of the agent to be updated
244+
:param updated_agent: Agent with updated fields
245+
246+
:return: updated agent object
247+
"""
248+
updated_skills = set()
249+
for skill in updated_agent.skills:
250+
if isinstance(skill, str):
251+
# Skill must exist.
252+
_ = self.skills.get(skill)
253+
updated_skills.add(skill)
254+
continue
255+
try:
256+
# Create the skill if it doesn't exist.
257+
_ = self.skills.get(skill.name)
258+
except HTTPError as e:
259+
if e.response.status_code != 404:
260+
raise e
261+
# Doesn't exist
262+
_ = self.skills.create(skill.name, skill.type, skill.params)
263+
updated_skills.add(skill.name)
264+
265+
existing_agent = self.api.agent(self.project, name)
266+
existing_skills = set([s['name'] for s in existing_agent['skills']])
267+
skills_to_add = updated_skills.difference(existing_skills)
268+
skills_to_remove = existing_skills.difference(updated_skills)
269+
data = self.api.update_agent(
270+
self.project,
271+
name,
272+
updated_agent.name,
273+
updated_agent.model_name,
274+
list(skills_to_add),
275+
list(skills_to_remove),
276+
updated_agent.params
277+
)
278+
return Agent.from_json(data, self)
279+
280+
def drop(self, name: str):
281+
"""
282+
Drop an agent by name.
283+
284+
:param name: Name of the agent to be dropped
285+
"""
286+
_ = self.api.delete_agent(self.project, name)

mindsdb_sdk/connect.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
from mindsdb_sdk.connectors.rest_api import RestAPI
44

5+
DEFAULT_LOCAL_API_URL = 'http://127.0.0.1:47334'
6+
DEFAULT_CLOUD_API_URL = 'https://cloud.mindsdb.com'
57

6-
def connect(url: str = None, login: str = None, password: str = None, is_managed: bool = False) -> Server:
8+
9+
def connect(url: str = None, login: str = None, password: str = None, is_managed: bool = False, headers=None) -> Server:
710
"""
811
Create connection to mindsdb server
912
1013
:param url: url to mindsdb server
1114
:param login: user login, for cloud version it contents email
1215
:param password: user password to login (for cloud version)
1316
:param is_managed: whether or not the URL points to a managed instance
17+
:param headers: addtional headers to send with the connection, optional
1418
:return: Server object
1519
1620
Examples
@@ -36,11 +40,11 @@ def connect(url: str = None, login: str = None, password: str = None, is_managed
3640
if url is None:
3741
if login is not None:
3842
# default is cloud
39-
url = 'https://cloud.mindsdb.com'
43+
url = DEFAULT_CLOUD_API_URL
4044
else:
4145
# is local
42-
url = 'http://127.0.0.1:47334'
46+
url = DEFAULT_LOCAL_API_URL
4347

44-
api = RestAPI(url, login, password, is_managed)
48+
api = RestAPI(url, login, password, is_managed, headers=headers)
4549

46-
return Server(api)
50+
return Server(api)

0 commit comments

Comments
 (0)