666ghj/MiroFish · error · ValueError

Zep search query must be a string

Error message

Zep search query must be a string

What it means

ValueError raised by normalize_zep_search_query when the query argument is not a str instance (None, int, list, dict, bytes). It is a type gate in front of Zep Cloud's search endpoint, which expects a non-empty string capped at MAX_ZEP_SEARCH_QUERY_CHARS (400).

Source

Thrown at backend/app/utils/zep.py:37

T = TypeVar("T")

ZEP_CLOUD_BASE_URL = "https://api.getzep.com/api/v2"
# Keep request behavior aligned with the zep-cloud 3.25.0 SDK default that
# MiroFish used before introducing the shared client. This is an internal
# integration policy, not a deployment setting users need to tune.
ZEP_HTTP_REQUEST_TIMEOUT_SECONDS = 60.0
# Zep ingestion is asynchronous and may take several minutes. Preserve the
# original GraphBuilder deadline while keeping it separate from HTTP timeout.
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS = 600
MAX_ZEP_SEARCH_QUERY_CHARS = 400
MAX_ZEP_SEARCH_RESULTS = 50


def normalize_zep_search_query(query: Any) -> str:
    """Return a non-empty query within Zep Cloud's endpoint limit."""

    if not isinstance(query, str):
        raise ValueError("Zep search query must be a string")
    normalized = query.strip()
    if not normalized:
        raise ValueError("Zep search query must not be empty")
    return normalized[:MAX_ZEP_SEARCH_QUERY_CHARS]


def normalize_zep_search_limit(limit: Any) -> int:
    """Clamp a search result limit to the current Zep Cloud contract."""

    try:
        normalized = int(limit)
    except (TypeError, ValueError) as exc:
        raise ValueError("Zep search limit must be an integer") from exc
    if normalized < 1:
        raise ValueError("Zep search limit must be at least 1")
    return min(normalized, MAX_ZEP_SEARCH_RESULTS)

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Coerce or validate the query to a non-empty string before calling the search helper: str(query).strip() if it is scalar, else reject
  2. At the API/tool boundary, validate request parameters against a schema (e.g. pydantic) so None never reaches here
  3. For LLM tool calls, re-ask the model or substitute a clear error message when the argument is the wrong type

Example fix

# before
results = zep_search(query=params.get("query"))  # None -> ValueError

# after
raw = params.get("query")
if not isinstance(raw, str) or not raw.strip():
    raise ValueError("'query' must be a non-empty string")
results = zep_search(query=raw)
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(query, str) or not query.strip():
    raise ValueError("'query' must be a non-empty string")

Type guard

def is_valid_zep_query(q: Any) -> bool:
    return isinstance(q, str) and bool(q.strip())

Try / catch

try:
    results = zep_search(query=q)
except ValueError as e:
    if "must be a string" in str(e):
        q = str(q).strip() if q else None
        if not q:
            raise
        results = zep_search(query=q)

Prevention

When it happens

Trigger: Passing a non-string into a Zep search helper: None from an unset variable, a dict/list from upstream JSON, or a number coerced nowhere. The check runs before any network call, so it fails locally and immediately.

Common situations: LLM-generated tool-call arguments (a model emitting null or a nested object for the query parameter), unvalidated API request bodies, or default parameter values like None not replaced before the call.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/13e4e9b5f6f227e2. Report an issue: GitHub.