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.
- select_ai/__init__.py +66 -0
- select_ai/_abc.py +82 -0
- select_ai/_enums.py +14 -0
- select_ai/_validations.py +123 -0
- select_ai/action.py +23 -0
- select_ai/agent/__init__.py +25 -0
- select_ai/agent/core.py +511 -0
- select_ai/agent/sql.py +82 -0
- select_ai/agent/task.py +521 -0
- select_ai/agent/team.py +590 -0
- select_ai/agent/tool.py +1129 -0
- select_ai/async_profile.py +648 -0
- select_ai/base_profile.py +265 -0
- select_ai/conversation.py +295 -0
- select_ai/credential.py +135 -0
- select_ai/db.py +191 -0
- select_ai/errors.py +113 -0
- select_ai/feedback.py +19 -0
- select_ai/privilege.py +135 -0
- select_ai/profile.py +579 -0
- select_ai/provider.py +195 -0
- select_ai/sql.py +111 -0
- select_ai/summary.py +61 -0
- select_ai/synthetic_data.py +90 -0
- select_ai/vector_index.py +642 -0
- select_ai/version.py +8 -0
- select_ai-1.2.0rc3.dist-info/METADATA +129 -0
- select_ai-1.2.0rc3.dist-info/RECORD +31 -0
- select_ai-1.2.0rc3.dist-info/WHEEL +5 -0
- select_ai-1.2.0rc3.dist-info/licenses/LICENSE.txt +35 -0
- select_ai-1.2.0rc3.dist-info/top_level.txt +1 -0
select_ai/db.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
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 contextlib
|
|
9
|
+
import os
|
|
10
|
+
from threading import get_ident
|
|
11
|
+
from typing import Dict, Hashable
|
|
12
|
+
|
|
13
|
+
import oracledb
|
|
14
|
+
|
|
15
|
+
from select_ai.errors import DatabaseNotConnectedError
|
|
16
|
+
|
|
17
|
+
__conn__: Dict[Hashable, oracledb.Connection] = {}
|
|
18
|
+
__async_conn__: Dict[Hashable, oracledb.AsyncConnection] = {}
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"connect",
|
|
22
|
+
"async_connect",
|
|
23
|
+
"is_connected",
|
|
24
|
+
"async_is_connected",
|
|
25
|
+
"get_connection",
|
|
26
|
+
"async_get_connection",
|
|
27
|
+
"cursor",
|
|
28
|
+
"async_cursor",
|
|
29
|
+
"disconnect",
|
|
30
|
+
"async_disconnect",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def connect(user: str, password: str, dsn: str, *args, **kwargs):
|
|
35
|
+
"""Creates an oracledb.Connection object
|
|
36
|
+
and saves it global dictionary __conn__
|
|
37
|
+
The connection object is thread local meaning
|
|
38
|
+
in a multithreaded application, individual
|
|
39
|
+
threads cannot see each other's connection
|
|
40
|
+
object
|
|
41
|
+
"""
|
|
42
|
+
conn = oracledb.connect(
|
|
43
|
+
user=user,
|
|
44
|
+
password=password,
|
|
45
|
+
dsn=dsn,
|
|
46
|
+
connection_id_prefix="python-select-ai",
|
|
47
|
+
*args,
|
|
48
|
+
**kwargs,
|
|
49
|
+
)
|
|
50
|
+
_set_connection(conn=conn)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def async_connect(user: str, password: str, dsn: str, *args, **kwargs):
|
|
54
|
+
"""Creates an oracledb.AsyncConnection object
|
|
55
|
+
and saves it global dictionary __async_conn__
|
|
56
|
+
The connection object is thread local meaning
|
|
57
|
+
in a multithreaded application, individual
|
|
58
|
+
threads cannot see each other's connection
|
|
59
|
+
object
|
|
60
|
+
"""
|
|
61
|
+
async_conn = await oracledb.connect_async(
|
|
62
|
+
user=user,
|
|
63
|
+
password=password,
|
|
64
|
+
dsn=dsn,
|
|
65
|
+
connection_id_prefix="async-python-select-ai",
|
|
66
|
+
*args,
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
69
|
+
_set_connection(async_conn=async_conn)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def is_connected() -> bool:
|
|
73
|
+
"""Checks if database connection is open and healthy"""
|
|
74
|
+
global __conn__
|
|
75
|
+
key = (os.getpid(), get_ident())
|
|
76
|
+
conn = __conn__.get(key)
|
|
77
|
+
if conn is None:
|
|
78
|
+
return False
|
|
79
|
+
try:
|
|
80
|
+
return conn.ping() is None
|
|
81
|
+
except (oracledb.DatabaseError, oracledb.InterfaceError):
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def async_is_connected() -> bool:
|
|
86
|
+
"""Asynchronously checks if database connection is open and healthy"""
|
|
87
|
+
|
|
88
|
+
global __async_conn__
|
|
89
|
+
key = (os.getpid(), get_ident())
|
|
90
|
+
conn = __async_conn__.get(key)
|
|
91
|
+
if conn is None:
|
|
92
|
+
return False
|
|
93
|
+
try:
|
|
94
|
+
return await conn.ping() is None
|
|
95
|
+
except (oracledb.DatabaseError, oracledb.InterfaceError):
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _set_connection(
|
|
100
|
+
conn: oracledb.Connection = None,
|
|
101
|
+
async_conn: oracledb.AsyncConnection = None,
|
|
102
|
+
):
|
|
103
|
+
"""Set existing connection for select_ai Python API to reuse
|
|
104
|
+
|
|
105
|
+
:param conn: python-oracledb Connection object
|
|
106
|
+
:param async_conn: python-oracledb
|
|
107
|
+
:return:
|
|
108
|
+
"""
|
|
109
|
+
key = (os.getpid(), get_ident())
|
|
110
|
+
if conn:
|
|
111
|
+
global __conn__
|
|
112
|
+
__conn__[key] = conn
|
|
113
|
+
if async_conn:
|
|
114
|
+
global __async_conn__
|
|
115
|
+
__async_conn__[key] = async_conn
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_connection() -> oracledb.Connection:
|
|
119
|
+
"""Returns the connection object if connection is healthy"""
|
|
120
|
+
if not is_connected():
|
|
121
|
+
raise DatabaseNotConnectedError()
|
|
122
|
+
global __conn__
|
|
123
|
+
key = (os.getpid(), get_ident())
|
|
124
|
+
return __conn__[key]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def async_get_connection() -> oracledb.AsyncConnection:
|
|
128
|
+
"""Returns the AsyncConnection object if connection is healthy"""
|
|
129
|
+
if not await async_is_connected():
|
|
130
|
+
raise DatabaseNotConnectedError()
|
|
131
|
+
global __async_conn__
|
|
132
|
+
key = (os.getpid(), get_ident())
|
|
133
|
+
return __async_conn__[key]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@contextlib.contextmanager
|
|
137
|
+
def cursor():
|
|
138
|
+
"""
|
|
139
|
+
Creates a context manager for database cursor
|
|
140
|
+
|
|
141
|
+
Typical usage:
|
|
142
|
+
|
|
143
|
+
with select_ai.cursor() as cr:
|
|
144
|
+
cr.execute(<QUERY>)
|
|
145
|
+
|
|
146
|
+
This ensures that the cursor is closed regardless
|
|
147
|
+
of whether an exception occurred
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
cr = get_connection().cursor()
|
|
151
|
+
try:
|
|
152
|
+
yield cr
|
|
153
|
+
finally:
|
|
154
|
+
cr.close()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@contextlib.asynccontextmanager
|
|
158
|
+
async def async_cursor():
|
|
159
|
+
"""
|
|
160
|
+
Creates an async context manager for database cursor
|
|
161
|
+
|
|
162
|
+
Typical usage:
|
|
163
|
+
|
|
164
|
+
async with select_ai.cursor() as cr:
|
|
165
|
+
await cr.execute(<QUERY>)
|
|
166
|
+
:return:
|
|
167
|
+
"""
|
|
168
|
+
conn = await async_get_connection()
|
|
169
|
+
cr = conn.cursor()
|
|
170
|
+
try:
|
|
171
|
+
yield cr
|
|
172
|
+
finally:
|
|
173
|
+
cr.close()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def disconnect():
|
|
177
|
+
try:
|
|
178
|
+
conn = get_connection()
|
|
179
|
+
except DatabaseNotConnectedError:
|
|
180
|
+
pass
|
|
181
|
+
else:
|
|
182
|
+
conn.close()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def async_disconnect():
|
|
186
|
+
try:
|
|
187
|
+
conn = await async_get_connection()
|
|
188
|
+
except DatabaseNotConnectedError:
|
|
189
|
+
pass
|
|
190
|
+
else:
|
|
191
|
+
await conn.close()
|
select_ai/errors.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
class SelectAIError(Exception):
|
|
10
|
+
"""Base class for any SelectAIErrors"""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatabaseNotConnectedError(SelectAIError):
|
|
16
|
+
"""Raised when a database is not connected"""
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return (
|
|
20
|
+
"Not connected to the Database. "
|
|
21
|
+
"Use select_ai.connect() or select_ai.async_connect() "
|
|
22
|
+
"to establish connection"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConversationNotFoundError(SelectAIError):
|
|
27
|
+
"""Conversation not found in the database"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, conversation_id: str):
|
|
30
|
+
self.conversation_id = conversation_id
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
return f"Conversation with id {self.conversation_id} not found"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ProfileNotFoundError(SelectAIError):
|
|
37
|
+
"""Profile not found in the database"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, profile_name: str):
|
|
40
|
+
self.profile_name = profile_name
|
|
41
|
+
|
|
42
|
+
def __str__(self):
|
|
43
|
+
return f"Profile {self.profile_name} not found"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProfileExistsError(SelectAIError):
|
|
47
|
+
"""Profile already exists in the database"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, profile_name: str):
|
|
50
|
+
self.profile_name = profile_name
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
return (
|
|
54
|
+
f"Profile {self.profile_name} already exists. "
|
|
55
|
+
f"Use either replace=True or merge=True"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class VectorIndexNotFoundError(SelectAIError):
|
|
60
|
+
"""VectorIndex not found in the database"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, index_name: str, profile_name: str = None):
|
|
63
|
+
self.index_name = index_name
|
|
64
|
+
self.profile_name = profile_name
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
if self.profile_name:
|
|
68
|
+
return (
|
|
69
|
+
f"VectorIndex {self.index_name} "
|
|
70
|
+
f"not found for profile {self.profile_name}"
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
return f"VectorIndex {self.index_name} not found"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AgentNotFoundError(SelectAIError):
|
|
77
|
+
"""Agent not found in the database"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, agent_name: str):
|
|
80
|
+
self.agent_name = agent_name
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
return f"Agent {self.agent_name} not found"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AgentTaskNotFoundError(SelectAIError):
|
|
87
|
+
"""Agent task not found in the database"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, task_name: str):
|
|
90
|
+
self.task_name = task_name
|
|
91
|
+
|
|
92
|
+
def __str__(self):
|
|
93
|
+
return f"Agent Task {self.task_name} not found"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AgentToolNotFoundError(SelectAIError):
|
|
97
|
+
"""Agent tool not found in the database"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, tool_name: str):
|
|
100
|
+
self.tool_name = tool_name
|
|
101
|
+
|
|
102
|
+
def __str__(self):
|
|
103
|
+
return f"Agent Tool {self.tool_name} not found"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class AgentTeamNotFoundError(SelectAIError):
|
|
107
|
+
"""Agent team not found in the database"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, team_name: str):
|
|
110
|
+
self.team_name = team_name
|
|
111
|
+
|
|
112
|
+
def __str__(self):
|
|
113
|
+
return f"Agent Team {self.team_name} not found"
|
select_ai/feedback.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
from select_ai._enums import StrEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FeedbackType(StrEnum):
|
|
11
|
+
|
|
12
|
+
POSITIVE = "positive"
|
|
13
|
+
NEGATIVE = "negative"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FeedbackOperation(StrEnum):
|
|
17
|
+
|
|
18
|
+
ADD = "add"
|
|
19
|
+
DELETE = "delete"
|
select_ai/privilege.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
from typing import List, Union
|
|
8
|
+
|
|
9
|
+
from .db import async_cursor, cursor
|
|
10
|
+
from .sql import (
|
|
11
|
+
DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
12
|
+
ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
13
|
+
GRANT_PRIVILEGES_TO_USER,
|
|
14
|
+
REVOKE_PRIVILEGES_FROM_USER,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def async_grant_privileges(users: Union[str, List[str]]):
|
|
19
|
+
"""
|
|
20
|
+
This method grants execute privilege on the packages DBMS_CLOUD,
|
|
21
|
+
DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(users, str):
|
|
25
|
+
users = [users]
|
|
26
|
+
|
|
27
|
+
async with async_cursor() as cr:
|
|
28
|
+
for user in users:
|
|
29
|
+
await cr.execute(GRANT_PRIVILEGES_TO_USER.format(user.strip()))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def async_revoke_privileges(users: Union[str, List[str]]):
|
|
33
|
+
"""
|
|
34
|
+
This method revokes execute privilege on the packages DBMS_CLOUD,
|
|
35
|
+
DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(users, str):
|
|
39
|
+
users = [users]
|
|
40
|
+
|
|
41
|
+
async with async_cursor() as cr:
|
|
42
|
+
for user in users:
|
|
43
|
+
await cr.execute(REVOKE_PRIVILEGES_FROM_USER.format(user.strip()))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def async_grant_http_access(
|
|
47
|
+
users: Union[str, List[str]],
|
|
48
|
+
provider_endpoint: str,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Async method to add ACL for HTTP access.
|
|
52
|
+
"""
|
|
53
|
+
if isinstance(users, str):
|
|
54
|
+
users = [users]
|
|
55
|
+
|
|
56
|
+
async with async_cursor() as cr:
|
|
57
|
+
for user in users:
|
|
58
|
+
await cr.execute(
|
|
59
|
+
ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
60
|
+
user=user,
|
|
61
|
+
host=provider_endpoint,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def async_revoke_http_access(
|
|
66
|
+
users: Union[str, List[str]],
|
|
67
|
+
provider_endpoint: str,
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Async method to remove ACL for HTTP access.
|
|
71
|
+
"""
|
|
72
|
+
if isinstance(users, str):
|
|
73
|
+
users = [users]
|
|
74
|
+
|
|
75
|
+
async with async_cursor() as cr:
|
|
76
|
+
for user in users:
|
|
77
|
+
await cr.execute(
|
|
78
|
+
DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
79
|
+
user=user,
|
|
80
|
+
host=provider_endpoint,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def grant_privileges(users: Union[str, List[str]]):
|
|
85
|
+
"""
|
|
86
|
+
This method grants execute privilege on the packages DBMS_CLOUD,
|
|
87
|
+
DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE
|
|
88
|
+
"""
|
|
89
|
+
if isinstance(users, str):
|
|
90
|
+
users = [users]
|
|
91
|
+
with cursor() as cr:
|
|
92
|
+
for user in users:
|
|
93
|
+
cr.execute(GRANT_PRIVILEGES_TO_USER.format(user.strip()))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def revoke_privileges(users: Union[str, List[str]]):
|
|
97
|
+
"""
|
|
98
|
+
This method revokes execute privilege on the packages DBMS_CLOUD,
|
|
99
|
+
DBMS_CLOUD_AI, DBMS_CLOUD_AI_AGENT and DBMS_CLOUD_PIPELINE.
|
|
100
|
+
"""
|
|
101
|
+
if isinstance(users, str):
|
|
102
|
+
users = [users]
|
|
103
|
+
with cursor() as cr:
|
|
104
|
+
for user in users:
|
|
105
|
+
cr.execute(REVOKE_PRIVILEGES_FROM_USER.format(user.strip()))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def grant_http_access(users: Union[str, List[str]], provider_endpoint: str):
|
|
109
|
+
"""
|
|
110
|
+
Adds ACL entry for HTTP access
|
|
111
|
+
"""
|
|
112
|
+
if isinstance(users, str):
|
|
113
|
+
users = [users]
|
|
114
|
+
with cursor() as cr:
|
|
115
|
+
for user in users:
|
|
116
|
+
cr.execute(
|
|
117
|
+
ENABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
118
|
+
user=user,
|
|
119
|
+
host=provider_endpoint,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def revoke_http_access(users: Union[str, List[str]], provider_endpoint: str):
|
|
124
|
+
"""
|
|
125
|
+
Removes ACL entry for HTTP access
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(users, str):
|
|
128
|
+
users = [users]
|
|
129
|
+
with cursor() as cr:
|
|
130
|
+
for user in users:
|
|
131
|
+
cr.execute(
|
|
132
|
+
DISABLE_AI_PROFILE_DOMAIN_FOR_USER,
|
|
133
|
+
user=user,
|
|
134
|
+
host=provider_endpoint,
|
|
135
|
+
)
|