@@ -146,6 +146,20 @@ pub struct Embedder {
146146 /// Configures embedder to vectorize search queries (composite embedders only)
147147 #[ serde( skip_serializing_if = "Option::is_none" ) ]
148148 pub search_embedder : Option < Box < Embedder > > ,
149+
150+ /// Configures multimodal embedding generation at indexing time.
151+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
152+ pub indexing_fragments : Option < HashMap < String , EmbedderFragment > > ,
153+
154+ /// Configures incoming media fragments for multimodal search queries.
155+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
156+ pub search_fragments : Option < HashMap < String , EmbedderFragment > > ,
157+ }
158+
159+ #[ derive( Serialize , Deserialize , Debug , Clone , PartialEq , Default ) ]
160+ #[ serde( rename_all = "camelCase" ) ]
161+ pub struct EmbedderFragment {
162+ pub value : serde_json:: Value ,
149163}
150164
151165#[ derive( Serialize , Deserialize , Default , Debug , Clone , Eq , PartialEq ) ]
@@ -2798,6 +2812,7 @@ mod tests {
27982812
27992813 use crate :: client:: * ;
28002814 use meilisearch_test_macro:: meilisearch_test;
2815+ use serde_json:: json;
28012816
28022817 #[ meilisearch_test]
28032818 async fn test_set_faceting_settings ( client : Client , index : Index ) {
@@ -3139,6 +3154,92 @@ mod tests {
31393154 assert_eq ! ( embedders, res) ;
31403155 }
31413156
3157+ #[ test]
3158+ fn embedder_with_fragments_serializes ( ) {
3159+ let embedder = Embedder {
3160+ source : EmbedderSource :: Rest ,
3161+ url : Some ( String :: from ( "https://example.com/embeddings" ) ) ,
3162+ indexing_fragments : Some ( HashMap :: from ( [ (
3163+ String :: from ( "default" ) ,
3164+ EmbedderFragment {
3165+ value : json ! ( {
3166+ "content" : [
3167+ { "type" : "text" , "text" : "{{ doc.description }}" }
3168+ ]
3169+ } ) ,
3170+ } ,
3171+ ) ] ) ) ,
3172+ search_fragments : Some ( HashMap :: from ( [ (
3173+ String :: from ( "default" ) ,
3174+ EmbedderFragment {
3175+ value : json ! ( {
3176+ "content" : [
3177+ { "type" : "text" , "text" : "{{ query.q }}" }
3178+ ]
3179+ } ) ,
3180+ } ,
3181+ ) ] ) ) ,
3182+ request : Some ( json ! ( {
3183+ "input" : [
3184+ "{{fragment}}" ,
3185+ "{{..}}"
3186+ ] ,
3187+ "model" : "example-model"
3188+ } ) ) ,
3189+ response : Some ( json ! ( {
3190+ "data" : [
3191+ {
3192+ "embedding" : "{{embedding}}"
3193+ } ,
3194+ "{{..}}"
3195+ ]
3196+ } ) ) ,
3197+ ..Default :: default ( )
3198+ } ;
3199+
3200+ let serialized = serde_json:: to_value ( & embedder) . unwrap ( ) ;
3201+
3202+ assert_eq ! (
3203+ serialized
3204+ . get( "indexingFragments" )
3205+ . and_then( |value| value. get( "default" ) )
3206+ . and_then( |value| value. get( "value" ) )
3207+ . and_then( |value| value. get( "content" ) )
3208+ . and_then( |value| value. get( 0 ) )
3209+ . and_then( |value| value. get( "text" ) ) ,
3210+ Some ( & json!( "{{ doc.description }}" ) )
3211+ ) ;
3212+
3213+ assert_eq ! (
3214+ serialized
3215+ . get( "searchFragments" )
3216+ . and_then( |value| value. get( "default" ) )
3217+ . and_then( |value| value. get( "value" ) )
3218+ . and_then( |value| value. get( "content" ) )
3219+ . and_then( |value| value. get( 0 ) )
3220+ . and_then( |value| value. get( "text" ) ) ,
3221+ Some ( & json!( "{{ query.q }}" ) )
3222+ ) ;
3223+
3224+ assert_eq ! (
3225+ serialized. get( "request" ) ,
3226+ Some ( & json!( {
3227+ "input" : [ "{{fragment}}" , "{{..}}" ] ,
3228+ "model" : "example-model"
3229+ } ) )
3230+ ) ;
3231+
3232+ assert_eq ! (
3233+ serialized. get( "response" ) ,
3234+ Some ( & json!( {
3235+ "data" : [
3236+ { "embedding" : "{{embedding}}" } ,
3237+ "{{..}}"
3238+ ]
3239+ } ) )
3240+ ) ;
3241+ }
3242+
31423243 #[ meilisearch_test]
31433244 async fn test_reset_proximity_precision ( index : Index ) {
31443245 let expected = "byWord" . to_string ( ) ;
0 commit comments