Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion python/semantic_kernel/connectors/chroma.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ def _get_score_from_result(self, result: Any) -> float | None:
return result["distance"]

@override
def _lambda_parser(self, node: ast.AST) -> dict[str, Any] | str | int | float | bool | None: # type: ignore
def _lambda_parser(self, node: ast.AST) -> dict[str, Any] | list[Any] | str | int | float | bool | None: # type: ignore
# Comparison operations
match node:
case ast.Compare():
Expand Down Expand Up @@ -430,6 +430,8 @@ def _lambda_parser(self, node: ast.AST) -> dict[str, Any] | str | int | float |
if isinstance(value, (int, float, bool)) or value is None:
return value
raise VectorStoreOperationException(f"Unsupported constant type: {type(value)}")
case ast.List():
return [self._lambda_parser(elt) for elt in node.elts]
Comment thread
eavanvalkenburg marked this conversation as resolved.
Resolved
raise NotImplementedError(f"Unsupported AST node: {type(node)}")


Expand Down
2 changes: 2 additions & 0 deletions python/semantic_kernel/connectors/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ def _lambda_parser(self, node: ast.AST) -> Any:
return node.id
case ast.Constant():
return node.value
case ast.List():
return [self._lambda_parser(elt) for elt in node.elts]
raise NotImplementedError(f"Unsupported AST node: {type(node)}")

@override
Expand Down
15 changes: 6 additions & 9 deletions python/semantic_kernel/connectors/pinecone.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,15 +541,10 @@ def _lambda_parser(self, node: ast.AST) -> Any:
case ast.Not():
operand = self._lambda_parser(node.operand)
# Pinecone only supports $not over $in (becomes $nin)
if (
isinstance(operand, dict)
and len(operand) == 1
and isinstance(next(operand.values()), dict) # type: ignore
and "$in" in next(operand.values()) # type: ignore
):
field = next(operand.keys()) # type: ignore
values = next(operand.values())["$in"] # type: ignore
return {field: {"$nin": values}}
if isinstance(operand, dict) and len(operand) == 1:
field, condition = next(iter(operand.items()))
if isinstance(condition, dict) and "$in" in condition:
return {field: {"$nin": condition["$in"]}}
raise NotImplementedError(
"$not is only supported over $in (i.e., for ![...].contains(field)). "
"Other NOT expressions are not supported by Pinecone."
Expand All @@ -574,6 +569,8 @@ def _lambda_parser(self, node: ast.AST) -> Any:
if node.value is None:
raise NotImplementedError("Pinecone does not support null checks in vector search pre-filters.")
return node.value
case ast.List():
return [self._lambda_parser(elt) for elt in node.elts]
raise NotImplementedError(f"Unsupported AST node: {type(node)}")

@override
Expand Down
2 changes: 2 additions & 0 deletions python/semantic_kernel/connectors/weaviate.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ def _lambda_parser(self, node: ast.AST) -> "_Filters | FilterValues":
if isinstance(value, (int, float, bool)) or value is None:
return value
raise VectorStoreOperationException(f"Unsupported constant type: {type(value)}")
case ast.List():
return [self._lambda_parser(elt) for elt in node.elts] # type: ignore
Comment thread
eavanvalkenburg marked this conversation as resolved.
Resolved
raise NotImplementedError(f"Unsupported AST node: {type(node)}")

async def _inner_vectorized_search(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,22 @@ async def test_mongodb_atlas_collection_collection_exists(mongodb_atlas_unit_tes
with patch.object(collection, "_get_database", new=mock_get_database) as mock_get:
mock_get.return_value.list_collection_names.return_value = ["test_collection"]
assert await collection.collection_exists()


@mark.parametrize(
"filter_str, expected",
[
("lambda x: x.content in ['a', 'b']", {"content": {"$in": ["a", "b"]}}),
("lambda x: x.content not in ['a', 'b']", {"content": {"$nin": ["a", "b"]}}),
],
)
def test_mongodb_atlas_collection_build_filter_list(
mongodb_atlas_unit_test_env, definition, mock_mongo_client, filter_str, expected
):
collection = MongoDBAtlasCollection(
record_type=dict,
definition=definition,
collection_name="test_collection",
mongo_client=mock_mongo_client,
)
assert collection._build_filter(filter_str) == expected
8 changes: 8 additions & 0 deletions python/tests/unit/connectors/memory/test_chroma.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,11 @@ async def test_chroma_collection_search(chroma_collection, mock_client, include_
async for res in results.results:
assert res.record["id"] == "1"
assert res.score == 0.1


def test_chroma_collection_build_filter_in_list(chroma_collection):
assert chroma_collection._build_filter("lambda x: x.content in ['a', 'b']") == {"content": {"$in": ["a", "b"]}}


def test_chroma_collection_build_filter_not_in_list(chroma_collection):
assert chroma_collection._build_filter("lambda x: x.content not in ['a', 'b']") == {"content": {"$nin": ["a", "b"]}}
17 changes: 17 additions & 0 deletions python/tests/unit/connectors/memory/test_pinecone.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,20 @@ async def test_search_embed(collection):
async for result in query_response.results:
assert result.record == record
assert result.score == 0.1


async def test_build_filter_in_list(collection):
assert collection._build_filter("lambda x: x.content in ['a', 'b']") == {"content": {"$in": ["a", "b"]}}


async def test_build_filter_not_in_list(collection):
assert collection._build_filter("lambda x: x.content not in ['a', 'b']") == {"content": {"$nin": ["a", "b"]}}


async def test_build_filter_not_over_in_becomes_nin(collection):
assert collection._build_filter("lambda x: not (x.content in ['a', 'b'])") == {"content": {"$nin": ["a", "b"]}}


async def test_build_filter_not_over_other_expression_raises(collection):
with raises(NotImplementedError, match="only supported over"):
collection._build_filter("lambda x: not (x.content == 'a')")
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,20 @@ async def test_weaviate_collection_deserialize_data(
await collection.get(key=data.id)

mock_inner_get.assert_called_once_with([data.id], include_vectors=False, options=None)


def test_weaviate_collection_build_filter_in_list(clear_weaviate_env, record_type, definition) -> None:
"""A list literal on the right side of `in` is translated to a contains_any filter."""
collection = WeaviateCollection(
record_type=record_type,
definition=definition,
collection_name="TestCollection",
async_client=AsyncMock(spec=WeaviateAsyncClient),
env_file_path="fake_env_file_path.env",
)

result = collection._build_filter("lambda x: x.content in ['a', 'b']")

assert result.target == "content"
assert result.value == ["a", "b"]
assert result.operator.name == "CONTAINS_ANY"
Loading