@@ -17,9 +17,11 @@ package genai
1717import (
1818 "context"
1919 "encoding/json"
20+ "fmt"
2021 "os"
2122 "path/filepath"
2223 "reflect"
24+ "strings"
2325 "time"
2426)
2527
@@ -33,6 +35,7 @@ var customTestMethods = map[string]func(ctx context.Context, client *Client, ite
3335 "shared/files/upload_get_delete" : uploadGetDelete ,
3436 "shared/models/generate_content_stream" : generateContentStream ,
3537 "shared/tunings/create_get_cancel" : createGetCancelTunings ,
38+ "file_search_stores/multimodal_flow" : multimodalSearchFlow ,
3639}
3740
3841func wrapResults (resp any , err error ) []reflect.Value {
@@ -303,3 +306,122 @@ func generateContentStream(ctx context.Context, client *Client, item *testTableI
303306 }
304307 return wrapResults (lastResp , nil )
305308}
309+
310+ func multimodalSearchFlow (ctx context.Context , client * Client , item * testTableItem ) []reflect.Value {
311+ params := struct {
312+ DisplayName string `json:"displayName"`
313+ Query string `json:"query"`
314+ TextContent string `json:"textContent"`
315+ ImageRelativePath string `json:"imageRelativePath"`
316+ }{}
317+ paramsJSON , _ := json .Marshal (item .Parameters )
318+ if err := json .Unmarshal (paramsJSON , & params ); err != nil {
319+ return wrapResults (nil , err )
320+ }
321+
322+ store , err := client .FileSearchStores .Create (ctx , & CreateFileSearchStoreConfig {
323+ DisplayName : params .DisplayName ,
324+ })
325+ if err != nil {
326+ return wrapResults (nil , err )
327+ }
328+
329+ defer func () {
330+ trueVar := true
331+ client .FileSearchStores .Delete (ctx , store .Name , & DeleteFileSearchStoreConfig {Force : & trueVar }) // nolint:errcheck
332+ }()
333+
334+ // Upload Text
335+ textFilePath := "tests/data/test_file.txt"
336+ if err := os .MkdirAll (filepath .Dir (textFilePath ), 0755 ); err != nil {
337+ return wrapResults (nil , err )
338+ }
339+ if err := os .WriteFile (textFilePath , []byte (params .TextContent ), 0644 ); err != nil {
340+ return wrapResults (nil , err )
341+ }
342+ defer os .Remove (textFilePath )
343+
344+ opText , err := client .FileSearchStores .UploadToFileSearchStoreFromPath (ctx , textFilePath , store .Name , & UploadToFileSearchStoreConfig {
345+ MIMEType : "text/plain" ,
346+ })
347+ if err != nil {
348+ return wrapResults (nil , err )
349+ }
350+
351+ // Upload Image
352+ // Resolve path relative to google3
353+ currentDir , _ := os .Getwd ()
354+ google3Path := ""
355+ lastIndex := strings .LastIndex (currentDir , "google3/" )
356+ if lastIndex != - 1 {
357+ google3Path = currentDir [:lastIndex + len ("google3/" )]
358+ }
359+ resolvedImagePath := filepath .Join (google3Path , "third_party/py/google/genai/tests/data/dog.jpg" )
360+
361+ opImage , err := client .FileSearchStores .UploadToFileSearchStoreFromPath (ctx , resolvedImagePath , store .Name , & UploadToFileSearchStoreConfig {
362+ MIMEType : "image/png" ,
363+ })
364+ if err != nil {
365+ return wrapResults (nil , err )
366+ }
367+
368+ // Wait for operations
369+ for ! opText .Done {
370+ time .Sleep (1 * time .Second )
371+ opText , err = client .Operations .GetUploadToFileSearchStoreOperation (ctx , opText , nil )
372+ if err != nil {
373+ return wrapResults (nil , err )
374+ }
375+ }
376+
377+ for ! opImage .Done {
378+ time .Sleep (1 * time .Second )
379+ opImage , err = client .Operations .GetUploadToFileSearchStoreOperation (ctx , opImage , nil )
380+ if err != nil {
381+ return wrapResults (nil , err )
382+ }
383+ }
384+
385+ // Search
386+ response , err := client .Models .GenerateContent (ctx , "gemini-2.5-flash" , Text (params .Query ), & GenerateContentConfig {
387+ Tools : []* Tool {
388+ {
389+ FileSearch : & FileSearch {
390+ FileSearchStoreNames : []string {store .Name },
391+ },
392+ },
393+ },
394+ })
395+ if err != nil {
396+ return wrapResults (nil , err )
397+ }
398+
399+ // Verify response has grounding metadata
400+ if len (response .Candidates ) == 0 || response .Candidates [0 ].GroundingMetadata == nil {
401+ return wrapResults (nil , fmt .Errorf ("no grounding metadata in response" ))
402+ }
403+
404+ // Download Media
405+ var blobMediaId string
406+ for _ , chunk := range response .Candidates [0 ].GroundingMetadata .GroundingChunks {
407+ if chunk .RetrievedContext != nil && chunk .RetrievedContext .MediaID != "" {
408+ blobMediaId = chunk .RetrievedContext .MediaID
409+ break
410+ }
411+ }
412+
413+ if client .clientConfig .Backend != BackendVertexAI {
414+ if blobMediaId == "" {
415+ return wrapResults (nil , fmt .Errorf ("no mediaId found in grounding metadata to test download" ))
416+ }
417+ content , err := client .FileSearchStores .DownloadMedia (ctx , blobMediaId , nil )
418+ if err != nil {
419+ return wrapResults (nil , err )
420+ }
421+ if content == nil {
422+ return wrapResults (nil , fmt .Errorf ("downloaded content is null" ))
423+ }
424+ }
425+
426+ return wrapResults (response , nil )
427+ }
0 commit comments