Skip to content

Commit 9c535ce

Browse files
authored
Merge pull request #96 from mindsdb/agents-base
Add Agents Support to SDK
2 parents f6c1672 + 5844ff0 commit 9c535ce

File tree

8 files changed

+879
-8
lines changed

8 files changed

+879
-8
lines changed

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/agents.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
from requests.exceptions import HTTPError
2+
from typing import List, Union
3+
import datetime
4+
5+
from mindsdb_sdk.models import Model
6+
from mindsdb_sdk.skills import Skill, Skills
7+
from mindsdb_sdk.utils.objects_collection import CollectionBase
8+
9+
10+
class AgentCompletion:
11+
"""Represents a full MindsDB agent completion"""
12+
def __init__(self, content: str):
13+
self.content = content
14+
15+
def __repr__(self):
16+
return self.content
17+
18+
19+
class Agent:
20+
"""Represents a MindsDB agent.
21+
22+
Working with agents:
23+
24+
Get an agent by name:
25+
26+
>>> agent = agents.get('my_agent')
27+
28+
Query an agent:
29+
30+
>>> completion = agent.completion([{'question': 'What is your name?', 'answer': None}])
31+
>>> print(completion.content)
32+
33+
List all agents:
34+
35+
>>> agents = agents.list()
36+
37+
Create a new agent:
38+
39+
>>> model = models.get('my_model') # Or use models.create(...)
40+
>>> # Connect your agent to a MindsDB table.
41+
>>> text_to_sql_skill = skills.create('text_to_sql', 'sql', { 'tables': ['my_table'], 'database': 'my_database' })
42+
>>> agent = agents.create('my_agent', model, [text_to_sql_skill])
43+
44+
Update an agent:
45+
46+
>>> new_model = models.get('new_model')
47+
>>> agent.model_name = new_model.name
48+
>>> new_skill = skills.create('new_skill', 'sql', { 'tables': ['new_table'], 'database': 'new_database' })
49+
>>> updated_agent.skills.append(new_skill)
50+
>>> updated_agent = agents.update('my_agent', agent)
51+
52+
Delete an agent by name:
53+
54+
>>> agents.delete('my_agent')
55+
"""
56+
def __init__(
57+
self,
58+
name: str,
59+
model_name: str,
60+
skills: List[Skill],
61+
params: dict,
62+
created_at: datetime.datetime,
63+
updated_at: datetime.datetime,
64+
collection: CollectionBase = None
65+
):
66+
self.name = name
67+
self.model_name = model_name
68+
self.skills = skills
69+
self.params = params
70+
self.created_at = created_at
71+
self.updated_at = updated_at
72+
self.collection = collection
73+
74+
def completion(self, messages: List[dict]) -> AgentCompletion:
75+
return self.collection.completion(self.name, messages)
76+
77+
def __repr__(self):
78+
return f'{self.__class__.__name__}(name: {self.name})'
79+
80+
def __eq__(self, other):
81+
if self.name != other.name:
82+
return False
83+
if self.model_name != other.model_name:
84+
return False
85+
if self.skills != other.skills:
86+
return False
87+
if self.params != other.params:
88+
return False
89+
if self.created_at != other.created_at:
90+
return False
91+
return self.updated_at == other.updated_at
92+
93+
@classmethod
94+
def from_json(cls, json: dict, collection: CollectionBase):
95+
return cls(
96+
json['name'],
97+
json['model_name'],
98+
[Skill.from_json(skill) for skill in json['skills']],
99+
json['params'],
100+
json['created_at'],
101+
json['updated_at'],
102+
collection
103+
)
104+
105+
106+
class Agents(CollectionBase):
107+
"""Collection for agents"""
108+
def __init__(self, api, project: str, skills: Skills = None):
109+
self.api = api
110+
self.project = project
111+
self.skills = skills or Skills(self.api, project)
112+
113+
def list(self) -> List[Agent]:
114+
"""
115+
List available agents.
116+
117+
:return: list of agents
118+
"""
119+
data = self.api.agents(self.project)
120+
return [Agent.from_json(agent, self) for agent in data]
121+
122+
def get(self, name: str) -> Agent:
123+
"""
124+
Gets an agent by name.
125+
126+
:param name: Name of the agent
127+
128+
:return: agent with given name
129+
"""
130+
data = self.api.agent(self.project, name)
131+
return Agent.from_json(data, self)
132+
133+
def completion(self, name: str, messages: List[dict]) -> AgentCompletion:
134+
"""
135+
Queries the agent for a completion.
136+
137+
:param name: Name of the agent
138+
:param messages: List of messages to be sent to the agent
139+
140+
:return: completion from querying the agent
141+
"""
142+
data = self.api.agent_completion(self.project, name, messages)
143+
return AgentCompletion(data['message']['content'])
144+
145+
def create(
146+
self,
147+
name: str,
148+
model: Model,
149+
skills: List[Union[Skill, str]] = None,
150+
params: dict = None) -> Agent:
151+
"""
152+
Create new agent and return it
153+
154+
:param name: Name of the agent to be created
155+
:param model: Model to be used by the agent
156+
:param skills: List of skills to be used by the agent. Currently only 'sql' is supported.
157+
:param params: Parameters for the agent
158+
159+
:return: created agent object
160+
"""
161+
skills = skills or []
162+
skill_names = []
163+
for skill in skills:
164+
if isinstance(skill, str):
165+
# Check if skill exists.
166+
_ = self.skills.get(skill)
167+
skill_names.append(skill)
168+
continue
169+
# Create the skill if it doesn't exist.
170+
_ = self.skills.create(skill.name, skill.type, skill.params)
171+
skill_names.append(skill.name)
172+
173+
data = self.api.create_agent(self.project, name, model.name, skill_names, params)
174+
return Agent.from_json(data, self)
175+
176+
def update(self, name: str, updated_agent: Agent):
177+
"""
178+
Update an agent by name.
179+
180+
:param name: Name of the agent to be updated
181+
:param updated_agent: Agent with updated fields
182+
183+
:return: updated agent object
184+
"""
185+
updated_skills = set()
186+
for skill in updated_agent.skills:
187+
if isinstance(skill, str):
188+
# Skill must exist.
189+
_ = self.skills.get(skill)
190+
updated_skills.add(skill)
191+
continue
192+
try:
193+
# Create the skill if it doesn't exist.
194+
_ = self.skills.get(skill.name)
195+
except HTTPError as e:
196+
if e.response.status_code != 404:
197+
raise e
198+
# Doesn't exist
199+
_ = self.skills.create(skill.name, skill.type, skill.params)
200+
updated_skills.add(skill.name)
201+
202+
existing_agent = self.api.agent(self.project, name)
203+
existing_skills = set([s.name for s in existing_agent['skills']])
204+
skills_to_add = updated_skills.difference(existing_skills)
205+
skills_to_remove = existing_skills.difference(updated_skills)
206+
data = self.api.update_agent(
207+
self.project,
208+
name,
209+
updated_agent.name,
210+
updated_agent.model_name,
211+
list(skills_to_add),
212+
list(skills_to_remove),
213+
updated_agent.params
214+
)
215+
return Agent.from_json(data, self)
216+
217+
def drop(self, name: str):
218+
"""
219+
Drop an agent by name.
220+
221+
:param name: Name of the agent to be dropped
222+
"""
223+
_ = self.api.delete_agent(self.project, name)

mindsdb_sdk/connect.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
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'
7+
58

69
def connect(url: str = None, login: str = None, password: str = None, is_managed: bool = False, headers=None) -> Server:
710
"""
@@ -37,11 +40,11 @@ def connect(url: str = None, login: str = None, password: str = None, is_managed
3740
if url is None:
3841
if login is not None:
3942
# default is cloud
40-
url = 'https://cloud.mindsdb.com'
43+
url = DEFAULT_CLOUD_API_URL
4144
else:
4245
# is local
43-
url = 'http://127.0.0.1:47334'
46+
url = DEFAULT_LOCAL_API_URL
4447

4548
api = RestAPI(url, login, password, is_managed, headers=headers)
4649

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

0 commit comments

Comments
 (0)