6969from feast .feast_object import FeastObject
7070from feast .feature_service import FeatureService
7171from feast .feature_view import DUMMY_ENTITY , DUMMY_ENTITY_NAME , FeatureView
72+ from feast .filter_models import ComparisonFilter , CompoundFilter , convert_dict_to_filter
7273from feast .inference import (
7374 update_data_sources_with_inferred_event_timestamp_col ,
7475 update_feature_views_with_inferred_features_and_entities ,
@@ -2677,6 +2678,7 @@ def retrieve_online_documents_v2(
26772678 text_weight : float = 0.5 ,
26782679 image_weight : float = 0.5 ,
26792680 combine_strategy : str = "weighted_sum" ,
2681+ filters : Optional [Union [ComparisonFilter , CompoundFilter ]] = None ,
26802682 ) -> OnlineResponse :
26812683 """
26822684 Retrieves the top k closest document features. Note, embeddings are a subset of features.
@@ -2829,8 +2831,164 @@ def retrieve_online_documents_v2(
28292831 top_k ,
28302832 distance_metric ,
28312833 query_string ,
2834+ filters ,
28322835 )
28332836
2837+ def retrieve_online_documents_openai (
2838+ self ,
2839+ vector_store_id : str ,
2840+ query : Union [str , List [str ]],
2841+ max_num_results : int = 10 ,
2842+ filters : Optional [Dict [str , Any ]] = None ,
2843+ ranking_options : Optional [Dict [str , Any ]] = None ,
2844+ rewrite_query : Optional [bool ] = None ,
2845+ features_to_retrieve : Optional [List [str ]] = None ,
2846+ ) -> Dict [str , Any ]:
2847+ """
2848+ OpenAI-compatible vector store search.
2849+
2850+ Accepts a raw query string, optionally embeds it via LiteLLM
2851+ (when ``query_embedding_model`` is configured in feature_store.yaml),
2852+ and returns results in OpenAI's ``vector_store.search_results.page``
2853+ format.
2854+
2855+ Args:
2856+ vector_store_id: Feature view name (maps to the OpenAI
2857+ ``vector_store_id`` path parameter).
2858+ query: Natural language query string, or list of strings.
2859+ max_num_results: Maximum number of results to return.
2860+ filters: OpenAI-compatible filters (accepted but not yet
2861+ applied).
2862+ ranking_options: OpenAI-compatible ranking options (accepted
2863+ but not yet applied).
2864+ rewrite_query: Whether to rewrite the query (accepted but
2865+ not yet applied).
2866+ features_to_retrieve: Specific feature names to return.
2867+ If None, all features from the feature view are used.
2868+
2869+ Returns:
2870+ Dict matching the OpenAI ``vector_store.search_results.page``
2871+ schema.
2872+
2873+ Examples:
2874+ Keyword search (no embedding model configured)::
2875+
2876+ result = store.retrieve_online_documents_openai(
2877+ vector_store_id="city_embeddings",
2878+ query="cities in California",
2879+ max_num_results=5,
2880+ )
2881+
2882+ Vector search (embedding model configured in YAML)::
2883+
2884+ # feature_store.yaml has:
2885+ # feature_server:
2886+ # query_embedding_model: text-embedding-3-small
2887+ result = store.retrieve_online_documents_openai(
2888+ vector_store_id="product_embeddings",
2889+ query="wireless audio device",
2890+ max_num_results=3,
2891+ features_to_retrieve=["name", "description"],
2892+ )
2893+ """
2894+ feature_view = self .get_feature_view (vector_store_id )
2895+
2896+ if features_to_retrieve :
2897+ feature_names = features_to_retrieve
2898+ else :
2899+ feature_names = [f .name for f in feature_view .features ]
2900+
2901+ features = [f"{ feature_view .name } :{ name } " for name in feature_names ]
2902+ query_text = query if isinstance (query , str ) else " " .join (query )
2903+
2904+ embed_cfg = self .config .embedding_model
2905+ if embed_cfg is None :
2906+ raise ValueError (
2907+ "embedding_model is not configured in feature_store.yaml. "
2908+ "Add an 'embedding_model' section with at least a 'model' "
2909+ "field to use retrieve_online_documents_openai.\n "
2910+ "Example:\n "
2911+ " embedding_model:\n "
2912+ " model: text-embedding-3-small\n "
2913+ " api_key: sk-..."
2914+ )
2915+
2916+ try :
2917+ from litellm import embedding as litellm_embedding
2918+ except ImportError :
2919+ raise ImportError (
2920+ "litellm is required for query embedding. "
2921+ "Install with: pip install litellm"
2922+ )
2923+
2924+ litellm_kwargs : Dict [str , Any ] = {
2925+ "model" : embed_cfg .model ,
2926+ "input" : [query_text ],
2927+ }
2928+ if embed_cfg .api_key :
2929+ litellm_kwargs ["api_key" ] = embed_cfg .api_key
2930+ if embed_cfg .api_base :
2931+ litellm_kwargs ["api_base" ] = embed_cfg .api_base
2932+ if embed_cfg .api_version :
2933+ litellm_kwargs ["api_version" ] = embed_cfg .api_version
2934+ if embed_cfg .dimensions :
2935+ litellm_kwargs ["dimensions" ] = embed_cfg .dimensions
2936+
2937+ embed_response = litellm_embedding (** litellm_kwargs )
2938+ query_embedding = embed_response .data [0 ]["embedding" ]
2939+
2940+ typed_filters : Optional [Union [ComparisonFilter , CompoundFilter ]] = None
2941+ if filters is not None :
2942+ typed_filters = convert_dict_to_filter (filters )
2943+
2944+ response = self .retrieve_online_documents_v2 (
2945+ features = features ,
2946+ query = query_embedding ,
2947+ top_k = max_num_results ,
2948+ filters = typed_filters ,
2949+ )
2950+
2951+ response_dict = response .to_dict ()
2952+
2953+ result_data = []
2954+ if response_dict :
2955+ first_key = next (iter (response_dict ))
2956+ num_rows = len (response_dict .get (first_key , []))
2957+ for i in range (num_rows ):
2958+ score = 0.0
2959+ attributes : Dict [str , Any ] = {}
2960+ content_parts : List [Dict [str , str ]] = []
2961+
2962+ for key , values in response_dict .items ():
2963+ val = values [i ] if i < len (values ) else None
2964+ if key == "distance" :
2965+ score = float (val ) if val is not None else 0.0
2966+ else :
2967+ attributes [key ] = val
2968+ if isinstance (val , str ):
2969+ content_parts .append ({"type" : "text" , "text" : val })
2970+
2971+ result_data .append (
2972+ {
2973+ "file_id" : f"{ vector_store_id } _{ i } " ,
2974+ "filename" : vector_store_id ,
2975+ "score" : score ,
2976+ "attributes" : attributes ,
2977+ "content" : content_parts
2978+ if content_parts
2979+ else [{"type" : "text" , "text" : str (attributes )}],
2980+ }
2981+ )
2982+
2983+ search_query = query if isinstance (query , list ) else [query ]
2984+ return {
2985+ "object" : "vector_store.search_results.page" ,
2986+ "search_query" : search_query ,
2987+ "data" : result_data ,
2988+ "has_more" : False ,
2989+ "next_page" : None ,
2990+ }
2991+
28342992 def _retrieve_from_online_store (
28352993 self ,
28362994 provider : Provider ,
@@ -2893,6 +3051,7 @@ def _retrieve_from_online_store_v2(
28933051 top_k : int ,
28943052 distance_metric : Optional [str ],
28953053 query_string : Optional [str ],
3054+ filters : Optional [Union [ComparisonFilter , CompoundFilter ]] = None ,
28963055 ) -> OnlineResponse :
28973056 """
28983057 Search and return document features from the online document store.
@@ -2909,6 +3068,7 @@ def _retrieve_from_online_store_v2(
29093068 top_k = top_k ,
29103069 distance_metric = distance_metric ,
29113070 query_string = query_string ,
3071+ filters = filters ,
29123072 )
29133073
29143074 entity_key_dict : Dict [str , List [ValueProto ]] = {}
0 commit comments