langgraph-oracledb 1.0.0__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_oracledb-1.0.0/PKG-INFO +154 -0
- langgraph_oracledb-1.0.0/README.md +134 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/__init__.py +6 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/_ainternal.py +24 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/_internal.py +21 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/aio.py +593 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/base.py +636 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/py.typed +0 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/checkpoint/oracle/sync.py +523 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/store/oracle/__init__.py +15 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/store/oracle/aio.py +740 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/store/oracle/base.py +1832 -0
- langgraph_oracledb-1.0.0/langgraph_oracledb/store/oracle/py.typed +0 -0
- langgraph_oracledb-1.0.0/pyproject.toml +126 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langgraph-oracledb
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An integration package connecting Oracle Database and LangGraph
|
|
5
|
+
License-Expression: UPL-1.0
|
|
6
|
+
Requires-Python: >=3.10,<4.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: langgraph-checkpoint (>=2.1.2,<5.0.0)
|
|
14
|
+
Requires-Dist: oracledb (>=2.2.0)
|
|
15
|
+
Requires-Dist: orjson (>=3.11.5)
|
|
16
|
+
Project-URL: Repository, https://github.com/oracle/langchain-oracle
|
|
17
|
+
Project-URL: Source Code, https://github.com/oracle/langchain-oracle/tree/main/libs/langgraph-oracledb
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# LangGraph Oracle Persistence (Checkpoint + Store)
|
|
21
|
+
|
|
22
|
+
Oracle-backed implementations for:
|
|
23
|
+
- Checkpoints: OracleSaver (sync) and AsyncOracleSaver (async)
|
|
24
|
+
- Key/Value Store with optional vector search: OracleStore (sync) and AsyncOracleStore (async)
|
|
25
|
+
|
|
26
|
+
Supports:
|
|
27
|
+
- Oracle Database for AI Vector Search
|
|
28
|
+
- Python 3.10+ and `oracledb` driver
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
### Checkpoints (Async)
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import os
|
|
36
|
+
import asyncio
|
|
37
|
+
from dotenv import load_dotenv
|
|
38
|
+
from langgraph_oracledb.checkpoint.oracle import AsyncOracleSaver
|
|
39
|
+
|
|
40
|
+
load_dotenv()
|
|
41
|
+
|
|
42
|
+
async def main():
|
|
43
|
+
conn_string = f"{os.environ['ORACLE_USERNAME']}/{os.environ['ORACLE_PASSWORD']}@{os.environ['ORACLE_DSN']}"
|
|
44
|
+
async with AsyncOracleSaver.from_conn_string(conn_string) as checkpointer:
|
|
45
|
+
await checkpointer.setup() # Create tables & apply migrations once
|
|
46
|
+
|
|
47
|
+
# Then pass to your graph compile (example)
|
|
48
|
+
# graph = app.compile(checkpointer=checkpointer)
|
|
49
|
+
# await graph.ainvoke(...)
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Sync variant:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from langgraph_oracledb.checkpoint.oracle import OracleSaver
|
|
59
|
+
|
|
60
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
61
|
+
with OracleSaver.from_conn_string(conn_string) as checkpointer:
|
|
62
|
+
checkpointer.setup()
|
|
63
|
+
# graph = app.compile(checkpointer=checkpointer)
|
|
64
|
+
# graph.invoke(...)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Store (Async, basic key/value)
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import asyncio
|
|
71
|
+
from langgraph_oracledb.store.oracle import AsyncOracleStore
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
75
|
+
async with AsyncOracleStore.from_conn_string(conn_string) as store:
|
|
76
|
+
await store.setup() # Create tables & apply migrations once
|
|
77
|
+
|
|
78
|
+
ns = ("readme", "example")
|
|
79
|
+
await store.aput(ns, "doc1", {"text": "hello"})
|
|
80
|
+
item = await store.aget(ns, "doc1")
|
|
81
|
+
print(item.value) # {"text": "hello"}
|
|
82
|
+
|
|
83
|
+
# Non-vector search (lists items by namespace)
|
|
84
|
+
results = await store.asearch(ns, limit=10)
|
|
85
|
+
print(len(results) >= 1)
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Sync variant:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from langgraph_oracledb.store.oracle import OracleStore
|
|
95
|
+
|
|
96
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
97
|
+
with OracleStore.from_conn_string(conn_string) as store:
|
|
98
|
+
store.setup()
|
|
99
|
+
ns = ("readme", "example")
|
|
100
|
+
store.put(ns, "doc1", {"text": "hello"})
|
|
101
|
+
item = store.get(ns, "doc1")
|
|
102
|
+
print(item.value)
|
|
103
|
+
results = store.search(ns, limit=10)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Vector Search (optional)
|
|
107
|
+
|
|
108
|
+
Vector search is enabled by passing an `index` configuration with:
|
|
109
|
+
- `dims`: embedding dimension
|
|
110
|
+
- `embed`: a LangChain Embeddings implementation (e.g., OpenAI, HF, or your own)
|
|
111
|
+
- optional `fields`: which JSON fields to embed (default: whole document)
|
|
112
|
+
- optional `index_type`: HNSW/IVF and parameters
|
|
113
|
+
|
|
114
|
+
Example (async):
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# Pseudo-embeddings for illustration; use any LangChain Embeddings implementation
|
|
118
|
+
from langchain_core.embeddings import Embeddings
|
|
119
|
+
from langgraph_oracledb.store.oracle import AsyncOracleStore
|
|
120
|
+
|
|
121
|
+
class FakeEmbeddings(Embeddings):
|
|
122
|
+
def embed_documents(self, texts): return [[0.0]*8 for _ in texts]
|
|
123
|
+
def embed_query(self, text): return [0.0]*8
|
|
124
|
+
|
|
125
|
+
async with AsyncOracleStore.from_conn_string(
|
|
126
|
+
"user/password@localhost:1521/FREEPDB1",
|
|
127
|
+
index={
|
|
128
|
+
"dims": 8,
|
|
129
|
+
"embed": FakeEmbeddings(),
|
|
130
|
+
"fields": ["text"], # embed only the 'text' field
|
|
131
|
+
"index_type": {"type": "hnsw", "neighbors": 16, "efconstruction": 200, "distance_metric": "COSINE"},
|
|
132
|
+
},
|
|
133
|
+
) as store:
|
|
134
|
+
await store.setup()
|
|
135
|
+
ns = ("docs",)
|
|
136
|
+
await store.aput(ns, "a", {"text": "alpha"})
|
|
137
|
+
await store.aput(ns, "b", {"text": "beta"})
|
|
138
|
+
results = await store.asearch(ns, query="alphabet", limit=2)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Notes:
|
|
142
|
+
- Call `setup()` once per database/schema to create/upgrade tables.
|
|
143
|
+
- Vector search requires Oracle 23c/23ai+ with AI Vector Search enabled.
|
|
144
|
+
|
|
145
|
+
## Testing the Examples
|
|
146
|
+
|
|
147
|
+
The repository's tests will skip automatically if an Oracle instance is not reachable.
|
|
148
|
+
- Place your Oracle credentials in `.env` (see Configuration)
|
|
149
|
+
- Run: `pytest -q`
|
|
150
|
+
|
|
151
|
+
This repository includes tests that validate the examples above:
|
|
152
|
+
- Async checkpoint setup works
|
|
153
|
+
- Async store put/get/search works
|
|
154
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# LangGraph Oracle Persistence (Checkpoint + Store)
|
|
2
|
+
|
|
3
|
+
Oracle-backed implementations for:
|
|
4
|
+
- Checkpoints: OracleSaver (sync) and AsyncOracleSaver (async)
|
|
5
|
+
- Key/Value Store with optional vector search: OracleStore (sync) and AsyncOracleStore (async)
|
|
6
|
+
|
|
7
|
+
Supports:
|
|
8
|
+
- Oracle Database for AI Vector Search
|
|
9
|
+
- Python 3.10+ and `oracledb` driver
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
### Checkpoints (Async)
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import os
|
|
17
|
+
import asyncio
|
|
18
|
+
from dotenv import load_dotenv
|
|
19
|
+
from langgraph_oracledb.checkpoint.oracle import AsyncOracleSaver
|
|
20
|
+
|
|
21
|
+
load_dotenv()
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
conn_string = f"{os.environ['ORACLE_USERNAME']}/{os.environ['ORACLE_PASSWORD']}@{os.environ['ORACLE_DSN']}"
|
|
25
|
+
async with AsyncOracleSaver.from_conn_string(conn_string) as checkpointer:
|
|
26
|
+
await checkpointer.setup() # Create tables & apply migrations once
|
|
27
|
+
|
|
28
|
+
# Then pass to your graph compile (example)
|
|
29
|
+
# graph = app.compile(checkpointer=checkpointer)
|
|
30
|
+
# await graph.ainvoke(...)
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
asyncio.run(main())
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Sync variant:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from langgraph_oracledb.checkpoint.oracle import OracleSaver
|
|
40
|
+
|
|
41
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
42
|
+
with OracleSaver.from_conn_string(conn_string) as checkpointer:
|
|
43
|
+
checkpointer.setup()
|
|
44
|
+
# graph = app.compile(checkpointer=checkpointer)
|
|
45
|
+
# graph.invoke(...)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Store (Async, basic key/value)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from langgraph_oracledb.store.oracle import AsyncOracleStore
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
56
|
+
async with AsyncOracleStore.from_conn_string(conn_string) as store:
|
|
57
|
+
await store.setup() # Create tables & apply migrations once
|
|
58
|
+
|
|
59
|
+
ns = ("readme", "example")
|
|
60
|
+
await store.aput(ns, "doc1", {"text": "hello"})
|
|
61
|
+
item = await store.aget(ns, "doc1")
|
|
62
|
+
print(item.value) # {"text": "hello"}
|
|
63
|
+
|
|
64
|
+
# Non-vector search (lists items by namespace)
|
|
65
|
+
results = await store.asearch(ns, limit=10)
|
|
66
|
+
print(len(results) >= 1)
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Sync variant:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from langgraph_oracledb.store.oracle import OracleStore
|
|
76
|
+
|
|
77
|
+
conn_string = "user/password@localhost:1521/FREEPDB1"
|
|
78
|
+
with OracleStore.from_conn_string(conn_string) as store:
|
|
79
|
+
store.setup()
|
|
80
|
+
ns = ("readme", "example")
|
|
81
|
+
store.put(ns, "doc1", {"text": "hello"})
|
|
82
|
+
item = store.get(ns, "doc1")
|
|
83
|
+
print(item.value)
|
|
84
|
+
results = store.search(ns, limit=10)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Vector Search (optional)
|
|
88
|
+
|
|
89
|
+
Vector search is enabled by passing an `index` configuration with:
|
|
90
|
+
- `dims`: embedding dimension
|
|
91
|
+
- `embed`: a LangChain Embeddings implementation (e.g., OpenAI, HF, or your own)
|
|
92
|
+
- optional `fields`: which JSON fields to embed (default: whole document)
|
|
93
|
+
- optional `index_type`: HNSW/IVF and parameters
|
|
94
|
+
|
|
95
|
+
Example (async):
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Pseudo-embeddings for illustration; use any LangChain Embeddings implementation
|
|
99
|
+
from langchain_core.embeddings import Embeddings
|
|
100
|
+
from langgraph_oracledb.store.oracle import AsyncOracleStore
|
|
101
|
+
|
|
102
|
+
class FakeEmbeddings(Embeddings):
|
|
103
|
+
def embed_documents(self, texts): return [[0.0]*8 for _ in texts]
|
|
104
|
+
def embed_query(self, text): return [0.0]*8
|
|
105
|
+
|
|
106
|
+
async with AsyncOracleStore.from_conn_string(
|
|
107
|
+
"user/password@localhost:1521/FREEPDB1",
|
|
108
|
+
index={
|
|
109
|
+
"dims": 8,
|
|
110
|
+
"embed": FakeEmbeddings(),
|
|
111
|
+
"fields": ["text"], # embed only the 'text' field
|
|
112
|
+
"index_type": {"type": "hnsw", "neighbors": 16, "efconstruction": 200, "distance_metric": "COSINE"},
|
|
113
|
+
},
|
|
114
|
+
) as store:
|
|
115
|
+
await store.setup()
|
|
116
|
+
ns = ("docs",)
|
|
117
|
+
await store.aput(ns, "a", {"text": "alpha"})
|
|
118
|
+
await store.aput(ns, "b", {"text": "beta"})
|
|
119
|
+
results = await store.asearch(ns, query="alphabet", limit=2)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Notes:
|
|
123
|
+
- Call `setup()` once per database/schema to create/upgrade tables.
|
|
124
|
+
- Vector search requires Oracle 23c/23ai+ with AI Vector Search enabled.
|
|
125
|
+
|
|
126
|
+
## Testing the Examples
|
|
127
|
+
|
|
128
|
+
The repository's tests will skip automatically if an Oracle instance is not reachable.
|
|
129
|
+
- Place your Oracle credentials in `.env` (see Configuration)
|
|
130
|
+
- Run: `pytest -q`
|
|
131
|
+
|
|
132
|
+
This repository includes tests that validate the examples above:
|
|
133
|
+
- Async checkpoint setup works
|
|
134
|
+
- Async store put/get/search works
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
import oracledb
|
|
8
|
+
from oracledb import AsyncConnection, AsyncConnectionPool
|
|
9
|
+
|
|
10
|
+
# Type alias for connection types
|
|
11
|
+
Conn = Union[oracledb.AsyncConnection, AsyncConnectionPool]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@asynccontextmanager
|
|
15
|
+
async def get_connection(conn: Conn) -> AsyncIterator[oracledb.AsyncConnection]:
|
|
16
|
+
"""Get a connection from either a single connection or connection pool."""
|
|
17
|
+
|
|
18
|
+
if isinstance(conn, AsyncConnection):
|
|
19
|
+
yield conn
|
|
20
|
+
elif isinstance(conn, AsyncConnectionPool):
|
|
21
|
+
async with conn.acquire() as connection:
|
|
22
|
+
yield connection
|
|
23
|
+
else:
|
|
24
|
+
raise TypeError(f"Invalid connection type: {type(conn)}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Shared utility functions for the Oracle checkpoint & storage classes."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from oracledb import Connection, ConnectionPool
|
|
8
|
+
|
|
9
|
+
Conn = Union[Connection, ConnectionPool]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextmanager
|
|
13
|
+
def get_connection(conn: Conn) -> Iterator[Connection]:
|
|
14
|
+
"""Get a connection from either a single connection or connection pool."""
|
|
15
|
+
if isinstance(conn, Connection):
|
|
16
|
+
yield conn
|
|
17
|
+
elif isinstance(conn, ConnectionPool):
|
|
18
|
+
with conn.acquire() as connection:
|
|
19
|
+
yield connection
|
|
20
|
+
else:
|
|
21
|
+
raise TypeError(f"Invalid connection type: {type(conn)}")
|