microsoft/semantic-kernel · warning · ValueError

City '{city}' is not in the list of cities: {', '.join(citie

Error message

City '{city}' is not in the list of cities: {', '.join(cities)}

What it means

ValueError raised by the sample filter_update callback used with the Azure AI Search hotel sample: when a 'city' argument is provided, it validates the value against a fixed list of known cities and refuses unknown values before constructing the search filter. It prevents injecting an arbitrary string into the Azure AI Search OData filter lambda.

Source

Thrown at python/samples/concepts/memory/azure_ai_search_hotel_samples/2_use_as_a_plugin.py:64

# This function has to adhere to the `DynamicFilterFunction` signature.
# which consists of 2 named arguments, `filter`, and `parameters`.
# and kwargs.
# It returns the updated filter.
# The default version that is used when not supplying this, reads the parameters and if there is
# a parameter that is not `query`, `top`, or 'skip`, and it can find a value for it, either in the kwargs
# or the default value specified in the parameter, it will add a filter to the options.
# In this case, we are adding a filter to the options to filter by the city, but since the technical name
# of that field in the index is `address/city`, want to do this manually.
# this can also be used to replace a complex technical name in your index with a friendly name towards the LLM.
def filter_update(
    filter: OptionalOneOrList[Callable | str] | None = None,
    parameters: list["KernelParameterMetadata"] | None = None,
    **kwargs: Any,
) -> OptionalOneOrList[Callable | str] | None:
    if "city" in kwargs:
        city = kwargs["city"]
        if city not in cities:
            raise ValueError(f"City '{city}' is not in the list of cities: {', '.join(cities)}")
        # we need the actual value and not a named param, otherwise the parser will not be able to find it.
        new_filter = f"lambda x: x.Address.City == '{city}'"
        if filter is None:
            filter = new_filter
        elif isinstance(filter, list):
            filter.append(new_filter)
        else:
            filter = [filter, new_filter]
    return filter


instructions = """You are a travel agent. Your name is Mosscap and
you have one goal: help people find a hotel.
Your full name, should you need to know it, is
Splendid Speckled Mosscap. You communicate
effectively, but you tend to answer with long
flowery prose. You always make sure to include the
hotel_id in your answers so that the user can

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Pass one of the cities listed in the error message (it joins the allowed set).
  2. Expand the cities list to include the requested city only if that city actually exists in the index.
  3. Use the enum/choices pattern so the model only proposes valid cities (annotate the parameter with allowed values).
  4. Sanitize by mapping near-matches to the closest valid city before validation.

Example fix

# before
if city not in cities:
    raise ValueError(f"City '{city}' is not in the list of cities: {', '.join(cities)}")
# after - suggest the closest match
from difflib import get_close_matches
if city not in cities:
    suggestion = get_close_matches(city, cities, n=1)
    hint = f" Did you mean {suggestion[0]!r}?" if suggestion else ""
    raise ValueError(f"City '{city}' is not in the list of cities: {', '.join(cities)}{hint}")
Defensive patterns

Strategy: validation

Validate before calling

def validate_city(city: str, cities):
    if city not in cities:
        from difflib import get_close_matches
        suggestion = get_close_matches(city, cities, n=1)
        hint = f" Did you mean {suggestion[0]!r}?" if suggestion else ""
        raise ValueError(f"City '{city}' is not in the list: {', '.join(cities)}{hint}")
    return city

Type guard

def is_known_city(city: object, cities) -> bool:
    return isinstance(city, str) and city in cities

Try / catch

try:
    result = await kernel.invoke(search_func, city=city)
except ValueError as e:
    # surface allowed cities back to the model/user
    print(e)

Prevention

When it happens

Trigger: The LLM or a caller passes a city value not in the cities list (e.g. a typo, a city not in the index, or an injection attempt) as the 'city' argument to the function whose options hook is filter_update.

Common situations: The model hallucinates a city not in the index; a user typo; the cities list was edited but the index was not; or an attempt to abuse the free-text city into the OData filter string.

Related errors


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