Add query filters for service-backed retrieval#2258
Conversation
Greptile SummaryThis PR threads typed query filters (
|
| Filename | Overview |
|---|---|
| nemo_retriever/src/nemo_retriever/query/filters.py | New module: translates QueryFilterOptions into LanceDB LIKE predicates; LIKE metacharacters are properly escaped via _like_pattern_literal + ESCAPE clause; raw where is pass-through; previously flagged SQL injection and LIKE escape concerns are documented as known trade-offs. |
| nemo_retriever/src/nemo_retriever/query/options.py | Adds QueryFilterOptions frozen dataclass and threads it into QueryRequest and ServiceQueryRequest; class is a public contract but lacks a docstring, violating the docstrings-public-interface rule. |
| nemo_retriever/src/nemo_retriever/service/query_schema.py | Adds source_id, source, page_number (ge=0 validated), and where fields to QueryRequest Pydantic model; backward-compatible additive change with proper Field descriptions. |
| nemo_retriever/src/nemo_retriever/service/vectordb_app.py | Wires filter fields from QueryRequest through build_query_where_clause into VectorDBState.search; raw where predicate passes through with only whitespace stripping (previously flagged); search call ordering (where before limit) is correct. |
| nemo_retriever/src/nemo_retriever/service/client.py | query() gains an optional filters parameter; query_filter_payload merges structured fields into the POST body; backward-compatible (filters defaults to None). |
| nemo_retriever/src/nemo_retriever/cli/query/app.py | Adds --source-id, --source, --page-number, --where CLI options to both _local_command and _service_command; agentic path correctly rejects filters; filter options are cleanly factored via _filter_options helper. |
| nemo_retriever/src/nemo_retriever/query/workflow.py | Adds where field to ResolvedQueryPlan and threads build_query_where_clause result into vdb_kwargs; vdb_kwargs is only set when where is non-None, preserving existing behavior. |
| nemo_retriever/src/nemo_retriever/cli/query/options.py | Adds SourceIdOption, SourceOption, PageNumberOption (min=0), and WhereOption typer type aliases; straightforward and consistent with existing option definitions. |
| nemo_retriever/src/nemo_retriever/query/service.py | One-line change: passes request.filters to client.query; correctly threads the filter options into the service call. |
| nemo_retriever/tests/test_query_filters.py | New test file covering predicate building, LIKE wildcard escaping, pass-through where, has_query_filters, and negative page_number rejection; good coverage of happy and error paths. |
| nemo_retriever/tests/test_service_vectordb_app.py | Adds tests for negative page_number rejection (HTTP 422), where-before-limit call ordering, and end-to-end route filter application; uses object.new to bypass VectorDBState init which may be fragile if internals change. |
| nemo_retriever/tests/test_service_query_client.py | Adds test verifying all filter fields are serialized into the POST body; straightforward and sufficient for the contract being tested. |
| nemo_retriever/tests/test_root_query_cli.py | New test cases cover filter pass-through to vdb_kwargs, output shape preservation, agentic rejection, service help text visibility, and service mode JSON output; comprehensive end-to-end CLI coverage. |
| nemo_retriever/tests/test_query_workflow_options.py | Extends workflow tests to cover filter options threading into vdb_kwargs and service client filter propagation; correctly updates FakeClient signature to match the new filters parameter. |
| nemo_retriever/tests/test_lancedb_retrieval_where.py | Adds a test for compact sidecar metadata LIKE predicate matching; validates LanceDB retrieval layer correctly applies the where clause to filter results. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant CLI as CLI / Programmatic API
participant App as cli/query/app.py
participant Filters as query/filters.py
participant SvcClient as service/client.py
participant Gateway as Gateway /v1/query
participant VDB as VectorDB /v1/query
participant LanceDB as LanceDB table
CLI->>App: query(text, --source-id, --page-number, --where)
App->>Filters: build_query_where_clause(QueryFilterOptions)
Note over Filters: Escapes LIKE metacharacters, composes AND clauses
alt Local mode
Filters-->>App: "where_clause (str | None)"
App->>LanceDB: "Retriever.query(vdb_kwargs={where: where_clause})"
LanceDB-->>App: hits
else Service mode
App->>Filters: query_filter_payload(QueryFilterOptions)
Filters-->>App: "{source_id, source, page_number, where}"
App->>SvcClient: client.query(text, top_k, filters)
SvcClient->>Gateway: "POST /v1/query {query, top_k, source_id, ...}"
Gateway->>VDB: POST /v1/query (forwarded)
VDB->>Filters: build_query_where_clause(QueryFilterOptions)
Filters-->>VDB: combined where_clause
VDB->>LanceDB: table.search(vector).where(where_clause).limit(top_k)
LanceDB-->>VDB: hits
VDB-->>Gateway: QueryResponse
Gateway-->>SvcClient: QueryResponse
SvcClient-->>App: list[list[dict]]
end
App-->>CLI: JSON hits
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant CLI as CLI / Programmatic API
participant App as cli/query/app.py
participant Filters as query/filters.py
participant SvcClient as service/client.py
participant Gateway as Gateway /v1/query
participant VDB as VectorDB /v1/query
participant LanceDB as LanceDB table
CLI->>App: query(text, --source-id, --page-number, --where)
App->>Filters: build_query_where_clause(QueryFilterOptions)
Note over Filters: Escapes LIKE metacharacters, composes AND clauses
alt Local mode
Filters-->>App: "where_clause (str | None)"
App->>LanceDB: "Retriever.query(vdb_kwargs={where: where_clause})"
LanceDB-->>App: hits
else Service mode
App->>Filters: query_filter_payload(QueryFilterOptions)
Filters-->>App: "{source_id, source, page_number, where}"
App->>SvcClient: client.query(text, top_k, filters)
SvcClient->>Gateway: "POST /v1/query {query, top_k, source_id, ...}"
Gateway->>VDB: POST /v1/query (forwarded)
VDB->>Filters: build_query_where_clause(QueryFilterOptions)
Filters-->>VDB: combined where_clause
VDB->>LanceDB: table.search(vector).where(where_clause).limit(top_k)
LanceDB-->>VDB: hits
VDB-->>Gateway: QueryResponse
Gateway-->>SvcClient: QueryResponse
SvcClient-->>App: list[list[dict]]
end
App-->>CLI: JSON hits
Reviews (11): Last reviewed commit: "Reject unsupported agentic query filters" | Re-trigger Greptile
| top_k: int = Field(default=10, ge=1, le=1000) | ||
| source_id: str | None = None | ||
| source: str | None = None | ||
| page_number: int | None = None |
There was a problem hiding this comment.
The
page_number field has no lower-bound validation, unlike top_k which uses ge=1, le=1000. A negative or zero page number is almost certainly invalid for a document page and would produce nonsensical LIKE patterns in _page_number_clause. Adding ge=0 makes the contract explicit and catches bad inputs at the API boundary.
| page_number: int | None = None | |
| page_number: int | None = Field(default=None, ge=0) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemo_retriever/src/nemo_retriever/service/vectordb_app.py
Line: 57
Comment:
The `page_number` field has no lower-bound validation, unlike `top_k` which uses `ge=1, le=1000`. A negative or zero page number is almost certainly invalid for a document page and would produce nonsensical LIKE patterns in `_page_number_clause`. Adding `ge=0` makes the contract explicit and catches bad inputs at the API boundary.
```suggestion
page_number: int | None = Field(default=None, ge=0)
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
ed929cd to
d4789d3
Compare
2f851aa to
fb2e58c
Compare
2149a51 to
9f4df77
Compare
|
waiting for @jdye64 to get back to take a look and talk this through |
Summary
wherepredicatesRetrieverServiceClient.query(..., filters=...)-> gateway/v1/query-> VectorDB/v1/query-> LanceDB.where(...)Retriever.query(..., vdb_kwargs={"where": ...})behavior and root query JSON output shapemetadata LIKE '%"meta_a":"alpha"%'Scope / Follow-Up
vdbs.md.Raw
whereContractwhereis intentionally advanced/trusted-caller pass-through to LanceDB/DataFusion, applied as a read-only pre-filter beforelimit().source_id,source, andpage_numberfields.whereonly inside the same auth/trust boundary as unfiltered/v1/queryaccess. The public service schema now documents that posture.Design Lineage
Retriever.query(..., vdb_kwargs={"where": ...})and LanceDB_filter/wheresupport.Validation
/localhome/local-jioffe/retriever-skills/nemo_retriever/.venv/bin/python -m pytest nemo_retriever/tests/test_lancedb_retrieval_where.py nemo_retriever/tests/test_query_filters.py nemo_retriever/tests/test_query_workflow_options.py nemo_retriever/tests/test_root_query_cli.py nemo_retriever/tests/test_service_query_client.py nemo_retriever/tests/test_service_vectordb_app.py nemo_retriever/tests/test_retriever_queries.py nemo_retriever/tests/test_root_cli_workflow.py -q(157 passed)/localhome/local-jioffe/.local/venvs/pre-commit/bin/pre-commit run --files nemo_retriever/src/nemo_retriever/cli/query/options.py nemo_retriever/src/nemo_retriever/service/query_schema.py nemo_retriever/tests/test_lancedb_retrieval_where.py nemo_retriever/tests/test_service_vectordb_app.pytest_retriever_queries.py::TestQueriesGraphExecution::test_queries_thread_top_k_and_vdb_kwargsand LanceDB predicate execution is covered bytest_lancedb_retrieval_where.py, including the compact sidecar metadata predicate.test_service_query_client.py::test_service_client_query_posts_filters_to_v1_query./v1/queryroute filter application is covered bytest_service_vectordb_app.py::test_query_route_accepts_filters_and_applies_where.test_root_query_cli.py::test_root_query_service_mode_uses_service_options_and_prints_json.Live Helm Smoke With Real Embed NIM
Environment:
nrl-real-filter-smoke./nemo_retriever/helmnrl-service:2258-filter-smoke-fullsrc, built fromnvcr.io/nvidia/nemo-microservices/nrl-service:26.5.0with this branch'snemo_retriever/src/nemo_retrieveroverlaidnv-ingest/llama-nemotron-embed-vl-1b-v2deployment from 0 to 1; pod becameReady, usingnvcr.io/nim/nvidia/llama-nemotron-embed-vl-1b-v2:1.12.0http://llama-nemotron-embed-vl-1b-v2.nv-ingest.svc.cluster.local:8000/v1/embeddingsnvidia/llama-nemotron-embed-vl-1b-v2/internal/vectordb/writewith two rows whose vectors were generated by the real embed NIM:alpha sidecar metadata chunk,metadata={"meta_a":"alpha","page_number":1}bravo sidecar metadata chunk,metadata={"meta_a":"bravo","page_number":2}Real embed probe:
Probe summary:
model:nvidia/llama-nemotron-embed-vl-1b-v2count:1dim:2048Gateway request:
Gateway result summary:
wherereturned 2 hits: alpha and bravo.alpha sidecar metadata chunkfromnotebook-alpha.pdf, page1, withmetadata.meta_a == "alpha".Root CLI service smoke against the same real-NIM Helm gateway:
Root CLI result summary:
source=notebook-alpha.pdf,page_number=1,text="alpha sidecar metadata chunk",modality=text, real-service score1.435014009475708.