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