docs: document the real search filter grammar (#6906)
This commit is contained in:
@@ -6,6 +6,10 @@ icon: "filter"
|
||||
|
||||
Enhanced metadata filtering in Mem0 lets you run complex queries across memory metadata. Combine comparisons, logical operators, and wildcard matches to zero in on the exact memories your agent needs.
|
||||
|
||||
<Info>
|
||||
This page covers the self-hosted `Memory` / `AsyncMemory` grammar. Sibling top-level keys are implicitly ANDed on both self-hosted and the hosted Platform API, so a flat filter like `{"user_id": "alice", "category": "work"}` works without wrapping it in `AND` on either. Two real differences remain: the `nin` operator documented below is not part of the Platform contract, and Platform validates every top-level key against a fixed allow-list, rejecting anything else with a 400. See [Memory Filters (Platform)](/platform/features/v2-memory-filters) for the hosted grammar.
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Feature anatomy
|
||||
@@ -19,7 +23,7 @@ Enhanced metadata filtering in Mem0 lets you run complex queries across memory m
|
||||
| `lt` / `lte` | Less than / less than or equal | Cap numeric values (e.g., ratings, timestamps). |
|
||||
| `in` / `nin` | In list / not in list | Pre-approve or block sets of values without chaining multiple filters. |
|
||||
| `contains` / `icontains` | Case-sensitive / case-insensitive substring match | Scan text fields for keywords. |
|
||||
| `*` | Wildcard | Require that a field exists, regardless of value. |
|
||||
| `*` | Wildcard | Match regardless of value. Exact semantics (field-must-exist vs. no-op) vary by vector store, see [Wildcard matching](#wildcard-matching). |
|
||||
| `AND` / `OR` / `NOT` | Combine filters | Build logic trees so multiple conditions work together. |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
@@ -111,7 +115,7 @@ results = m.search(
|
||||
|
||||
### Wildcard matching
|
||||
|
||||
Allow any value for a field while still requiring the field to exist: handy when the mere presence of a field matters.
|
||||
Match any value for a field, handy when the mere presence of a field matters.
|
||||
|
||||
```python
|
||||
# Match any value for a field
|
||||
@@ -124,10 +128,18 @@ results = m.search(
|
||||
)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Wildcard semantics differ by vector store. pgvector requires the key to exist in the payload (a real "field exists" check). Qdrant and Chroma have no native "field exists" filter, so `*` is a no-op there: the field condition is dropped and every record passes, including ones where the field is missing entirely. Do not rely on `*` to exclude records with a missing field unless you have confirmed your store's behavior in `mem0/vector_stores/<provider>.py`.
|
||||
</Warning>
|
||||
|
||||
### Logical combinations
|
||||
|
||||
Combine filters with `AND`, `OR`, and `NOT` to express complex decision trees. Nest logical operators to encode multi-branch workflows.
|
||||
|
||||
<Warning>
|
||||
The examples on this page use `search()`. `get_all()` accepts the same entity and comparison-operator filters, but the `AND` / `OR` / `NOT` wrapper is only translated at the `search()` layer before it reaches the vector store. Whether it also works on `get_all()` depends on your vector store: Qdrant recognizes raw `AND` / `OR` / `NOT` keys natively, but pgvector does not, so a logical wrapper passed to `get_all()` on pgvector silently matches nothing. Stick to `search()` for logical trees, or use plain sibling keys (implicitly ANDed) with `get_all()`.
|
||||
</Warning>
|
||||
|
||||
```python
|
||||
# Logical AND
|
||||
results = m.search(
|
||||
@@ -245,25 +257,18 @@ avoid_filters = {
|
||||
When you reorder filters so indexed fields come first (`good_filters` example), queries typically return faster than the `avoid_filters` pattern where expensive text searches run before simple checks.
|
||||
</Info>
|
||||
|
||||
Vector store support varies. Confirm operator coverage before shipping:
|
||||
Vector store support varies widely. This table reflects what each provider's filter-translation code (`mem0/vector_stores/<provider>.py`) actually implements, confirm before shipping if you use a store not listed:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Qdrant">
|
||||
Full comparison, list, and logical support. Handles deeply nested boolean logic efficiently.
|
||||
</Accordion>
|
||||
<Accordion title="Chroma">
|
||||
Equality and basic comparisons only. Limited nesting: break large trees into smaller calls.
|
||||
</Accordion>
|
||||
<Accordion title="Pinecone">
|
||||
Comparisons plus `in`/`nin`. Text operators are constrained; rely on tags where possible.
|
||||
</Accordion>
|
||||
<Accordion title="Weaviate">
|
||||
Full operator coverage with advanced text filters. Best option when you need hybrid text + metadata queries.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
| Store | `eq`/`ne`/`gt`/`gte`/`lt`/`lte`/`in`/`nin` | `contains`/`icontains` | `AND`/`OR`/`NOT` | `*` wildcard |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Qdrant | Full support (`in`/`nin` must be a list, or the Qdrant client raises a validation error) | Yes | Yes, nested | No-op: matches every record regardless of whether the field is present |
|
||||
| pgvector | Full support (`in`/`nin` must be a list, or the call raises `ValueError`) | Yes, via SQL `LIKE`/`ILIKE` | Yes, nested | Requires the key to exist in the payload |
|
||||
| Chroma | Full support | No: silently falls back to equality | Yes, one level of nesting | No-op: the filter is dropped |
|
||||
| Pinecone | Full support | Not implemented | Not implemented: filters are only ANDed field-by-field | Not implemented: `"*"` is matched as the literal string `"*"`, not a wildcard |
|
||||
| Weaviate | Not implemented: only `user_id`, `agent_id` and `run_id` are filtered on, as exact equality. Every other key, including all metadata, is silently dropped | Not implemented | Not implemented: only an implicit AND across the three supported keys | Not implemented |
|
||||
|
||||
<Warning>
|
||||
If an operator is unsupported, most stores silently ignore that branch. Add validation before execution so you can fall back to simpler queries instead of returning empty results.
|
||||
If an operator is unsupported, most stores silently ignore it or fall back to equality rather than raising an error. Test filters against your actual store instead of assuming operator parity with Qdrant.
|
||||
</Warning>
|
||||
|
||||
### Migrate from earlier filters
|
||||
@@ -427,4 +432,7 @@ except ValueError as e:
|
||||
<Card title="Tag and Organize Memories" icon="tag" href="/cookbooks/essentials/tagging-and-organizing-memories">
|
||||
Practice building workflows that label and retrieve memories with clear metadata filters.
|
||||
</Card>
|
||||
<Card title="Memory Filters (Platform)" icon="cloud" href="/platform/features/v2-memory-filters">
|
||||
Using the hosted API instead? See the allow-listed top-level fields, the missing `nin` operator, and Platform-only fields like `created_at` and `memory_ids`.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -29,6 +29,46 @@ Filters use a nested JSON structure with logical operators at the root:
|
||||
}
|
||||
```
|
||||
|
||||
A single bare condition needs no wrapper:
|
||||
|
||||
```python
|
||||
# Works: one condition, no wrapper needed
|
||||
filters = {"user_id": "user_123"}
|
||||
```
|
||||
|
||||
Sibling top-level keys in a flat object are accepted too: the API implicitly ANDs them, so the flat form and the explicit `AND` form are equivalent:
|
||||
|
||||
```python
|
||||
# Works: sibling keys are implicitly ANDed
|
||||
filters = {
|
||||
"user_id": "user_123",
|
||||
"categories": {"in": ["finance"]}
|
||||
}
|
||||
|
||||
# Equivalent, explicit form
|
||||
filters = {
|
||||
"AND": [
|
||||
{"user_id": "user_123"},
|
||||
{"categories": {"in": ["finance"]}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Reach for an explicit `AND`, `OR`, or `NOT` wrapper when you need `OR` or `NOT` semantics, or when you need to nest conditions. What the API does reject is an unrecognized top-level key:
|
||||
|
||||
```python
|
||||
# Rejected: unknown top-level key
|
||||
filters = {"user_id": "user_123", "bogus_key": "z"}
|
||||
# 400: Top-level key must be a logical operator or an allowed field:
|
||||
# ['AND', 'OR', 'NOT', 'user_id', 'agent_id', 'app_id', 'run_id',
|
||||
# 'created_at', 'updated_at', 'timestamp', 'expiration_date', 'text',
|
||||
# 'categories', 'metadata', 'keywords_search', 'memory_ids', 'keywords']
|
||||
```
|
||||
|
||||
<Callout type="info" icon="cloud" color="#00A8FF">
|
||||
Both the hosted Platform API and the self-hosted OSS SDK implicitly AND sibling top-level keys, so the flat and explicit `AND` forms behave the same way on either. The real differences: Platform validates each top-level key against a fixed allow-list and returns a 400 for anything else, and Platform has no `nin` operator. See [Enhanced Metadata Filtering](/open-source/features/metadata-filtering) for the OSS grammar.
|
||||
</Callout>
|
||||
|
||||
## Available fields and operators
|
||||
|
||||
### Entity fields
|
||||
@@ -67,6 +107,14 @@ The `*` wildcard matches any non-null value. Records with null values for that f
|
||||
Use operator keywords exactly as shown (`eq`, `ne`, `gte`, etc.). SQL-style symbols such as `>=` or `!=` are rejected by the Platform API.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning" icon="exclamation-triangle" color="#E74C3C">
|
||||
There is no `nin` ("not in") operator on Platform. It is an OSS-only operator; see [Enhanced Metadata Filtering](/open-source/features/metadata-filtering). To exclude a set of values on Platform, wrap an `in` clause in `NOT`:
|
||||
|
||||
```python
|
||||
{"NOT": {"categories": {"in": ["spam", "test"]}}}
|
||||
```
|
||||
</Callout>
|
||||
|
||||
## Common filter patterns
|
||||
|
||||
Use these ready-made filters to target typical retrieval scenarios without rebuilding logic from scratch.
|
||||
|
||||
Reference in New Issue
Block a user