diff --git a/python/semantic_kernel/connectors/chroma.py b/python/semantic_kernel/connectors/chroma.py index 2bffb64ecd5e..e5fa0ef61ada 100644 --- a/python/semantic_kernel/connectors/chroma.py +++ b/python/semantic_kernel/connectors/chroma.py @@ -424,9 +424,9 @@ def _lambda_parser(self, node: ast.AST) -> dict[str, Any] | str | int | float | case ast.Constant(): value = node.value if isinstance(value, str): - return value.replace("'", "''") + return value if isinstance(value, bytes): - return value.decode("utf-8").replace("'", "''") + return value.decode("utf-8") if isinstance(value, (int, float, bool)) or value is None: return value raise VectorStoreOperationException(f"Unsupported constant type: {type(value)}") diff --git a/python/semantic_kernel/connectors/weaviate.py b/python/semantic_kernel/connectors/weaviate.py index 2e04753b0011..b7e75faa3806 100644 --- a/python/semantic_kernel/connectors/weaviate.py +++ b/python/semantic_kernel/connectors/weaviate.py @@ -471,9 +471,9 @@ def _lambda_parser(self, node: ast.AST) -> "_Filters | FilterValues": case ast.Constant(): value = node.value if isinstance(value, str): - return value.replace("'", "''") + return value if isinstance(value, bytes): - return value.decode("utf-8").replace("'", "''") + return value.decode("utf-8") if isinstance(value, (int, float, bool)) or value is None: return value raise VectorStoreOperationException(f"Unsupported constant type: {type(value)}") diff --git a/python/tests/unit/connectors/memory/test_chroma.py b/python/tests/unit/connectors/memory/test_chroma.py index 6f48fa6663af..67b4e5471c27 100644 --- a/python/tests/unit/connectors/memory/test_chroma.py +++ b/python/tests/unit/connectors/memory/test_chroma.py @@ -113,3 +113,9 @@ 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_keeps_apostrophes(chroma_collection): + """Chroma filters are dicts, not SQL, so the value must reach Chroma unchanged.""" + assert chroma_collection._build_filter('lambda x: x.content == "O\'Brien"') == {"content": "O'Brien"} + assert chroma_collection._build_filter('lambda x: x.content != "O\'Brien"') == {"content": {"$ne": "O'Brien"}} diff --git a/python/tests/unit/connectors/memory/weaviate/test_weaviate_collection.py b/python/tests/unit/connectors/memory/weaviate/test_weaviate_collection.py index c4ebd838aba6..d65d2addd074 100644 --- a/python/tests/unit/connectors/memory/weaviate/test_weaviate_collection.py +++ b/python/tests/unit/connectors/memory/weaviate/test_weaviate_collection.py @@ -427,3 +427,19 @@ 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_keeps_apostrophes(clear_weaviate_env, record_type, definition) -> None: + """Weaviate filters are objects, not query strings, so the value must be passed through unchanged.""" + 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 == "O\'Brien"') + + assert result.target == "content" + assert result.value == "O'Brien"