codekingpro/portable-devtools
114k
1"""Base classes and types for persistent key-value stores.2 3Stores provide long-term memory that persists across threads and conversations.4Supports hierarchical namespaces, key-value storage, and optional vector search.5 6Core types:7 - `BaseStore`: Store interface with sync/async operations8 - `Item`: Stored key-value pairs with metadata9 - `Op`: Get/Put/Search/List operations10"""11 12from __future__ import annotations13 14from abc import ABC, abstractmethod15from collections.abc import Iterable16from datetime import datetime17from typing import (18 Any,19 Literal,20 NamedTuple,21 TypedDict,22 cast,23)24 25from langchain_core.embeddings import Embeddings26from typing_extensions import override27 28from langgraph.store.base.embed import (29 AEmbeddingsFunc,30 EmbeddingsFunc,31 ensure_embeddings,32 get_text_at_path,33 tokenize_path,34)35 36 37class NotProvided:38 """Sentinel singleton."""39 40 def __bool__(self) -> Literal[False]:41 return False42 43 @override44 def __repr__(self) -> str:45 return "NOT_GIVEN"46 47 48NOT_PROVIDED = NotProvided()49 50 51class Item:52 """Represents a stored item with metadata.53 54 Args:55 value: The stored data as a dictionary. Keys are filterable.56 key: Unique identifier within the namespace.57 namespace: Hierarchical path defining the collection in which this document resides.58 Represented as a tuple of strings, allowing for nested categorization.59 For example: `("documents", 'user123')`60 created_at: Timestamp of item creation.61 updated_at: Timestamp of last update.62 """63 64 __slots__ = ("value", "key", "namespace", "created_at", "updated_at")65 66 def __init__(67 self,68 *,69 value: dict[str, Any],70 key: str,71 namespace: tuple[str, ...],72 created_at: datetime,73 updated_at: datetime,74 ):75 self.value = value76 self.key = key77 # The casting from json-like types is for if this object is78 # deserialized.79 self.namespace = tuple(namespace)80 self.created_at = (81 datetime.fromisoformat(cast(str, created_at))82 if isinstance(created_at, str)83 else created_at84 )85 self.updated_at = (86 datetime.fromisoformat(cast(str, updated_at))87 if isinstance(updated_at, str)88 else updated_at89 )90 91 def __eq__(self, other: object) -> bool:92 if not isinstance(other, Item):93 return False94 return (95 self.value == other.value96 and self.key == other.key97 and self.namespace == other.namespace98 and self.created_at == other.created_at99 and self.updated_at == other.updated_at100 )101 102 def __hash__(self) -> int:103 return hash((self.namespace, self.key))104 105 def dict(self) -> dict:106 return {107 "namespace": list(self.namespace),108 "key": self.key,109 "value": self.value,110 "created_at": self.created_at.isoformat(),111 "updated_at": self.updated_at.isoformat(),112 }113 114 def __repr__(self) -> str:115 return f"Item({', '.join(f'{k}={v!r}' for k, v in self.dict().items())})"116 117 118class SearchItem(Item):119 """Represents an item returned from a search operation with additional metadata."""120 121 __slots__ = ("score",)122 123 def __init__(124 self,125 namespace: tuple[str, ...],126 key: str,127 value: dict[str, Any],128 created_at: datetime,129 updated_at: datetime,130 score: float | None = None,131 ) -> None:132 """Initialize a result item.133 134 Args:135 namespace: Hierarchical path to the item.136 key: Unique identifier within the namespace.137 value: The stored value.138 created_at: When the item was first created.139 updated_at: When the item was last updated.140 score: Relevance/similarity score if from a ranked operation.141 """142 super().__init__(143 value=value,144 key=key,145 namespace=namespace,146 created_at=created_at,147 updated_at=updated_at,148 )149 self.score = score150 151 def dict(self) -> dict:152 result = super().dict()153 result["score"] = self.score154 return result155 156 157class GetOp(NamedTuple):158 """Operation to retrieve a specific item by its namespace and key.159 160 This operation allows precise retrieval of stored items using their full path161 (namespace) and unique identifier (key) combination.162 163 ???+ example "Examples"164 165 Basic item retrieval:166 167 ```python168 GetOp(namespace=("users", "profiles"), key="user123")169 GetOp(namespace=("cache", "embeddings"), key="doc456")170 ```171 """172 173 namespace: tuple[str, ...]174 """Hierarchical path that uniquely identifies the item's location.175 176 ???+ example "Examples"177 178 ```python179 ("users",) # Root level users namespace180 ("users", "profiles") # Profiles within users namespace181 ```182 """183 184 key: str185 """Unique identifier for the item within its specific namespace.186 187 ???+ example "Examples"188 189 ```python190 "user123" # For a user profile191 "doc456" # For a document192 ```193 """194 refresh_ttl: bool = True195 """Whether to refresh TTLs for the returned item.196 197 If no TTL was specified for the original item(s),198 or if TTL support is not enabled for your adapter,199 this argument is ignored.200 """201 202 203class SearchOp(NamedTuple):204 """Operation to search for items within a specified namespace hierarchy.205 206 This operation supports both structured filtering and natural language search207 within a given namespace prefix. It provides pagination through limit and offset208 parameters.209 210 !!! note211 212 Natural language search support depends on your store implementation.213 214 ???+ example "Examples"215 216 Search with filters and pagination:217 218 ```python219 SearchOp(220 namespace_prefix=("documents",),221 filter={"type": "report", "status": "active"},222 limit=5,223 offset=10224 )225 ```226 227 Natural language search:228 229 ```python230 SearchOp(231 namespace_prefix=("users", "content"),232 query="technical documentation about APIs",233 limit=20234 )235 ```236 """237 238 namespace_prefix: tuple[str, ...]239 """Hierarchical path prefix defining the search scope.240 241 ???+ example "Examples"242 243 ```python244 () # Search entire store245 ("documents",) # Search all documents246 ("users", "content") # Search within user content247 ```248 """249 250 filter: dict[str, Any] | None = None251 """Key-value pairs for filtering results based on exact matches or comparison operators.252 253 The filter supports both exact matches and operator-based comparisons.254 255 Supported Operators:256 - `$eq`: Equal to (same as direct value comparison)257 - `$ne`: Not equal to258 - `$gt`: Greater than259 - `$gte`: Greater than or equal to260 - `$lt`: Less than261 - `$lte`: Less than or equal to262 263 ???+ example "Examples"264 265 Simple exact match:266 267 ```python268 {"status": "active"}269 ```270 271 Comparison operators:272 273 ```python274 {"score": {"$gt": 4.99}} # Score greater than 4.99275 ```276 277 Multiple conditions:278 279 ```python280 {281 "score": {"$gte": 3.0},282 "color": "red"283 }284 ```285 """286 287 limit: int = 10288 """Maximum number of items to return in the search results."""289 290 offset: int = 0291 """Number of matching items to skip for pagination."""292 293 query: str | None = None294 """Natural language search query for semantic search capabilities.295 296 ???+ example "Examples"297 298 - "technical documentation about REST APIs"299 - "machine learning papers from 2023"300 """301 refresh_ttl: bool = True302 """Whether to refresh TTLs for the returned item.303 304 If no TTL was specified for the original item(s),305 or if TTL support is not enabled for your adapter,306 this argument is ignored.307 """308 309 310# Type representing a namespace path that can include wildcards311NamespacePath = tuple[str | Literal["*"], ...]312"""A tuple representing a namespace path that can include wildcards.313 314???+ example "Examples"315 316 ```python317 ("users",) # Exact users namespace318 ("documents", "*") # Any sub-namespace under documents319 ("cache", "*", "v1") # Any cache category with v1 version320 ```321"""322 323# Type for specifying how to match namespaces324NamespaceMatchType = Literal["prefix", "suffix"]325"""Specifies how to match namespace paths.326 327Values:328 "prefix": Match from the start of the namespace329 "suffix": Match from the end of the namespace330"""331 332 333class MatchCondition(NamedTuple):334 """Represents a pattern for matching namespaces in the store.335 336 This class combines a match type (prefix or suffix) with a namespace path337 pattern that can include wildcards to flexibly match different namespace338 hierarchies.339 340 ???+ example "Examples"341 342 Prefix matching:343 344 ```python345 MatchCondition(match_type="prefix", path=("users", "profiles"))346 ```347 348 Suffix matching with wildcard:349 350 ```python351 MatchCondition(match_type="suffix", path=("cache", "*"))352 ```353 354 Simple suffix matching:355 356 ```python357 MatchCondition(match_type="suffix", path=("v1",))358 ```359 """360 361 match_type: NamespaceMatchType362 """Type of namespace matching to perform."""363 364 path: NamespacePath365 """Namespace path pattern that can include wildcards."""366 367 368class ListNamespacesOp(NamedTuple):369 """Operation to list and filter namespaces in the store.370 371 This operation allows exploring the organization of data, finding specific372 collections, and navigating the namespace hierarchy.373 374 ???+ example "Examples"375 376 List all namespaces under the `"documents"` path:377 378 ```python379 ListNamespacesOp(380 match_conditions=(MatchCondition(match_type="prefix", path=("documents",)),),381 max_depth=2382 )383 ```384 385 List all namespaces that end with `"v1"`:386 387 ```python388 ListNamespacesOp(389 match_conditions=(MatchCondition(match_type="suffix", path=("v1",)),),390 limit=50391 )392 ```393 394 """395 396 match_conditions: tuple[MatchCondition, ...] | None = None397 """Optional conditions for filtering namespaces.398 399 ???+ example "Examples"400 401 All user namespaces:402 403 ```python404 (MatchCondition(match_type="prefix", path=("users",)),)405 ```406 407 All namespaces that start with `"docs"` and end with `"draft"`:408 409 ```python410 (411 MatchCondition(match_type="prefix", path=("docs",)),412 MatchCondition(match_type="suffix", path=("draft",))413 ) 414 ```415 """416 417 max_depth: int | None = None418 """Maximum depth of namespace hierarchy to return.419 420 Note:421 Namespaces deeper than this level will be truncated.422 """423 424 limit: int = 100425 """Maximum number of namespaces to return."""426 427 offset: int = 0428 """Number of namespaces to skip for pagination."""429 430 431class PutOp(NamedTuple):432 """Operation to store, update, or delete an item in the store.433 434 This class represents a single operation to modify the store's contents,435 whether adding new items, updating existing ones, or removing them.436 """437 438 namespace: tuple[str, ...]439 """Hierarchical path that identifies the location of the item.440 441 The namespace acts as a folder-like structure to organize items.442 Each element in the tuple represents one level in the hierarchy.443 444 ???+ example "Examples"445 446 Root level documents:447 448 ```python449 ("documents",)450 ```451 452 User-specific documents:453 454 ```python455 ("documents", "user123")456 ```457 458 Nested cache structure:459 460 ```python461 ("cache", "embeddings", "v1")462 ```463 """464 465 key: str466 """Unique identifier for the item within its namespace.467 468 The key must be unique within the specific namespace to avoid conflicts.469 Together with the namespace, it forms a complete path to the item.470 471 Example:472 If namespace is `("documents", "user123")` and key is `"report1"`,473 the full path would effectively be `"documents/user123/report1"`474 """475 476 value: dict[str, Any] | None477 """The data to store, or `None` to mark the item for deletion.478 479 The value must be a dictionary with string keys and JSON-serializable values.480 Setting this to `None` signals that the item should be deleted.481 482 Example:483 {484 "field1": "string value",485 "field2": 123,486 "nested": {"can": "contain", "any": "serializable data"}487 }488 """489 490 index: Literal[False] | list[str] | None = None # type: ignore[assignment]491 """Controls how the item's fields are indexed for search operations.492 493 Indexing configuration determines how the item can be found through search:494 - `None` (default): Uses the store's default indexing configuration (if provided)495 - `False`: Disables indexing for this item496 - `list[str]`: Specifies which json path fields to index for search497 498 The item remains accessible through direct get() operations regardless of indexing.499 When indexed, fields can be searched using natural language queries through500 vector similarity search (if supported by the store implementation).501 502 Path Syntax:503 - Simple field access: `"field"`504 - Nested fields: `"parent.child.grandchild"`505 - Array indexing:506 - Specific index: `"array[0]"`507 - Last element: `"array[-1]"`508 - All elements (each individually): `"array[*]"`509 510 ???+ example "Examples"511 512 - `None` - Use store defaults (whole item)513 - `list[str]` - List of fields to index514 515 ```python516 [517 "metadata.title", # Nested field access518 "context[*].content", # Index content from all context as separate vectors519 "authors[0].name", # First author's name520 "revisions[-1].changes", # Most recent revision's changes521 "sections[*].paragraphs[*].text", # All text from all paragraphs in all sections522 "metadata.tags[*]", # All tags in metadata523 ]524 ```525 """526 ttl: float | None = None527 """Controls the TTL (time-to-live) for the item in minutes.528 529 If provided, and if the store you are using supports this feature, the item530 will expire this many minutes after it was last accessed. The expiration timer531 refreshes on both read operations (get/search) and write operations (put/update).532 When the TTL expires, the item will be scheduled for deletion on a best-effort basis.533 Defaults to `None` (no expiration).534 """535 536 537Op = GetOp | SearchOp | PutOp | ListNamespacesOp538Result = Item | list[Item] | list[SearchItem] | list[tuple[str, ...]] | None539 540 541class InvalidNamespaceError(ValueError):542 """Provided namespace is invalid."""543 544 545class TTLConfig(TypedDict, total=False):546 """Configuration for TTL (time-to-live) behavior in the store."""547 548 refresh_on_read: bool549 """Default behavior for refreshing TTLs on read operations (`GET` and `SEARCH`).550 551 If `True`, TTLs will be refreshed on read operations (get/search) by default.552 This can be overridden per-operation by explicitly setting `refresh_ttl`.553 Defaults to `True` if not configured.554 """555 default_ttl: float | None556 """Default TTL (time-to-live) in minutes for new items.557 558 If provided, new items will expire after this many minutes after their last access.559 The expiration timer refreshes on both read and write operations.560 Defaults to `None` (no expiration).561 """562 sweep_interval_minutes: int | None563 """Interval in minutes between TTL sweep operations.564 565 If provided, the store will periodically delete expired items based on TTL.566 Defaults to None (no sweeping).567 """568 569 570class IndexConfig(TypedDict, total=False):571 """Configuration for indexing documents for semantic search in the store.572 573 If not provided to the store, the store will not support vector search.574 In that case, all `index` arguments to `put()` and `aput()` operations will be ignored.575 """576 577 dims: int578 """Number of dimensions in the embedding vectors.579 580 Common embedding models have the following dimensions:581 - `openai:text-embedding-3-large`: `3072`582 - `openai:text-embedding-3-small`: `1536`583 - `openai:text-embedding-ada-002`: `1536`584 - `cohere:embed-english-v3.0`: `1024`585 - `cohere:embed-english-light-v3.0`: `384`586 - `cohere:embed-multilingual-v3.0`: `1024`587 - `cohere:embed-multilingual-light-v3.0`: `384`588 """589 590 embed: Embeddings | EmbeddingsFunc | AEmbeddingsFunc | str591 """Optional function to generate embeddings from text.592 593 Can be specified in three ways:594 1. A LangChain `Embeddings` instance595 2. A synchronous embedding function (`EmbeddingsFunc`)596 3. An asynchronous embedding function (`AEmbeddingsFunc`)597 4. A provider string (e.g., `"openai:text-embedding-3-small"`)598 599 ???+ example "Examples"600 601 Using LangChain's initialization with `InMemoryStore`:602 603 ```python604 from langchain.embeddings import init_embeddings605 from langgraph.store.memory import InMemoryStore606 607 store = InMemoryStore(608 index={609 "dims": 1536,610 "embed": init_embeddings("openai:text-embedding-3-small")611 }612 )613 ```614 615 Using a custom embedding function with `InMemoryStore`:616 617 ```python618 from openai import OpenAI619 from langgraph.store.memory import InMemoryStore620 621 client = OpenAI()622 623 def embed_texts(texts: list[str]) -> list[list[float]]:624 response = client.embeddings.create(625 model="text-embedding-3-small",626 input=texts627 )628 return [e.embedding for e in response.data]629 630 store = InMemoryStore(631 index={632 "dims": 1536,633 "embed": embed_texts634 }635 )636 ```637 638 Using an asynchronous embedding function with `InMemoryStore`:639 640 ```python641 from openai import AsyncOpenAI642 from langgraph.store.memory import InMemoryStore643 644 client = AsyncOpenAI()645 646 async def aembed_texts(texts: list[str]) -> list[list[float]]:647 response = await client.embeddings.create(648 model="text-embedding-3-small",649 input=texts650 )651 return [e.embedding for e in response.data]652 653 store = InMemoryStore(654 index={655 "dims": 1536,656 "embed": aembed_texts657 }658 )659 ```660 """661 662 fields: list[str] | None663 """Fields to extract text from for embedding generation.664 665 Controls which parts of stored items are embedded for semantic search. Follows JSON path syntax:666 667 - `["$"]`: Embeds the entire JSON object as one vector (default)668 - `["field1", "field2"]`: Embeds specific top-level fields669 - `["parent.child"]`: Embeds nested fields using dot notation670 - `["array[*].field"]`: Embeds field from each array element separately671 672 Note:673 You can always override this behavior when storing an item using the674 `index` parameter in the `put` or `aput` operations.675 676 ???+ example "Examples"677 678 ```python679 # Embed entire document (default)680 fields=["$"]681 682 # Embed specific fields683 fields=["text", "summary"]684 685 # Embed nested fields686 fields=["metadata.title", "content.body"]687 688 # Embed from arrays689 fields=["messages[*].content"] # Each message content separately690 fields=["context[0].text"] # First context item's text691 ```692 693 Note:694 - Fields missing from a document are skipped695 - Array notation creates separate embeddings for each element696 - Complex nested paths are supported (e.g., `"a.b[*].c.d"`)697 """698 699 700class BaseStore(ABC):701 """Abstract base class for persistent key-value stores.702 703 Stores enable persistence and memory that can be shared across threads,704 scoped to user IDs, assistant IDs, or other arbitrary namespaces.705 Some implementations may support semantic search capabilities through706 an optional `index` configuration.707 708 Note:709 Semantic search capabilities vary by implementation and are typically710 disabled by default. Stores that support this feature can be configured711 by providing an `index` configuration at creation time. Without this712 configuration, semantic search is disabled and any `index` arguments713 to storage operations will have no effect.714 715 Similarly, TTL (time-to-live) support is disabled by default.716 Subclasses must explicitly set `supports_ttl = True` to enable this feature.717 """718 719 supports_ttl: bool = False720 ttl_config: TTLConfig | None = None721 722 __slots__ = ("__weakref__",)723 724 @abstractmethod725 def batch(self, ops: Iterable[Op]) -> list[Result]:726 """Execute multiple operations synchronously in a single batch.727 728 Args:729 ops: An iterable of operations to execute.730 731 Returns:732 A list of results, where each result corresponds to an operation in the input.733 The order of results matches the order of input operations.734 """735 736 @abstractmethod737 async def abatch(self, ops: Iterable[Op]) -> list[Result]:738 """Execute multiple operations asynchronously in a single batch.739 740 Args:741 ops: An iterable of operations to execute.742 743 Returns:744 A list of results, where each result corresponds to an operation in the input.745 The order of results matches the order of input operations.746 """747 748 def get(749 self,750 namespace: tuple[str, ...],751 key: str,752 *,753 refresh_ttl: bool | None = None,754 ) -> Item | None:755 """Retrieve a single item.756 757 Args:758 namespace: Hierarchical path for the item.759 key: Unique identifier within the namespace.760 refresh_ttl: Whether to refresh TTLs for the returned item.761 If `None`, uses the store's default `refresh_ttl` setting.762 If no TTL is specified, this argument is ignored.763 764 Returns:765 The retrieved item or `None` if not found.766 """767 return self.batch(768 [GetOp(namespace, str(key), _ensure_refresh(self.ttl_config, refresh_ttl))]769 )[0]770 771 def search(772 self,773 namespace_prefix: tuple[str, ...],774 /,775 *,776 query: str | None = None,777 filter: dict[str, Any] | None = None,778 limit: int = 10,779 offset: int = 0,780 refresh_ttl: bool | None = None,781 ) -> list[SearchItem]:782 """Search for items within a namespace prefix.783 784 Args:785 namespace_prefix: Hierarchical path prefix to search within.786 query: Optional query for natural language search.787 filter: Key-value pairs to filter results.788 limit: Maximum number of items to return.789 offset: Number of items to skip before returning results.790 refresh_ttl: Whether to refresh TTLs for the returned items.791 If no TTL is specified, this argument is ignored.792 793 Returns:794 List of items matching the search criteria.795 796 ???+ example "Examples"797 798 Basic filtering:799 800 ```python801 # Search for documents with specific metadata802 results = store.search(803 ("docs",),804 filter={"type": "article", "status": "published"}805 )806 ```807 808 Natural language search (requires vector store implementation):809 810 ```python811 # Initialize store with embedding configuration812 store = YourStore( # e.g., InMemoryStore, AsyncPostgresStore813 index={814 "dims": 1536, # embedding dimensions815 "embed": your_embedding_function, # function to create embeddings816 "fields": ["text"] # fields to embed. Defaults to ["$"]817 }818 )819 820 # Search for semantically similar documents821 822 results = store.search(823 ("docs",),824 query="machine learning applications in healthcare",825 filter={"type": "research_paper"},826 limit=5827 )828 ```829 830 !!! note831 832 Natural language search support depends on your store implementation833 and requires proper embedding configuration.834 """835 return self.batch(836 [837 SearchOp(838 namespace_prefix,839 filter,840 limit,841 offset,842 query,843 _ensure_refresh(self.ttl_config, refresh_ttl),844 )845 ]846 )[0]847 848 def put(849 self,850 namespace: tuple[str, ...],851 key: str,852 value: dict[str, Any],853 index: Literal[False] | list[str] | None = None,854 *,855 ttl: float | None | NotProvided = NOT_PROVIDED,856 ) -> None:857 """Store or update an item in the store.858 859 Args:860 namespace: Hierarchical path for the item, represented as a tuple of strings.861 Example: `("documents", "user123")`862 key: Unique identifier within the namespace. Together with namespace forms863 the complete path to the item.864 value: Dictionary containing the item's data. Must contain string keys865 and JSON-serializable values.866 index: Controls how the item's fields are indexed for search:867 868 - None (default): Use `fields` you configured when creating the store (if any)869 If you do not initialize the store with indexing capabilities,870 the `index` parameter will be ignored871 - False: Disable indexing for this item872 - `list[str]`: List of field paths to index, supporting:873 - Nested fields: `"metadata.title"`874 - Array access: `"chapters[*].content"` (each indexed separately)875 - Specific indices: `"authors[0].name"`876 ttl: Time to live in minutes. Support for this argument depends on your store adapter.877 If specified, the item will expire after this many minutes from when it was last accessed.878 None means no expiration. Expired runs will be deleted opportunistically.879 By default, the expiration timer refreshes on both read operations (get/search)880 and write operations (put/update), whenever the item is included in the operation.881 882 Note:883 Indexing support depends on your store implementation.884 If you do not initialize the store with indexing capabilities,885 the `index` parameter will be ignored.886 887 Similarly, TTL support depends on the specific store implementation.888 Some implementations may not support expiration of items.889 890 ???+ example "Examples"891 892 Store item. Indexing depends on how you configure the store:893 894 ```python895 store.put(("docs",), "report", {"memory": "Will likes ai"})896 ```897 898 Do not index item for semantic search. Still accessible through `get()`899 and `search()` operations but won't have a vector representation.900 901 ```python902 store.put(("docs",), "report", {"memory": "Will likes ai"}, index=False)903 ```904 905 Index specific fields for search:906 907 ```python908 store.put(("docs",), "report", {"memory": "Will likes ai"}, index=["memory"])909 ```910 """911 _validate_namespace(namespace)912 if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:913 raise NotImplementedError(914 f"TTL is not supported by {self.__class__.__name__}. "915 f"Use a store implementation that supports TTL or set ttl=None."916 )917 self.batch(918 [919 PutOp(920 namespace,921 str(key),922 value,923 index=index,924 ttl=_ensure_ttl(self.ttl_config, ttl),925 )926 ]927 )928 929 def delete(self, namespace: tuple[str, ...], key: str) -> None:930 """Delete an item.931 932 Args:933 namespace: Hierarchical path for the item.934 key: Unique identifier within the namespace.935 """936 self.batch([PutOp(namespace, str(key), None, ttl=None)])937 938 def list_namespaces(939 self,940 *,941 prefix: NamespacePath | None = None,942 suffix: NamespacePath | None = None,943 max_depth: int | None = None,944 limit: int = 100,945 offset: int = 0,946 ) -> list[tuple[str, ...]]:947 """List and filter namespaces in the store.948 949 Used to explore the organization of data,950 find specific collections, or navigate the namespace hierarchy.951 952 Args:953 prefix: Filter namespaces that start with this path.954 suffix: Filter namespaces that end with this path.955 max_depth: Return namespaces up to this depth in the hierarchy.956 Namespaces deeper than this level will be truncated.957 limit: Maximum number of namespaces to return.958 offset: Number of namespaces to skip for pagination.959 960 Returns:961 A list of namespace tuples that match the criteria. Each tuple represents a962 full namespace path up to `max_depth`.963 964 ???+ example "Examples":965 966 Setting `max_depth=3`. Given the namespaces:967 968 ```python969 # Example if you have the following namespaces:970 # ("a", "b", "c")971 # ("a", "b", "d", "e")972 # ("a", "b", "d", "i")973 # ("a", "b", "f")974 # ("a", "c", "f")975 store.list_namespaces(prefix=("a", "b"), max_depth=3)976 # [("a", "b", "c"), ("a", "b", "d"), ("a", "b", "f")]977 ```978 """979 match_conditions = []980 if prefix:981 match_conditions.append(MatchCondition(match_type="prefix", path=prefix))982 if suffix:983 match_conditions.append(MatchCondition(match_type="suffix", path=suffix))984 985 op = ListNamespacesOp(986 match_conditions=tuple(match_conditions),987 max_depth=max_depth,988 limit=limit,989 offset=offset,990 )991 return self.batch([op])[0]992 993 async def aget(994 self,995 namespace: tuple[str, ...],996 key: str,997 *,998 refresh_ttl: bool | None = None,999 ) -> Item | None:1000 """Asynchronously retrieve a single item.1001 1002 Args:1003 namespace: Hierarchical path for the item.1004 key: Unique identifier within the namespace.1005 1006 Returns:1007 The retrieved item or `None` if not found.1008 """1009 return (1010 await self.abatch(1011 [1012 GetOp(1013 namespace,1014 str(key),1015 _ensure_refresh(self.ttl_config, refresh_ttl),1016 )1017 ]1018 )1019 )[0]1020 1021 async def asearch(1022 self,1023 namespace_prefix: tuple[str, ...],1024 /,1025 *,1026 query: str | None = None,1027 filter: dict[str, Any] | None = None,1028 limit: int = 10,1029 offset: int = 0,1030 refresh_ttl: bool | None = None,1031 ) -> list[SearchItem]:1032 """Asynchronously search for items within a namespace prefix.1033 1034 Args:1035 namespace_prefix: Hierarchical path prefix to search within.1036 query: Optional query for natural language search.1037 filter: Key-value pairs to filter results.1038 limit: Maximum number of items to return.1039 offset: Number of items to skip before returning results.1040 refresh_ttl: Whether to refresh TTLs for the returned items.1041 If `None`, uses the store's `TTLConfig.refresh_default` setting.1042 If `TTLConfig` is not provided or no TTL is specified, this argument is ignored.1043 1044 Returns:1045 List of items matching the search criteria.1046 1047 ???+ example "Examples"1048 1049 Basic filtering:1050 1051 ```python1052 # Search for documents with specific metadata1053 results = await store.asearch(1054 ("docs",),1055 filter={"type": "article", "status": "published"}1056 )1057 ```1058 1059 Natural language search (requires vector store implementation):1060 1061 ```python1062 # Initialize store with embedding configuration1063 store = YourStore( # e.g., InMemoryStore, AsyncPostgresStore1064 index={1065 "dims": 1536, # embedding dimensions1066 "embed": your_embedding_function, # function to create embeddings1067 "fields": ["text"] # fields to embed1068 }1069 )1070 1071 # Search for semantically similar documents1072 1073 results = await store.asearch(1074 ("docs",),1075 query="machine learning applications in healthcare",1076 filter={"type": "research_paper"},1077 limit=51078 )1079 ```1080 1081 !!! note1082 1083 Natural language search support depends on your store implementation1084 and requires proper embedding configuration.1085 """1086 return (1087 await self.abatch(1088 [1089 SearchOp(1090 namespace_prefix,1091 filter,1092 limit,1093 offset,1094 query,1095 _ensure_refresh(self.ttl_config, refresh_ttl),1096 )1097 ]1098 )1099 )[0]1100 1101 async def aput(1102 self,1103 namespace: tuple[str, ...],1104 key: str,1105 value: dict[str, Any],1106 index: Literal[False] | list[str] | None = None,1107 *,1108 ttl: float | None | NotProvided = NOT_PROVIDED,1109 ) -> None:1110 """Asynchronously store or update an item in the store.1111 1112 Args:1113 namespace: Hierarchical path for the item, represented as a tuple of strings.1114 Example: `("documents", "user123")`1115 key: Unique identifier within the namespace. Together with namespace forms1116 the complete path to the item.1117 value: Dictionary containing the item's data. Must contain string keys1118 and JSON-serializable values.1119 index: Controls how the item's fields are indexed for search:1120 1121 - None (default): Use `fields` you configured when creating the store (if any)1122 If you do not initialize the store with indexing capabilities,1123 the `index` parameter will be ignored1124 - False: Disable indexing for this item1125 - `list[str]`: List of field paths to index, supporting:1126 - Nested fields: `"metadata.title"`1127 - Array access: `"chapters[*].content"` (each indexed separately)1128 - Specific indices: `"authors[0].name"`1129 ttl: Time to live in minutes. Support for this argument depends on your store adapter.1130 If specified, the item will expire after this many minutes from when it was last accessed.1131 None means no expiration. Expired runs will be deleted opportunistically.1132 By default, the expiration timer refreshes on both read operations (get/search)1133 and write operations (put/update), whenever the item is included in the operation.1134 1135 Note:1136 Indexing support depends on your store implementation.1137 If you do not initialize the store with indexing capabilities,1138 the `index` parameter will be ignored.1139 1140 Similarly, TTL support depends on the specific store implementation.1141 Some implementations may not support expiration of items.1142 1143 ???+ example "Examples"1144 1145 Store item. Indexing depends on how you configure the store:1146 1147 ```python1148 await store.aput(("docs",), "report", {"memory": "Will likes ai"})1149 ```1150 1151 Do not index item for semantic search. Still accessible through `get()`1152 and `search()` operations but won't have a vector representation.1153 1154 ```python1155 await store.aput(("docs",), "report", {"memory": "Will likes ai"}, index=False)1156 ```1157 1158 Index specific fields for search (if store configured to index items):1159 1160 ```python1161 await store.aput(1162 ("docs",),1163 "report",1164 {1165 "memory": "Will likes ai",1166 "context": [{"content": "..."}, {"content": "..."}]1167 },1168 index=["memory", "context[*].content"]1169 )1170 ```1171 """1172 _validate_namespace(namespace)1173 if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:1174 raise NotImplementedError(1175 f"TTL is not supported by {self.__class__.__name__}. "1176 f"Use a store implementation that supports TTL or set ttl=None."1177 )1178 await self.abatch(1179 [1180 PutOp(1181 namespace,1182 str(key),1183 value,1184 index=index,1185 ttl=_ensure_ttl(self.ttl_config, ttl),1186 )1187 ]1188 )1189 1190 async def adelete(self, namespace: tuple[str, ...], key: str) -> None:1191 """Asynchronously delete an item.1192 1193 Args:1194 namespace: Hierarchical path for the item.1195 key: Unique identifier within the namespace.1196 """1197 await self.abatch([PutOp(namespace, str(key), None)])1198 1199 async def alist_namespaces(1200 self,