mem0ai/mem0 · error · Error

${key} filter value must be a list of filter dicts, got ${ty

Error message

${key} filter value must be a list of filter dicts, got ${typeof value}

What it means

Logical operators AND, OR, NOT in Qdrant filters must map to an array of sub-filter objects (mirroring Qdrant's must/should/must_not lists). If the value next to AND/OR/NOT is not an array (e.g. a plain object, string, or number), this error is thrown before any API call.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/qdrant.ts:235

    // Normalize $or/$not/$and → OR/NOT/AND and deduplicate
    const normalized: Record<string, any> = {};
    for (const [key, value] of Object.entries(filters)) {
      const normKey = KEY_MAP[key] || key;
      if (!(normKey in normalized)) {
        normalized[normKey] = value;
      }
    }

    const must: (QdrantCondition | QdrantFilter)[] = [];
    const should: (QdrantCondition | QdrantFilter)[] = [];
    const mustNot: (QdrantCondition | QdrantFilter)[] = [];

    for (const [key, value] of Object.entries(normalized)) {
      // Handle logical operators
      if (key === "AND" || key === "OR" || key === "NOT") {
        if (!Array.isArray(value)) {
          throw new Error(
            `${key} filter value must be a list of filter dicts, got ${typeof value}`,
          );
        }
        for (let i = 0; i < value.length; i++) {
          const item = value[i];
          if (
            typeof item !== "object" ||
            item === null ||
            Array.isArray(item)
          ) {
            throw new Error(
              `${key} filter list item at index ${i} must be a dict, got ${typeof item}`,
            );
          }
        }

        if (key === "AND") {
          for (const sub of value) {

View on GitHub (pinned to 001c235229)

Solutions

  1. Wrap the logical value in an array: { AND: [ { a: 1 } ] } even for a single condition
  2. When building filters dynamically, always map conditions into an array: { AND: conditions.map(c => ({ [c.key]: c.value })) }

Example fix

// before
const r = await vs.search(vec, { filters: { AND: { user_id: 'u1' } } });

// after
const r = await vs.search(vec, { filters: { AND: [ { user_id: 'u1' } ] } });
Defensive patterns

Strategy: type-guard

Validate before calling

function normalizeLogicalFilters(filters: any): any {
  const out: any = {};
  for (const [k, v] of Object.entries(filters)) {
    if (['AND','OR','NOT'].includes(k) && v && typeof v === 'object' && !Array.isArray(v)) {
      out[k] = [v]; // wrap single dict into a list
    } else {
      out[k] = v;
    }
  }
  return out;
}

Type guard

const isFilterDictList = (v: unknown): v is Record<string, any>[] =>
  Array.isArray(v) && v.length > 0 && v.every(i => i && typeof i === 'object' && !Array.isArray(i));

Try / catch

try { await vs.search(vec, { filters }); } catch (e) { if (e instanceof Error && e.message.includes('must be a list of filter dicts')) { /* wrap value in [], retry */ } throw e; }

Prevention

When it happens

Trigger: filters: { AND: { a: 1 } } (object instead of array), { OR: 'x' }, { NOT: 5 }, or code that assigns a single condition directly to a logical key instead of wrapping it in a list.

Common situations: Refactoring nested filters and forgetting the array wrapper; conditionally building filters where a single sub-filter is not wrapped in []; merging user-supplied JSON filters of unknown shape.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/84a218366fdbb008. Report an issue: GitHub.