Skip to content

Commit 6b59f0b

Browse files
committed
feat: allow non-uuid data types for vectorstore primary key
1 parent 42baac6 commit 6b59f0b

3 files changed

Lines changed: 90 additions & 27 deletions

File tree

src/langchain_google_cloud_sql_pg/engine.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,13 @@ async def _ainit_vectorstore_table(
410410
embedding_column: str = "embedding",
411411
metadata_columns: List[Column] = [],
412412
metadata_json_column: str = "langchain_metadata",
413-
id_column: str = "langchain_id",
413+
id_column: Union[str, Column] = "langchain_id",
414414
overwrite_existing: bool = False,
415415
store_metadata: bool = True,
416416
) -> None:
417417
"""
418418
Create a table for saving of vectors to be used with PostgresVectorStore.
419+
If id column data type is invalid, an UNDEFINED_OBJECT_ERROR is thrown.
419420
420421
Args:
421422
table_name (str): The Postgres database table name.
@@ -430,14 +431,14 @@ async def _ainit_vectorstore_table(
430431
metadata. Default: []. Optional.
431432
metadata_json_column (str): The column to store extra metadata in JSON format.
432433
Default: "langchain_metadata". Optional.
433-
id_column (str): Name of the column to store ids.
434-
Default: "langchain_id". Optional,
434+
id_column (Union[str, Column]) : Column to store ids.
435+
Default: "langchain_id" column name with data type UUID. Optional,
435436
overwrite_existing (bool): Whether to drop existing table. Default: False.
436437
store_metadata (bool): Whether to store metadata in the table.
437438
Default: True.
438-
439439
Raises:
440440
:class:`DuplicateTableError <asyncpg.exceptions.DuplicateTableError>`: if table already exists and overwrite flag is not set.
441+
:class:`UndefinedObjectError <asyncpg.exceptions.UndefinedObjectError>`: if id column data type is invalid.
441442
"""
442443
async with self._pool.connect() as conn:
443444
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
@@ -450,8 +451,11 @@ async def _ainit_vectorstore_table(
450451
)
451452
await conn.commit()
452453

454+
id_data_type = "UUID" if isinstance(id_column, str) else id_column.data_type
455+
id_column_name = id_column if isinstance(id_column, str) else id_column.name
456+
453457
query = f"""CREATE TABLE "{schema_name}"."{table_name}"(
454-
"{id_column}" UUID PRIMARY KEY,
458+
"{id_column_name}" {id_data_type} PRIMARY KEY,
455459
"{content_column}" TEXT NOT NULL,
456460
"{embedding_column}" vector({vector_size}) NOT NULL"""
457461
for column in metadata_columns:
@@ -524,12 +528,13 @@ def init_vectorstore_table(
524528
embedding_column: str = "embedding",
525529
metadata_columns: List[Column] = [],
526530
metadata_json_column: str = "langchain_metadata",
527-
id_column: str = "langchain_id",
531+
id_column: Union[str, Column] = "langchain_id",
528532
overwrite_existing: bool = False,
529533
store_metadata: bool = True,
530534
) -> None:
531535
"""
532536
Create a table for saving of vectors to be used with PostgresVectorStore.
537+
If id column data type is invalid, an UNDEFINED_OBJECT_ERROR is thrown.
533538
534539
Args:
535540
table_name (str): The Postgres database table name.
@@ -544,11 +549,13 @@ def init_vectorstore_table(
544549
metadata. Default: []. Optional.
545550
metadata_json_column (str): The column to store extra metadata in JSON format.
546551
Default: "langchain_metadata". Optional.
547-
id_column (str): Name of the column to store ids.
552+
id_column (Union[str, Column]): Name of the column to store ids.
548553
Default: "langchain_id". Optional,
549554
overwrite_existing (bool): Whether to drop existing table. Default: False.
550555
store_metadata (bool): Whether to store metadata in the table.
551556
Default: True.
557+
Raises:
558+
:class:`UndefinedObjectError <asyncpg.exceptions.UndefinedObjectError>`: if id column data type is invalid.
552559
"""
553560
self._run_as_sync(
554561
self._ainit_vectorstore_table(

src/langchain_google_cloud_sql_pg/vectorstore.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,10 @@ async def aadd_texts(
187187
self,
188188
texts: Iterable[str],
189189
metadatas: Optional[List[dict]] = None,
190-
ids: Optional[List[str]] = None,
190+
ids: Optional[List] = None,
191191
**kwargs: Any,
192192
) -> List[str]:
193-
"""Embed texts and add to the table."""
193+
"""Embed texts and add to the table. Throws an error if Id column data type doesn't match with the ids."""
194194
return await self._engine._run_as_async(
195195
self.__vs.aadd_texts(texts, metadatas, ids, **kwargs)
196196
)
@@ -199,50 +199,50 @@ def add_texts(
199199
self,
200200
texts: Iterable[str],
201201
metadatas: Optional[List[dict]] = None,
202-
ids: Optional[List[str]] = None,
202+
ids: Optional[List] = None,
203203
**kwargs: Any,
204204
) -> List[str]:
205-
"""Embed texts and add to the table."""
205+
"""Embed texts and add to the table. Throws an error if Id column data type doesn't match with the ids."""
206206
return self._engine._run_as_sync(
207207
self.__vs.aadd_texts(texts, metadatas, ids, **kwargs)
208208
)
209209

210210
async def aadd_documents(
211211
self,
212212
documents: List[Document],
213-
ids: Optional[List[str]] = None,
213+
ids: Optional[List] = None,
214214
**kwargs: Any,
215215
) -> List[str]:
216-
"""Embed documents and add to the table"""
216+
"""Embed documents and add to the table. Throws an error if Id column data type doesn't match with the ids."""
217217
return await self._engine._run_as_async(
218218
self.__vs.aadd_documents(documents, ids, **kwargs)
219219
)
220220

221221
def add_documents(
222222
self,
223223
documents: List[Document],
224-
ids: Optional[List[str]] = None,
224+
ids: Optional[List] = None,
225225
**kwargs: Any,
226226
) -> List[str]:
227-
"""Embed documents and add to the table."""
227+
"""Embed documents and add to the table. Throws an error if Id column data type doesn't match with the ids."""
228228
return self._engine._run_as_sync(
229229
self.__vs.aadd_documents(documents, ids, **kwargs)
230230
)
231231

232232
async def adelete(
233233
self,
234-
ids: Optional[List[str]] = None,
234+
ids: Optional[List] = None,
235235
**kwargs: Any,
236236
) -> Optional[bool]:
237-
"""Delete records from the table."""
237+
"""Delete records from the table. Throws an error if Id column data type doesn't match with the ids."""
238238
return await self._engine._run_as_async(self.__vs.adelete(ids, **kwargs))
239239

240240
def delete(
241241
self,
242-
ids: Optional[List[str]] = None,
242+
ids: Optional[List] = None,
243243
**kwargs: Any,
244244
) -> Optional[bool]:
245-
"""Delete records from the table."""
245+
"""Delete records from the table. Throws an error if Id column data type doesn't match with the ids."""
246246
return self._engine._run_as_sync(self.__vs.adelete(ids, **kwargs))
247247

248248
@classmethod
@@ -254,7 +254,7 @@ async def afrom_texts( # type: ignore[override]
254254
table_name: str,
255255
schema_name: str = "public",
256256
metadatas: Optional[List[dict]] = None,
257-
ids: Optional[List[str]] = None,
257+
ids: Optional[List] = None,
258258
content_column: str = "content",
259259
embedding_column: str = "embedding",
260260
metadata_columns: List[str] = [],
@@ -268,14 +268,16 @@ async def afrom_texts( # type: ignore[override]
268268
index_query_options: Optional[QueryOptions] = None,
269269
) -> PostgresVectorStore:
270270
"""Create an PostgresVectorStore instance from texts.
271+
Throws an error if Id column data type doesn't match with the ids.
272+
271273
Args:
272274
texts (List[str]): Texts to add to the vector store.
273275
embedding (Embeddings): Text embedding model to use.
274276
engine (PostgresEngine): Connection pool engine for managing connections to Postgres database.
275277
table_name (str): Name of the existing table or the table to be created.
276278
schema_name (str, optional): Database schema name of the table. Defaults to "public".
277279
metadatas (Optional[List[dict]]): List of metadatas to add to table records.
278-
ids: (Optional[List[str]]): List of IDs to add to table records.
280+
ids: (Optional[List]): List of IDs to add to table records.
279281
content_column (str): Column that represent a Document’s page_content. Defaults to "content".
280282
embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding".
281283
metadata_columns (List[str]): Column(s) that represent a document's metadata.
@@ -319,7 +321,7 @@ async def afrom_documents( # type: ignore[override]
319321
engine: PostgresEngine,
320322
table_name: str,
321323
schema_name: str = "public",
322-
ids: Optional[List[str]] = None,
324+
ids: Optional[List] = None,
323325
content_column: str = "content",
324326
embedding_column: str = "embedding",
325327
metadata_columns: List[str] = [],
@@ -333,6 +335,7 @@ async def afrom_documents( # type: ignore[override]
333335
index_query_options: Optional[QueryOptions] = None,
334336
) -> PostgresVectorStore:
335337
"""Create an PostgresVectorStore instance from documents.
338+
Throws an error if Id column data type doesn't match with the ids.
336339
337340
Args:
338341
documents (List[Document]): Documents to add to the vector store.
@@ -341,7 +344,7 @@ async def afrom_documents( # type: ignore[override]
341344
table_name (str): Name of the existing table or the table to be created.
342345
schema_name (str, optional): Database schema name of the table. Defaults to "public".
343346
metadatas (Optional[List[dict]]): List of metadatas to add to table records.
344-
ids: (Optional[List[str]]): List of IDs to add to table records.
347+
ids: (Optional[List]): List of IDs to add to table records.
345348
content_column (str): Column that represent a Document’s page_content. Defaults to "content".
346349
embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding".
347350
metadata_columns (List[str]): Column(s) that represent a document's metadata.
@@ -386,7 +389,7 @@ def from_texts( # type: ignore[override]
386389
table_name: str,
387390
schema_name: str = "public",
388391
metadatas: Optional[List[dict]] = None,
389-
ids: Optional[List[str]] = None,
392+
ids: Optional[List] = None,
390393
content_column: str = "content",
391394
embedding_column: str = "embedding",
392395
metadata_columns: List[str] = [],
@@ -400,14 +403,16 @@ def from_texts( # type: ignore[override]
400403
index_query_options: Optional[QueryOptions] = None,
401404
) -> PostgresVectorStore:
402405
"""Create an PostgresVectorStore instance from texts.
406+
Throws an error if Id column data type doesn't match with the ids.
407+
403408
Args:
404409
texts (List[str]): Texts to add to the vector store.
405410
embedding (Embeddings): Text embedding model to use.
406411
engine (PostgresEngine): Connection pool engine for managing connections to Postgres database.
407412
table_name (str): Name of the existing table or the table to be created.
408413
schema_name (str, optional): Database schema name of the table. Defaults to "public".
409414
metadatas (Optional[List[dict]]): List of metadatas to add to table records.
410-
ids: (Optional[List[str]]): List of IDs to add to table records.
415+
ids: (Optional[List]): List of IDs to add to table records.
411416
content_column (str): Column that represent a Document’s page_content. Defaults to "content".
412417
embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding".
413418
metadata_columns (List[str]): Column(s) that represent a document's metadata.
@@ -451,7 +456,7 @@ def from_documents( # type: ignore[override]
451456
engine: PostgresEngine,
452457
table_name: str,
453458
schema_name: str = "public",
454-
ids: Optional[List[str]] = None,
459+
ids: Optional[List] = None,
455460
content_column: str = "content",
456461
embedding_column: str = "embedding",
457462
metadata_columns: List[str] = [],
@@ -465,6 +470,7 @@ def from_documents( # type: ignore[override]
465470
index_query_options: Optional[QueryOptions] = None,
466471
) -> PostgresVectorStore:
467472
"""Create an PostgresVectorStore instance from documents.
473+
Throws an error if Id column data type doesn't match with the ids.
468474
469475
Args:
470476
documents (List[Document]): Documents to add to the vector store.
@@ -473,7 +479,7 @@ def from_documents( # type: ignore[override]
473479
table_name (str): Name of the existing table or the table to be created.
474480
schema_name (str, optional): Database schema name of the table. Defaults to "public".
475481
metadatas (Optional[List[dict]]): List of metadatas to add to table records.
476-
ids: (Optional[List[str]]): List of IDs to add to table records.
482+
ids: (Optional[List]): List of IDs to add to table records.
477483
content_column (str): Column that represent a Document’s page_content. Defaults to "content".
478484
embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding".
479485
metadata_columns (List[str]): Column(s) that represent a document's metadata.

tests/test_engine.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131

3232
DEFAULT_TABLE = "test_table" + str(uuid.uuid4()).replace("-", "_")
3333
CUSTOM_TABLE = "test_table_custom" + str(uuid.uuid4()).replace("-", "_")
34+
INT_ID_CUSTOM_TABLE = "test_table_custom_int_id" + str(uuid.uuid4()).replace("-", "_")
3435
DEFAULT_TABLE_SYNC = "test_table" + str(uuid.uuid4()).replace("-", "_")
3536
CUSTOM_TABLE_SYNC = "test_table_custom" + str(uuid.uuid4()).replace("-", "_")
37+
INT_ID_CUSTOM_TABLE_SYNC = "test_table_custom_int_id" + str(uuid.uuid4()).replace("-", "_")
3638
VECTOR_SIZE = 768
3739

3840
embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE)
@@ -110,6 +112,7 @@ async def engine(self, db_project, db_region, db_instance, db_name):
110112
yield engine
111113
await aexecute(engine, f'DROP TABLE "{CUSTOM_TABLE}"')
112114
await aexecute(engine, f'DROP TABLE "{DEFAULT_TABLE}"')
115+
await aexecute(engine, f'DROP TABLE "{INT_ID_CUSTOM_TABLE}"')
113116
await engine.close()
114117

115118
async def test_init_table(self, engine):
@@ -143,6 +146,29 @@ async def test_init_table_custom(self, engine):
143146
for row in results:
144147
assert row in expected
145148

149+
async def test_init_table_with_int_id(self, engine):
150+
await engine.ainit_vectorstore_table(
151+
INT_ID_CUSTOM_TABLE,
152+
VECTOR_SIZE,
153+
id_column=Column(name="integer_id", data_type="INTEGER", nullable="False"),
154+
content_column="my-content",
155+
embedding_column="my_embedding",
156+
metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")],
157+
store_metadata=True,
158+
)
159+
stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{INT_ID_CUSTOM_TABLE}';"
160+
results = await afetch(engine, stmt)
161+
expected = [
162+
{"column_name": "integer_id", "data_type": "integer"},
163+
{"column_name": "my_embedding", "data_type": "USER-DEFINED"},
164+
{"column_name": "langchain_metadata", "data_type": "json"},
165+
{"column_name": "my-content", "data_type": "text"},
166+
{"column_name": "page", "data_type": "text"},
167+
{"column_name": "source", "data_type": "text"},
168+
]
169+
for row in results:
170+
assert row in expected
171+
146172
async def test_password(
147173
self,
148174
db_project,
@@ -306,6 +332,7 @@ async def engine(self, db_project, db_region, db_instance, db_name):
306332
yield engine
307333
await aexecute(engine, f'DROP TABLE "{CUSTOM_TABLE_SYNC}"')
308334
await aexecute(engine, f'DROP TABLE "{DEFAULT_TABLE_SYNC}"')
335+
await aexecute(engine, f'DROP TABLE "{INT_ID_CUSTOM_TABLE_SYNC}"')
309336
await engine.close()
310337

311338
async def test_init_table(self, engine):
@@ -339,6 +366,29 @@ async def test_init_table_custom(self, engine):
339366
for row in results:
340367
assert row in expected
341368

369+
async def test_init_table_with_int_id(self, engine):
370+
engine.init_vectorstore_table(
371+
INT_ID_CUSTOM_TABLE_SYNC,
372+
VECTOR_SIZE,
373+
id_column=Column(name="integer_id", data_type="INTEGER", nullable=False),
374+
content_column="my-content",
375+
embedding_column="my_embedding",
376+
metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")],
377+
store_metadata=True,
378+
)
379+
stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{INT_ID_CUSTOM_TABLE_SYNC}';"
380+
results = await afetch(engine, stmt)
381+
expected = [
382+
{"column_name": "integer_id", "data_type": "integer"},
383+
{"column_name": "my_embedding", "data_type": "USER-DEFINED"},
384+
{"column_name": "langchain_metadata", "data_type": "json"},
385+
{"column_name": "my-content", "data_type": "text"},
386+
{"column_name": "page", "data_type": "text"},
387+
{"column_name": "source", "data_type": "text"},
388+
]
389+
for row in results:
390+
assert row in expected
391+
342392
async def test_password(
343393
self,
344394
db_project,

0 commit comments

Comments
 (0)