diff --git a/genai/live/live_examples_test.go b/genai/live/live_examples_test.go new file mode 100644 index 0000000000..0a89737fde --- /dev/null +++ b/genai/live/live_examples_test.go @@ -0,0 +1,70 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package live + +import ( + "bytes" + "testing" + + "github.com/GoogleCloudPlatform/golang-samples/internal/testutil" +) + +func TestLiveGeneration(t *testing.T) { + tc := testutil.SystemTest(t) + + t.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "1") + t.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1") + t.Setenv("GOOGLE_CLOUD_PROJECT", tc.ProjectID) + + buf := new(bytes.Buffer) + t.Run("generate Content in live ground googsearch", func(t *testing.T) { + buf.Reset() + err := generateGroundSearchWithTxt(buf) + if err != nil { + t.Fatalf("generateGroundSearchWithTxt failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("live Function Call With Text in live", func(t *testing.T) { + buf.Reset() + err := generateLiveFuncCallWithTxt(buf) + if err != nil { + t.Fatalf("generateLiveFuncCallWithTxt failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("generate structured output with txt", func(t *testing.T) { + buf.Reset() + if err := generateStructuredOutputWithTxt(buf); err != nil { + t.Fatalf("generateStructuredOutputWithTxt failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + +} diff --git a/genai/live/live_func_call_with_txt.go b/genai/live/live_func_call_with_txt.go new file mode 100644 index 0000000000..70533dda48 --- /dev/null +++ b/genai/live/live_func_call_with_txt.go @@ -0,0 +1,127 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package live shows how to use the GenAI SDK to generate text with live resources. +package live + +// [START googlegenaisdk_live_func_call_with_txt] +import ( + "context" + "fmt" + "io" + + "google.golang.org/genai" +) + +// generateLiveFuncCallWithTxt demonstrates using a live Gemini model +// that performs function calls and handles responses. +func generateLiveFuncCallWithTxt(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + modelID := "gemini-2.0-flash-live-preview-04-09" + + // Define simple function declarations. + turnOnLights := &genai.FunctionDeclaration{Name: "turn_on_the_lights"} + turnOffLights := &genai.FunctionDeclaration{Name: "turn_off_the_lights"} + + config := &genai.LiveConnectConfig{ + ResponseModalities: []genai.Modality{genai.ModalityText}, + Tools: []*genai.Tool{ + { + FunctionDeclarations: []*genai.FunctionDeclaration{ + turnOnLights, + turnOffLights, + }, + }, + }, + } + + session, err := client.Live.Connect(ctx, modelID, config) + if err != nil { + return fmt.Errorf("failed to connect live session: %w", err) + } + defer session.Close() + + textInput := "Turn on the lights please" + fmt.Fprintf(w, "> %s\n\n", textInput) + + // Send the user's text as a live content message. + if err := session.SendClientContent(genai.LiveClientContentInput{ + Turns: []*genai.Content{ + { + Role: "user", + Parts: []*genai.Part{ + {Text: textInput}, + }, + }, + }, + }); err != nil { + return fmt.Errorf("failed to send client content: %w", err) + } + + for { + chunk, err := session.Receive() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("error receiving chunk: %w", err) + } + + // Handle model-generated content + if chunk.ServerContent != nil && chunk.ServerContent.ModelTurn != nil { + for _, part := range chunk.ServerContent.ModelTurn.Parts { + if part.Text != "" { + fmt.Fprint(w, part.Text) + } + } + } + + // Handle tool (function) calls + if chunk.ToolCall != nil { + var functionResponses []*genai.FunctionResponse + for _, fc := range chunk.ToolCall.FunctionCalls { + functionResponse := &genai.FunctionResponse{ + Name: fc.Name, + Response: map[string]any{ + "result": "ok", + }, + } + functionResponses = append(functionResponses, functionResponse) + fmt.Fprintln(w, functionResponse.Response["result"]) + } + + if err := session.SendToolResponse(genai.LiveToolResponseInput{ + FunctionResponses: functionResponses, + }); err != nil { + return fmt.Errorf("failed to send tool response: %w", err) + } + } + } + + // Example output: + // > Turn on the lights please + // ok + + return nil +} + +// [END googlegenaisdk_live_func_call_with_txt] diff --git a/genai/live/live_ground_googsearch_with_txt.go b/genai/live/live_ground_googsearch_with_txt.go new file mode 100644 index 0000000000..ef77d05a70 --- /dev/null +++ b/genai/live/live_ground_googsearch_with_txt.go @@ -0,0 +1,108 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package live shows how to use the GenAI SDK to generate text with live resources. +package live + +// [START googlegenaisdk_live_ground_googsearch_with_txt] +import ( + "context" + "fmt" + "io" + + "google.golang.org/genai" +) + +// generateGroundSearchWithTxt demonstrates using a live Gemini model with Google Search grounded responses. +func generateGroundSearchWithTxt(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + modelName := "gemini-2.0-flash-live-preview-04-09" + + config := &genai.LiveConnectConfig{ + ResponseModalities: []genai.Modality{genai.ModalityText}, + Tools: []*genai.Tool{ + {GoogleSearch: &genai.GoogleSearch{}}, + }, + } + + session, err := client.Live.Connect(ctx, modelName, config) + if err != nil { + return fmt.Errorf("failed to connect live session: %w", err) + } + defer session.Close() + + textInput := "When did the last Brazil vs. Argentina soccer match happen?" + + // Send user input + userContent := &genai.Content{ + Role: "user", + Parts: []*genai.Part{ + {Text: textInput}, + }, + } + if err := session.SendClientContent(genai.LiveClientContentInput{ + Turns: []*genai.Content{userContent}, + }); err != nil { + return fmt.Errorf("failed to send client content: %w", err) + } + + var response string + + // Receive streaming responses + for { + chunk, err := session.Receive() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("error receiving stream: %w", err) + } + + // Handle the main model output + if chunk.ServerContent != nil { + if chunk.ServerContent.ModelTurn != nil { + for _, part := range chunk.ServerContent.ModelTurn.Parts { + if part == nil { + continue + } + if part.Text != "" { + response += part.Text + } + } + } + } + + if chunk.GoAway != nil { + break + } + } + + fmt.Fprintln(w, response) + + // Example output: + // > When did the last Brazil vs. Argentina soccer match happen? + // The most recent match between Argentina and Brazil took place on March 25, 2025, as part of the 2026 World Cup qualifiers. Argentina won 4-1. + + return nil +} + +// [END googlegenaisdk_live_ground_googsearch_with_txt] diff --git a/genai/live/live_structured_ouput_with_txt.go b/genai/live/live_structured_ouput_with_txt.go new file mode 100644 index 0000000000..dff6b56647 --- /dev/null +++ b/genai/live/live_structured_ouput_with_txt.go @@ -0,0 +1,127 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package live shows how to use the GenAI SDK to generate text with live resources. +package live + +// [START googlegenaisdk_live_structured_ouput_with_txt] +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + + "golang.org/x/oauth2/google" + "google.golang.org/genai" +) + +// CalendarEvent represents the structured output we want the model to produce. +type CalendarEvent struct { + Name string `json:"name"` + Date string `json:"date"` + Participants []string `json:"participants"` +} + +// generateStructuredOutputWithTxt demonstrates calling the model via an OpenAPI-style +// endpoint (base_url + api_key) and parsing JSON output into a Go struct. +func generateStructuredOutputWithTxt(w io.Writer) error { + ctx := context.Background() + + projectID := os.Getenv("GOOGLE_CLOUD_PROJECT") + if projectID == "" { + return fmt.Errorf("environment variable GOOGLE_CLOUD_PROJECT must be set") + } + + location := "us-central1" + // Use "openapi" to call the Gemini API via the OpenAPI-compatible endpoint. + endpointID := "openapi" + + // Programmatically obtain an access token. + ts, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") + if err != nil { + return fmt.Errorf("failed to get default token source: %w", err) + } + token, err := ts.Token() + if err != nil { + return fmt.Errorf("failed to fetch token: %w", err) + } + apiKey := token.AccessToken + + baseURL := fmt.Sprintf("https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/endpoints/%s", + location, projectID, location, endpointID) + + // Create a genai client that points to the OpenAPI endpoint, authenticating with the token. + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: apiKey, + HTTPOptions: genai.HTTPOptions{ + BaseURL: baseURL, + APIVersion: "v1", + }, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + // Build the messages (system + user) + contents := []*genai.Content{ + { + Role: "system", + Parts: []*genai.Part{ + {Text: "Extract the event information."}, + }, + }, + { + Role: "user", + Parts: []*genai.Part{ + {Text: "Alice and Bob are going to a science fair on Friday."}, + }, + }, + } + + // Ask the model to return JSON by setting a strict instruction and also request JSON mime type + // to encourage machine-readable output. + config := &genai.GenerateContentConfig{ + ResponseMIMEType: "application/json", + } + + modelName := "google/gemini-2.0-flash-001" + + resp, err := client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("generate content failed: %w", err) + } + + // Resp.Text() returns concatenated text of the top candidate. + respText := resp.Text() + if respText == "" { + return fmt.Errorf("empty response text") + } + + // Try to parse the JSON into our struct. + var event CalendarEvent + if err := json.Unmarshal([]byte(respText), &event); err != nil { + return fmt.Errorf("Model output was not valid JSON. Raw output:\n%s\n", respText) + } + + // Print parsed struct in the same friendly format. + fmt.Fprintln(w, event) + + // Example expected output: + // Parsed struct: {Name:science fair Date:Friday Participants:[Alice Bob]} + + return nil +} + +// [END googlegenaisdk_live_structured_ouput_with_txt]