langchain-postgres 0.0.17__tar.gz → 0.0.19__tar.gz
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.
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_lint.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_release.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_test.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/ci.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/PKG-INFO +18 -17
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/README.md +14 -13
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/async_vectorstore.py +36 -15
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/pyproject.toml +12 -4
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_vectorstore.py +4 -5
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore.py +91 -1
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/uv.lock +650 -389
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/actions/uv_setup/action.yml +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.gitignore +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/CONTRIBUTING.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/DEVELOPMENT.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/LICENSE +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/Makefile +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/docker-compose.yml +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/docs/v2_design_overview.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/migrate_pgvector_to_pgvectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/migrate_pgvector_to_pgvectorstore.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/pg_vectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/pg_vectorstore_how_to.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/vectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/_utils.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/chat_message_histories.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/py.typed +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/translator.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/utils/pgvector_migrator.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/engine.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/hybrid_search_config.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/indexes.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/vectorstores.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/vectorstores.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/security.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fake_embeddings.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/filtering_test_cases.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/metadata_filtering_data.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/query_constructors/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/query_constructors/test_pgvector.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/test_imports.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_chat_histories.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_vectorstore_standard_tests.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_index.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_search.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_engine.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_hybrid_search_config.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_indexes.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_index.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_search.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/utils.py +0 -0
|
@@ -13,6 +13,9 @@ on:
|
|
|
13
13
|
# There's no point in testing an outdated version of the code. GitHub only allows
|
|
14
14
|
# a limited number of job runners to be active at the same time, so it's better to cancel
|
|
15
15
|
# pointless jobs early so that more useful jobs can run sooner.
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
16
19
|
concurrency:
|
|
17
20
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
18
21
|
cancel-in-progress: true
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: langchain-postgres
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.19
|
|
4
4
|
Summary: An integration package connecting Postgres and LangChain
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
8
|
Requires-Dist: asyncpg>=0.30.0
|
|
9
|
-
Requires-Dist: langchain-core<2.0,>=
|
|
9
|
+
Requires-Dist: langchain-core<2.0,>=1.2.11
|
|
10
10
|
Requires-Dist: numpy<3,>=1.21
|
|
11
11
|
Requires-Dist: pgvector<0.4,>=0.2.5
|
|
12
12
|
Requires-Dist: psycopg-pool<4,>=3.2.1
|
|
@@ -51,6 +51,9 @@ pip install -U langchain-postgres
|
|
|
51
51
|
* [Quickstart](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/pg_vectorstore.ipynb)
|
|
52
52
|
* [How-to](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/pg_vectorstore_how_to.ipynb)
|
|
53
53
|
|
|
54
|
+
> [!TIP]
|
|
55
|
+
> For developing, debugging, and deploying AI agents and LLM applications, see [LangSmith](https://docs.langchain.com/langsmith/home).
|
|
56
|
+
|
|
54
57
|
### Example
|
|
55
58
|
|
|
56
59
|
```python
|
|
@@ -82,7 +85,7 @@ store = PGVectorStore.create_sync(
|
|
|
82
85
|
docs = [
|
|
83
86
|
Document(page_content="Apples and oranges"),
|
|
84
87
|
Document(page_content="Cars and airplanes"),
|
|
85
|
-
Document(page_content="Train")
|
|
88
|
+
Document(page_content="Train"),
|
|
86
89
|
]
|
|
87
90
|
|
|
88
91
|
store.add_documents(docs)
|
|
@@ -104,9 +107,7 @@ vs = PGVectorStore.create_sync(
|
|
|
104
107
|
engine=engine,
|
|
105
108
|
table_name=TABLE_NAME,
|
|
106
109
|
embedding_service=embedding,
|
|
107
|
-
hybrid_search_config=HybridSearchConfig(
|
|
108
|
-
fusion_function=reciprocal_rank_fusion
|
|
109
|
-
),
|
|
110
|
+
hybrid_search_config=HybridSearchConfig(fusion_function=reciprocal_rank_fusion),
|
|
110
111
|
)
|
|
111
112
|
hybrid_docs = vector_store.similarity_search("products", k=5)
|
|
112
113
|
```
|
|
@@ -135,7 +136,7 @@ import psycopg
|
|
|
135
136
|
|
|
136
137
|
# Establish a synchronous connection to the database
|
|
137
138
|
# (or use psycopg.AsyncConnection for async)
|
|
138
|
-
conn_info = ...
|
|
139
|
+
conn_info = ... # Fill in with your connection info
|
|
139
140
|
sync_connection = psycopg.connect(conn_info)
|
|
140
141
|
|
|
141
142
|
# Create the table schema (only needs to be done once)
|
|
@@ -146,17 +147,17 @@ session_id = str(uuid.uuid4())
|
|
|
146
147
|
|
|
147
148
|
# Initialize the chat history manager
|
|
148
149
|
chat_history = PostgresChatMessageHistory(
|
|
149
|
-
table_name,
|
|
150
|
-
session_id,
|
|
151
|
-
sync_connection=sync_connection
|
|
150
|
+
table_name, session_id, sync_connection=sync_connection
|
|
152
151
|
)
|
|
153
152
|
|
|
154
153
|
# Add messages to the chat history
|
|
155
|
-
chat_history.add_messages(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
chat_history.add_messages(
|
|
155
|
+
[
|
|
156
|
+
SystemMessage(content="Meow"),
|
|
157
|
+
AIMessage(content="woof"),
|
|
158
|
+
HumanMessage(content="bark"),
|
|
159
|
+
]
|
|
160
|
+
)
|
|
160
161
|
|
|
161
162
|
print(chat_history.messages)
|
|
162
163
|
```
|
|
@@ -35,6 +35,9 @@ pip install -U langchain-postgres
|
|
|
35
35
|
* [Quickstart](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/pg_vectorstore.ipynb)
|
|
36
36
|
* [How-to](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/pg_vectorstore_how_to.ipynb)
|
|
37
37
|
|
|
38
|
+
> [!TIP]
|
|
39
|
+
> For developing, debugging, and deploying AI agents and LLM applications, see [LangSmith](https://docs.langchain.com/langsmith/home).
|
|
40
|
+
|
|
38
41
|
### Example
|
|
39
42
|
|
|
40
43
|
```python
|
|
@@ -66,7 +69,7 @@ store = PGVectorStore.create_sync(
|
|
|
66
69
|
docs = [
|
|
67
70
|
Document(page_content="Apples and oranges"),
|
|
68
71
|
Document(page_content="Cars and airplanes"),
|
|
69
|
-
Document(page_content="Train")
|
|
72
|
+
Document(page_content="Train"),
|
|
70
73
|
]
|
|
71
74
|
|
|
72
75
|
store.add_documents(docs)
|
|
@@ -88,9 +91,7 @@ vs = PGVectorStore.create_sync(
|
|
|
88
91
|
engine=engine,
|
|
89
92
|
table_name=TABLE_NAME,
|
|
90
93
|
embedding_service=embedding,
|
|
91
|
-
hybrid_search_config=HybridSearchConfig(
|
|
92
|
-
fusion_function=reciprocal_rank_fusion
|
|
93
|
-
),
|
|
94
|
+
hybrid_search_config=HybridSearchConfig(fusion_function=reciprocal_rank_fusion),
|
|
94
95
|
)
|
|
95
96
|
hybrid_docs = vector_store.similarity_search("products", k=5)
|
|
96
97
|
```
|
|
@@ -119,7 +120,7 @@ import psycopg
|
|
|
119
120
|
|
|
120
121
|
# Establish a synchronous connection to the database
|
|
121
122
|
# (or use psycopg.AsyncConnection for async)
|
|
122
|
-
conn_info = ...
|
|
123
|
+
conn_info = ... # Fill in with your connection info
|
|
123
124
|
sync_connection = psycopg.connect(conn_info)
|
|
124
125
|
|
|
125
126
|
# Create the table schema (only needs to be done once)
|
|
@@ -130,17 +131,17 @@ session_id = str(uuid.uuid4())
|
|
|
130
131
|
|
|
131
132
|
# Initialize the chat history manager
|
|
132
133
|
chat_history = PostgresChatMessageHistory(
|
|
133
|
-
table_name,
|
|
134
|
-
session_id,
|
|
135
|
-
sync_connection=sync_connection
|
|
134
|
+
table_name, session_id, sync_connection=sync_connection
|
|
136
135
|
)
|
|
137
136
|
|
|
138
137
|
# Add messages to the chat history
|
|
139
|
-
chat_history.add_messages(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
chat_history.add_messages(
|
|
139
|
+
[
|
|
140
|
+
SystemMessage(content="Meow"),
|
|
141
|
+
AIMessage(content="woof"),
|
|
142
|
+
HumanMessage(content="bark"),
|
|
143
|
+
]
|
|
144
|
+
)
|
|
144
145
|
|
|
145
146
|
print(chat_history.messages)
|
|
146
147
|
```
|
{langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/async_vectorstore.py
RENAMED
|
@@ -119,6 +119,15 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
119
119
|
"Only create class through 'create' or 'create_sync' methods!"
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
+
if hasattr(embedding_service, "embed_query_inline") and not hasattr(
|
|
123
|
+
embedding_service, "embed_query_inline_template"
|
|
124
|
+
):
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"The provided embedding_service is using an outdated inline embedding implementation "
|
|
127
|
+
"that is susceptible to SQL injection. Please update your package "
|
|
128
|
+
"to a version that supports 'embed_query_inline_template'."
|
|
129
|
+
)
|
|
130
|
+
|
|
122
131
|
self.engine = engine
|
|
123
132
|
self.embedding_service = embedding_service
|
|
124
133
|
self.table_name = table_name
|
|
@@ -291,8 +300,11 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
291
300
|
metadatas = [{} for _ in texts]
|
|
292
301
|
|
|
293
302
|
# Check for inline embedding capability
|
|
294
|
-
|
|
295
|
-
|
|
303
|
+
inline_template_func = getattr(
|
|
304
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
305
|
+
)
|
|
306
|
+
can_inline_embed_template = callable(inline_template_func)
|
|
307
|
+
|
|
296
308
|
# Insert embeddings
|
|
297
309
|
for id, content, embedding, metadata in zip(ids, texts, embeddings, metadatas):
|
|
298
310
|
metadata_col_names = (
|
|
@@ -313,8 +325,8 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
313
325
|
}
|
|
314
326
|
values_stmt = "VALUES (:langchain_id, :content, :embedding"
|
|
315
327
|
|
|
316
|
-
if not embedding and
|
|
317
|
-
values_stmt = f"VALUES (:langchain_id, :content, {
|
|
328
|
+
if not embedding and can_inline_embed_template:
|
|
329
|
+
values_stmt = f"VALUES (:langchain_id, :content, {inline_template_func(':content')}" # type: ignore
|
|
318
330
|
|
|
319
331
|
if self.hybrid_search_config and self.hybrid_search_config.tsv_column:
|
|
320
332
|
lang = (
|
|
@@ -383,8 +395,10 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
383
395
|
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.
|
|
384
396
|
"""
|
|
385
397
|
# Check for inline embedding query
|
|
386
|
-
|
|
387
|
-
|
|
398
|
+
inline_template_func = getattr(
|
|
399
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
400
|
+
)
|
|
401
|
+
if callable(inline_template_func):
|
|
388
402
|
embeddings: list[list[float]] = [[] for _ in list(texts)]
|
|
389
403
|
else:
|
|
390
404
|
embeddings = await self.embedding_service.aembed_documents(list(texts))
|
|
@@ -664,18 +678,21 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
664
678
|
if filter and isinstance(filter, dict):
|
|
665
679
|
safe_filter, filter_dict = self._create_filter_clause(filter)
|
|
666
680
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
681
|
+
inline_template_func = getattr(
|
|
682
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
683
|
+
)
|
|
684
|
+
param_dict: dict[str, Any] = {"dense_limit": dense_limit}
|
|
685
|
+
if not embedding and callable(inline_template_func) and "query" in kwargs:
|
|
686
|
+
embedding_data_string = inline_template_func(":query_text")
|
|
687
|
+
param_dict["query_text"] = kwargs["query"]
|
|
671
688
|
else:
|
|
672
689
|
query_embedding = f"{[float(dimension) for dimension in embedding]}"
|
|
673
690
|
embedding_data_string = ":query_embedding"
|
|
691
|
+
param_dict["query_embedding"] = query_embedding
|
|
674
692
|
where_filters = f"WHERE {safe_filter}" if safe_filter else ""
|
|
675
693
|
dense_query_stmt = f"""SELECT {column_names}, {search_function}("{self.embedding_column}", {embedding_data_string}) as distance
|
|
676
694
|
FROM "{self.schema_name}"."{self.table_name}" {where_filters} ORDER BY "{self.embedding_column}" {operator} {embedding_data_string} LIMIT :dense_limit;
|
|
677
695
|
"""
|
|
678
|
-
param_dict = {"query_embedding": query_embedding, "dense_limit": dense_limit}
|
|
679
696
|
if filter_dict:
|
|
680
697
|
param_dict.update(filter_dict)
|
|
681
698
|
if self.index_query_options:
|
|
@@ -769,10 +786,12 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
769
786
|
**kwargs: Any,
|
|
770
787
|
) -> list[Document]:
|
|
771
788
|
"""Return docs selected by similarity search on query."""
|
|
772
|
-
|
|
789
|
+
inline_template_func = getattr(
|
|
790
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
791
|
+
)
|
|
773
792
|
embedding = (
|
|
774
793
|
[]
|
|
775
|
-
if callable(
|
|
794
|
+
if callable(inline_template_func)
|
|
776
795
|
else await self.embedding_service.aembed_query(text=query)
|
|
777
796
|
)
|
|
778
797
|
kwargs["query"] = query
|
|
@@ -808,10 +827,12 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
808
827
|
**kwargs: Any,
|
|
809
828
|
) -> list[tuple[Document, float]]:
|
|
810
829
|
"""Return docs and distance scores selected by similarity search on query."""
|
|
811
|
-
|
|
830
|
+
inline_template_func = getattr(
|
|
831
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
832
|
+
)
|
|
812
833
|
embedding = (
|
|
813
834
|
[]
|
|
814
|
-
if callable(
|
|
835
|
+
if callable(inline_template_func)
|
|
815
836
|
else await self.embedding_service.aembed_query(text=query)
|
|
816
837
|
)
|
|
817
838
|
kwargs["query"] = query
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "langchain-postgres"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.19"
|
|
4
4
|
description = "An integration package connecting Postgres and LangChain"
|
|
5
5
|
authors = []
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
repository = "https://github.com/langchain-ai/langchain-postgres"
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"asyncpg>=0.30.0",
|
|
12
|
-
"langchain-core>=
|
|
12
|
+
"langchain-core>=1.2.11,<2.0",
|
|
13
13
|
"pgvector>=0.2.5,<0.4",
|
|
14
14
|
"psycopg[binary]>=3,<4",
|
|
15
15
|
"psycopg-pool>=3.2.1,<4",
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
[dependency-groups]
|
|
24
24
|
test = [
|
|
25
25
|
"codespell>=2.4.1",
|
|
26
|
-
"langchain-tests
|
|
26
|
+
"langchain-tests>=1.1.5",
|
|
27
27
|
"mypy>=1.15.0",
|
|
28
28
|
"psycopg[binary]>=3,<4",
|
|
29
29
|
"pytest>=8.3.4",
|
|
@@ -50,6 +50,14 @@ disallow_untyped_defs = "True"
|
|
|
50
50
|
[tool.coverage.run]
|
|
51
51
|
omit = ["tests/*"]
|
|
52
52
|
|
|
53
|
+
[tool.uv]
|
|
54
|
+
constraint-dependencies = [
|
|
55
|
+
# CVE-2026-25528: langsmith < 0.6.3 is vulnerable to SSRF via tracing header injection.
|
|
56
|
+
# langchain-core 1.x allows langsmith >=0.3.45,<1.0.0 so the resolver may pick an old version.
|
|
57
|
+
# Remove this constraint once langchain-core requires langsmith >= 0.6.3.
|
|
58
|
+
"langsmith>=0.6.3",
|
|
59
|
+
]
|
|
60
|
+
|
|
53
61
|
[build-system]
|
|
54
62
|
requires = ["hatchling"]
|
|
55
63
|
build-backend = "hatchling.build"
|
{langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_vectorstore.py
RENAMED
|
@@ -647,7 +647,7 @@ def test_pgvector_retriever_search_threshold() -> None:
|
|
|
647
647
|
search_type="similarity_score_threshold",
|
|
648
648
|
search_kwargs={"k": 3, "score_threshold": 0.999},
|
|
649
649
|
)
|
|
650
|
-
output = retriever.
|
|
650
|
+
output = retriever.invoke("summer")
|
|
651
651
|
_compare_documents(
|
|
652
652
|
output,
|
|
653
653
|
[
|
|
@@ -675,7 +675,7 @@ async def test_async_pgvector_retriever_search_threshold() -> None:
|
|
|
675
675
|
search_type="similarity_score_threshold",
|
|
676
676
|
search_kwargs={"k": 3, "score_threshold": 0.999},
|
|
677
677
|
)
|
|
678
|
-
output = await retriever.
|
|
678
|
+
output = await retriever.ainvoke("summer")
|
|
679
679
|
_compare_documents(
|
|
680
680
|
output,
|
|
681
681
|
[
|
|
@@ -703,7 +703,7 @@ def test_pgvector_retriever_search_threshold_custom_normalization_fn() -> None:
|
|
|
703
703
|
search_type="similarity_score_threshold",
|
|
704
704
|
search_kwargs={"k": 3, "score_threshold": 0.5},
|
|
705
705
|
)
|
|
706
|
-
output = retriever.
|
|
706
|
+
output = retriever.invoke("foo")
|
|
707
707
|
assert output == []
|
|
708
708
|
|
|
709
709
|
|
|
@@ -728,7 +728,7 @@ async def test_async_pgvector_retriever_search_threshold_custom_normalization_fn
|
|
|
728
728
|
search_type="similarity_score_threshold",
|
|
729
729
|
search_kwargs={"k": 3, "score_threshold": 0.5},
|
|
730
730
|
)
|
|
731
|
-
output = await retriever.
|
|
731
|
+
output = await retriever.ainvoke("foo")
|
|
732
732
|
assert output == []
|
|
733
733
|
|
|
734
734
|
|
|
@@ -857,7 +857,6 @@ def pgvector() -> Generator[PGVector, None, None]:
|
|
|
857
857
|
yield vector_store
|
|
858
858
|
|
|
859
859
|
|
|
860
|
-
@pytest.mark.asyncio
|
|
861
860
|
@pytest.fixture
|
|
862
861
|
async def async_pgvector() -> AsyncGenerator[PGVector, None]:
|
|
863
862
|
"""Create an async PGVector instance."""
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from typing import AsyncIterator, Sequence
|
|
3
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
import pytest_asyncio
|
|
6
7
|
from langchain_core.documents import Document
|
|
7
|
-
from langchain_core.embeddings import DeterministicFakeEmbedding
|
|
8
|
+
from langchain_core.embeddings import DeterministicFakeEmbedding, Embeddings
|
|
8
9
|
from sqlalchemy import text
|
|
9
10
|
from sqlalchemy.engine.row import RowMapping
|
|
10
11
|
|
|
@@ -540,3 +541,92 @@ class TestVectorStore:
|
|
|
540
541
|
embedding_column="myembedding",
|
|
541
542
|
metadata_columns=["random_column"], # invalid metadata column
|
|
542
543
|
)
|
|
544
|
+
|
|
545
|
+
async def test_aadd_embeddings_with_inline_template(self) -> None:
|
|
546
|
+
class TemplateEmbeddings(Embeddings):
|
|
547
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
548
|
+
return []
|
|
549
|
+
|
|
550
|
+
def embed_query(self, text: str) -> list[float]:
|
|
551
|
+
return []
|
|
552
|
+
|
|
553
|
+
def embed_query_inline_template(self, param_name: str) -> str:
|
|
554
|
+
return f"embedding('model_id', {param_name})::vector"
|
|
555
|
+
|
|
556
|
+
mock_conn = AsyncMock()
|
|
557
|
+
mock_engine = MagicMock()
|
|
558
|
+
mock_engine.connect.return_value.__aenter__.return_value = mock_conn
|
|
559
|
+
mock_engine.connect.return_value.__aexit__.return_value = None
|
|
560
|
+
|
|
561
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
562
|
+
vs = AsyncPGVectorStore(
|
|
563
|
+
create_key,
|
|
564
|
+
engine=mock_engine,
|
|
565
|
+
embedding_service=TemplateEmbeddings(),
|
|
566
|
+
table_name="test_table",
|
|
567
|
+
)
|
|
568
|
+
await vs.aadd_embeddings(texts=["hello world"], embeddings=[[]])
|
|
569
|
+
call_args = mock_conn.execute.call_args
|
|
570
|
+
sql_text = str(call_args[0][0])
|
|
571
|
+
params = call_args[0][1]
|
|
572
|
+
assert ":content, embedding('model_id', :content)::vector" in sql_text
|
|
573
|
+
assert params["content"] == "hello world"
|
|
574
|
+
|
|
575
|
+
async def test_asimilarity_search_with_inline_template(self) -> None:
|
|
576
|
+
class TemplateEmbeddings(Embeddings):
|
|
577
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
578
|
+
return []
|
|
579
|
+
|
|
580
|
+
def embed_query(self, text: str) -> list[float]:
|
|
581
|
+
return []
|
|
582
|
+
|
|
583
|
+
def embed_query_inline_template(self, param_name: str) -> str:
|
|
584
|
+
return f"embedding('model_id', {param_name})::vector"
|
|
585
|
+
|
|
586
|
+
mock_conn = AsyncMock()
|
|
587
|
+
mock_result = MagicMock()
|
|
588
|
+
mock_result.mappings.return_value.fetchall.return_value = []
|
|
589
|
+
mock_conn.execute.return_value = mock_result
|
|
590
|
+
|
|
591
|
+
mock_engine = MagicMock()
|
|
592
|
+
mock_engine.connect.return_value.__aenter__.return_value = mock_conn
|
|
593
|
+
mock_engine.connect.return_value.__aexit__.return_value = None
|
|
594
|
+
|
|
595
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
596
|
+
vs = AsyncPGVectorStore(
|
|
597
|
+
create_key,
|
|
598
|
+
engine=mock_engine,
|
|
599
|
+
embedding_service=TemplateEmbeddings(),
|
|
600
|
+
table_name="test_table",
|
|
601
|
+
)
|
|
602
|
+
await vs.asimilarity_search_with_score_by_vector(
|
|
603
|
+
embedding=[], query="search query"
|
|
604
|
+
)
|
|
605
|
+
call_args = mock_conn.execute.call_args
|
|
606
|
+
sql_text = str(call_args[0][0])
|
|
607
|
+
params = call_args[0][1]
|
|
608
|
+
assert "embedding('model_id', :query_text)::vector" in sql_text
|
|
609
|
+
assert params["query_text"] == "search query"
|
|
610
|
+
|
|
611
|
+
async def test_init_raises_for_outdated_inline_embedding(self) -> None:
|
|
612
|
+
class OutdatedAlloyDBEmbeddings(Embeddings):
|
|
613
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
614
|
+
return []
|
|
615
|
+
|
|
616
|
+
def embed_query(self, text: str) -> list[float]:
|
|
617
|
+
return []
|
|
618
|
+
|
|
619
|
+
def embed_query_inline(self, query: str) -> str:
|
|
620
|
+
return f"embedding('model_id', '{query}')::vector"
|
|
621
|
+
|
|
622
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
623
|
+
|
|
624
|
+
with pytest.raises(
|
|
625
|
+
ValueError, match="outdated inline embedding implementation"
|
|
626
|
+
):
|
|
627
|
+
AsyncPGVectorStore(
|
|
628
|
+
create_key,
|
|
629
|
+
engine=None, # type: ignore
|
|
630
|
+
embedding_service=OutdatedAlloyDBEmbeddings(),
|
|
631
|
+
table_name="test_table",
|
|
632
|
+
)
|