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.
Files changed (62) hide show
  1. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/_lint.yml +3 -0
  2. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/_release.yml +3 -0
  3. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/_test.yml +3 -0
  4. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/workflows/ci.yml +3 -0
  5. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/PKG-INFO +7 -4
  6. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/README.md +3 -0
  7. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/async_vectorstore.py +29 -6
  8. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/pyproject.toml +12 -4
  9. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_vectorstore.py +4 -5
  10. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore.py +137 -1
  11. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/uv.lock +647 -376
  12. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.github/actions/uv_setup/action.yml +0 -0
  13. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/.gitignore +0 -0
  14. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/CONTRIBUTING.md +0 -0
  15. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/DEVELOPMENT.md +0 -0
  16. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/LICENSE +0 -0
  17. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/Makefile +0 -0
  18. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/docker-compose.yml +0 -0
  19. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/docs/v2_design_overview.md +0 -0
  20. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/migrate_pgvector_to_pgvectorstore.ipynb +0 -0
  21. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/migrate_pgvector_to_pgvectorstore.md +0 -0
  22. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/pg_vectorstore.ipynb +0 -0
  23. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/pg_vectorstore_how_to.ipynb +0 -0
  24. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/examples/vectorstore.ipynb +0 -0
  25. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/__init__.py +0 -0
  26. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/_utils.py +0 -0
  27. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/chat_message_histories.py +0 -0
  28. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/py.typed +0 -0
  29. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/translator.py +0 -0
  30. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/utils/pgvector_migrator.py +0 -0
  31. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/__init__.py +0 -0
  32. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/engine.py +0 -0
  33. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/hybrid_search_config.py +0 -0
  34. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/indexes.py +0 -0
  35. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/v2/vectorstores.py +0 -0
  36. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/langchain_postgres/vectorstores.py +0 -0
  37. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/security.md +0 -0
  38. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/__init__.py +0 -0
  39. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/__init__.py +0 -0
  40. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fake_embeddings.py +0 -0
  41. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/__init__.py +0 -0
  42. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/filtering_test_cases.py +0 -0
  43. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/fixtures/metadata_filtering_data.py +0 -0
  44. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/query_constructors/__init__.py +0 -0
  45. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/query_constructors/test_pgvector.py +0 -0
  46. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/test_imports.py +0 -0
  47. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/__init__.py +0 -0
  48. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_chat_histories.py +0 -0
  49. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v1/test_vectorstore_standard_tests.py +0 -0
  50. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/__init__.py +0 -0
  51. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py +0 -0
  52. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_index.py +0 -0
  53. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_async_pg_vectorstore_search.py +0 -0
  54. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_engine.py +0 -0
  55. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_hybrid_search_config.py +0 -0
  56. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_indexes.py +0 -0
  57. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore.py +0 -0
  58. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py +0 -0
  59. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_index.py +0 -0
  60. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_search.py +0 -0
  61. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py +0 -0
  62. {langchain_postgres-0.0.17 → langchain_postgres-0.0.18}/tests/utils.py +0 -0
@@ -12,6 +12,9 @@ on:
12
12
  type: string
13
13
  description: "Python version to use"
14
14
 
15
+ permissions:
16
+ contents: read
17
+
15
18
  env:
16
19
  WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }}
17
20
 
@@ -22,6 +22,9 @@ on:
22
22
  default: false
23
23
  description: "Release from a non-main branch (danger!)"
24
24
 
25
+ permissions:
26
+ contents: read
27
+
25
28
  env:
26
29
  PYTHON_VERSION: "3.11"
27
30
  UV_FROZEN: "true"
@@ -8,6 +8,9 @@ on:
8
8
  type: string
9
9
  description: "From which folder this pipeline executes"
10
10
 
11
+ permissions:
12
+ contents: read
13
+
11
14
  env:
12
15
  UV_FROZEN: "true"
13
16
  UV_NO_SYNC: "true"
@@ -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.4
1
+ Metadata-Version: 2.5
2
2
  Name: langchain-postgres
3
- Version: 0.0.17
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.9
7
+ Requires-Python: >=3.10
8
8
  Requires-Dist: asyncpg>=0.30.0
9
- Requires-Dist: langchain-core<2.0,>=0.2.13
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
@@ -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 can_inline_embed:
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
- if not embedding and callable(inline_embed_func) and "query" in kwargs:
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.17"
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.9"
8
+ requires-python = ">=3.10"
9
9
  license = "MIT"
10
10
  dependencies = [
11
11
  "asyncpg>=0.30.0",
12
- "langchain-core>=0.2.13,<2.0",
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==0.3.7",
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"
@@ -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.get_relevant_documents("summer")
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.aget_relevant_documents("summer")
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.get_relevant_documents("foo")
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.aget_relevant_documents("foo")
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
+ )