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.
Files changed (62) hide show
  1. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_lint.yml +3 -0
  2. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_release.yml +3 -0
  3. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/_test.yml +3 -0
  4. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/workflows/ci.yml +3 -0
  5. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/PKG-INFO +18 -17
  6. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/README.md +14 -13
  7. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/async_vectorstore.py +36 -15
  8. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/pyproject.toml +12 -4
  9. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_vectorstore.py +4 -5
  10. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore.py +91 -1
  11. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/uv.lock +650 -389
  12. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.github/actions/uv_setup/action.yml +0 -0
  13. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/.gitignore +0 -0
  14. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/CONTRIBUTING.md +0 -0
  15. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/DEVELOPMENT.md +0 -0
  16. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/LICENSE +0 -0
  17. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/Makefile +0 -0
  18. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/docker-compose.yml +0 -0
  19. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/docs/v2_design_overview.md +0 -0
  20. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/migrate_pgvector_to_pgvectorstore.ipynb +0 -0
  21. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/migrate_pgvector_to_pgvectorstore.md +0 -0
  22. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/pg_vectorstore.ipynb +0 -0
  23. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/pg_vectorstore_how_to.ipynb +0 -0
  24. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/examples/vectorstore.ipynb +0 -0
  25. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/__init__.py +0 -0
  26. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/_utils.py +0 -0
  27. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/chat_message_histories.py +0 -0
  28. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/py.typed +0 -0
  29. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/translator.py +0 -0
  30. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/utils/pgvector_migrator.py +0 -0
  31. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/__init__.py +0 -0
  32. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/engine.py +0 -0
  33. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/hybrid_search_config.py +0 -0
  34. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/indexes.py +0 -0
  35. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/v2/vectorstores.py +0 -0
  36. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/langchain_postgres/vectorstores.py +0 -0
  37. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/security.md +0 -0
  38. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/__init__.py +0 -0
  39. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/__init__.py +0 -0
  40. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fake_embeddings.py +0 -0
  41. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/__init__.py +0 -0
  42. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/filtering_test_cases.py +0 -0
  43. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/fixtures/metadata_filtering_data.py +0 -0
  44. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/query_constructors/__init__.py +0 -0
  45. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/query_constructors/test_pgvector.py +0 -0
  46. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/test_imports.py +0 -0
  47. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/__init__.py +0 -0
  48. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_chat_histories.py +0 -0
  49. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v1/test_vectorstore_standard_tests.py +0 -0
  50. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/__init__.py +0 -0
  51. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py +0 -0
  52. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_index.py +0 -0
  53. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_async_pg_vectorstore_search.py +0 -0
  54. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_engine.py +0 -0
  55. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_hybrid_search_config.py +0 -0
  56. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_indexes.py +0 -0
  57. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore.py +0 -0
  58. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py +0 -0
  59. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_index.py +0 -0
  60. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_search.py +0 -0
  61. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py +0 -0
  62. {langchain_postgres-0.0.17 → langchain_postgres-0.0.19}/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.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.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
@@ -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 = ... # Fill in with your connection 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
- SystemMessage(content="Meow"),
157
- AIMessage(content="woof"),
158
- HumanMessage(content="bark"),
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 = ... # Fill in with your connection 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
- SystemMessage(content="Meow"),
141
- AIMessage(content="woof"),
142
- HumanMessage(content="bark"),
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
  ```
@@ -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
- inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
295
- can_inline_embed = callable(inline_embed_func)
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 can_inline_embed:
317
- values_stmt = f"VALUES (:langchain_id, :content, {self.embedding_service.embed_query_inline(content)}" # type: ignore
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
- inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
387
- if callable(inline_embed_func):
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
- inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
668
- if not embedding and callable(inline_embed_func) and "query" in kwargs:
669
- query_embedding = self.embedding_service.embed_query_inline(kwargs["query"]) # type: ignore
670
- embedding_data_string = f"{query_embedding}"
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
- inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
789
+ inline_template_func = getattr(
790
+ self.embedding_service, "embed_query_inline_template", None
791
+ )
773
792
  embedding = (
774
793
  []
775
- if callable(inline_embed_func)
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
- inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None)
830
+ inline_template_func = getattr(
831
+ self.embedding_service, "embed_query_inline_template", None
832
+ )
812
833
  embedding = (
813
834
  []
814
- if callable(inline_embed_func)
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.17"
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.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,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
+ )