Skip to content

Commit d1ccbfe

Browse files
feat: Enable Filters in OpenAI Search API (#4471)
# What does this PR do? Finishes #2435; i.e., adds support for OpenAI Search API typed filters for vector IO. This PR adds support for OpenAI's typed metadata filter API across vector IO providers, enabling callers to filter search results by metadata fields using comparison operators (eq, ne, gt, gte, lt, lte, in, nin) and compound logic (and, or). Core changes: - API & routing (core/routers/vector_io.py): Propagates filter parameters from the query request through the provider dispatch layer. - Filter type definitions (providers/utils/vector_io/filters.py): Introduces ComparisonFilter, CompoundFilter, and Filter union type used across all providers. - Inline providers (faiss, sqlite-vec): Implement filter evaluation in-memory (faiss) and via SQL WHERE clauses using JSON_EXTRACT (sqlite-vec). Operator dispatch uses a module-level `COMPARISON_OPS` / `SQL_OPS` dict instead of if/elif chains. - Remote providers (Milvus, Chroma, Elasticsearch, pgvector, Qdrant, Weaviate, OCI): Translate filters to each backend's native expression or query language. - OpenAI vector store mixin (openai_vector_store_mixin.py): Post-retrieval Python-side filter evaluation using a shared COMPARISON_OPERATORS dispatch dict, covering providers that don't support server-side filtering natively. - Milvus bug fix: filter parameter was not being passed to keyword search — corrected. ## Test Plan - New integration test suite: tests/integration/vector_io/test_vector_io_filters.py — covers all 8 comparison operators and compound (and/or) logic against faiss and sqlite-vec - New integration test: test_openai_vector_store_search_with_typed_filters in test_openai_vector_stores.py — validates eq/lte/compound-or filters over the OpenAI vector store path - Run integration tests locally via: ### faiss .venv/bin/python -m pytest tests/integration/vector_io/test_vector_io_filters.py \ --stack-config="inference=inline::sentence-transformers,vector_io=inline::faiss,files=inline::localfs" -v ### sqlite-vec .venv/bin/python -m pytest tests/integration/vector_io/test_vector_io_filters.py \ --stack-config="inference=inline::sentence-transformers,vector_io=inline::sqlite-vec,files=inline::localfs" -v #### OpenAI vector store filter (mixin path) .venv/bin/python -m pytest tests/integration/vector_io/test_openai_vector_stores.py::test_openai_vector_store_search_with_typed_filters \ --stack-config="inference=inline::sentence-transformers,vector_io=inline::sqlite-vec,files=inline::localfs" -v --------- Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> Co-authored-by: Claude Sonnet 4 <noreply@anthropic.com>
1 parent 756b8b7 commit d1ccbfe

25 files changed

Lines changed: 2204 additions & 232 deletions

File tree

client-sdks/stainless/openapi.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15651,6 +15651,50 @@ components:
1565115651
nullable: true
1565215652
title: ProcessFileRequest
1565315653
type: object
15654+
ComparisonFilter:
15655+
description: A filter that compares a metadata field against a value.
15656+
properties:
15657+
type:
15658+
enum:
15659+
- eq
15660+
- ne
15661+
- gt
15662+
- gte
15663+
- lt
15664+
- lte
15665+
- in
15666+
- nin
15667+
title: Type
15668+
type: string
15669+
key:
15670+
title: Key
15671+
type: string
15672+
value:
15673+
title: Value
15674+
required:
15675+
- type
15676+
- key
15677+
- value
15678+
title: ComparisonFilter
15679+
type: object
15680+
CompoundFilter:
15681+
description: A filter that combines multiple filters with a logical operator.
15682+
properties:
15683+
type:
15684+
enum:
15685+
- and
15686+
- or
15687+
title: Type
15688+
type: string
15689+
filters:
15690+
items: {}
15691+
title: Filters
15692+
type: array
15693+
required:
15694+
- type
15695+
- filters
15696+
title: CompoundFilter
15697+
type: object
1565415698
ListFilesRequest:
1565515699
description: Request model for listing files.
1565615700
properties:

docs/static/deprecated-llama-stack-spec.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12197,6 +12197,50 @@ components:
1219712197
nullable: true
1219812198
title: ProcessFileRequest
1219912199
type: object
12200+
ComparisonFilter:
12201+
description: A filter that compares a metadata field against a value.
12202+
properties:
12203+
type:
12204+
enum:
12205+
- eq
12206+
- ne
12207+
- gt
12208+
- gte
12209+
- lt
12210+
- lte
12211+
- in
12212+
- nin
12213+
title: Type
12214+
type: string
12215+
key:
12216+
title: Key
12217+
type: string
12218+
value:
12219+
title: Value
12220+
required:
12221+
- type
12222+
- key
12223+
- value
12224+
title: ComparisonFilter
12225+
type: object
12226+
CompoundFilter:
12227+
description: A filter that combines multiple filters with a logical operator.
12228+
properties:
12229+
type:
12230+
enum:
12231+
- and
12232+
- or
12233+
title: Type
12234+
type: string
12235+
filters:
12236+
items: {}
12237+
title: Filters
12238+
type: array
12239+
required:
12240+
- type
12241+
- filters
12242+
title: CompoundFilter
12243+
type: object
1220012244
ListFilesRequest:
1220112245
description: Request model for listing files.
1220212246
properties:

docs/static/experimental-llama-stack-spec.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11935,6 +11935,50 @@ components:
1193511935
nullable: true
1193611936
title: ProcessFileRequest
1193711937
type: object
11938+
ComparisonFilter:
11939+
description: A filter that compares a metadata field against a value.
11940+
properties:
11941+
type:
11942+
enum:
11943+
- eq
11944+
- ne
11945+
- gt
11946+
- gte
11947+
- lt
11948+
- lte
11949+
- in
11950+
- nin
11951+
title: Type
11952+
type: string
11953+
key:
11954+
title: Key
11955+
type: string
11956+
value:
11957+
title: Value
11958+
required:
11959+
- type
11960+
- key
11961+
- value
11962+
title: ComparisonFilter
11963+
type: object
11964+
CompoundFilter:
11965+
description: A filter that combines multiple filters with a logical operator.
11966+
properties:
11967+
type:
11968+
enum:
11969+
- and
11970+
- or
11971+
title: Type
11972+
type: string
11973+
filters:
11974+
items: {}
11975+
title: Filters
11976+
type: array
11977+
required:
11978+
- type
11979+
- filters
11980+
title: CompoundFilter
11981+
type: object
1193811982
ListFilesRequest:
1193911983
description: Request model for listing files.
1194011984
properties:

docs/static/llama-stack-spec.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14006,6 +14006,50 @@ components:
1400614006
nullable: true
1400714007
title: ProcessFileRequest
1400814008
type: object
14009+
ComparisonFilter:
14010+
description: A filter that compares a metadata field against a value.
14011+
properties:
14012+
type:
14013+
enum:
14014+
- eq
14015+
- ne
14016+
- gt
14017+
- gte
14018+
- lt
14019+
- lte
14020+
- in
14021+
- nin
14022+
title: Type
14023+
type: string
14024+
key:
14025+
title: Key
14026+
type: string
14027+
value:
14028+
title: Value
14029+
required:
14030+
- type
14031+
- key
14032+
- value
14033+
title: ComparisonFilter
14034+
type: object
14035+
CompoundFilter:
14036+
description: A filter that combines multiple filters with a logical operator.
14037+
properties:
14038+
type:
14039+
enum:
14040+
- and
14041+
- or
14042+
title: Type
14043+
type: string
14044+
filters:
14045+
items: {}
14046+
title: Filters
14047+
type: array
14048+
required:
14049+
- type
14050+
- filters
14051+
title: CompoundFilter
14052+
type: object
1400914053
ListFilesRequest:
1401014054
description: Request model for listing files.
1401114055
properties:

docs/static/stainless-llama-stack-spec.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15651,6 +15651,50 @@ components:
1565115651
nullable: true
1565215652
title: ProcessFileRequest
1565315653
type: object
15654+
ComparisonFilter:
15655+
description: A filter that compares a metadata field against a value.
15656+
properties:
15657+
type:
15658+
enum:
15659+
- eq
15660+
- ne
15661+
- gt
15662+
- gte
15663+
- lt
15664+
- lte
15665+
- in
15666+
- nin
15667+
title: Type
15668+
type: string
15669+
key:
15670+
title: Key
15671+
type: string
15672+
value:
15673+
title: Value
15674+
required:
15675+
- type
15676+
- key
15677+
- value
15678+
title: ComparisonFilter
15679+
type: object
15680+
CompoundFilter:
15681+
description: A filter that combines multiple filters with a logical operator.
15682+
properties:
15683+
type:
15684+
enum:
15685+
- and
15686+
- or
15687+
title: Type
15688+
type: string
15689+
filters:
15690+
items: {}
15691+
title: Filters
15692+
type: array
15693+
required:
15694+
- type
15695+
- filters
15696+
title: CompoundFilter
15697+
type: object
1565415698
ListFilesRequest:
1565515699
description: Request model for listing files.
1565615700
properties:

src/llama_stack/core/routers/vector_io.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from llama_stack.core.datatypes import VectorStoresConfig
1414
from llama_stack.log import get_logger
15+
from llama_stack.providers.utils.vector_io.filters import parse_filter
1516
from llama_stack_api import (
1617
DEFAULT_CHUNK_OVERLAP_TOKENS,
1718
DEFAULT_CHUNK_SIZE_TOKENS,
@@ -142,7 +143,30 @@ async def query_chunks(
142143
request: QueryChunksRequest,
143144
) -> QueryChunksResponse:
144145
logger.debug(f"VectorIORouter.query_chunks: {request.vector_store_id}")
145-
return await self.routing_table.query_chunks(request)
146+
147+
# Handle the no-filters case early
148+
if not request.params or "filters" not in request.params:
149+
return await self.routing_table.query_chunks(request)
150+
151+
# Extract and parse filters from request params
152+
# Create a shallow copy to avoid mutating the caller's request
153+
params_copy = dict(request.params)
154+
filter_data = params_copy.pop("filters")
155+
156+
try:
157+
parsed_filters = parse_filter(filter_data)
158+
except ValueError as e:
159+
logger.error(f"Invalid filter data: {e}")
160+
raise ValueError(f"Invalid filter: {e}") from e
161+
162+
# Create a new request with the modified params
163+
# Add the parsed filters back to params for the provider
164+
params_copy["filters"] = parsed_filters
165+
modified_request = QueryChunksRequest(
166+
vector_store_id=request.vector_store_id, query=request.query, params=params_copy
167+
)
168+
169+
return await self.routing_table.query_chunks(modified_request)
146170

147171
# OpenAI Vector Stores API endpoints
148172
async def openai_create_vector_store(

0 commit comments

Comments
 (0)