microsoft/semantic-kernel · error · VectorStoreOperationException

Error evaluating filter: {e}

Error message

Error evaluating filter: {e}

What it means

Thrown by _get_filtered_records (in_memory.py:734-735) as VectorStoreOperationException wrapping any exception raised while preparing string filters inside _parse_and_validate_filter. The original error is chained as __cause__, and its text is embedded after 'Error evaluating filter: '. So this is the top-level message users see; the precise reason (oversized, invalid python, disallowed node, blocked attribute, etc.) lives in e.__cause__.

Source

Thrown at python/semantic_kernel/connectors/in_memory.py:735

        for idx, key in enumerate(return_records.keys()):
            if idx >= skip:
                returned += 1
                rec = self.inner_storage[key]
                rec[IN_MEMORY_SCORE_KEY] = return_records[key]
                yield rec
                if returned >= top:
                    break

    def _get_filtered_records(self, options: VectorSearchOptions) -> dict[TKey, AttributeDict]:
        if not options.filter:
            return self.inner_storage
        try:
            callable_filters = [
                self._parse_and_validate_filter(filter) if isinstance(filter, str) else filter
                for filter in ([options.filter] if not isinstance(options.filter, list) else options.filter)
            ]
        except Exception as e:
            raise VectorStoreOperationException(f"Error evaluating filter: {e}") from e
        filtered_records: dict[TKey, AttributeDict] = {}
        for key, record in self.inner_storage.items():
            for filter in callable_filters:
                if self._run_filter(filter, record):
                    filtered_records[key] = record
        return filtered_records

    def _parse_and_validate_filter(self, filter_str: str) -> Callable:
        """Parse and validate a string filter as a lambda expression, then return the callable.

        Uses an allowlist approach - only explicitly permitted AST node types and function names
        are allowed. This can be customized by overriding `allowed_filter_ast_nodes` and
        `allowed_filter_functions` class attributes.
        """
        if len(filter_str) > self.max_filter_source_length:
            raise VectorStoreOperationException("Filter string exceeds the maximum allowed length.")

        try:

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Inspect e.__cause__ (or the text after the colon) to find the specific failure, then apply the matching fix (1334-1339).
  2. If the filter is dynamically built, validate it with ast.parse + a lambda check before passing it to search.
  3. Pass options.filter as a Python callable to bypass string parsing entirely when the filter is trusted and complex.
  4. Catch VectorStoreOperationException at the search boundary and fall back to an unfiltered or simpler query.

Example fix

# before
opts = VectorSearchOptions(filter="x.id == 1")  # not a lambda -> wrapped error
# after
opts = VectorSearchOptions(filter="lambda x: x.id == 1")
Defensive patterns

Strategy: try-catch

Validate before calling

import ast

SAFE_NODES = {
    ast.Expression, ast.Lambda, ast.arguments, ast.arg, ast.Compare, ast.BoolOp,
    ast.UnaryOp, ast.And, ast.Or, ast.Not, ast.Eq, ast.NotEq, ast.Lt, ast.LtE,
    ast.Gt, ast.GtE, ast.In, ast.NotIn, ast.Is, ast.IsNot, ast.Name, ast.Load,
    ast.Attribute, ast.Subscript, ast.Slice, ast.Constant, ast.List, ast.Tuple,
    ast.Set, ast.Dict, ast.BinOp, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod,
    ast.FloorDiv, ast.Call,
}

def preflight_filter(expr: str, *, max_len: int = 2048, max_nodes: int = 128) -> None:
    if len(expr) > max_len:
        raise ValueError("filter too long")
    try:
        tree = ast.parse(expr, mode="eval")
    except SyntaxError as e:
        raise ValueError(f"invalid python: {e}") from e
    if not (isinstance(tree, ast.Expression) and isinstance(tree.body, ast.Lambda)):
        raise ValueError("filter must be a lambda expression")
    blocked = {"__class__", "__globals__", "__subclasses__", "__builtins__", "__code__"}
    for n in ast.walk(tree):
        if isinstance(n, ast.Attribute) and n.attr in blocked:
            raise ValueError(f"blocked attribute: {n.attr}")
        if type(n) not in SAFE_NODES:
            raise ValueError(f"disallowed node: {type(n).__name__}")
    if sum(1 for _ in ast.walk(tree)) > max_nodes:
        raise ValueError("filter too complex")

Try / catch

try:
    results = await collection.search(search_type=SearchType.VECTOR, options=opts)
except VectorStoreOperationException as e:
    logger.warning("filter rejected: %s", e.__cause__ or e)
    results = None

Prevention

When it happens

Trigger: Any string filter that fails parse-time validation, e.g. a typo'd lambda, an oversized string, a disallowed AST node, or a blocked dunder attribute; also fires for a non-string/non-callable filter value that breaks the comprehension.

Common situations: Building filter strings dynamically and hitting a limit or a typo; user-supplied filter text that is malformed; passing a dict instead of a string/callable as options.filter.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/9e7d8ef1903ecfc0. Report an issue: GitHub.