Add default TTL in store & CLI (#3786)

This commit is contained in:
William FH
2025-03-12 06:14:58 -07:00
committed by GitHub
9 changed files with 184 additions and 16 deletions
@@ -11,9 +11,19 @@ Core types:
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any, Iterable, Literal, NamedTuple, Optional, TypedDict, Union, cast
from typing import (
Any,
Iterable,
Literal,
NamedTuple,
Optional,
TypedDict,
Union,
cast,
)
from langchain_core.embeddings import Embeddings
from typing_extensions import override
from langgraph.store.base.embed import (
AEmbeddingsFunc,
@@ -24,6 +34,20 @@ from langgraph.store.base.embed import (
)
class NotProvided:
"""Sentinel singleton."""
def __bool__(self) -> Literal[False]:
return False
@override
def __repr__(self) -> str:
return "NOT_GIVEN"
NOT_PROVIDED = NotProvided()
class Item:
"""Represents a stored item with metadata.
@@ -506,6 +530,13 @@ class TTLConfig(TypedDict, total=False):
This can be overridden per-operation by explicitly setting refresh_ttl.
Defaults to True if not configured.
"""
default_ttl: Optional[float]
"""Default TTL (time-to-live) in minutes for new items.
If provided, new items will expire after this many minutes after their last access.
The expiration timer refreshes on both read and write operations.
Defaults to None (no expiration).
"""
class IndexConfig(TypedDict, total=False):
@@ -782,7 +813,7 @@ class BaseStore(ABC):
value: dict[str, Any],
index: Optional[Union[Literal[False], list[str]]] = None,
*,
ttl: Optional[float] = None,
ttl: Union[Optional[float], "NotProvided"] = NOT_PROVIDED,
) -> None:
"""Store or update an item in the store.
@@ -835,12 +866,22 @@ class BaseStore(ABC):
```
"""
_validate_namespace(namespace)
if ttl is not None and not self.supports_ttl:
if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:
raise NotImplementedError(
f"TTL is not supported by {self.__class__.__name__}. "
f"Use a store implementation that supports TTL or set ttl=None."
)
self.batch([PutOp(namespace, str(key), value, index=index, ttl=ttl)])
self.batch(
[
PutOp(
namespace,
str(key),
value,
index=index,
ttl=_ensure_ttl(self.ttl_config, ttl),
)
]
)
def delete(self, namespace: tuple[str, ...], key: str) -> None:
"""Delete an item.
@@ -1013,7 +1054,7 @@ class BaseStore(ABC):
value: dict[str, Any],
index: Optional[Union[Literal[False], list[str]]] = None,
*,
ttl: Optional[float] = None,
ttl: Union[Optional[float], "NotProvided"] = NOT_PROVIDED,
) -> None:
"""Asynchronously store or update an item in the store.
@@ -1074,12 +1115,22 @@ class BaseStore(ABC):
```
"""
_validate_namespace(namespace)
if ttl is not None and not self.supports_ttl:
if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:
raise NotImplementedError(
f"TTL is not supported by {self.__class__.__name__}. "
f"Use a store implementation that supports TTL or set ttl=None."
)
await self.abatch([PutOp(namespace, str(key), value, index=index, ttl=ttl)])
await self.abatch(
[
PutOp(
namespace,
str(key),
value,
index=index,
ttl=_ensure_ttl(self.ttl_config, ttl),
)
]
)
async def adelete(self, namespace: tuple[str, ...], key: str) -> None:
"""Asynchronously delete an item.
@@ -1178,6 +1229,17 @@ def _ensure_refresh(
return True
def _ensure_ttl(
ttl_config: Optional[TTLConfig],
ttl: Union[Optional[float], "NotProvided"] = NOT_PROVIDED,
) -> Optional[float]:
if ttl is NOT_PROVIDED:
if ttl_config:
return ttl_config.get("default_ttl")
return None
return ttl
__all__ = [
"BaseStore",
"Item",
+21 -4
View File
@@ -5,18 +5,21 @@ from collections.abc import Iterable
from typing import Any, Callable, Literal, Optional, TypeVar, Union
from langgraph.store.base import (
NOT_PROVIDED,
BaseStore,
GetOp,
Item,
ListNamespacesOp,
MatchCondition,
NamespacePath,
NotProvided,
Op,
PutOp,
Result,
SearchItem,
SearchOp,
_ensure_refresh,
_ensure_ttl,
_validate_namespace,
)
@@ -121,12 +124,19 @@ class AsyncBatchedBaseStore(BaseStore):
value: dict[str, Any],
index: Optional[Union[Literal[False], list[str]]] = None,
*,
ttl: Optional[float] = None,
ttl: Union[Optional[float], "NotProvided"] = NOT_PROVIDED,
) -> None:
assert not self._task.done()
_validate_namespace(namespace)
fut = self._loop.create_future()
self._aqueue.put_nowait((fut, PutOp(namespace, key, value, index, ttl=ttl)))
self._aqueue.put_nowait(
(
fut,
PutOp(
namespace, key, value, index, ttl=_ensure_ttl(self.ttl_config, ttl)
),
)
)
return await fut
async def adelete(
@@ -213,11 +223,18 @@ class AsyncBatchedBaseStore(BaseStore):
value: dict[str, Any],
index: Optional[Union[Literal[False], list[str]]] = None,
*,
ttl: Optional[float] = None,
ttl: Union[Optional[float], "NotProvided"] = NOT_PROVIDED,
) -> None:
_validate_namespace(namespace)
asyncio.run_coroutine_threadsafe(
self.aput(namespace, key=key, value=value, index=index, ttl=ttl), self._loop
self.aput(
namespace,
key=key,
value=value,
index=index,
ttl=_ensure_ttl(self.ttl_config, ttl),
),
self._loop,
).result()
@_check_loop
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-checkpoint"
version = "2.0.18"
version = "2.0.19"
description = "Library with base interfaces for LangGraph checkpoint savers."
authors = []
license = "MIT"
+25
View File
@@ -11,6 +11,24 @@ MIN_NODE_VERSION = "20"
MIN_PYTHON_VERSION = "3.11"
class TTLConfig(TypedDict, total=False):
"""Configuration for TTL (time-to-live) behavior in the store."""
refresh_on_read: bool
"""Default behavior for refreshing TTLs on read operations (GET and SEARCH).
If True, TTLs will be refreshed on read operations (get/search) by default.
This can be overridden per-operation by explicitly setting refresh_ttl.
Defaults to True if not configured.
"""
default_ttl: Optional[float]
"""Optional. Default TTL (time-to-live) in minutes for new items.
If provided, all new items will have this TTL unless explicitly overridden.
If omitted, items will have no TTL by default.
"""
class IndexConfig(TypedDict, total=False):
"""Configuration for indexing documents for semantic search in the store.
@@ -79,6 +97,13 @@ class StoreConfig(TypedDict, total=False):
If omitted, no vector index is initialized.
"""
ttl: Optional[TTLConfig]
"""Optional. Defines the TTL (time-to-live) behavior configuration.
If provided, the store will apply TTL settings according to the configuration.
If omitted, no TTL behavior is configured.
"""
class SecurityConfig(TypedDict, total=False):
"""Configuration for OpenAPI security definitions and requirements.
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.75"
version = "0.1.76"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
+32
View File
@@ -397,6 +397,17 @@
}
],
"description": "Optional. Defines the vector-based semantic search configuration.\n\n- Generate embeddings according to `index.embed`\n- Enforce the embedding dimension given by `index.dims`\n- Embed only specified JSON fields (if any) from `index.fields`\n\nIf omitted, no vector index is initialized.\n"
},
"ttl": {
"anyOf": [
{
"$ref": "#/$defs/TTLConfig"
},
{
"type": "null"
}
],
"description": "Optional. Defines the TTL (time-to-live) behavior configuration.\n\nIf provided, the store will apply TTL settings according to the configuration.\nIf omitted, no TTL behavior is configured.\n"
}
},
"required": []
@@ -430,6 +441,27 @@
}
},
"required": []
},
"TTLConfig": {
"title": "TTLConfig",
"description": "Configuration for TTL (time-to-live) behavior in the store.",
"type": "object",
"properties": {
"default_ttl": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"refresh_on_read": {
"type": "boolean"
}
},
"required": []
}
},
"title": "LangGraph CLI Configuration",
+32
View File
@@ -397,6 +397,17 @@
}
],
"description": "Optional. Defines the vector-based semantic search configuration.\n\n- Generate embeddings according to `index.embed`\n- Enforce the embedding dimension given by `index.dims`\n- Embed only specified JSON fields (if any) from `index.fields`\n\nIf omitted, no vector index is initialized.\n"
},
"ttl": {
"anyOf": [
{
"$ref": "#/$defs/TTLConfig"
},
{
"type": "null"
}
],
"description": "Optional. Defines the TTL (time-to-live) behavior configuration.\n\nIf provided, the store will apply TTL settings according to the configuration.\nIf omitted, no TTL behavior is configured.\n"
}
},
"required": []
@@ -430,6 +441,27 @@
}
},
"required": []
},
"TTLConfig": {
"title": "TTLConfig",
"description": "Configuration for TTL (time-to-live) behavior in the store.",
"type": "object",
"properties": {
"default_ttl": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"refresh_on_read": {
"type": "boolean"
}
},
"required": []
}
},
"title": "LangGraph CLI Configuration",
+2 -2
View File
@@ -2163,7 +2163,7 @@ class StoreClient:
"index": index,
"ttl": ttl,
}
await self.http.put("/store/items", json=payload)
await self.http.put("/store/items", json=_provided_vals(payload))
async def get_item(
self,
@@ -4307,7 +4307,7 @@ class SyncStoreClient:
"index": index,
"ttl": ttl,
}
self.http.put("/store/items", json=payload)
self.http.put("/store/items", json=_provided_vals(payload))
def get_item(
self,
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.55"
version = "0.1.56"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"