chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory
from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore
__all__ = ["SemanticTextMemory", "VolatileMemoryStore"]
@@ -0,0 +1,86 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from numpy import ndarray
from semantic_kernel.memory.memory_record import MemoryRecord
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
@deprecated("This class will be removed in a future version.")
class MemoryQueryResult:
"""The memory query result."""
is_reference: bool
external_source_name: str | None
id: str
description: str | None
text: str | None
additional_metadata: str | None
relevance: float
embedding: ndarray | None
def __init__(
self,
is_reference: bool,
external_source_name: str | None,
id: str,
description: str | None,
text: str | None,
additional_metadata: str | None,
embedding: ndarray | None,
relevance: float,
) -> None:
"""Initialize a new instance of MemoryQueryResult.
Args:
is_reference (bool): Whether the record is a reference record.
external_source_name (Optional[str]): The name of the external source.
id (str): A unique for the record.
description (Optional[str]): The description of the record.
text (Optional[str]): The text of the record.
additional_metadata (Optional[str]): Custom metadata for the record.
embedding (ndarray): The embedding of the record.
relevance (float): The relevance of the record to a known query.
Returns:
None: None.
"""
self.is_reference = is_reference
self.external_source_name = external_source_name
self.id = id
self.description = description
self.text = text
self.additional_metadata = additional_metadata
self.relevance = relevance
self.embedding = embedding
@staticmethod
def from_memory_record(
record: MemoryRecord,
relevance: float,
) -> "MemoryQueryResult":
"""Create a new instance of MemoryQueryResult from a MemoryRecord.
Args:
record (MemoryRecord): The MemoryRecord to create the MemoryQueryResult from.
relevance (float): The relevance of the record to a known query.
Returns:
MemoryQueryResult: The created MemoryQueryResult.
"""
return MemoryQueryResult(
is_reference=record._is_reference,
external_source_name=record._external_source_name,
id=record._id,
description=record._description,
text=record._text,
additional_metadata=record._additional_metadata,
embedding=record._embedding,
relevance=relevance,
)
@@ -0,0 +1,154 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from datetime import datetime
from numpy import ndarray
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
@deprecated("This class will be removed in a future version.")
class MemoryRecord:
"""The in-built memory record."""
_key: str
_timestamp: datetime | None
_is_reference: bool
_external_source_name: str | None
_id: str
_description: str | None
_text: str | None
_additional_metadata: str | None
_embedding: ndarray
def __init__(
self,
is_reference: bool,
external_source_name: str | None,
id: str,
description: str | None,
text: str | None,
additional_metadata: str | None,
embedding: ndarray | None,
key: str | None = None,
timestamp: datetime | None = None,
) -> None:
"""Initialize a new instance of MemoryRecord.
Args:
is_reference (bool): Whether the record is a reference record.
external_source_name (Optional[str]): The name of the external source.
id (str): A unique for the record.
description (Optional[str]): The description of the record.
text (Optional[str]): The text of the record.
additional_metadata (Optional[str]): Custom metadata for the record.
embedding (ndarray): The embedding of the record.
key (Optional[str]): The key of the record.
timestamp (Optional[datetime]): The timestamp of the record.
"""
self._key = key
self._timestamp = timestamp
self._is_reference = is_reference
self._external_source_name = external_source_name
self._id = id
self._description = description
self._text = text
self._additional_metadata = additional_metadata
self._embedding = embedding
@staticmethod
def reference_record(
external_id: str,
source_name: str,
description: str | None,
additional_metadata: str | None,
embedding: ndarray,
) -> "MemoryRecord":
"""Create a reference record.
Args:
external_id (str): The external id of the record.
source_name (str): The name of the external source.
description (Optional[str]): The description of the record.
additional_metadata (Optional[str]): Custom metadata for the record.
embedding (ndarray): The embedding of the record.
Returns:
MemoryRecord: The reference record.
"""
return MemoryRecord(
is_reference=True,
external_source_name=source_name,
id=external_id,
description=description,
text=None,
additional_metadata=additional_metadata,
embedding=embedding,
)
@staticmethod
def local_record(
id: str,
text: str,
description: str | None,
additional_metadata: str | None,
embedding: ndarray,
timestamp: datetime | None = None,
) -> "MemoryRecord":
"""Create a local record.
Args:
id (str): A unique for the record.
text (str): The text of the record.
description (Optional[str]): The description of the record.
additional_metadata (Optional[str]): Custom metadata for the record.
embedding (ndarray): The embedding of the record.
timestamp (Optional[datetime]): The timestamp of the record.
Returns:
MemoryRecord: The local record.
"""
return MemoryRecord(
is_reference=False,
external_source_name=None,
id=id,
description=description,
text=text,
additional_metadata=additional_metadata,
timestamp=timestamp,
embedding=embedding,
)
@property
def id(self):
"""Get the unique identifier for the memory record."""
return self._id
@property
def embedding(self) -> ndarray:
"""Get the embedding of the memory record."""
return self._embedding
@property
def text(self):
"""Get the text of the memory record."""
return self._text
@property
def additional_metadata(self):
"""Get the additional metadata of the memory record."""
return self._additional_metadata
@property
def description(self):
"""Get the description of the memory record."""
return self._description
@property
def timestamp(self):
"""Get the timestamp of the memory record."""
return self._timestamp
@@ -0,0 +1,203 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from abc import ABC, abstractmethod
from numpy import ndarray
from semantic_kernel.memory.memory_record import MemoryRecord
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
@deprecated("This class will be removed in a future version.")
class MemoryStoreBase(ABC):
"""Base class for memory store."""
async def __aenter__(self):
"""Enter the context manager."""
return self
async def __aexit__(self, *args):
"""Exit the context manager."""
await self.close()
async def close(self):
"""Close the connection."""
pass
@abstractmethod
async def create_collection(self, collection_name: str) -> None:
"""Creates a new collection in the data store.
Args:
collection_name (str): The name associated with a collection of embeddings.
"""
pass
@abstractmethod
async def get_collections(
self,
) -> list[str]:
"""Gets all collection names in the data store.
Returns:
List[str]: A group of collection names.
"""
pass
@abstractmethod
async def delete_collection(self, collection_name: str) -> None:
"""Deletes a collection from the data store.
Args:
collection_name (str): The name associated with a collection of embeddings.
"""
pass
@abstractmethod
async def does_collection_exist(self, collection_name: str) -> bool:
"""Determines if a collection exists in the data store.
Args:
collection_name (str): The name associated with a collection of embeddings.
Returns:
bool: True if given collection exists, False if not.
"""
pass
@abstractmethod
async def upsert(self, collection_name: str, record: MemoryRecord) -> str:
"""Upserts a memory record into the data store.
Does not guarantee that the collection exists.
If the record already exists, it will be updated.
If the record does not exist, it will be created.
Args:
collection_name (str): The name associated with a collection of embeddings.
record (MemoryRecord): The memory record to upsert.
Returns:
str: The unique identifier for the memory record.
"""
pass
@abstractmethod
async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]:
"""Upserts a group of memory records into the data store.
Does not guarantee that the collection exists.
If the record already exists, it will be updated.
If the record does not exist, it will be created.
Args:
collection_name (str): The name associated with a collection of embeddings.
records (MemoryRecord): The memory records to upsert.
Returns:
List[str]: The unique identifiers for the memory records.
"""
pass
@abstractmethod
async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord:
"""Gets a memory record from the data store. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
key (str): The unique id associated with the memory record to get.
with_embedding (bool): If true, the embedding will be returned in the memory record.
Returns:
MemoryRecord: The memory record if found
"""
pass
@abstractmethod
async def get_batch(
self,
collection_name: str,
keys: list[str],
with_embeddings: bool,
) -> list[MemoryRecord]:
"""Gets a batch of memory records from the data store. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
keys (List[str]): The unique ids associated with the memory records to get.
with_embeddings (bool): If true, the embedding will be returned in the memory records.
Returns:
List[MemoryRecord]: The memory records associated with the unique keys provided.
"""
pass
@abstractmethod
async def remove(self, collection_name: str, key: str) -> None:
"""Removes a memory record from the data store. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
key (str): The unique id associated with the memory record to remove.
"""
pass
@abstractmethod
async def remove_batch(self, collection_name: str, keys: list[str]) -> None:
"""Removes a batch of memory records from the data store. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
keys (List[str]): The unique ids associated with the memory records to remove.
"""
pass
@abstractmethod
async def get_nearest_matches(
self,
collection_name: str,
embedding: ndarray,
limit: int,
min_relevance_score: float,
with_embeddings: bool,
) -> list[tuple[MemoryRecord, float]]:
"""Gets the nearest matches to an embedding of type float. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
embedding (ndarray): The embedding to compare the collection's embeddings with.
limit (int): The maximum number of similarity results to return.
min_relevance_score (float): The minimum relevance threshold for returned results.
with_embeddings (bool): If true, the embeddings will be returned in the memory records.
Returns:
List[Tuple[MemoryRecord, float]]: A list of tuples where item1 is a MemoryRecord and item2
is its similarity score as a float.
"""
pass
@abstractmethod
async def get_nearest_match(
self,
collection_name: str,
embedding: ndarray,
min_relevance_score: float,
with_embedding: bool,
) -> tuple[MemoryRecord, float]:
"""Gets the nearest match to an embedding of type float. Does not guarantee that the collection exists.
Args:
collection_name (str): The name associated with a collection of embeddings.
embedding (ndarray): The embedding to compare the collection's embeddings with.
min_relevance_score (float): The minimum relevance threshold for returned result.
with_embedding (bool): If true, the embeddings will be returned in the memory record.
Returns:
Tuple[MemoryRecord, float]: A tuple consisting of the MemoryRecord and the similarity score as a float.
"""
pass
@@ -0,0 +1,60 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from semantic_kernel.memory.memory_query_result import MemoryQueryResult
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
@deprecated("This class will be removed in a future version.")
class NullMemory(SemanticTextMemoryBase):
"""Class for null memory."""
async def save_information(
self,
collection: str,
text: str,
id: str,
description: str | None = None,
additional_metadata: str | None = None,
) -> None:
"""Nullifies behavior of SemanticTextMemoryBase save_information."""
return
async def save_reference(
self,
collection: str,
text: str,
external_id: str,
external_source_name: str,
description: str | None = None,
additional_metadata: str | None = None,
) -> None:
"""Nullifies behavior of SemanticTextMemoryBase save_reference."""
return
async def get(self, collection: str, query: str) -> MemoryQueryResult | None:
"""Nullifies behavior of SemanticTextMemoryBase get."""
return None
async def search(
self,
collection: str,
query: str,
limit: int = 1,
min_relevance_score: float = 0.7,
) -> list[MemoryQueryResult]:
"""Nullifies behavior of SemanticTextMemoryBase search."""
return []
async def get_collections(self) -> list[str]:
"""Nullifies behavior of SemanticTextMemoryBase get_collections."""
return []
NullMemory.instance = NullMemory() # type: ignore
@@ -0,0 +1,165 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from typing import Any
from pydantic import PrivateAttr
from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.memory.memory_query_result import MemoryQueryResult
from semantic_kernel.memory.memory_record import MemoryRecord
from semantic_kernel.memory.memory_store_base import MemoryStoreBase
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
@deprecated("This class will be removed in a future version.")
class SemanticTextMemory(SemanticTextMemoryBase):
"""Class for semantic text memory."""
_storage: MemoryStoreBase = PrivateAttr()
_embeddings_generator: EmbeddingGeneratorBase = PrivateAttr()
def __init__(self, storage: MemoryStoreBase, embeddings_generator: EmbeddingGeneratorBase) -> None:
"""Initialize a new instance of SemanticTextMemory.
Args:
storage (MemoryStoreBase): The MemoryStoreBase to use for storage.
embeddings_generator (EmbeddingGeneratorBase): The EmbeddingGeneratorBase
to use for generating embeddings.
"""
super().__init__()
self._storage = storage
self._embeddings_generator = embeddings_generator
async def save_information(
self,
collection: str,
text: str,
id: str,
description: str | None = None,
additional_metadata: str | None = None,
embeddings_kwargs: dict[str, Any] | None = None,
) -> None:
"""Save information to the memory (calls the memory store's upsert method).
Args:
collection (str): The collection to save the information to.
text (str): The text to save.
id (str): The id of the information.
description (Optional[str]): The description of the information.
additional_metadata (Optional[str]): Additional metadata of the information.
embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information.
"""
if not await self._storage.does_collection_exist(collection_name=collection):
await self._storage.create_collection(collection_name=collection)
embedding = (await self._embeddings_generator.generate_embeddings([text], **(embeddings_kwargs or {})))[0]
data = MemoryRecord.local_record(
id=id,
text=text,
description=description,
additional_metadata=additional_metadata,
embedding=embedding,
)
await self._storage.upsert(collection_name=collection, record=data)
async def save_reference(
self,
collection: str,
text: str,
external_id: str,
external_source_name: str,
description: str | None = None,
additional_metadata: str | None = None,
embeddings_kwargs: dict[str, Any] | None = None,
) -> None:
"""Save a reference to the memory (calls the memory store's upsert method).
Args:
collection (str): The collection to save the reference to.
text (str): The text to save.
external_id (str): The external id of the reference.
external_source_name (str): The external source name of the reference.
description (Optional[str]): The description of the reference.
additional_metadata (Optional[str]): Additional metadata of the reference.
embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the reference.
"""
if not await self._storage.does_collection_exist(collection_name=collection):
await self._storage.create_collection(collection_name=collection)
embedding = (await self._embeddings_generator.generate_embeddings([text], **(embeddings_kwargs or {})))[0]
data = MemoryRecord.reference_record(
external_id=external_id,
source_name=external_source_name,
description=description,
additional_metadata=additional_metadata,
embedding=embedding,
)
await self._storage.upsert(collection_name=collection, record=data)
async def get(
self,
collection: str,
key: str,
) -> MemoryQueryResult | None:
"""Get information from the memory (calls the memory store's get method).
Args:
collection (str): The collection to get the information from.
key (str): The key of the information.
Returns:
Optional[MemoryQueryResult]: The MemoryQueryResult if found, None otherwise.
"""
record = await self._storage.get(collection_name=collection, key=key)
return MemoryQueryResult.from_memory_record(record, 1.0) if record else None
async def search(
self,
collection: str,
query: str,
limit: int = 1,
min_relevance_score: float = 0.0,
with_embeddings: bool = False,
embeddings_kwargs: dict[str, Any] | None = None,
) -> list[MemoryQueryResult]:
"""Search the memory (calls the memory store's get_nearest_matches method).
Args:
collection (str): The collection to search in.
query (str): The query to search for.
limit (int): The maximum number of results to return. (default: {1})
min_relevance_score (float): The minimum relevance score to return. (default: {0.0})
with_embeddings (bool): Whether to return the embeddings of the results. (default: {False})
embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information.
Returns:
List[MemoryQueryResult]: The list of MemoryQueryResult found.
"""
query_embedding = (await self._embeddings_generator.generate_embeddings([query], **(embeddings_kwargs or {})))[
0
]
results = await self._storage.get_nearest_matches(
collection_name=collection,
embedding=query_embedding,
limit=limit,
min_relevance_score=min_relevance_score,
with_embeddings=with_embeddings,
)
return [MemoryQueryResult.from_memory_record(r[0], r[1]) for r in results]
async def get_collections(self) -> list[str]:
"""Get the list of collections in the memory (calls the memory store's get_collections method).
Returns:
List[str]: The list of all the memory collection names.
"""
return await self._storage.get_collections()
@@ -0,0 +1,116 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from abc import abstractmethod
from typing import TYPE_CHECKING, Any, TypeVar
from semantic_kernel.kernel_pydantic import KernelBaseModel
if TYPE_CHECKING:
from semantic_kernel.memory.memory_query_result import MemoryQueryResult
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
SemanticTextMemoryT = TypeVar("SemanticTextMemoryT", bound="SemanticTextMemoryBase")
@deprecated("This class will be removed in a future version.")
class SemanticTextMemoryBase(KernelBaseModel):
"""Base class for semantic text memory."""
@abstractmethod
async def save_information(
self,
collection: str,
text: str,
id: str,
description: str | None = None,
additional_metadata: str | None = None,
embeddings_kwargs: dict[str, Any] | None = None,
) -> None:
"""Save information to the memory (calls the memory store's upsert method).
Args:
collection (str): The collection to save the information to.
text (str): The text to save.
id (str): The id of the information.
description (Optional[str]): The description of the information.
additional_metadata (Optional[str]): Additional metadata of the information.
embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information.
"""
pass
@abstractmethod
async def save_reference(
self,
collection: str,
text: str,
external_id: str,
external_source_name: str,
description: str | None = None,
additional_metadata: str | None = None,
) -> None:
"""Save a reference to the memory (calls the memory store's upsert method).
Args:
collection (str): The collection to save the reference to.
text (str): The text to save.
external_id (str): The external id of the reference.
external_source_name (str): The external source name of the reference.
description (Optional[str]): The description of the reference.
additional_metadata (Optional[str]): Additional metadata of the reference.
"""
pass
@abstractmethod
async def get(
self,
collection: str,
key: str,
) -> "MemoryQueryResult | None":
"""Get information from the memory (calls the memory store's get method).
Args:
collection (str): The collection to get the information from.
key (str): The key of the information.
Returns:
Optional[MemoryQueryResult]: The MemoryQueryResult if found, None otherwise.
"""
pass
@abstractmethod
async def search(
self,
collection: str,
query: str,
limit: int = 1,
min_relevance_score: float = 0.7,
) -> list["MemoryQueryResult"]:
"""Search the memory (calls the memory store's get_nearest_matches method).
Args:
collection (str): The collection to search in.
query (str): The query to search for.
limit (int): The maximum number of results to return. (default: {1})
min_relevance_score (float): The minimum relevance score to return. (default: {0.0})
with_embeddings (bool): Whether to return the embeddings of the results. (default: {False})
Returns:
List[MemoryQueryResult]: The list of MemoryQueryResult found.
"""
pass
@abstractmethod
async def get_collections(self) -> list[str]:
"""Get the list of collections in the memory (calls the memory store's get_collections method).
Returns:
List[str]: The list of all the memory collection names.
"""
pass
@@ -0,0 +1,324 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
import sys
from copy import deepcopy
from numpy import array, linalg, ndarray
from semantic_kernel.exceptions import ServiceResourceNotFoundError
from semantic_kernel.memory.memory_record import MemoryRecord
from semantic_kernel.memory.memory_store_base import MemoryStoreBase
if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated
logger: logging.Logger = logging.getLogger(__name__)
@deprecated("This class will be removed in a future version. Please use the InMemoryStore and Collection instead.")
class VolatileMemoryStore(MemoryStoreBase):
"""A volatile memory store that stores data in memory."""
_store: dict[str, dict[str, MemoryRecord]]
def __init__(self) -> None:
"""Initializes a new instance of the VolatileMemoryStore class."""
self._store = {}
async def create_collection(self, collection_name: str) -> None:
"""Creates a new collection if it does not exist.
Args:
collection_name (str): The name of the collection to create.
Returns:
None
"""
if collection_name in self._store:
pass
else:
self._store[collection_name] = {}
async def get_collections(
self,
) -> list[str]:
"""Gets the list of collections.
Returns:
List[str]: The list of collections.
"""
return list(self._store.keys())
async def delete_collection(self, collection_name: str) -> None:
"""Deletes a collection.
Args:
collection_name (str): The name of the collection to delete.
Returns:
None
"""
if collection_name in self._store:
del self._store[collection_name]
async def does_collection_exist(self, collection_name: str) -> bool:
"""Checks if a collection exists.
Args:
collection_name (str): The name of the collection to check.
Returns:
bool: True if the collection exists; otherwise, False.
"""
return collection_name in self._store
async def upsert(self, collection_name: str, record: MemoryRecord) -> str:
"""Upserts a record.
Args:
collection_name (str): The name of the collection to upsert the record into.
record (MemoryRecord): The record to upsert.
Returns:
str: The unique database key of the record.
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
record._key = record._id
self._store[collection_name][record._key] = record
return record._key
async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]:
"""Upserts a batch of records.
Args:
collection_name (str): The name of the collection to upsert the records into.
records (List[MemoryRecord]): The records to upsert.
Returns:
List[str]: The unique database keys of the records.
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
for record in records:
record._key = record._id
self._store[collection_name][record._key] = record
return [record._key for record in records]
async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord:
"""Gets a record.
Args:
collection_name (str): The name of the collection to get the record from.
key (str): The unique database key of the record.
with_embedding (bool): Whether to include the embedding in the result. (default: {False})
Returns:
MemoryRecord: The record.
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
if key not in self._store[collection_name]:
raise ServiceResourceNotFoundError(f"Key '{key}' not found in collection '{collection_name}'")
result = self._store[collection_name][key]
if not with_embedding:
# create copy of results without embeddings
result = deepcopy(result)
result._embedding = None
return result
async def get_batch(
self, collection_name: str, keys: list[str], with_embeddings: bool = False
) -> list[MemoryRecord]:
"""Gets a batch of records.
Args:
collection_name (str): The name of the collection to get the records from.
keys (List[str]): The unique database keys of the records.
with_embeddings (bool): Whether to include the embeddings in the results. (default: {False})
Returns:
List[MemoryRecord]: The records.
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
results = [self._store[collection_name][key] for key in keys if key in self._store[collection_name]]
if not with_embeddings:
# create copy of results without embeddings
for result in results:
result = deepcopy(result)
result._embedding = None
return results
async def remove(self, collection_name: str, key: str) -> None:
"""Removes a record.
Args:
collection_name (str): The name of the collection to remove the record from.
key (str): The unique database key of the record to remove.
Returns:
None
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
if key not in self._store[collection_name]:
raise ServiceResourceNotFoundError(f"Key '{key}' not found in collection '{collection_name}'")
del self._store[collection_name][key]
async def remove_batch(self, collection_name: str, keys: list[str]) -> None:
"""Removes a batch of records.
Args:
collection_name (str): The name of the collection to remove the records from.
keys (List[str]): The unique database keys of the records to remove.
Returns:
None
"""
if collection_name not in self._store:
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
for key in keys:
if key in self._store[collection_name]:
del self._store[collection_name][key]
async def get_nearest_match(
self,
collection_name: str,
embedding: ndarray,
min_relevance_score: float = 0.0,
with_embedding: bool = False,
) -> tuple[MemoryRecord, float]:
"""Gets the nearest match to an embedding using cosine similarity.
Args:
collection_name (str): The name of the collection to get the nearest match from.
embedding (ndarray): The embedding to find the nearest match to.
min_relevance_score (float): The minimum relevance score of the match. (default: {0.0})
with_embedding (bool): Whether to include the embedding in the result. (default: {False})
Returns:
Tuple[MemoryRecord, float]: The record and the relevance score.
"""
return self.get_nearest_matches(
collection_name=collection_name,
embedding=embedding,
limit=1,
min_relevance_score=min_relevance_score,
with_embeddings=with_embedding,
)
async def get_nearest_matches(
self,
collection_name: str,
embedding: ndarray,
limit: int,
min_relevance_score: float = 0.0,
with_embeddings: bool = False,
) -> list[tuple[MemoryRecord, float]]:
"""Gets the nearest matches to an embedding using cosine similarity.
Args:
collection_name (str): The name of the collection to get the nearest matches from.
embedding (ndarray): The embedding to find the nearest matches to.
limit (int): The maximum number of matches to return.
min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0})
with_embeddings (bool): Whether to include the embeddings in the results. (default: {False})
Returns:
List[Tuple[MemoryRecord, float]]: The records and their relevance scores.
"""
if collection_name not in self._store:
logger.warning(
f"Collection '{collection_name}' does not exist in collections: "
f"{', '.join([collection for collection in await self.get_collections()])}"
)
return []
# Get all the records in the collection
memory_records = list(self._store[collection_name].values())
# Convert the collection of embeddings into a numpy array (stacked)
embeddings = array([x._embedding for x in memory_records], dtype=float)
embeddings = embeddings.reshape(embeddings.shape[0], -1)
# If the query embedding has shape (1, embedding_size),
# reshape it to (embedding_size,)
if len(embedding.shape) == 2:
embedding = embedding.reshape(
embedding.shape[1],
)
# Use numpy to get the cosine similarity between the query
# embedding and all the embeddings in the collection
similarity_scores = self.compute_similarity_scores(embedding, embeddings)
# Then, sort the results by the similarity score
sorted_results = sorted(
zip(memory_records, similarity_scores),
key=lambda x: x[1],
reverse=True,
)
# Then, filter out the results that are below the minimum relevance score
filtered_results = [x for x in sorted_results if x[1] >= min_relevance_score]
# Then, take the top N results
top_results = filtered_results[:limit]
if not with_embeddings:
# create copy of results without embeddings
for result in top_results:
result = deepcopy(result)
result[0]._embedding = None
return top_results
def compute_similarity_scores(self, embedding: ndarray, embedding_array: ndarray) -> ndarray:
"""Computes the cosine similarity scores between a query embedding and a group of embeddings.
Args:
embedding (ndarray): The query embedding.
embedding_array (ndarray): The group of embeddings.
Returns:
ndarray: The cosine similarity scores.
"""
query_norm = linalg.norm(embedding)
collection_norm = linalg.norm(embedding_array, axis=1)
# Compute indices for which the similarity scores can be computed
valid_indices = (query_norm != 0) & (collection_norm != 0)
# Initialize the similarity scores with -1 to distinguish the cases
# between zero similarity from orthogonal vectors and invalid similarity
similarity_scores = array([-1.0] * embedding_array.shape[0])
if valid_indices.any():
similarity_scores[valid_indices] = embedding.dot(embedding_array[valid_indices].T) / (
query_norm * collection_norm[valid_indices]
)
if not valid_indices.all():
logger.warning(
"Some vectors in the embedding collection are zero vectors."
"Ignoring cosine similarity score computation for those vectors."
)
else:
raise ValueError(
f"Invalid vectors, cannot compute cosine similarity scores"
f"for zero vectors"
f"{embedding_array} or {embedding}"
)
return similarity_scores