Skip to navigation

Filtering

View as Markdown

Most list endpoints in NeMo Helix accept a filter parameter for narrowing results. Filter values can be expressed as dot flags (CLI only), bracket notation (URLs), text syntax (compact, URL-aware callers), or JSON (SDKs and programmatic builders).

Prefer the form that’s most natural for the caller:

  • CLI: --filter.<field> dot flags for simple cases → --filter '<text>' when you need operators/boolean logic → --filter '<json>' for complex nested queries.
  • REST URL: ?filter[field][$op]=value bracket notation → JSON blob (?filter={...} URL-encoded) → text syntax (readable but requires URL-encoding spaces and quotes).
  • SDK: pass filter inside the endpoint’s query_params TypedDict. Most list endpoints type it as str (text or JSON, use json.dumps for dicts); client.jobs.list_jobs also accepts a JSON dict directly.

Filter syntaxes

CLI dot flags

Generated CLI list commands expose each simple filter field as its own option:

nemo files filesets list --filter.purpose dataset --filter.storage-type local

Dot flags cover exact-match on scalar fields. For operators ($gte, $like, …) or boolean combinations, fall through to --filter with text or JSON:

nemo files filesets list --filter 'purpose:"dataset" AND created_at>="2025-01-01"'
nemo files filesets list --filter '{"created_at":{"$gte":"2025-01-01"}}'

Bracket notation

The preferred form for REST query strings. Each ?filter[field][$op]=value pair is URL-encoding-friendly and composable:

GET /apis/files/v2/workspaces/{workspace}/filesets?filter[purpose]=dataset&filter[created_at][$gte]=2025-01-01

When no operator is specified, the default is $eq:

?filter[purpose]=dataset # equivalent to filter[purpose][$eq]=dataset

On datetime fields, bare bracket values also default to $eq — ?filter[created_at]=2025-01-01 matches only rows whose timestamp equals 2025-01-01 exactly, which is rarely what you want. Use an explicit comparison operator ($gte, $lte, $gt, $lt) for datetime fields.

Text syntax

A compact, human-readable query string. Handy for CLI and SDK string filters; in URLs you need to URL-encode spaces and quotes, so bracket notation is usually easier there.

nemo files filesets list --filter 'purpose:"dataset" AND created_at>"2025-01-01"'
import json
from nemo_helix_plugin.client.client import NemoClient
from nemo_helix_plugin.evals.types import ListMetricsQueryParams
from nemo_helix_plugin.files.types import ListFilesetsQueryParams
from nemo_helix_plugin.jobs.types import ListJobsQueryParams
from nemo_helix_plugin.models.types import ListModelsQueryParams
from nemo_helix_plugin.projects.types import ListProjectsQueryParams
client = NemoClient.from_config()
projects = client.projects.list_projects(
query_params=ListProjectsQueryParams(filter='name~"eval" AND created_at>"2025-01-01"')
)

Text operators:

OperatorMeaningExample
:Exact match (case-sensitive)name:"mmlu"
~Substring match (case-insensitive)name~"llam"
>Greater thancreated_at>"2025-01-01"
>=Greater than or equalcreated_at>="2025-01-01"
<Less thanupdated_at<"2025-12-31"
<=Less than or equalupdated_at<="2025-12-31"
INValue in liststatus IN ["active", "pending"]
NOT INValue not in liststatus NOT IN ["deleted"]
-Negation (prefix)-status:"draft"

Combine conditions with AND, OR, and parentheses:

name~"llama" AND (status:"active" OR status:"pending")

Spaces between conditions are treated as implicit AND.

String values must be double-quoted.

JSON

Pass a JSON object to the filter parameter. Keys are field names; values are either a literal (for exact match) or an operator object. On endpoints whose filter is typed as str, such as the typed files client’s list_filesets, serialize the object with json.dumps. This form is most useful when building queries programmatically:

import json
# Exact match
results = client.files.list_filesets(
query_params=ListFilesetsQueryParams(filter=json.dumps({"purpose": "dataset"}))
)
# Operator syntax
results = client.files.list_filesets(
query_params=ListFilesetsQueryParams(filter=json.dumps({"name": {"$like": "training"}}))
)
# Multiple conditions
results = client.files.list_filesets(
query_params=ListFilesetsQueryParams(
filter=json.dumps({"$and": [{"purpose": "dataset"}, {"description": {"$like": "training"}}]})
)
)
# Date range
results = client.files.list_filesets(
query_params=ListFilesetsQueryParams(
filter=json.dumps({"created_at": {"$gte": "2025-01-01T00:00:00", "$lte": "2025-06-30T23:59:59"}})
)
)

When a bare value is provided (without an operator object), it defaults to $eq:

# These are equivalent
client.files.list_filesets(query_params=ListFilesetsQueryParams(filter='{"purpose": "dataset"}'))
client.files.list_filesets(query_params=ListFilesetsQueryParams(filter='{"purpose": {"$eq": "dataset"}}'))

Operators reference

OperatorMeaningExample (JSON)
$eqExact match (case-sensitive){"name": {"$eq": "mmlu"}}
$likeSubstring match (case-insensitive){"name": {"$like": "mmlu"}}
$ltLess than{"created_at": {"$lt": "2025-06-01"}}
$lteLess than or equal{"created_at": {"$lte": "2025-06-01"}}
$gtGreater than{"created_at": {"$gt": "2025-01-01"}}
$gteGreater than or equal{"created_at": {"$gte": "2025-01-01"}}
$inValue in set{"status": {"$in": ["active", "pending"]}}
$ninValue not in set{"status": {"$nin": ["deleted"]}}
$startsWithStarts with the value (case-sensitive){"name": {"$startsWith": "llama"}}
$endsWithEnds with the value (case-sensitive){"name": {"$endsWith": "-instruct"}}
$containsArray has the element{"data.tags": {"$contains": "red"}}
$elemMatchArray has one element matching every condition{"data.metadata": {"$elemMatch": {"key": "suite", "value": "geography"}}}
$hasKeyObject has the key with a non-null value{"data.tags": {"$hasKey": "v1.2"}}
$andLogical AND{"$and": [{"name": "a"}, {"status": "active"}]}
$orLogical OR{"$or": [{"name": "a"}, {"name": "b"}]}
$notLogical NOT{"$not": {"status": "draft"}}

Array and object operators

$contains, $elemMatch, and $hasKey operate on JSON array or object fields, such as an entity’s data.* fields. They and $startsWith/$endsWith have no text-syntax form, and in bracket notation the $ is required: ?filter[data.tags][$hasKey]=v1.2.

$elemMatch needs one element to satisfy every condition. It takes one of two forms:

  • Object elements: {"key": "suite", "value": "geography"} matches an element with both. A value can also be an operator object, such as {"key": "owner", "value": {"$startsWith": "al"}}. Against [{"key": "suite", "value": "math"}, {"key": "owner", "value": "geography"}], {"key": "suite", "value": "geography"} does not match, because no single element has both.
  • Plain elements: an operator object on its own, such as {"$startsWith": "default/task-a#"}, matches an array of strings or numbers.

Inside $elemMatch you can use $eq, $in, $nin, $like, $lt, $lte, $gt, $gte, $startsWith, and $endsWith. Several on one value must all hold.

$elemMatch and $contains compare array elements by JSON type. A string matches only a string, a number only a number (3 matches 3.0), and true only true, so "1", 1, and true are all different. Range operators compare numbers with a number operand and strings with a string operand. $like, $startsWith, and $endsWith match strings only.

  • $hasKey treats the whole value as one key, so v1.2 is not read as a v1 → 2 path.
  • For $contains, $elemMatch, and $hasKey, a missing field, or one that is not an array or object, never matches. Under $not, it always matches.

Services often wrap these in named filters that are easier to use. For example, the Evaluator’s filter[metadata.suite]=geography becomes the $elemMatch above. See Filter tasks and tasksets.

Nested fields

Dotted field names reach into nested objects such as labels:

# Filter by a custom label
metrics = client.evals.list_metrics(
query_params=ListMetricsQueryParams(filter=json.dumps({"labels.eval_category": "agentic"}))
)

The equivalent REST call:

?filter[labels.eval_category]=agentic

On fields that hold key/value data, such as an agent’s labels, a task’s metadata, or a job’s spec, the bracket and nested JSON forms mean the same thing as the dotted form. Each key must match, and keys the filter does not name are ignored. These three filters are equivalent:

?filter[labels.eval_category]=agentic&filter[labels.team]=search
?filter[labels][eval_category]=agentic&filter[labels][team]=search
?filter={"labels": {"eval_category": "agentic", "team": "search"}}

An operator goes after the key it applies to: ?filter[labels][team][$in]=search,eval matches either team. An operator placed directly on the field, such as ?filter[labels][$hasKey]=team, or a plain value such as ?filter[labels]=agentic, compares against the whole object and returns 400 Bad Request.

Any other field takes a plain value or an operator object. An object of plain keys, such as {"name": {"first": "x"}}, returns 400 Bad Request, and so does an object that mixes operators with plain keys, such as {"name": {"$eq": "x", "tier": "gold"}}. In JSON filters, operators need the $ prefix: write {"created_at": {"$gte": "2025-01-01"}}, not {"created_at": {"gte": "2025-01-01"}}.

Common patterns

Date range

results = client.evals.list_metrics(
query_params=ListMetricsQueryParams(
filter=json.dumps({"created_at": {"$gte": "2025-01-01T00:00:00", "$lte": "2025-06-30T23:59:59"}})
)
)

Substring match

models = client.models.list_models(
query_params=ListModelsQueryParams(filter=json.dumps({"name": {"$like": "llama"}}))
)

Multiple conditions

metrics = client.evals.list_metrics(
query_params=ListMetricsQueryParams(
filter=json.dumps({"$and": [{"name": {"$like": "bleu"}}, {"type": "llm-as-a-judge"}]})
)
)

Filtering by status

jobs = client.jobs.list_jobs(
workspace="default",
query_params=ListJobsQueryParams(filter={"source": "customization", "status": "completed"}),
)