Skip to content

feat: Search annotations in pipeline run API#107

Open
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-pydantic-modelsfrom
ycq/search-pipeline-run-annotations
Open

feat: Search annotations in pipeline run API#107
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-pydantic-modelsfrom
ycq/search-pipeline-run-annotations

Conversation

@yuechao-qin
Copy link
Collaborator

@yuechao-qin yuechao-qin commented Feb 24, 2026

TL;DR

Implemented annotation-based filtering for Pipeline Run API and proper pagination handling.

What changed?

API GET /api/pipeline_runs/

Functionality

  • Implemented support for filter_query which handles annotation searches in pipeline runs.
  • Added new database index for annotation-based queries. Covering index (key, pipeline_run_id, value).
  • Enhanced pagination to preserve both legacy filter and new filter_query across pages.

Other

  • Created new filter_query_sql.py module with comprehensive filter query processing:
    • PageToken class for encoding/decoding pagination state
    • Support for annotation-based predicates: key_exists, value_equals, value_contains, value_in
    • Logical operators: and, or, not with proper nesting
    • Integration with existing legacy filter system
  • Refactored pipeline run listing to use the new filter query system while maintaining backward compatibility

How to test?

uv run pytest tests/test_api_server_sql.py tests/test_filter_query_sql.py
  • Test basic annotation filtering: {"and": [{"key_exists": {"key": "team"}}]}
  • Test value-based filtering: {"and": [{"value_equals": {"key": "env", "value": "prod"}}]}
  • Test logical combinations with nested and/or/not predicates
  • Verify mutual exclusivity validation between filter and filter_query parameters
  • Test pagination preservation of filter_query state across multiple pages
  • Confirm legacy filter functionality remains unchanged

Why make this change?

Annotation search support is the backbone for all the upcoming search features (create_by, pipeline name, status) for Pipeline Runs. The other searches will be search against the annotations table.

@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-pydantic-models to graphite-base/107 February 25, 2026 05:27
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from 41a18a2 to 7606c83 Compare February 25, 2026 06:34
@yuechao-qin yuechao-qin changed the base branch from graphite-base/107 to ycq/search-pipeline-run-pydantic-models February 25, 2026 06:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch 2 times, most recently from 98e7d41 to ecd7842 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from f7dc9bc to b5f0ba9 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from b5f0ba9 to 88b265f Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from ecd7842 to ba5594e Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-pydantic-models to graphite-base/107 February 28, 2026 10:21
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from ba5594e to d6f04df Compare February 28, 2026 10:23
@yuechao-qin yuechao-qin changed the base branch from graphite-base/107 to ycq/search-pipeline-run-pydantic-models February 28, 2026 10:23

@dataclasses.dataclass(kw_only=True)
class PageToken:
offset: int = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you be moving to cursor way of filtering? If you plan to make changes soon, this class should not be public. Otherwise it's a breaking change.
Maybe you should just make the whole module private. Like _api_server_search_utils.py. ("utils" is an anti-pattern, but I do not have a better idea yet.)


# # Needed to put a union type into DB
# class SqlIOTypeStruct(_BaseModel):
# type: structures.TypeSpecType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indentation was correct (it's a class variable inside a class).

predicate,
) -> sql.ColumnElement:
match predicate:
case filter_query_models.AndPredicate():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we construct all these different objects just for comparison.

You can do:

    match type(predicate):
        case filter_query_models.AndPredicate:

)
clause = filter_query_sql.filter_query_to_where_clause(filter_query=fq)
compiled = _compile(clause)
assert "ml-ops" in compiled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can assert the whole query in some of these tests? It would be nice to see the full queries here.
I guess, there is a possibility that this query could be brittle and change with new version of SqlAlchemy. But we can wait for that moment and then make tests more robust if that happens.

@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-pydantic-models to graphite-base/107 March 4, 2026 00:41
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from d6f04df to 7369ffc Compare March 4, 2026 00:41
@yuechao-qin yuechao-qin changed the base branch from graphite-base/107 to ycq/search-pipeline-run-pydantic-models March 4, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants