langchain-postgres 0.0.17__tar.gz → 0.0.18__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.18}/.github/workflows/_lint.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/_release.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/_test.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/ci.yml +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/PKG-INFO +7 -4
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/README.md +3 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/async_vectorstore.py +29 -6
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/pyproject.toml +12 -4
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_vectorstore.py +4 -5
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore.py +137 -1
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/uv.lock +647 -376
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/actions/uv_setup/action.yml +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.gitignore +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/CONTRIBUTING.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/DEVELOPMENT.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/LICENSE +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/Makefile +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/docker-compose.yml +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/docs/v2_design_overview.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/migrate_pgvector_to_pgvectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/migrate_pgvector_to_pgvectorstore.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/pg_vectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/pg_vectorstore_how_to.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/vectorstore.ipynb +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/_utils.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/chat_message_histories.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/py.typed +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/translator.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/utils/pgvector_migrator.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/engine.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/hybrid_search_config.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/indexes.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/vectorstores.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/vectorstores.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/security.md +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fake_embeddings.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/filtering_test_cases.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/metadata_filtering_data.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/query_constructors/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/query_constructors/test_pgvector.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/test_imports.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_chat_histories.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_vectorstore_standard_tests.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/__init__.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_index.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_search.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_engine.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_hybrid_search_config.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_indexes.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_index.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_search.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py +0 -0
- {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/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.18
|
|
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
|
|
@@ -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
|
{langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/async_vectorstore.py
RENAMED
|
@@ -291,6 +291,10 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
291
291
|
metadatas = [{} for _ in texts]
|
|
292
292
|
|
|
293
293
|
# Check for inline embedding capability
|
|
294
|
+
inline_template_func = getattr(
|
|
295
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
296
|
+
)
|
|
297
|
+
can_inline_embed_template = callable(inline_template_func)
|
|
294
298
|
inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
|
|
295
299
|
can_inline_embed = callable(inline_embed_func)
|
|
296
300
|
# Insert embeddings
|
|
@@ -313,7 +317,9 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
313
317
|
}
|
|
314
318
|
values_stmt = "VALUES (:langchain_id, :content, :embedding"
|
|
315
319
|
|
|
316
|
-
if not embedding and
|
|
320
|
+
if not embedding and can_inline_embed_template:
|
|
321
|
+
values_stmt = f"VALUES (:langchain_id, :content, {inline_template_func(':content')}" # type: ignore
|
|
322
|
+
elif not embedding and can_inline_embed:
|
|
317
323
|
values_stmt = f"VALUES (:langchain_id, :content, {self.embedding_service.embed_query_inline(content)}" # type: ignore
|
|
318
324
|
|
|
319
325
|
if self.hybrid_search_config and self.hybrid_search_config.tsv_column:
|
|
@@ -383,8 +389,11 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
383
389
|
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.
|
|
384
390
|
"""
|
|
385
391
|
# Check for inline embedding query
|
|
392
|
+
inline_template_func = getattr(
|
|
393
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
394
|
+
)
|
|
386
395
|
inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
|
|
387
|
-
if callable(inline_embed_func):
|
|
396
|
+
if callable(inline_template_func) or callable(inline_embed_func):
|
|
388
397
|
embeddings: list[list[float]] = [[] for _ in list(texts)]
|
|
389
398
|
else:
|
|
390
399
|
embeddings = await self.embedding_service.aembed_documents(list(texts))
|
|
@@ -664,18 +673,26 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
664
673
|
if filter and isinstance(filter, dict):
|
|
665
674
|
safe_filter, filter_dict = self._create_filter_clause(filter)
|
|
666
675
|
|
|
676
|
+
inline_template_func = getattr(
|
|
677
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
678
|
+
)
|
|
667
679
|
inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
|
|
668
|
-
|
|
680
|
+
param_dict: dict[str, Any] = {"dense_limit": dense_limit}
|
|
681
|
+
if not embedding and callable(inline_template_func) and "query" in kwargs:
|
|
682
|
+
embedding_data_string = inline_template_func(":query_text")
|
|
683
|
+
param_dict["query_text"] = kwargs["query"]
|
|
684
|
+
elif not embedding and callable(inline_embed_func) and "query" in kwargs:
|
|
669
685
|
query_embedding = self.embedding_service.embed_query_inline(kwargs["query"]) # type: ignore
|
|
670
686
|
embedding_data_string = f"{query_embedding}"
|
|
687
|
+
param_dict["query_embedding"] = query_embedding
|
|
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,13 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
769
786
|
**kwargs: Any,
|
|
770
787
|
) -> list[Document]:
|
|
771
788
|
"""Return docs selected by similarity search on query."""
|
|
789
|
+
inline_template_func = getattr(
|
|
790
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
791
|
+
)
|
|
772
792
|
inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
|
|
773
793
|
embedding = (
|
|
774
794
|
[]
|
|
775
|
-
if callable(inline_embed_func)
|
|
795
|
+
if callable(inline_template_func) or callable(inline_embed_func)
|
|
776
796
|
else await self.embedding_service.aembed_query(text=query)
|
|
777
797
|
)
|
|
778
798
|
kwargs["query"] = query
|
|
@@ -808,10 +828,13 @@ class AsyncPGVectorStore(VectorStore):
|
|
|
808
828
|
**kwargs: Any,
|
|
809
829
|
) -> list[tuple[Document, float]]:
|
|
810
830
|
"""Return docs and distance scores selected by similarity search on query."""
|
|
831
|
+
inline_template_func = getattr(
|
|
832
|
+
self.embedding_service, "embed_query_inline_template", None
|
|
833
|
+
)
|
|
811
834
|
inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
|
|
812
835
|
embedding = (
|
|
813
836
|
[]
|
|
814
|
-
if callable(inline_embed_func)
|
|
837
|
+
if callable(inline_template_func) or callable(inline_embed_func)
|
|
815
838
|
else await self.embedding_service.aembed_query(text=query)
|
|
816
839
|
)
|
|
817
840
|
kwargs["query"] = query
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "langchain-postgres"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.18"
|
|
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.18}/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,138 @@ 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_aadd_embeddings_with_legacy_inline(self) -> None:
|
|
576
|
+
class LegacyEmbeddings(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(self, query: str) -> str:
|
|
584
|
+
return f"embedding('model_id', '{query}')::vector"
|
|
585
|
+
|
|
586
|
+
mock_conn = AsyncMock()
|
|
587
|
+
mock_engine = MagicMock()
|
|
588
|
+
mock_engine.connect.return_value.__aenter__.return_value = mock_conn
|
|
589
|
+
mock_engine.connect.return_value.__aexit__.return_value = None
|
|
590
|
+
|
|
591
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
592
|
+
vs = AsyncPGVectorStore(
|
|
593
|
+
create_key,
|
|
594
|
+
engine=mock_engine,
|
|
595
|
+
embedding_service=LegacyEmbeddings(),
|
|
596
|
+
table_name="test_table",
|
|
597
|
+
)
|
|
598
|
+
await vs.aadd_embeddings(texts=["hello world"], embeddings=[[]])
|
|
599
|
+
call_args = mock_conn.execute.call_args
|
|
600
|
+
sql_text = str(call_args[0][0])
|
|
601
|
+
params = call_args[0][1]
|
|
602
|
+
assert "embedding('model_id', 'hello world')::vector" in sql_text
|
|
603
|
+
assert params["content"] == "hello world"
|
|
604
|
+
|
|
605
|
+
async def test_asimilarity_search_with_inline_template(self) -> None:
|
|
606
|
+
class TemplateEmbeddings(Embeddings):
|
|
607
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
608
|
+
return []
|
|
609
|
+
|
|
610
|
+
def embed_query(self, text: str) -> list[float]:
|
|
611
|
+
return []
|
|
612
|
+
|
|
613
|
+
def embed_query_inline_template(self, param_name: str) -> str:
|
|
614
|
+
return f"embedding('model_id', {param_name})::vector"
|
|
615
|
+
|
|
616
|
+
mock_conn = AsyncMock()
|
|
617
|
+
mock_result = MagicMock()
|
|
618
|
+
mock_result.mappings.return_value.fetchall.return_value = []
|
|
619
|
+
mock_conn.execute.return_value = mock_result
|
|
620
|
+
|
|
621
|
+
mock_engine = MagicMock()
|
|
622
|
+
mock_engine.connect.return_value.__aenter__.return_value = mock_conn
|
|
623
|
+
mock_engine.connect.return_value.__aexit__.return_value = None
|
|
624
|
+
|
|
625
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
626
|
+
vs = AsyncPGVectorStore(
|
|
627
|
+
create_key,
|
|
628
|
+
engine=mock_engine,
|
|
629
|
+
embedding_service=TemplateEmbeddings(),
|
|
630
|
+
table_name="test_table",
|
|
631
|
+
)
|
|
632
|
+
await vs.asimilarity_search_with_score_by_vector(
|
|
633
|
+
embedding=[], query="search query"
|
|
634
|
+
)
|
|
635
|
+
call_args = mock_conn.execute.call_args
|
|
636
|
+
sql_text = str(call_args[0][0])
|
|
637
|
+
params = call_args[0][1]
|
|
638
|
+
assert "embedding('model_id', :query_text)::vector" in sql_text
|
|
639
|
+
assert params["query_text"] == "search query"
|
|
640
|
+
|
|
641
|
+
async def test_asimilarity_search_with_legacy_inline(self) -> None:
|
|
642
|
+
class LegacyEmbeddings(Embeddings):
|
|
643
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
644
|
+
return []
|
|
645
|
+
|
|
646
|
+
def embed_query(self, text: str) -> list[float]:
|
|
647
|
+
return []
|
|
648
|
+
|
|
649
|
+
def embed_query_inline(self, query: str) -> str:
|
|
650
|
+
return f"embedding('model_id', '{query}')::vector"
|
|
651
|
+
|
|
652
|
+
mock_conn = AsyncMock()
|
|
653
|
+
mock_result = MagicMock()
|
|
654
|
+
mock_result.mappings.return_value.fetchall.return_value = []
|
|
655
|
+
mock_conn.execute.return_value = mock_result
|
|
656
|
+
|
|
657
|
+
mock_engine = MagicMock()
|
|
658
|
+
mock_engine.connect.return_value.__aenter__.return_value = mock_conn
|
|
659
|
+
mock_engine.connect.return_value.__aexit__.return_value = None
|
|
660
|
+
|
|
661
|
+
create_key = getattr(AsyncPGVectorStore, "_AsyncPGVectorStore__create_key")
|
|
662
|
+
vs = AsyncPGVectorStore(
|
|
663
|
+
create_key,
|
|
664
|
+
engine=mock_engine,
|
|
665
|
+
embedding_service=LegacyEmbeddings(),
|
|
666
|
+
table_name="test_table",
|
|
667
|
+
)
|
|
668
|
+
await vs.asimilarity_search_with_score_by_vector(
|
|
669
|
+
embedding=[], query="legacy search"
|
|
670
|
+
)
|
|
671
|
+
call_args = mock_conn.execute.call_args
|
|
672
|
+
sql_text = str(call_args[0][0])
|
|
673
|
+
params = call_args[0][1]
|
|
674
|
+
assert "embedding('model_id', 'legacy search')::vector" in sql_text
|
|
675
|
+
assert (
|
|
676
|
+
params["query_embedding"]
|
|
677
|
+
== "embedding('model_id', 'legacy search')::vector"
|
|
678
|
+
)
|