Compare commits

...
4 changed files with 104 additions and 8 deletions
@@ -115,6 +115,20 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
If you provide a TTL configuration, you must explicitly call `start_ttl_sweeper()` to begin If you provide a TTL configuration, you must explicitly call `start_ttl_sweeper()` to begin
the background task that removes expired items. Call `stop_ttl_sweeper()` to properly the background task that removes expired items. Call `stop_ttl_sweeper()` to properly
clean up resources when you're done with the store. clean up resources when you're done with the store.
Note:
The core data operations -- `put`/`aput`, `get`/`aget`, `search`/`asearch`,
`delete`/`adelete`, and `list_namespaces`/`alist_namespaces` -- are inherited
from `langgraph.store.base.BaseStore`, which is the canonical reference for
their arguments and behavior. (This is LangGraph's `BaseStore`, distinct from
`langchain_core.stores.BaseStore`.) Key semantics:
- `put`/`aput` `value` must be a `dict` of JSON-serializable data. `None` is
not a valid stored value: a top-level `None` is reserved as the delete
signal (prefer `delete`/`adelete`). To store a marker or "empty" entry,
use an empty dict (`{}`), not `None`.
- `list_namespaces`/`alist_namespaces` match `prefix`/`suffix` exactly and
case-sensitively; there is no case-insensitive matching option.
""" """
__slots__ = ( __slots__ = (
@@ -713,6 +713,20 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
the background thread that removes expired items. Call `stop_ttl_sweeper()` to properly the background thread that removes expired items. Call `stop_ttl_sweeper()` to properly
clean up resources when you're done with the store. clean up resources when you're done with the store.
Note:
The core data operations -- `put`/`aput`, `get`/`aget`, `search`/`asearch`,
`delete`/`adelete`, and `list_namespaces`/`alist_namespaces` -- are inherited
from `langgraph.store.base.BaseStore`, which is the canonical reference for
their arguments and behavior. (This is LangGraph's `BaseStore`, distinct from
`langchain_core.stores.BaseStore`.) Key semantics:
- `put`/`aput` `value` must be a `dict` of JSON-serializable data. `None` is
not a valid stored value: a top-level `None` is reserved as the delete
signal (prefer `delete`/`adelete`). To store a marker or "empty" entry,
use an empty dict (`{}`), not `None`.
- `list_namespaces`/`alist_namespaces` match `prefix`/`suffix` exactly and
case-sensitively; there is no case-insensitive matching option.
""" """
__slots__ = ( __slots__ = (
@@ -861,8 +861,11 @@ class BaseStore(ABC):
Example: `("documents", "user123")` Example: `("documents", "user123")`
key: Unique identifier within the namespace. Together with namespace forms key: Unique identifier within the namespace. Together with namespace forms
the complete path to the item. the complete path to the item.
value: Dictionary containing the item's data. Must contain string keys value: Dictionary containing the item's data. Must be a `dict` with
and JSON-serializable values. string keys and JSON-serializable values. `None` is not a valid
stored value: a top-level `None` is reserved as the delete signal
(prefer `delete()`). To store a marker or "empty" entry, use an
empty dict (`{}`), not `None`.
index: Controls how the item's fields are indexed for search: index: Controls how the item's fields are indexed for search:
- None (default): Use `fields` you configured when creating the store (if any) - None (default): Use `fields` you configured when creating the store (if any)
@@ -895,6 +898,13 @@ class BaseStore(ABC):
store.put(("docs",), "report", {"memory": "Will likes ai"}) store.put(("docs",), "report", {"memory": "Will likes ai"})
``` ```
Store a set/list-like marker entry. Use an empty dict, not `None`
(which would delete the item):
```python
store.put(("seen",), "user123", {})
```
Do not index item for semantic search. Still accessible through `get()` Do not index item for semantic search. Still accessible through `get()`
and `search()` operations but won't have a vector representation. and `search()` operations but won't have a vector representation.
@@ -950,8 +960,10 @@ class BaseStore(ABC):
find specific collections, or navigate the namespace hierarchy. find specific collections, or navigate the namespace hierarchy.
Args: Args:
prefix: Filter namespaces that start with this path. prefix: Filter namespaces that start with this path. Matched exactly and
suffix: Filter namespaces that end with this path. case-sensitively.
suffix: Filter namespaces that end with this path. Matched exactly and
case-sensitively.
max_depth: Return namespaces up to this depth in the hierarchy. max_depth: Return namespaces up to this depth in the hierarchy.
Namespaces deeper than this level will be truncated. Namespaces deeper than this level will be truncated.
limit: Maximum number of namespaces to return. limit: Maximum number of namespaces to return.
@@ -961,6 +973,11 @@ class BaseStore(ABC):
A list of namespace tuples that match the criteria. Each tuple represents a A list of namespace tuples that match the criteria. Each tuple represents a
full namespace path up to `max_depth`. full namespace path up to `max_depth`.
Note:
Namespace matching is case-sensitive. There is no case-insensitive
matching option. If you need case-insensitive lookups, normalize the
namespace casing when you write items (e.g. lowercase each label).
???+ example "Examples": ???+ example "Examples":
Setting `max_depth=3`. Given the namespaces: Setting `max_depth=3`. Given the namespaces:
@@ -1114,8 +1131,11 @@ class BaseStore(ABC):
Example: `("documents", "user123")` Example: `("documents", "user123")`
key: Unique identifier within the namespace. Together with namespace forms key: Unique identifier within the namespace. Together with namespace forms
the complete path to the item. the complete path to the item.
value: Dictionary containing the item's data. Must contain string keys value: Dictionary containing the item's data. Must be a `dict` with
and JSON-serializable values. string keys and JSON-serializable values. `None` is not a valid
stored value: a top-level `None` is reserved as the delete signal
(prefer `adelete()`). To store a marker or "empty" entry, use an
empty dict (`{}`), not `None`.
index: Controls how the item's fields are indexed for search: index: Controls how the item's fields are indexed for search:
- None (default): Use `fields` you configured when creating the store (if any) - None (default): Use `fields` you configured when creating the store (if any)
@@ -1211,8 +1231,10 @@ class BaseStore(ABC):
find specific collections, or navigate the namespace hierarchy. find specific collections, or navigate the namespace hierarchy.
Args: Args:
prefix: Filter namespaces that start with this path. prefix: Filter namespaces that start with this path. Matched exactly and
suffix: Filter namespaces that end with this path. case-sensitively.
suffix: Filter namespaces that end with this path. Matched exactly and
case-sensitively.
max_depth: Return namespaces up to this depth in the hierarchy. max_depth: Return namespaces up to this depth in the hierarchy.
Namespaces deeper than this level will be truncated to this depth. Namespaces deeper than this level will be truncated to this depth.
limit: Maximum number of namespaces to return. limit: Maximum number of namespaces to return.
@@ -1222,6 +1244,11 @@ class BaseStore(ABC):
A list of namespace tuples that match the criteria. Each tuple represents a A list of namespace tuples that match the criteria. Each tuple represents a
full namespace path up to `max_depth`. full namespace path up to `max_depth`.
Note:
Namespace matching is case-sensitive. There is no case-insensitive
matching option. If you need case-insensitive lookups, normalize the
namespace casing when you write items (e.g. lowercase each label).
???+ example "Examples" ???+ example "Examples"
Setting `max_depth=3` with existing namespaces: Setting `max_depth=3` with existing namespaces:
@@ -86,6 +86,10 @@ class AsyncBatchedBaseStore(BaseStore):
*, *,
refresh_ttl: bool | None = None, refresh_ttl: bool | None = None,
) -> Item | None: ) -> Item | None:
"""Asynchronously retrieve a single item.
See `BaseStore.aget` for the full description of arguments and behavior.
"""
self._ensure_task() self._ensure_task()
fut = self._loop.create_future() fut = self._loop.create_future()
self._aqueue.put_nowait( self._aqueue.put_nowait(
@@ -111,6 +115,10 @@ class AsyncBatchedBaseStore(BaseStore):
offset: int = 0, offset: int = 0,
refresh_ttl: bool | None = None, refresh_ttl: bool | None = None,
) -> list[SearchItem]: ) -> list[SearchItem]:
"""Asynchronously search for items within a namespace prefix.
See `BaseStore.asearch` for the full description of arguments and behavior.
"""
self._ensure_task() self._ensure_task()
fut = self._loop.create_future() fut = self._loop.create_future()
self._aqueue.put_nowait( self._aqueue.put_nowait(
@@ -137,6 +145,12 @@ class AsyncBatchedBaseStore(BaseStore):
*, *,
ttl: float | None | NotProvided = NOT_PROVIDED, ttl: float | None | NotProvided = NOT_PROVIDED,
) -> None: ) -> None:
"""Asynchronously store or update an item.
`value` must be a `dict`; a top-level `None` is reserved as the delete
signal (prefer `adelete`). See `BaseStore.aput` for the full description
of arguments and behavior.
"""
self._ensure_task() self._ensure_task()
_validate_namespace(namespace) _validate_namespace(namespace)
fut = self._loop.create_future() fut = self._loop.create_future()
@@ -155,6 +169,10 @@ class AsyncBatchedBaseStore(BaseStore):
namespace: tuple[str, ...], namespace: tuple[str, ...],
key: str, key: str,
) -> None: ) -> None:
"""Asynchronously delete an item.
See `BaseStore.adelete` for the full description of arguments and behavior.
"""
self._ensure_task() self._ensure_task()
fut = self._loop.create_future() fut = self._loop.create_future()
self._aqueue.put_nowait((fut, PutOp(namespace, key, None))) self._aqueue.put_nowait((fut, PutOp(namespace, key, None)))
@@ -169,6 +187,11 @@ class AsyncBatchedBaseStore(BaseStore):
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
) -> list[tuple[str, ...]]: ) -> list[tuple[str, ...]]:
"""Asynchronously list and filter namespaces in the store.
Namespace matching is case-sensitive. See `BaseStore.alist_namespaces`
for the full description of arguments and behavior.
"""
self._ensure_task() self._ensure_task()
fut = self._loop.create_future() fut = self._loop.create_future()
match_conditions = [] match_conditions = []
@@ -188,6 +211,10 @@ class AsyncBatchedBaseStore(BaseStore):
@_check_loop @_check_loop
def batch(self, ops: Iterable[Op]) -> list[Result]: def batch(self, ops: Iterable[Op]) -> list[Result]:
"""Execute multiple operations synchronously in a single batch.
See `BaseStore.batch` for the full description of arguments and behavior.
"""
return asyncio.run_coroutine_threadsafe(self.abatch(ops), self._loop).result() return asyncio.run_coroutine_threadsafe(self.abatch(ops), self._loop).result()
@_check_loop @_check_loop
@@ -198,6 +225,10 @@ class AsyncBatchedBaseStore(BaseStore):
*, *,
refresh_ttl: bool | None = None, refresh_ttl: bool | None = None,
) -> Item | None: ) -> Item | None:
"""Retrieve a single item.
See `BaseStore.get` for the full description of arguments and behavior.
"""
return asyncio.run_coroutine_threadsafe( return asyncio.run_coroutine_threadsafe(
self.aget(namespace, key=key, refresh_ttl=refresh_ttl), self._loop self.aget(namespace, key=key, refresh_ttl=refresh_ttl), self._loop
).result() ).result()
@@ -214,6 +245,10 @@ class AsyncBatchedBaseStore(BaseStore):
offset: int = 0, offset: int = 0,
refresh_ttl: bool | None = None, refresh_ttl: bool | None = None,
) -> list[SearchItem]: ) -> list[SearchItem]:
"""Search for items within a namespace prefix.
See `BaseStore.search` for the full description of arguments and behavior.
"""
return asyncio.run_coroutine_threadsafe( return asyncio.run_coroutine_threadsafe(
self.asearch( self.asearch(
namespace_prefix, namespace_prefix,
@@ -236,6 +271,12 @@ class AsyncBatchedBaseStore(BaseStore):
*, *,
ttl: float | None | NotProvided = NOT_PROVIDED, ttl: float | None | NotProvided = NOT_PROVIDED,
) -> None: ) -> None:
"""Store or update an item.
`value` must be a `dict`; a top-level `None` is reserved as the delete
signal (prefer `delete`). See `BaseStore.put` for the full description of
arguments and behavior.
"""
_validate_namespace(namespace) _validate_namespace(namespace)
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.aput( self.aput(