diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py index d8fd53d96..2a6d5b47c 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py @@ -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 the background task that removes expired items. Call `stop_ttl_sweeper()` to properly 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__ = ( diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/base.py b/libs/checkpoint-postgres/langgraph/store/postgres/base.py index 0d1cdc8dc..a974b8ed4 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/base.py @@ -713,6 +713,20 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]): the background thread that removes expired items. Call `stop_ttl_sweeper()` to properly 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__ = ( diff --git a/libs/checkpoint/langgraph/store/base/__init__.py b/libs/checkpoint/langgraph/store/base/__init__.py index 5b04fb51d..282a8e08a 100644 --- a/libs/checkpoint/langgraph/store/base/__init__.py +++ b/libs/checkpoint/langgraph/store/base/__init__.py @@ -861,8 +861,11 @@ class BaseStore(ABC): Example: `("documents", "user123")` key: Unique identifier within the namespace. Together with namespace forms the complete path to the item. - value: Dictionary containing the item's data. Must contain string keys - and JSON-serializable values. + value: Dictionary containing the item's data. Must be a `dict` with + 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: - 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 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()` 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. Args: - prefix: Filter namespaces that start with this path. - suffix: Filter namespaces that end with this path. + prefix: Filter namespaces that start with this path. Matched exactly and + 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. Namespaces deeper than this level will be truncated. 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 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": Setting `max_depth=3`. Given the namespaces: @@ -1114,8 +1131,11 @@ class BaseStore(ABC): Example: `("documents", "user123")` key: Unique identifier within the namespace. Together with namespace forms the complete path to the item. - value: Dictionary containing the item's data. Must contain string keys - and JSON-serializable values. + value: Dictionary containing the item's data. Must be a `dict` with + 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: - 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. Args: - prefix: Filter namespaces that start with this path. - suffix: Filter namespaces that end with this path. + prefix: Filter namespaces that start with this path. Matched exactly and + 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. Namespaces deeper than this level will be truncated to this depth. 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 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" Setting `max_depth=3` with existing namespaces: diff --git a/libs/checkpoint/langgraph/store/base/batch.py b/libs/checkpoint/langgraph/store/base/batch.py index 64019d68f..1aed8d466 100644 --- a/libs/checkpoint/langgraph/store/base/batch.py +++ b/libs/checkpoint/langgraph/store/base/batch.py @@ -86,6 +86,10 @@ class AsyncBatchedBaseStore(BaseStore): *, refresh_ttl: bool | None = None, ) -> Item | None: + """Asynchronously retrieve a single item. + + See `BaseStore.aget` for the full description of arguments and behavior. + """ self._ensure_task() fut = self._loop.create_future() self._aqueue.put_nowait( @@ -111,6 +115,10 @@ class AsyncBatchedBaseStore(BaseStore): offset: int = 0, refresh_ttl: bool | None = None, ) -> list[SearchItem]: + """Asynchronously search for items within a namespace prefix. + + See `BaseStore.asearch` for the full description of arguments and behavior. + """ self._ensure_task() fut = self._loop.create_future() self._aqueue.put_nowait( @@ -137,6 +145,12 @@ class AsyncBatchedBaseStore(BaseStore): *, ttl: float | None | NotProvided = NOT_PROVIDED, ) -> 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() _validate_namespace(namespace) fut = self._loop.create_future() @@ -155,6 +169,10 @@ class AsyncBatchedBaseStore(BaseStore): namespace: tuple[str, ...], key: str, ) -> None: + """Asynchronously delete an item. + + See `BaseStore.adelete` for the full description of arguments and behavior. + """ self._ensure_task() fut = self._loop.create_future() self._aqueue.put_nowait((fut, PutOp(namespace, key, None))) @@ -169,6 +187,11 @@ class AsyncBatchedBaseStore(BaseStore): limit: int = 100, offset: int = 0, ) -> 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() fut = self._loop.create_future() match_conditions = [] @@ -188,6 +211,10 @@ class AsyncBatchedBaseStore(BaseStore): @_check_loop 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() @_check_loop @@ -198,6 +225,10 @@ class AsyncBatchedBaseStore(BaseStore): *, refresh_ttl: bool | None = None, ) -> Item | None: + """Retrieve a single item. + + See `BaseStore.get` for the full description of arguments and behavior. + """ return asyncio.run_coroutine_threadsafe( self.aget(namespace, key=key, refresh_ttl=refresh_ttl), self._loop ).result() @@ -214,6 +245,10 @@ class AsyncBatchedBaseStore(BaseStore): offset: int = 0, refresh_ttl: bool | None = None, ) -> 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( self.asearch( namespace_prefix, @@ -236,6 +271,12 @@ class AsyncBatchedBaseStore(BaseStore): *, ttl: float | None | NotProvided = NOT_PROVIDED, ) -> 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) asyncio.run_coroutine_threadsafe( self.aput(