@@ -44,6 +44,10 @@ function parseFetchBody(fetchMock: { mock: { calls: unknown[][] } }, callIndex =
4444 return JSON . parse ( ( init ?. body as string ) ?? "{}" ) as Record < string , unknown > ;
4545}
4646
47+ function magnitude ( values : number [ ] ) {
48+ return Math . sqrt ( values . reduce ( ( sum , value ) => sum + value * value , 0 ) ) ;
49+ }
50+
4751afterEach ( ( ) => {
4852 vi . resetAllMocks ( ) ;
4953 vi . unstubAllGlobals ( ) ;
@@ -224,6 +228,25 @@ describe("gemini-embedding-2-preview provider", () => {
224228 expect ( body . content ) . toEqual ( { parts : [ { text : "test query" } ] } ) ;
225229 } ) ;
226230
231+ it ( "normalizes embedQuery response vectors" , async ( ) => {
232+ const fetchMock = createGeminiFetchMock ( [ 3 , 4 ] ) ;
233+ vi . stubGlobal ( "fetch" , fetchMock ) ;
234+ mockResolvedProviderKey ( ) ;
235+
236+ const { provider } = await createGeminiEmbeddingProvider ( {
237+ config : { } as never ,
238+ provider : "gemini" ,
239+ model : "gemini-embedding-2-preview" ,
240+ fallback : "none" ,
241+ } ) ;
242+
243+ const embedding = await provider . embedQuery ( "test query" ) ;
244+
245+ expect ( embedding [ 0 ] ) . toBeCloseTo ( 0.6 , 5 ) ;
246+ expect ( embedding [ 1 ] ) . toBeCloseTo ( 0.8 , 5 ) ;
247+ expect ( magnitude ( embedding ) ) . toBeCloseTo ( 1 , 5 ) ;
248+ } ) ;
249+
227250 it ( "includes outputDimensionality in embedBatch request" , async ( ) => {
228251 const fetchMock = createGeminiBatchFetchMock ( 2 ) ;
229252 vi . stubGlobal ( "fetch" , fetchMock ) ;
@@ -255,6 +278,28 @@ describe("gemini-embedding-2-preview provider", () => {
255278 ] ) ;
256279 } ) ;
257280
281+ it ( "normalizes embedBatch response vectors" , async ( ) => {
282+ const fetchMock = createGeminiBatchFetchMock ( 2 , [ 3 , 4 ] ) ;
283+ vi . stubGlobal ( "fetch" , fetchMock ) ;
284+ mockResolvedProviderKey ( ) ;
285+
286+ const { provider } = await createGeminiEmbeddingProvider ( {
287+ config : { } as never ,
288+ provider : "gemini" ,
289+ model : "gemini-embedding-2-preview" ,
290+ fallback : "none" ,
291+ } ) ;
292+
293+ const embeddings = await provider . embedBatch ( [ "text1" , "text2" ] ) ;
294+
295+ expect ( embeddings ) . toHaveLength ( 2 ) ;
296+ for ( const embedding of embeddings ) {
297+ expect ( embedding [ 0 ] ) . toBeCloseTo ( 0.6 , 5 ) ;
298+ expect ( embedding [ 1 ] ) . toBeCloseTo ( 0.8 , 5 ) ;
299+ expect ( magnitude ( embedding ) ) . toBeCloseTo ( 1 , 5 ) ;
300+ }
301+ } ) ;
302+
258303 it ( "respects custom outputDimensionality" , async ( ) => {
259304 const fetchMock = createGeminiFetchMock ( ) ;
260305 vi . stubGlobal ( "fetch" , fetchMock ) ;
@@ -310,6 +355,28 @@ describe("gemini-embedding-2-preview provider", () => {
310355 ) . rejects . toThrow ( / I n v a l i d o u t p u t D i m e n s i o n a l i t y 5 1 2 / ) ;
311356 } ) ;
312357
358+ it ( "sanitizes non-finite values before normalization" , async ( ) => {
359+ const fetchMock = createGeminiFetchMock ( [
360+ 1 ,
361+ Number . NaN ,
362+ Number . POSITIVE_INFINITY ,
363+ Number . NEGATIVE_INFINITY ,
364+ ] ) ;
365+ vi . stubGlobal ( "fetch" , fetchMock ) ;
366+ mockResolvedProviderKey ( ) ;
367+
368+ const { provider } = await createGeminiEmbeddingProvider ( {
369+ config : { } as never ,
370+ provider : "gemini" ,
371+ model : "gemini-embedding-2-preview" ,
372+ fallback : "none" ,
373+ } ) ;
374+
375+ const embedding = await provider . embedQuery ( "test" ) ;
376+
377+ expect ( embedding ) . toEqual ( [ 1 , 0 , 0 , 0 ] ) ;
378+ } ) ;
379+
313380 it ( "uses correct endpoint URL" , async ( ) => {
314381 const fetchMock = createGeminiFetchMock ( ) ;
315382 vi . stubGlobal ( "fetch" , fetchMock ) ;
0 commit comments