select-ai 1.2.0rc3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,511 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+
8
+ from abc import ABC
9
+ from dataclasses import dataclass
10
+ from typing import (
11
+ Any,
12
+ AsyncGenerator,
13
+ Iterator,
14
+ Optional,
15
+ Union,
16
+ )
17
+
18
+ import oracledb
19
+
20
+ from select_ai._abc import SelectAIDataClass
21
+ from select_ai.agent.sql import (
22
+ GET_USER_AI_AGENT,
23
+ GET_USER_AI_AGENT_ATTRIBUTES,
24
+ LIST_USER_AI_AGENTS,
25
+ )
26
+ from select_ai.db import async_cursor, cursor
27
+ from select_ai.errors import AgentNotFoundError
28
+
29
+
30
+ @dataclass
31
+ class AgentAttributes(SelectAIDataClass):
32
+ """AI Agent Attributes
33
+
34
+ :param str profile_name: Name of the AI Profile which agent will
35
+ use to send request to LLM
36
+ :param str role: Agent's role also sent to LLM
37
+ :param bool enable_human_tool: Enable human tool support. Agent
38
+ will ask question to the user for any clarification
39
+ """
40
+
41
+ profile_name: str
42
+ role: str
43
+ enable_human_tool: Optional[bool] = True
44
+
45
+
46
+ class BaseAgent(ABC):
47
+
48
+ def __init__(
49
+ self,
50
+ agent_name: Optional[str] = None,
51
+ description: Optional[str] = None,
52
+ attributes: Optional[AgentAttributes] = None,
53
+ ):
54
+ if attributes is not None and not isinstance(
55
+ attributes, AgentAttributes
56
+ ):
57
+ raise TypeError(
58
+ "attributes must be an object of type "
59
+ "select_ai.agent.AgentAttributes"
60
+ )
61
+ self.agent_name = agent_name
62
+ self.description = description
63
+ self.attributes = attributes
64
+
65
+ def __repr__(self):
66
+ return (
67
+ f"{self.__class__.__name__}("
68
+ f"agent_name={self.agent_name}, "
69
+ f"attributes={self.attributes}, description={self.description})"
70
+ )
71
+
72
+
73
+ class Agent(BaseAgent):
74
+ """
75
+ select_ai.agent.Agent class lets you create, delete, enable, disable
76
+ and list AI agents
77
+
78
+ :param str agent_name: The name of the AI Agent
79
+ :param str description: Optional description of the AI agent
80
+ :param select_ai.agent.AgentAttributes attributes: AI agent attributes
81
+
82
+ """
83
+
84
+ @staticmethod
85
+ def _get_attributes(agent_name: str) -> AgentAttributes:
86
+ with cursor() as cr:
87
+ cr.execute(
88
+ GET_USER_AI_AGENT_ATTRIBUTES, agent_name=agent_name.upper()
89
+ )
90
+ attributes = cr.fetchall()
91
+ if attributes:
92
+ post_processed_attributes = {}
93
+ for k, v in attributes:
94
+ if isinstance(v, oracledb.LOB):
95
+ post_processed_attributes[k] = v.read()
96
+ else:
97
+ post_processed_attributes[k] = v
98
+ return AgentAttributes(**post_processed_attributes)
99
+ else:
100
+ raise AgentNotFoundError(agent_name=agent_name)
101
+
102
+ @staticmethod
103
+ def _get_description(agent_name: str) -> Union[str, None]:
104
+ with cursor() as cr:
105
+ cr.execute(GET_USER_AI_AGENT, agent_name=agent_name.upper())
106
+ agent = cr.fetchone()
107
+ if agent:
108
+ if agent[1] is not None:
109
+ return agent[1].read()
110
+ else:
111
+ return None
112
+ else:
113
+ raise AgentNotFoundError(agent_name)
114
+
115
+ def create(
116
+ self, enabled: Optional[bool] = True, replace: Optional[bool] = False
117
+ ):
118
+ """
119
+ Register a new AI Agent within the Select AI framework
120
+
121
+ :param bool enabled: Whether the AI Agent should be enabled.
122
+ Default value is True.
123
+
124
+ :param bool replace: Whether the AI Agent should be replaced.
125
+ Default value is False.
126
+
127
+ """
128
+ if self.agent_name is None:
129
+ raise AttributeError("Agent must have a name")
130
+ if self.attributes is None:
131
+ raise AttributeError("Agent must have attributes")
132
+
133
+ parameters = {
134
+ "agent_name": self.agent_name,
135
+ "attributes": self.attributes.json(),
136
+ }
137
+ if self.description:
138
+ parameters["description"] = self.description
139
+
140
+ if not enabled:
141
+ parameters["status"] = "disabled"
142
+
143
+ with cursor() as cr:
144
+ try:
145
+ cr.callproc(
146
+ "DBMS_CLOUD_AI_AGENT.CREATE_AGENT",
147
+ keyword_parameters=parameters,
148
+ )
149
+ except oracledb.Error as err:
150
+ (err_obj,) = err.args
151
+ if err_obj.code in (20050, 20052) and replace:
152
+ self.delete(force=True)
153
+ cr.callproc(
154
+ "DBMS_CLOUD_AI_AGENT.CREATE_AGENT",
155
+ keyword_parameters=parameters,
156
+ )
157
+ else:
158
+ raise
159
+
160
+ def delete(self, force: Optional[bool] = False):
161
+ """
162
+ Delete AI Agent from the database
163
+
164
+ :param bool force: Force the deletion. Default value is False.
165
+
166
+ """
167
+ with cursor() as cr:
168
+ cr.callproc(
169
+ "DBMS_CLOUD_AI_AGENT.DROP_AGENT",
170
+ keyword_parameters={
171
+ "agent_name": self.agent_name,
172
+ "force": force,
173
+ },
174
+ )
175
+
176
+ def disable(self):
177
+ """
178
+ Disable AI Agent
179
+ """
180
+ with cursor as cr:
181
+ cr.callproc(
182
+ "DBMS_CLOUD_AI_AGENT.DISABLE_AGENT",
183
+ keyword_parameters={
184
+ "agent_name": self.agent_name,
185
+ },
186
+ )
187
+
188
+ def enable(self):
189
+ """
190
+ Enable AI Agent
191
+ """
192
+ with cursor as cr:
193
+ cr.callproc(
194
+ "DBMS_CLOUD_AI_AGENT.ENABLE_AGENT",
195
+ keyword_parameters={
196
+ "agent_name": self.agent_name,
197
+ },
198
+ )
199
+
200
+ @classmethod
201
+ def fetch(cls, agent_name: str) -> "Agent":
202
+ """
203
+ Fetch AI Agent attributes from the Database and build a proxy object in
204
+ the Python layer
205
+
206
+ :param str agent_name: The name of the AI Agent
207
+
208
+ :return: select_ai.agent.Agent
209
+
210
+ :raises select_ai.errors.AgentNotFoundError:
211
+ If the AI Agent is not found
212
+
213
+ """
214
+ attributes = cls._get_attributes(agent_name=agent_name)
215
+ description = cls._get_description(agent_name=agent_name)
216
+ return cls(
217
+ agent_name=agent_name,
218
+ attributes=attributes,
219
+ description=description,
220
+ )
221
+
222
+ @classmethod
223
+ def list(
224
+ cls, agent_name_pattern: Optional[str] = ".*"
225
+ ) -> Iterator["Agent"]:
226
+ """
227
+ List AI agents matching a pattern
228
+
229
+ :param str agent_name_pattern: Regular expressions can be used
230
+ to specify a pattern. Function REGEXP_LIKE is used to perform the
231
+ match. Default value is ".*" i.e. match all agent names.
232
+
233
+ :return: Iterator[Agent]
234
+ """
235
+ with cursor() as cr:
236
+ cr.execute(
237
+ LIST_USER_AI_AGENTS,
238
+ agent_name_pattern=agent_name_pattern,
239
+ )
240
+ for row in cr.fetchall():
241
+ agent_name = row[0]
242
+ if row[1]:
243
+ description = row[1].read() # Oracle.LOB
244
+ else:
245
+ description = None
246
+ attributes = cls._get_attributes(agent_name=agent_name)
247
+ yield cls(
248
+ agent_name=agent_name,
249
+ description=description,
250
+ attributes=attributes,
251
+ )
252
+
253
+ def set_attributes(self, attributes: AgentAttributes) -> None:
254
+ """
255
+ Set AI Agent attributes
256
+
257
+ :param select_ai.agent.AgentAttributes attributes: Multiple attributes
258
+ can be specified by passing an AgentAttributes object
259
+ """
260
+ parameters = {
261
+ "object_name": self.agent_name,
262
+ "object_type": "agent",
263
+ "attributes": attributes.json(),
264
+ }
265
+ with cursor() as cr:
266
+ cr.callproc(
267
+ "DBMS_CLOUD_AI_AGENT.SET_ATTRIBUTES",
268
+ keyword_parameters=parameters,
269
+ )
270
+ self.attributes = self._get_attributes(agent_name=self.agent_name)
271
+
272
+ def set_attribute(self, attribute_name: str, attribute_value: Any) -> None:
273
+ """
274
+ Set a single AI Agent attribute specified using name and value
275
+ """
276
+ parameters = {
277
+ "object_name": self.agent_name,
278
+ "object_type": "agent",
279
+ "attribute_name": attribute_name,
280
+ "attribute_value": attribute_value,
281
+ }
282
+ with cursor() as cr:
283
+ cr.callproc(
284
+ "DBMS_CLOUD_AI_AGENT.SET_ATTRIBUTE",
285
+ keyword_parameters=parameters,
286
+ )
287
+ self.attributes = self._get_attributes(agent_name=self.agent_name)
288
+
289
+
290
+ class AsyncAgent(BaseAgent):
291
+ """
292
+ select_ai.agent.AsyncAgent class lets you create, delete, enable, disable
293
+ and list AI agents asynchronously
294
+
295
+ :param str agent_name: The name of the AI Agent
296
+ :param str description: Optional description of the AI agent
297
+ :param select_ai.agent.AgentAttributes attributes: AI agent attributes
298
+
299
+ """
300
+
301
+ @staticmethod
302
+ async def _get_attributes(agent_name: str) -> AgentAttributes:
303
+ async with async_cursor() as cr:
304
+ await cr.execute(
305
+ GET_USER_AI_AGENT_ATTRIBUTES, agent_name=agent_name.upper()
306
+ )
307
+ attributes = await cr.fetchall()
308
+ if attributes:
309
+ post_processed_attributes = {}
310
+ for k, v in attributes:
311
+ if isinstance(v, oracledb.AsyncLOB):
312
+ post_processed_attributes[k] = await v.read()
313
+ else:
314
+ post_processed_attributes[k] = v
315
+ return AgentAttributes(**post_processed_attributes)
316
+ else:
317
+ raise AgentNotFoundError(agent_name=agent_name)
318
+
319
+ @staticmethod
320
+ async def _get_description(agent_name: str) -> Union[str, None]:
321
+ async with async_cursor() as cr:
322
+ await cr.execute(GET_USER_AI_AGENT, agent_name=agent_name.upper())
323
+ agent = await cr.fetchone()
324
+ if agent:
325
+ if agent[1] is not None:
326
+ return await agent[1].read()
327
+ else:
328
+ return None
329
+ else:
330
+ raise AgentNotFoundError(agent_name)
331
+
332
+ async def create(
333
+ self, enabled: Optional[bool] = True, replace: Optional[bool] = False
334
+ ):
335
+ """
336
+ Register a new AI Agent within the Select AI framework
337
+
338
+ :param bool enabled: Whether the AI Agent should be enabled.
339
+ Default value is True.
340
+
341
+ :param bool replace: Whether the AI Agent should be replaced.
342
+ Default value is False.
343
+
344
+ """
345
+ if self.agent_name is None:
346
+ raise AttributeError("Agent must have a name")
347
+ if self.attributes is None:
348
+ raise AttributeError("Agent must have attributes")
349
+
350
+ parameters = {
351
+ "agent_name": self.agent_name,
352
+ "attributes": self.attributes.json(),
353
+ }
354
+ if self.description:
355
+ parameters["description"] = self.description
356
+
357
+ if not enabled:
358
+ parameters["status"] = "disabled"
359
+
360
+ async with async_cursor() as cr:
361
+ try:
362
+ await cr.callproc(
363
+ "DBMS_CLOUD_AI_AGENT.CREATE_AGENT",
364
+ keyword_parameters=parameters,
365
+ )
366
+ except oracledb.Error as err:
367
+ (err_obj,) = err.args
368
+ if err_obj.code in (20050, 20052) and replace:
369
+ await self.delete(force=True)
370
+ await cr.callproc(
371
+ "DBMS_CLOUD_AI_AGENT.CREATE_AGENT",
372
+ keyword_parameters=parameters,
373
+ )
374
+ else:
375
+ raise
376
+
377
+ async def delete(self, force: Optional[bool] = False):
378
+ """
379
+ Delete AI Agent from the database
380
+
381
+ :param bool force: Force the deletion. Default value is False.
382
+
383
+ """
384
+ async with async_cursor() as cr:
385
+ await cr.callproc(
386
+ "DBMS_CLOUD_AI_AGENT.DROP_AGENT",
387
+ keyword_parameters={
388
+ "agent_name": self.agent_name,
389
+ "force": force,
390
+ },
391
+ )
392
+
393
+ async def disable(self):
394
+ """
395
+ Disable AI Agent
396
+ """
397
+ async with async_cursor as cr:
398
+ await cr.callproc(
399
+ "DBMS_CLOUD_AI_AGENT.DISABLE_AGENT",
400
+ keyword_parameters={
401
+ "agent_name": self.agent_name,
402
+ },
403
+ )
404
+
405
+ async def enable(self):
406
+ """
407
+ Enable AI Agent
408
+ """
409
+ async with async_cursor as cr:
410
+ await cr.callproc(
411
+ "DBMS_CLOUD_AI_AGENT.ENABLE_AGENT",
412
+ keyword_parameters={
413
+ "agent_name": self.agent_name,
414
+ },
415
+ )
416
+
417
+ @classmethod
418
+ async def fetch(cls, agent_name: str) -> "AsyncAgent":
419
+ """
420
+ Fetch AI Agent attributes from the Database and build a proxy object in
421
+ the Python layer
422
+
423
+ :param str agent_name: The name of the AI Agent
424
+
425
+ :return: select_ai.agent.Agent
426
+
427
+ :raises select_ai.errors.AgentNotFoundError:
428
+ If the AI Agent is not found
429
+
430
+ """
431
+ attributes = await cls._get_attributes(agent_name=agent_name)
432
+ description = await cls._get_description(agent_name=agent_name)
433
+ return cls(
434
+ agent_name=agent_name,
435
+ attributes=attributes,
436
+ description=description,
437
+ )
438
+
439
+ @classmethod
440
+ async def list(
441
+ cls, agent_name_pattern: Optional[str] = ".*"
442
+ ) -> AsyncGenerator["AsyncAgent", None]:
443
+ """
444
+ List AI agents matching a pattern
445
+
446
+ :param str agent_name_pattern: Regular expressions can be used
447
+ to specify a pattern. Function REGEXP_LIKE is used to perform the
448
+ match. Default value is ".*" i.e. match all agent names.
449
+
450
+ :return: AsyncGenerator[AsyncAgent]
451
+ """
452
+ async with async_cursor() as cr:
453
+ await cr.execute(
454
+ LIST_USER_AI_AGENTS,
455
+ agent_name_pattern=agent_name_pattern,
456
+ )
457
+ rows = await cr.fetchall()
458
+ for row in rows:
459
+ agent_name = row[0]
460
+ if row[1]:
461
+ description = await row[1].read() # Oracle.AsyncLOB
462
+ else:
463
+ description = None
464
+ attributes = await cls._get_attributes(agent_name=agent_name)
465
+ yield cls(
466
+ agent_name=agent_name,
467
+ description=description,
468
+ attributes=attributes,
469
+ )
470
+
471
+ async def set_attributes(self, attributes: AgentAttributes) -> None:
472
+ """
473
+ Set AI Agent attributes
474
+
475
+ :param select_ai.agent.AgentAttributes attributes: Multiple attributes
476
+ can be specified by passing an AgentAttributes object
477
+ """
478
+ parameters = {
479
+ "object_name": self.agent_name,
480
+ "object_type": "agent",
481
+ "attributes": attributes.json(),
482
+ }
483
+ async with async_cursor() as cr:
484
+ await cr.callproc(
485
+ "DBMS_CLOUD_AI_AGENT.SET_ATTRIBUTES",
486
+ keyword_parameters=parameters,
487
+ )
488
+ self.attributes = await self._get_attributes(
489
+ agent_name=self.agent_name
490
+ )
491
+
492
+ async def set_attribute(
493
+ self, attribute_name: str, attribute_value: Any
494
+ ) -> None:
495
+ """
496
+ Set a single AI Agent attribute specified using name and value
497
+ """
498
+ parameters = {
499
+ "object_name": self.agent_name,
500
+ "object_type": "agent",
501
+ "attribute_name": attribute_name,
502
+ "attribute_value": attribute_value,
503
+ }
504
+ async with async_cursor() as cr:
505
+ await cr.callproc(
506
+ "DBMS_CLOUD_AI_AGENT.SET_ATTRIBUTE",
507
+ keyword_parameters=parameters,
508
+ )
509
+ self.attributes = await self._get_attributes(
510
+ agent_name=self.agent_name
511
+ )
select_ai/agent/sql.py ADDED
@@ -0,0 +1,82 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) 2025, Oracle and/or its affiliates.
3
+ #
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at
5
+ # http://oss.oracle.com/licenses/upl.
6
+ # -----------------------------------------------------------------------------
7
+
8
+
9
+ GET_USER_AI_AGENT = """
10
+ SELECT a.agent_name, a.description
11
+ from USER_AI_AGENTS a
12
+ where a.agent_name = :agent_name
13
+ """
14
+
15
+ GET_USER_AI_AGENT_ATTRIBUTES = """
16
+ SELECT attribute_name, attribute_value
17
+ FROM USER_AI_AGENT_ATTRIBUTES
18
+ WHERE agent_name = :agent_name
19
+ """
20
+
21
+ LIST_USER_AI_AGENTS = """
22
+ SELECT a.agent_name, a.description
23
+ from USER_AI_AGENTS a
24
+ where REGEXP_LIKE(a.agent_name, :agent_name_pattern, 'i')
25
+ """
26
+
27
+ GET_USER_AI_AGENT_TASK = """
28
+ SELECT t.task_name, t.description
29
+ FROM USER_AI_AGENT_TASKS t
30
+ WHERE t.task_name= :task_name
31
+ """
32
+
33
+ GET_USER_AI_AGENT_TASK_ATTRIBUTES = """
34
+ SELECT attribute_name, attribute_value
35
+ FROM USER_AI_AGENT_TASK_ATTRIBUTES
36
+ WHERE task_name= :task_name
37
+ """
38
+
39
+ LIST_USER_AI_AGENT_TASKS = """
40
+ SELECT t.task_name, t.description
41
+ FROM USER_AI_AGENT_TASKS t
42
+ WHERE REGEXP_LIKE(t.task_name, :task_name_pattern, 'i')
43
+ """
44
+
45
+ GET_USER_AI_AGENT_TOOL = """
46
+ SELECT t.tool_name, t.description
47
+ FROM USER_AI_AGENT_TOOLS t
48
+ WHERE t.tool_name = :tool_name
49
+ """
50
+
51
+ GET_USER_AI_AGENT_TOOL_ATTRIBUTES = """
52
+ SELECT attribute_name, attribute_value
53
+ FROM USER_AI_AGENT_TOOL_ATTRIBUTES
54
+ WHERE tool_name = :tool_name
55
+ """
56
+
57
+ LIST_USER_AI_AGENT_TOOLS = """
58
+ SELECT t.tool_name, t.description
59
+ FROM USER_AI_AGENT_TOOLS t
60
+ WHERE REGEXP_LIKE(t.tool_name, :tool_name_pattern, 'i')
61
+ """
62
+
63
+
64
+ GET_USER_AI_AGENT_TEAM = """
65
+ SELECT t.agent_team_name as team_name, t.description
66
+ FROM USER_AI_AGENT_TEAMS t
67
+ WHERE t.agent_team_name = :team_name
68
+ """
69
+
70
+
71
+ GET_USER_AI_AGENT_TEAM_ATTRIBUTES = """
72
+ SELECT attribute_name, attribute_value
73
+ FROM USER_AI_AGENT_TEAM_ATTRIBUTES
74
+ WHERE agent_team_name = :team_name
75
+ """
76
+
77
+
78
+ LIST_USER_AI_AGENT_TEAMS = """
79
+ SELECT t.AGENT_TEAM_NAME as team_name, description
80
+ FROM USER_AI_AGENT_TEAMS t
81
+ WHERE REGEXP_LIKE(t.AGENT_TEAM_NAME, :team_name_pattern, 'i')
82
+ """