core[minor],langchain[patch],community[patch]: Move storage interfaces to core (#20750)

* Move storage interface to core
* Move in memory and file system implementation to core
This commit is contained in:
Eugene Yurtsev 2024-04-30 13:14:26 -04:00 committed by GitHub
parent 8f38b7a725
commit 3c064a757f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 159 additions and 157 deletions

View File

@ -1,5 +1,3 @@
from langchain_core.exceptions import LangChainException from langchain_core.stores import InvalidKeyException
__all__ = ["InvalidKeyException"]
class InvalidKeyException(LangChainException):
"""Raised when a key is invalid; e.g., uses incorrect characters."""

View File

@ -7,7 +7,9 @@ The primary goal of these storages is to support implementation of caching.
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import ( from typing import (
Any,
AsyncIterator, AsyncIterator,
Dict,
Generic, Generic,
Iterator, Iterator,
List, List,
@ -18,6 +20,7 @@ from typing import (
Union, Union,
) )
from langchain_core.exceptions import LangChainException
from langchain_core.runnables import run_in_executor from langchain_core.runnables import run_in_executor
K = TypeVar("K") K = TypeVar("K")
@ -123,3 +126,138 @@ class BaseStore(Generic[K, V], ABC):
ByteStore = BaseStore[str, bytes] ByteStore = BaseStore[str, bytes]
class InMemoryBaseStore(BaseStore[str, V], Generic[V]):
"""In-memory implementation of the BaseStore using a dictionary.
Attributes:
store (Dict[str, Any]): The underlying dictionary that stores
the key-value pairs.
Examples:
.. code-block:: python
from langchain.storage import InMemoryStore
store = InMemoryStore()
store.mset([('key1', 'value1'), ('key2', 'value2')])
store.mget(['key1', 'key2'])
# ['value1', 'value2']
store.mdelete(['key1'])
list(store.yield_keys())
# ['key2']
list(store.yield_keys(prefix='k'))
# ['key2']
"""
def __init__(self) -> None:
"""Initialize an empty store."""
self.store: Dict[str, V] = {}
def mget(self, keys: Sequence[str]) -> List[Optional[V]]:
"""Get the values associated with the given keys.
Args:
keys (Sequence[str]): A sequence of keys.
Returns:
A sequence of optional values associated with the keys.
If a key is not found, the corresponding value will be None.
"""
return [self.store.get(key) for key in keys]
async def amget(self, keys: Sequence[str]) -> List[Optional[V]]:
"""Get the values associated with the given keys.
Args:
keys (Sequence[str]): A sequence of keys.
Returns:
A sequence of optional values associated with the keys.
If a key is not found, the corresponding value will be None.
"""
return self.mget(keys)
def mset(self, key_value_pairs: Sequence[Tuple[str, V]]) -> None:
"""Set the values for the given keys.
Args:
key_value_pairs (Sequence[Tuple[str, V]]): A sequence of key-value pairs.
Returns:
None
"""
for key, value in key_value_pairs:
self.store[key] = value
async def amset(self, key_value_pairs: Sequence[Tuple[str, V]]) -> None:
"""Set the values for the given keys.
Args:
key_value_pairs (Sequence[Tuple[str, V]]): A sequence of key-value pairs.
Returns:
None
"""
return self.mset(key_value_pairs)
def mdelete(self, keys: Sequence[str]) -> None:
"""Delete the given keys and their associated values.
Args:
keys (Sequence[str]): A sequence of keys to delete.
"""
for key in keys:
if key in self.store:
del self.store[key]
async def amdelete(self, keys: Sequence[str]) -> None:
"""Delete the given keys and their associated values.
Args:
keys (Sequence[str]): A sequence of keys to delete.
"""
self.mdelete(keys)
def yield_keys(self, prefix: Optional[str] = None) -> Iterator[str]:
"""Get an iterator over keys that match the given prefix.
Args:
prefix (str, optional): The prefix to match. Defaults to None.
Returns:
Iterator[str]: An iterator over keys that match the given prefix.
"""
if prefix is None:
yield from self.store.keys()
else:
for key in self.store.keys():
if key.startswith(prefix):
yield key
async def ayield_keys(self, prefix: Optional[str] = None) -> AsyncIterator[str]:
"""Get an async iterator over keys that match the given prefix.
Args:
prefix (str, optional): The prefix to match. Defaults to None.
Returns:
AsyncIterator[str]: An async iterator over keys that match the given prefix.
"""
if prefix is None:
for key in self.store.keys():
yield key
else:
for key in self.store.keys():
if key.startswith(prefix):
yield key
InMemoryStore = InMemoryBaseStore[Any]
InMemoryByteStore = InMemoryBaseStore[bytes]
class InvalidKeyException(LangChainException):
"""Raised when a key is invalid; e.g., uses incorrect characters."""

View File

@ -1,4 +1,4 @@
from langchain.storage.in_memory import InMemoryStore from langchain_core.stores import InMemoryStore
def test_mget() -> None: def test_mget() -> None:

View File

@ -9,11 +9,15 @@ import warnings
from typing import Any from typing import Any
from langchain_core._api import LangChainDeprecationWarning from langchain_core._api import LangChainDeprecationWarning
from langchain_core.stores import (
InMemoryByteStore,
InMemoryStore,
InvalidKeyException,
)
from langchain.storage._lc_store import create_kv_docstore, create_lc_store from langchain.storage._lc_store import create_kv_docstore, create_lc_store
from langchain.storage.encoder_backed import EncoderBackedStore from langchain.storage.encoder_backed import EncoderBackedStore
from langchain.storage.file_system import LocalFileStore from langchain.storage.file_system import LocalFileStore
from langchain.storage.in_memory import InMemoryByteStore, InMemoryStore
from langchain.utils.interactive_env import is_interactive_env from langchain.utils.interactive_env import is_interactive_env
@ -36,12 +40,13 @@ def __getattr__(name: str) -> Any:
__all__ = [ __all__ = [
"EncoderBackedStore", "EncoderBackedStore",
"InMemoryStore",
"InMemoryByteStore",
"LocalFileStore",
"RedisStore", "RedisStore",
"create_lc_store", "create_lc_store",
"create_kv_docstore", "create_kv_docstore",
"LocalFileStore",
"InMemoryStore",
"InvalidKeyException",
"InMemoryByteStore",
"UpstashRedisByteStore", "UpstashRedisByteStore",
"UpstashRedisStore", "UpstashRedisStore",
] ]

View File

@ -1,3 +1,3 @@
from langchain_community.storage.exceptions import InvalidKeyException from langchain_core.stores import InvalidKeyException
__all__ = ["InvalidKeyException"] __all__ = ["InvalidKeyException"]

View File

@ -3,150 +3,10 @@
This is a simple implementation of the BaseStore using a dictionary that is useful This is a simple implementation of the BaseStore using a dictionary that is useful
primarily for unit testing purposes. primarily for unit testing purposes.
""" """
from typing import ( from langchain_core.stores import InMemoryBaseStore, InMemoryByteStore, InMemoryStore
Any,
AsyncIterator,
Dict,
Generic,
Iterator,
List,
Optional,
Sequence,
Tuple,
TypeVar,
)
from langchain_core.stores import BaseStore __all__ = [
"InMemoryStore",
V = TypeVar("V") "InMemoryBaseStore",
"InMemoryByteStore",
]
class InMemoryBaseStore(BaseStore[str, V], Generic[V]):
"""In-memory implementation of the BaseStore using a dictionary.
Attributes:
store (Dict[str, Any]): The underlying dictionary that stores
the key-value pairs.
Examples:
.. code-block:: python
from langchain.storage import InMemoryStore
store = InMemoryStore()
store.mset([('key1', 'value1'), ('key2', 'value2')])
store.mget(['key1', 'key2'])
# ['value1', 'value2']
store.mdelete(['key1'])
list(store.yield_keys())
# ['key2']
list(store.yield_keys(prefix='k'))
# ['key2']
"""
def __init__(self) -> None:
"""Initialize an empty store."""
self.store: Dict[str, V] = {}
def mget(self, keys: Sequence[str]) -> List[Optional[V]]:
"""Get the values associated with the given keys.
Args:
keys (Sequence[str]): A sequence of keys.
Returns:
A sequence of optional values associated with the keys.
If a key is not found, the corresponding value will be None.
"""
return [self.store.get(key) for key in keys]
async def amget(self, keys: Sequence[str]) -> List[Optional[V]]:
"""Get the values associated with the given keys.
Args:
keys (Sequence[str]): A sequence of keys.
Returns:
A sequence of optional values associated with the keys.
If a key is not found, the corresponding value will be None.
"""
return self.mget(keys)
def mset(self, key_value_pairs: Sequence[Tuple[str, V]]) -> None:
"""Set the values for the given keys.
Args:
key_value_pairs (Sequence[Tuple[str, V]]): A sequence of key-value pairs.
Returns:
None
"""
for key, value in key_value_pairs:
self.store[key] = value
async def amset(self, key_value_pairs: Sequence[Tuple[str, V]]) -> None:
"""Set the values for the given keys.
Args:
key_value_pairs (Sequence[Tuple[str, V]]): A sequence of key-value pairs.
Returns:
None
"""
return self.mset(key_value_pairs)
def mdelete(self, keys: Sequence[str]) -> None:
"""Delete the given keys and their associated values.
Args:
keys (Sequence[str]): A sequence of keys to delete.
"""
for key in keys:
if key in self.store:
del self.store[key]
async def amdelete(self, keys: Sequence[str]) -> None:
"""Delete the given keys and their associated values.
Args:
keys (Sequence[str]): A sequence of keys to delete.
"""
self.mdelete(keys)
def yield_keys(self, prefix: Optional[str] = None) -> Iterator[str]:
"""Get an iterator over keys that match the given prefix.
Args:
prefix (str, optional): The prefix to match. Defaults to None.
Returns:
Iterator[str]: An iterator over keys that match the given prefix.
"""
if prefix is None:
yield from self.store.keys()
else:
for key in self.store.keys():
if key.startswith(prefix):
yield key
async def ayield_keys(self, prefix: Optional[str] = None) -> AsyncIterator[str]:
"""Get an async iterator over keys that match the given prefix.
Args:
prefix (str, optional): The prefix to match. Defaults to None.
Returns:
AsyncIterator[str]: An async iterator over keys that match the given prefix.
"""
if prefix is None:
for key in self.store.keys():
yield key
else:
for key in self.store.keys():
if key.startswith(prefix):
yield key
InMemoryStore = InMemoryBaseStore[Any]
InMemoryByteStore = InMemoryBaseStore[bytes]

View File

@ -3,8 +3,8 @@ import tempfile
from typing import Generator from typing import Generator
import pytest import pytest
from langchain_core.stores import InvalidKeyException
from langchain.storage.exceptions import InvalidKeyException
from langchain.storage.file_system import LocalFileStore from langchain.storage.file_system import LocalFileStore

View File

@ -7,6 +7,7 @@ EXPECTED_ALL = [
"InMemoryByteStore", "InMemoryByteStore",
"LocalFileStore", "LocalFileStore",
"RedisStore", "RedisStore",
"InvalidKeyException",
"create_lc_store", "create_lc_store",
"create_kv_docstore", "create_kv_docstore",
"UpstashRedisByteStore", "UpstashRedisByteStore",