Skip to content

Commit b51a5a2

Browse files
committed
Added fetch API for vector index
1 parent fb9109c commit b51a5a2

File tree

11 files changed

+146
-80
lines changed

11 files changed

+146
-80
lines changed

doc/source/user_guide/installation.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ Installation requirements
1111

1212
To use ``select_ai`` you need:
1313

14-
- Python 3.9, 3.10, 3.11, 3.12 or 3.13
14+
- Python 3.9, 3.10, 3.11, 3.12, 3.13 or 3.14
15+
16+
.. warning::
17+
For async APIs, use Python 3.11 or higher. Python 3.11 introduced
18+
stabilized the async event loop management and introduced better-structured
19+
APIs
1520

1621
- ``python-oracledb`` - This package is automatically installed as a dependency requirement
1722

@@ -28,7 +33,7 @@ To use ``select_ai`` you need:
2833
`pip <https://pip.pypa.io/en/latest/installation/>`__.
2934

3035
1. Install `Python 3 <https://www.python.org/downloads>`__ if it is not already
31-
available. Use any version from Python 3.9 through 3.13.
36+
available. Use any version from Python 3.9 through 3.14.
3237

3338
2. Install ``select_ai``:
3439

doc/source/user_guide/vector_index.rst

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,9 @@ Get vector index attributes
8686
+++++++++++++++++++++++++++
8787

8888
You can fetch the vector index attributes and associated AI profile using
89-
``vector_index.get_attributes()`` and ``vector_index.get_profile()`` methods
90-
respectively.
89+
the class method ``VectorIndex.fetch(index_name)``
9190

92-
.. literalinclude:: ../../../samples/vector_index_get_attributes.py
91+
.. literalinclude:: ../../../samples/vector_index_fetch.py
9392
:language: python
9493
:lines: 14-
9594

@@ -200,10 +199,9 @@ Async get vector index attributes
200199
+++++++++++++++++++++++++++++++++
201200

202201
You can fetch the vector index attributes and associated AI profile using
203-
``async_vector_index.get_attributes()`` and ``async_vector_index.get_profile()``
204-
methods respectively.
202+
the class method ``AsyncVectorIndex.fetch(index_name)``
205203

206-
.. literalinclude:: ../../../samples/async/vector_index_get_attributes.py
204+
.. literalinclude:: ../../../samples/async/vector_index_fetch.py
207205
:language: python
208206
:lines: 14-
209207

samples/async/vector_index_get_attributes.py renamed to samples/async/vector_index_fetch.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# -----------------------------------------------------------------------------
77

88
# -----------------------------------------------------------------------------
9-
# async/vector_index_get_attributes.py
9+
# async/vector_index_fetch.py
1010
#
1111
# Get vector index attributes
1212
# -----------------------------------------------------------------------------
@@ -23,14 +23,11 @@
2323

2424
async def main():
2525
await select_ai.async_connect(user=user, password=password, dsn=dsn)
26-
27-
async_vector_index = select_ai.AsyncVectorIndex(
28-
index_name="test_vector_index",
26+
async_vector_index = await select_ai.AsyncVectorIndex.fetch(
27+
index_name="test_vector_index"
2928
)
30-
attributes = await async_vector_index.get_attributes()
31-
print(attributes)
32-
async_profile = await async_vector_index.get_profile()
33-
print(async_profile)
29+
print(async_vector_index.attributes)
30+
print(async_vector_index.profile)
3431

3532

3633
asyncio.run(main())

samples/async/vector_index_update_attributes.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,15 @@ async def main():
2929

3030
# Use vector_index.set_attributes to update a multiple attributes
3131
updated_attributes = select_ai.OracleVectorIndexAttributes(
32-
refresh_rate=1450,
32+
refresh_rate=1450
3333
)
3434
await async_vector_index.set_attributes(attributes=updated_attributes)
3535

3636
# Use vector_index.set_attribute to update a single attribute
3737
await async_vector_index.set_attribute(
3838
attribute_name="similarity_threshold", attribute_value=0.5
3939
)
40-
attributes = await async_vector_index.get_attributes()
41-
print(attributes)
40+
print(async_vector_index.attributes)
4241

4342

4443
asyncio.run(main())

samples/vector_index_get_attributes.py renamed to samples/vector_index_fetch.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# -----------------------------------------------------------------------------
77

88
# -----------------------------------------------------------------------------
9-
# vector_index_get_attributes.py
9+
# vector_index_fetch.py
1010
#
1111
# Gets attributes for a vector index
1212
# -----------------------------------------------------------------------------
@@ -21,9 +21,7 @@
2121

2222
select_ai.connect(user=user, password=password, dsn=dsn)
2323

24-
vector_index = select_ai.VectorIndex(
25-
index_name="test_vector_index",
26-
)
27-
28-
print(vector_index.get_attributes())
29-
print(vector_index.get_profile())
24+
vector_index = select_ai.VectorIndex.fetch(index_name="test_vector_index")
25+
print(vector_index.attributes)
26+
print(vector_index.profile)
27+
print(select_ai.VectorIndex(index_name=None).get_attributes())

samples/vector_index_update_attributes.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@
2424
)
2525

2626
# Use vector_index.set_attributes to update a multiple attributes
27-
updated_attributes = select_ai.OracleVectorIndexAttributes(
28-
refresh_rate=1450,
29-
)
27+
updated_attributes = select_ai.OracleVectorIndexAttributes(refresh_rate=1450)
3028
vector_index.set_attributes(attributes=updated_attributes)
3129

3230
# Use vector_index.set_attribute to update a single attribute
3331
vector_index.set_attribute(
3432
attribute_name="similarity_threshold", attribute_value=0.5
3533
)
36-
print(vector_index.get_attributes())
34+
print(vector_index.attributes)

src/select_ai/profile.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
import json
99
from contextlib import contextmanager
1010
from dataclasses import replace as dataclass_replace
11-
from pprint import pformat
12-
from typing import Generator, Iterator, Mapping, Optional, Tuple, Union
11+
from typing import Generator, Mapping, Optional, Tuple, Union
1312

1413
import oracledb
1514
import pandas

src/select_ai/sql.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@
8888
WHERE REGEXP_LIKE(v.index_name, :index_name_pattern, 'i')
8989
"""
9090

91+
GET_USER_VECTOR_INDEX = """
92+
select index_name, description
93+
from USER_CLOUD_VECTOR_INDEXES v
94+
where index_name = :index_name
95+
"""
96+
9197
GET_USER_VECTOR_INDEX_ATTRIBUTES = """
9298
SELECT attribute_name, attribute_value
9399
FROM USER_CLOUD_VECTOR_INDEX_ATTRIBUTES

src/select_ai/vector_index.py

Lines changed: 96 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,11 @@
2020
from select_ai.errors import ProfileNotFoundError, VectorIndexNotFoundError
2121
from select_ai.profile import Profile
2222
from select_ai.sql import (
23+
GET_USER_VECTOR_INDEX,
2324
GET_USER_VECTOR_INDEX_ATTRIBUTES,
2425
LIST_USER_VECTOR_INDEXES,
2526
)
2627

27-
UNMODIFIABLE_VECTOR_INDEX_ATTRIBUTES = (
28-
"location",
29-
"chunk_size",
30-
"chunk_overlap",
31-
"pipeline_name",
32-
"vector_dimension",
33-
"vector_table_name",
34-
"vector_distance_metric",
35-
)
36-
3728

3829
class VectorDBProvider(StrEnum):
3930
ORACLE = "oracle"
@@ -161,7 +152,7 @@ def _get_attributes(index_name: str) -> VectorIndexAttributes:
161152
:return: select_ai.VectorIndexAttributes
162153
:raises: VectorIndexNotFoundError
163154
"""
164-
if index_name is None:
155+
if not index_name:
165156
raise AttributeError("'index_name' is required")
166157
with cursor() as cr:
167158
cr.execute(
@@ -181,6 +172,27 @@ def _get_attributes(index_name: str) -> VectorIndexAttributes:
181172
else:
182173
raise VectorIndexNotFoundError(index_name=index_name)
183174

175+
@staticmethod
176+
def _get_description(index_name) -> Union[str, None]:
177+
"""Get description of the Vector Index from USER_CLOUD_VECTOR_INDEXES
178+
179+
:param str index_name: The name of the vector index
180+
:return: Union[str, None] profile description
181+
:raises: ProfileNotFoundError
182+
"""
183+
if not index_name:
184+
raise AttributeError("'index_name' is required")
185+
with cursor() as cr:
186+
cr.execute(GET_USER_VECTOR_INDEX, index_name=index_name.upper())
187+
index = cr.fetchone()
188+
if index:
189+
if index[1] is not None:
190+
return index[1].read()
191+
else:
192+
return None
193+
else:
194+
raise VectorIndexNotFoundError(index_name=index_name)
195+
184196
def create(self, replace: Optional[bool] = False):
185197
"""Create a vector index in the database and populates the index
186198
with data from an object store bucket using an async scheduler job
@@ -292,6 +304,28 @@ def disable(self):
292304
else:
293305
raise
294306

307+
@classmethod
308+
def fetch(cls, index_name: str) -> "VectorIndex":
309+
"""
310+
Fetches vector index attributes from the
311+
database and builds a proxy object for the
312+
passed index_name
313+
314+
:param str index_name: The name of the vector index
315+
"""
316+
description = cls._get_description(index_name)
317+
attributes = cls._get_attributes(index_name)
318+
try:
319+
profile = Profile(profile_name=attributes.profile_name)
320+
except ProfileNotFoundError:
321+
profile = None
322+
return cls(
323+
description=description,
324+
attributes=attributes,
325+
profile=profile,
326+
index_name=index_name,
327+
)
328+
295329
def set_attribute(
296330
self,
297331
attribute_name: str,
@@ -377,21 +411,7 @@ def list(cls, index_name_pattern: str = ".*") -> Iterator["VectorIndex"]:
377411
)
378412
for row in cr.fetchall():
379413
index_name = row[0]
380-
if row[1]:
381-
description = row[1].read() # Oracle.LOB
382-
else:
383-
description = None
384-
attributes = cls._get_attributes(index_name=index_name)
385-
try:
386-
profile = Profile(profile_name=attributes.profile_name)
387-
except ProfileNotFoundError:
388-
profile = None
389-
yield cls(
390-
index_name=index_name,
391-
description=description,
392-
attributes=attributes,
393-
profile=profile,
394-
)
414+
yield cls.fetch(index_name=index_name)
395415

396416

397417
class AsyncVectorIndex(_BaseVectorIndex):
@@ -412,6 +432,8 @@ async def _get_attributes(index_name: str) -> VectorIndexAttributes:
412432
:return: select_ai.VectorIndexAttributes
413433
:raises: VectorIndexNotFoundError
414434
"""
435+
if not index_name:
436+
raise AttributeError("'index_name' is required")
415437
async with async_cursor() as cr:
416438
await cr.execute(
417439
GET_USER_VECTOR_INDEX_ATTRIBUTES, index_name=index_name.upper()
@@ -430,6 +452,29 @@ async def _get_attributes(index_name: str) -> VectorIndexAttributes:
430452
else:
431453
raise VectorIndexNotFoundError(index_name=index_name)
432454

455+
@staticmethod
456+
async def _get_description(index_name) -> Union[str, None]:
457+
"""Get description of the Vector Index from USER_CLOUD_VECTOR_INDEXES
458+
459+
:param str index_name: The name of the vector index
460+
:return: Union[str, None] profile description
461+
:raises: ProfileNotFoundError
462+
"""
463+
if not index_name:
464+
raise AttributeError("'index_name' is required")
465+
async with async_cursor() as cr:
466+
await cr.execute(
467+
GET_USER_VECTOR_INDEX, index_name=index_name.upper()
468+
)
469+
index = await cr.fetchone()
470+
if index:
471+
if index[1] is not None:
472+
return await index[1].read()
473+
else:
474+
return None
475+
else:
476+
raise VectorIndexNotFoundError(index_name=index_name)
477+
433478
async def create(self, replace: Optional[bool] = False) -> None:
434479
"""Create a vector index in the database and populates it with data
435480
from an object store bucket using an async scheduler job
@@ -539,6 +584,28 @@ async def disable(self) -> None:
539584
else:
540585
raise
541586

587+
@classmethod
588+
async def fetch(cls, index_name: str) -> "AsyncVectorIndex":
589+
"""
590+
Fetches vector index attributes from the
591+
database and builds a proxy object for the
592+
passed index_name
593+
594+
:param str index_name: The name of the vector index
595+
"""
596+
description = await cls._get_description(index_name)
597+
attributes = await cls._get_attributes(index_name)
598+
try:
599+
profile = await AsyncProfile(profile_name=attributes.profile_name)
600+
except ProfileNotFoundError:
601+
profile = None
602+
return cls(
603+
description=description,
604+
attributes=attributes,
605+
profile=profile,
606+
index_name=index_name,
607+
)
608+
542609
async def set_attribute(
543610
self, attribute_name: str, attribute_value: Union[str, int, float]
544611
) -> None:
@@ -605,7 +672,7 @@ async def get_profile(self) -> AsyncProfile:
605672
@classmethod
606673
async def list(
607674
cls, index_name_pattern: str = ".*"
608-
) -> AsyncGenerator[VectorIndex, None]:
675+
) -> AsyncGenerator["AsyncVectorIndex", None]:
609676
"""List Vector Indexes.
610677
611678
:param str index_name_pattern: Regular expressions can be used
@@ -623,20 +690,5 @@ async def list(
623690
rows = await cr.fetchall()
624691
for row in rows:
625692
index_name = row[0]
626-
if row[1]:
627-
description = await row[1].read() # AsyncLOB
628-
else:
629-
description = None
630-
attributes = await cls._get_attributes(index_name=index_name)
631-
try:
632-
profile = await AsyncProfile(
633-
profile_name=attributes.profile_name,
634-
)
635-
except ProfileNotFoundError:
636-
profile = None
637-
yield VectorIndex(
638-
index_name=index_name,
639-
description=description,
640-
attributes=attributes,
641-
profile=profile,
642-
)
693+
index = await cls.fetch(index_name=index_name)
694+
yield index

tests/agents/test_3200_agents.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def python_gen_ai_profile(profile_attributes):
3434
def agent_attributes():
3535
agent_attributes = AgentAttributes(
3636
profile_name=PYSAI_3200_PROFILE_NAME,
37-
role="You are an AI Movie Analyst. "
38-
"Your can help answer a variety of questions related to movies. ",
37+
role="You are an AI Movie Analyst."
38+
"You can help answer a variety of questions related to movies.",
3939
enable_human_tool=False,
4040
)
4141
return agent_attributes
@@ -69,3 +69,10 @@ def test_3201(agent_name_pattern):
6969
agent_descriptions = set(agent.description for agent in agents)
7070
assert PYSAI_3200_AGENT_NAME in agent_names
7171
assert PYSAI_3200_AGENT_DESCRIPTION in agent_descriptions
72+
73+
74+
def test_3203(agent_attributes):
75+
agent = Agent.fetch(agent_name=PYSAI_3200_AGENT_NAME)
76+
assert agent.agent_name == PYSAI_3200_AGENT_NAME
77+
assert agent.attributes == agent_attributes
78+
assert agent.description == PYSAI_3200_AGENT_DESCRIPTION

0 commit comments

Comments
 (0)