Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions libs/checkpoint-postgres/langgraph/store/postgres/aio.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import annotations

import asyncio
Expand Down Expand Up @@ -115,6 +115,20 @@
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__ = (
Expand Down
14 changes: 14 additions & 0 deletions libs/checkpoint-postgres/langgraph/store/postgres/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = (
Expand Down
43 changes: 35 additions & 8 deletions libs/checkpoint/langgraph/store/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,8 +861,11 @@ def put(
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)
Expand Down Expand Up @@ -895,6 +898,13 @@ def put(
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.

Expand Down Expand Up @@ -950,8 +960,10 @@ def list_namespaces(
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.
Comment on lines +963 to +966

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Docs hide namespace wildcard matching

This newly added text says prefix/suffix are matched “exactly”, but NamespacePath explicitly allows * and both the in-memory matcher and Postgres query builder treat * as a wildcard for one namespace segment. Users reading this reference page would be told that wildcard namespace filters are not supported/exact-only even though list_namespaces(prefix=("users", "*")) is valid behavior. The same wording is repeated in alist_namespaces and the Postgres store notes, so those copies should be updated consistently to say matching is case-sensitive while preserving the documented * wildcard semantics.

(Refers to lines 963-966)


Was this helpful? React with 👍 or 👎 to provide feedback.

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.
Expand All @@ -961,6 +973,11 @@ def list_namespaces(
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:
Expand Down Expand Up @@ -1114,8 +1131,11 @@ async def aput(
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)
Expand Down Expand Up @@ -1211,8 +1231,10 @@ async def alist_namespaces(
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.
Expand All @@ -1222,6 +1244,11 @@ async def alist_namespaces(
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:
Expand Down
41 changes: 41 additions & 0 deletions libs/checkpoint/langgraph/store/base/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ async def aget(
*,
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(
Expand All @@ -111,6 +115,10 @@ async def asearch(
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(
Expand All @@ -137,6 +145,12 @@ async def aput(
*,
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()
Expand All @@ -155,6 +169,10 @@ async def adelete(
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)))
Expand All @@ -169,6 +187,11 @@ async def alist_namespaces(
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 = []
Expand All @@ -188,6 +211,10 @@ async def alist_namespaces(

@_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
Expand All @@ -198,6 +225,10 @@ def get(
*,
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()
Expand All @@ -214,6 +245,10 @@ def search(
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,
Expand All @@ -236,6 +271,12 @@ def put(
*,
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(
Expand Down
Loading