langgraph-checkpoint-postgres 2.0.7__tar.gz → 2.0.8__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.
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/PKG-INFO +1 -1
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/aio.py +1 -1
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/base.py +11 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/aio.py +81 -16
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/base.py +66 -18
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/pyproject.toml +1 -1
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/README.md +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/_internal.py +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/py.typed +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/__init__.py +0 -0
- {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/py.typed +0 -0
|
@@ -54,7 +54,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
|
|
54
54
|
pipeline: bool = False,
|
|
55
55
|
serde: Optional[SerializerProtocol] = None,
|
|
56
56
|
) -> AsyncIterator["AsyncPostgresSaver"]:
|
|
57
|
-
"""Create a new
|
|
57
|
+
"""Create a new AsyncPostgresSaver instance from a connection string.
|
|
58
58
|
|
|
59
59
|
Args:
|
|
60
60
|
conn_string (str): The Postgres connection info string.
|
|
@@ -57,6 +57,17 @@ MIGRATIONS = [
|
|
|
57
57
|
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
58
58
|
);""",
|
|
59
59
|
"ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
|
|
60
|
+
"""
|
|
61
|
+
""",
|
|
62
|
+
"""
|
|
63
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
|
|
64
|
+
""",
|
|
65
|
+
"""
|
|
66
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_blobs_thread_id_idx ON checkpoint_blobs(thread_id);
|
|
67
|
+
""",
|
|
68
|
+
"""
|
|
69
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_writes_thread_id_idx ON checkpoint_writes(thread_id);
|
|
70
|
+
""",
|
|
60
71
|
]
|
|
61
72
|
|
|
62
73
|
SELECT_SQL = f"""
|
|
@@ -6,7 +6,6 @@ from typing import Any, Callable, Optional, Union, cast
|
|
|
6
6
|
|
|
7
7
|
import orjson
|
|
8
8
|
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
|
9
|
-
from psycopg.errors import UndefinedTable
|
|
10
9
|
from psycopg.rows import DictRow, dict_row
|
|
11
10
|
from psycopg_pool import AsyncConnectionPool
|
|
12
11
|
|
|
@@ -37,6 +36,75 @@ logger = logging.getLogger(__name__)
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Conn]):
|
|
39
|
+
"""Asynchronous Postgres-backed store with optional vector search using pgvector.
|
|
40
|
+
|
|
41
|
+
!!! example "Examples"
|
|
42
|
+
Basic setup and key-value storage:
|
|
43
|
+
```python
|
|
44
|
+
from langgraph.store.postgres import AsyncPostgresStore
|
|
45
|
+
|
|
46
|
+
async with AsyncPostgresStore.from_conn_string(
|
|
47
|
+
"postgresql://user:pass@localhost:5432/dbname"
|
|
48
|
+
) as store:
|
|
49
|
+
await store.setup()
|
|
50
|
+
|
|
51
|
+
# Store and retrieve data
|
|
52
|
+
await store.aput(("users", "123"), "prefs", {"theme": "dark"})
|
|
53
|
+
item = await store.aget(("users", "123"), "prefs")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Vector search using LangChain embeddings:
|
|
57
|
+
```python
|
|
58
|
+
from langchain.embeddings import init_embeddings
|
|
59
|
+
from langgraph.store.postgres import AsyncPostgresStore
|
|
60
|
+
|
|
61
|
+
async with AsyncPostgresStore.from_conn_string(
|
|
62
|
+
"postgresql://user:pass@localhost:5432/dbname",
|
|
63
|
+
index={
|
|
64
|
+
"dims": 1536,
|
|
65
|
+
"embed": init_embeddings("openai:text-embedding-3-small"),
|
|
66
|
+
"fields": ["text"] # specify which fields to embed. Default is the whole serialized value
|
|
67
|
+
}
|
|
68
|
+
) as store:
|
|
69
|
+
await store.setup() # Do this once to run migrations
|
|
70
|
+
|
|
71
|
+
# Store documents
|
|
72
|
+
await store.aput(("docs",), "doc1", {"text": "Python tutorial"})
|
|
73
|
+
await store.aput(("docs",), "doc2", {"text": "TypeScript guide"})
|
|
74
|
+
# Don't index the following
|
|
75
|
+
await store.aput(("docs",), "doc3", {"text": "Other guide"}, index=False)
|
|
76
|
+
|
|
77
|
+
# Search by similarity
|
|
78
|
+
results = await store.asearch(("docs",), query="python programming")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Using connection pooling for better performance:
|
|
82
|
+
```python
|
|
83
|
+
from langgraph.store.postgres import AsyncPostgresStore, PoolConfig
|
|
84
|
+
|
|
85
|
+
async with AsyncPostgresStore.from_conn_string(
|
|
86
|
+
"postgresql://user:pass@localhost:5432/dbname",
|
|
87
|
+
pool_config=PoolConfig(
|
|
88
|
+
min_size=5,
|
|
89
|
+
max_size=20
|
|
90
|
+
)
|
|
91
|
+
) as store:
|
|
92
|
+
await store.setup()
|
|
93
|
+
# Use store with connection pooling...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Warning:
|
|
97
|
+
Make sure to:
|
|
98
|
+
1. Call `setup()` before first use to create necessary tables and indexes
|
|
99
|
+
2. Have the pgvector extension available to use vector search
|
|
100
|
+
3. Use Python 3.10+ for async functionality
|
|
101
|
+
|
|
102
|
+
Note:
|
|
103
|
+
Semantic search is disabled by default. You can enable it by providing an `index` configuration
|
|
104
|
+
when creating the store. Without this configuration, all `index` arguments passed to
|
|
105
|
+
`put` or `aput`will have no effect.
|
|
106
|
+
"""
|
|
107
|
+
|
|
40
108
|
__slots__ = (
|
|
41
109
|
"_deserializer",
|
|
42
110
|
"pipe",
|
|
@@ -150,22 +218,19 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
|
|
150
218
|
"""
|
|
151
219
|
|
|
152
220
|
async def _get_version(cur: AsyncCursor[DictRow], table: str) -> int:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
version = -1
|
|
158
|
-
else:
|
|
159
|
-
version = row["v"]
|
|
160
|
-
except UndefinedTable:
|
|
161
|
-
version = -1
|
|
162
|
-
await cur.execute(
|
|
163
|
-
f"""
|
|
164
|
-
CREATE TABLE IF NOT EXISTS {table} (
|
|
165
|
-
v INTEGER PRIMARY KEY
|
|
166
|
-
)
|
|
167
|
-
"""
|
|
221
|
+
await cur.execute(
|
|
222
|
+
f"""
|
|
223
|
+
CREATE TABLE IF NOT EXISTS {table} (
|
|
224
|
+
v INTEGER PRIMARY KEY
|
|
168
225
|
)
|
|
226
|
+
"""
|
|
227
|
+
)
|
|
228
|
+
await cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
|
|
229
|
+
row = cast(dict, await cur.fetchone())
|
|
230
|
+
if row is None:
|
|
231
|
+
version = -1
|
|
232
|
+
else:
|
|
233
|
+
version = row["v"]
|
|
169
234
|
return version
|
|
170
235
|
|
|
171
236
|
async with self._cursor() as cur:
|
|
@@ -21,7 +21,6 @@ from typing import (
|
|
|
21
21
|
|
|
22
22
|
import orjson
|
|
23
23
|
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
|
24
|
-
from psycopg.errors import UndefinedTable
|
|
25
24
|
from psycopg.rows import DictRow, dict_row
|
|
26
25
|
from psycopg.types.json import Jsonb
|
|
27
26
|
from psycopg_pool import ConnectionPool
|
|
@@ -73,7 +72,7 @@ CREATE TABLE IF NOT EXISTS store (
|
|
|
73
72
|
""",
|
|
74
73
|
"""
|
|
75
74
|
-- For faster lookups by prefix
|
|
76
|
-
CREATE INDEX IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops);
|
|
75
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops);
|
|
77
76
|
""",
|
|
78
77
|
]
|
|
79
78
|
|
|
@@ -107,7 +106,7 @@ CREATE TABLE IF NOT EXISTS store_vectors (
|
|
|
107
106
|
),
|
|
108
107
|
Migration(
|
|
109
108
|
"""
|
|
110
|
-
CREATE INDEX IF NOT EXISTS store_vectors_embedding_idx ON store_vectors
|
|
109
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS store_vectors_embedding_idx ON store_vectors
|
|
111
110
|
USING %(index_type)s (embedding %(ops)s)%(index_params)s;
|
|
112
111
|
""",
|
|
113
112
|
condition=lambda store: bool(
|
|
@@ -534,6 +533,58 @@ class BasePostgresStore(Generic[C]):
|
|
|
534
533
|
|
|
535
534
|
|
|
536
535
|
class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
|
536
|
+
"""Postgres-backed store with optional vector search using pgvector.
|
|
537
|
+
|
|
538
|
+
!!! example "Examples"
|
|
539
|
+
Basic setup and key-value storage:
|
|
540
|
+
```python
|
|
541
|
+
from langgraph.store.postgres import PostgresStore
|
|
542
|
+
|
|
543
|
+
store = PostgresStore(
|
|
544
|
+
connection_string="postgresql://user:pass@localhost:5432/dbname"
|
|
545
|
+
)
|
|
546
|
+
store.setup()
|
|
547
|
+
|
|
548
|
+
# Store and retrieve data
|
|
549
|
+
store.put(("users", "123"), "prefs", {"theme": "dark"})
|
|
550
|
+
item = store.get(("users", "123"), "prefs")
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Vector search using LangChain embeddings:
|
|
554
|
+
```python
|
|
555
|
+
from langchain.embeddings import init_embeddings
|
|
556
|
+
from langgraph.store.postgres import PostgresStore
|
|
557
|
+
|
|
558
|
+
store = PostgresStore(
|
|
559
|
+
connection_string="postgresql://user:pass@localhost:5432/dbname",
|
|
560
|
+
index={
|
|
561
|
+
"dims": 1536,
|
|
562
|
+
"embed": init_embeddings("openai:text-embedding-3-small"),
|
|
563
|
+
"fields": ["text"] # specify which fields to embed. Default is the whole serialized value
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
store.setup() # Do this once to run migrations
|
|
567
|
+
|
|
568
|
+
# Store documents
|
|
569
|
+
store.put(("docs",), "doc1", {"text": "Python tutorial"})
|
|
570
|
+
store.put(("docs",), "doc2", {"text": "TypeScript guide"})
|
|
571
|
+
store.put(("docs",), "doc2", {"text": "Other guide"}, index=False) # don't index
|
|
572
|
+
|
|
573
|
+
# Search by similarity
|
|
574
|
+
results = store.search(("docs",), query="python programming")
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Note:
|
|
578
|
+
Semantic search is disabled by default. You can enable it by providing an `index` configuration
|
|
579
|
+
when creating the store. Without this configuration, all `index` arguments passed to
|
|
580
|
+
`put` or `aput`will have no effect.
|
|
581
|
+
|
|
582
|
+
Warning:
|
|
583
|
+
Make sure to call `setup()` before first use to create necessary tables and indexes.
|
|
584
|
+
The pgvector extension must be available to use vector search.
|
|
585
|
+
|
|
586
|
+
"""
|
|
587
|
+
|
|
537
588
|
__slots__ = (
|
|
538
589
|
"_deserializer",
|
|
539
590
|
"pipe",
|
|
@@ -795,22 +846,19 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
|
|
795
846
|
"""
|
|
796
847
|
|
|
797
848
|
def _get_version(cur: Cursor[dict[str, Any]], table: str) -> int:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
version = -1
|
|
803
|
-
else:
|
|
804
|
-
version = row["v"]
|
|
805
|
-
except UndefinedTable:
|
|
806
|
-
version = -1
|
|
807
|
-
cur.execute(
|
|
808
|
-
f"""
|
|
809
|
-
CREATE TABLE IF NOT EXISTS {table} (
|
|
810
|
-
v INTEGER PRIMARY KEY
|
|
811
|
-
)
|
|
812
|
-
"""
|
|
849
|
+
cur.execute(
|
|
850
|
+
f"""
|
|
851
|
+
CREATE TABLE IF NOT EXISTS {table} (
|
|
852
|
+
v INTEGER PRIMARY KEY
|
|
813
853
|
)
|
|
854
|
+
"""
|
|
855
|
+
)
|
|
856
|
+
cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
|
|
857
|
+
row = cast(dict, cur.fetchone())
|
|
858
|
+
if row is None:
|
|
859
|
+
version = -1
|
|
860
|
+
else:
|
|
861
|
+
version = row["v"]
|
|
814
862
|
return version
|
|
815
863
|
|
|
816
864
|
with self._cursor() as cur:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|