diff --git a/genai/thinking/thinking_examples_test.go b/genai/thinking/thinking_examples_test.go new file mode 100644 index 0000000000..57478c0b8c --- /dev/null +++ b/genai/thinking/thinking_examples_test.go @@ -0,0 +1,45 @@ +// 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 thinking + +import ( + "bytes" + "testing" + + "github.com/GoogleCloudPlatform/golang-samples/internal/testutil" +) + +func TestThinkingGeneration(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 including with text", func(t *testing.T) { + buf.Reset() + err := generateContentWithTxt(buf) + if err != nil { + t.Fatalf("generateContentWithTxt failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) +} diff --git a/genai/thinking/thinking_with_txt.go b/genai/thinking/thinking_with_txt.go new file mode 100644 index 0000000000..37f2926e78 --- /dev/null +++ b/genai/thinking/thinking_with_txt.go @@ -0,0 +1,115 @@ +// 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 thinking shows how to use the GenAI SDK to include thoughts with txt. +package thinking + +// [START googlegenaisdk_thinking_with_txt] +import ( + "context" + "fmt" + "io" + + "google.golang.org/genai" +) + +// generateContentWithTxt demonstrates how to generate text with thinking model. +func generateContentWithTxt(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.5-pro" + contents := []*genai.Content{ + { + Parts: []*genai.Part{ + {Text: "solve x^2 + 4x + 4 = 0"}, + }, + Role: "user", + }, + } + + resp, err := client.Models.GenerateContent(ctx, + modelName, + contents, + nil, + ) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + fmt.Fprintln(w, resp.Text()) + + // Example response: + // Answer: + // Of course! We can solve this quadratic equation in a couple of ways. + // + // The equation is: + // **x² + 4x + 4 = 0** + // + //### Method 1: Factoring + // This equation is a perfect square trinomial. This means it fits the pattern a² + 2ab + b² = (a + b)². + //1. **Recognize the pattern.** The expression `x² + 4x + 4` is a perfect square trinomial. It fits the pattern `a² + 2ab + b² = (a + b)²`. In this case, `a = x` and `b = 2`. + // + //2. **Factor the equation.** + // `x² + 4x + 4 = (x + 2)(x + 2) = (x + 2)²` + // + //3. **Solve for x.** Now set the factored expression to zero: + // `(x + 2)² = 0` + // + // Take the square root of both sides: + // `x + 2 = 0` + // + // Subtract 2 from both sides: + // `x = -2` + // + //This type of solution is called a "repeated root" or a "double root" because the factor `(x+2)` appears twice. + // + //--- + // + //### Method 2: Using the Quadratic Formula + // + //You can use the quadratic formula for any equation in the form `ax² + bx + c = 0`. + // + //The formula is: `x = [-b ± sqrt(b² - 4ac)] / 2a` + // + //1. **Identify a, b, and c.** + // * a = 1 + // * b = 4 + // * c = 4 + // + //2. **Plug the values into the formula.** + // `x = [-4 ± sqrt(4² - 4 * 1 * 4)] / (2 * 1)` + // + //3. **Simplify.** + // `x = [-4 ± sqrt(16 - 16)] / 2` + // `x = [-4 ± sqrt(0)] / 2` + // `x = -4 / 2` + // + //4. **Solve for x.** + // `x = -2` + //Alright, the user wants to solve the quadratic equation `x² + 4x + 4 = 0`. My first instinct is to see if I can factor it; that's often the fastest approach if it works. Looking at the coefficients, I see `a = 1`, `b = 4`, and `c = 4`. Factoring is clearly the most direct path here. I need to find two numbers that multiply to 4 (c) and add up to 4 (b). Hmm, let's see… 1 and 4? Nope, that adds to 5. 2 and 2? Perfect! 2 times 2 is 4, and 2 plus 2 is also 4. + // + //So, `x² + 4x + 4` factors nicely into `(x + 2)(x + 2)`. Ah, a perfect square trinomial! That's useful to note. Now, I can write the equation as `(x + 2)² = 0`. Taking the square root of both sides gives me `x + 2 = 0`. And finally, subtracting 2 from both sides, I get `x = -2`. That's the solution. + // + //Just to be thorough, and maybe to offer an alternative explanation, let's verify this using the quadratic formula. It's `x = [-b ± √(b² - 4ac)] / 2a`. Plugging in my values: `x = [-4 ± √(4² - 4 * 1 * 4)] / (2 * 1)`. That simplifies to `x = [-4 ± √(16 - 16)] / 2`, or `x = [-4 ± 0] / 2`. Therefore, `x = -2`. The discriminant being zero tells me I have exactly one real, repeated root. Great. So, whether I factor or use the quadratic formula, the answer is the same. + return nil +} + +// [END googlegenaisdk_thinking_with_txt] diff --git a/genai/tuning/tuning_create_job.go b/genai/tuning/tuning_create_job.go new file mode 100644 index 0000000000..5803a23441 --- /dev/null +++ b/genai/tuning/tuning_create_job.go @@ -0,0 +1,85 @@ +// 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 tuning shows how to use the GenAI SDK for tuning jobs. +package tuning + +// [START googlegenaisdk_tuning_job_create] +import ( + "context" + "fmt" + "io" + "time" + + "google.golang.org/genai" +) + +// createTuningJob creates a supervised tuning job using training and validation datasets. +func createTuningJob(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1beta1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + // Training dataset (JSONL in GCS) + training := &genai.TuningDataset{ + GCSURI: "gs://cloud-samples-data/ai-platform/generative_ai/gemini/text/sft_train_data.jsonl", + } + + validation := &genai.TuningValidationDataset{ + GCSURI: "gs://cloud-samples-data/ai-platform/generative_ai/gemini/text/sft_validation_data.jsonl", + } + + // Config for the tuning job + config := &genai.CreateTuningJobConfig{ + TunedModelDisplayName: "Example tuning job", + ValidationDataset: validation, + } + + // Start tuning job + job, err := client.Tunings.Tune(ctx, "gemini-2.5-flash", training, config) + if err != nil { + return fmt.Errorf("failed to create tuning job: %w", err) + } + + // Poll until the job leaves running states + for job.State == genai.JobStateQueued || job.State == genai.JobStatePending || job.State == genai.JobStateRunning { + time.Sleep(30 * time.Second) + + job, err = client.Tunings.Get(ctx, job.Name, nil) + if err != nil { + return fmt.Errorf("failed to get tuning job: %w", err) + } + fmt.Fprintln(w, "Job state:", job.State) + } + + // Print results when finished + if job.TunedModel != nil { + fmt.Fprintln(w, "Tuned model:", job.TunedModel.Model) + fmt.Fprintln(w, "Endpoint:", job.TunedModel.Endpoint) + } + fmt.Fprintln(w, "Final state:", job.State) + + // Example response: + // Checkpoint 1: checkpoint_id='1' epoch=1 step=10 endpoint='projects/123456789012/locations/us-central1/endpoints/123456789000000' + // Checkpoint 2: checkpoint_id='2' epoch=2 step=20 endpoint='projects/123456789012/locations/us-central1/endpoints/123456789012345' + + return nil +} + +// [END googlegenaisdk_tuning_job_create] diff --git a/genai/tuning/tuning_examples_test.go b/genai/tuning/tuning_examples_test.go new file mode 100644 index 0000000000..7aea43b7a7 --- /dev/null +++ b/genai/tuning/tuning_examples_test.go @@ -0,0 +1,71 @@ +// 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 tuning + +import ( + "bytes" + "testing" + + "github.com/GoogleCloudPlatform/golang-samples/internal/testutil" +) + +func TestTuningGeneration(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("create tuning job in project", func(t *testing.T) { + buf.Reset() + err := createTuningJob(buf) + if err != nil { + t.Fatalf("createTuningJob failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("predictWithTunedEndpoint in project", func(t *testing.T) { + buf.Reset() + err := predictWithTunedEndpoint(buf, "test-tuning-job") + if err != nil { + t.Fatalf("predictWithTunedEndpoint failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("get Tuning Job in project", func(t *testing.T) { + buf.Reset() + err := getTuningJob(buf, "test-tuning-job") + if err != nil { + t.Fatalf("getTuningJob failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) +} diff --git a/genai/tuning/tuning_get_job.go b/genai/tuning/tuning_get_job.go new file mode 100644 index 0000000000..049036613e --- /dev/null +++ b/genai/tuning/tuning_get_job.go @@ -0,0 +1,56 @@ +// 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 tuning shows how to use the GenAI SDK for tuning jobs. +package tuning + +// [START googlegenaisdk_tuning_job_get] +import ( + "context" + "fmt" + "io" + + "google.golang.org/genai" +) + +// getTuningJob retrieves details of a tuning job, including tuned model and endpoint +func getTuningJob(w io.Writer, tuningJobName string) 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) + } + + // Eg. tuningJobName = "projects/123456789012/locations/us-central1/tuningJobs/123456789012345" + tuningJob, err := client.Tunings.Get(ctx, tuningJobName, nil) + if err != nil { + return fmt.Errorf("failed to get tuning job: %w", err) + } + + fmt.Fprintln(w, tuningJob.TunedModel.Model) + fmt.Fprintln(w, tuningJob.TunedModel.Endpoint) + fmt.Fprintln(w, tuningJob.Experiment) + + // Example response: + // projects/123456789012/locations/us-central1/models/1234567890@1 + // projects/123456789012/locations/us-central1/endpoints/123456789012345 + // projects/123456789012/locations/us-central1/metadataStores/default/contexts/tuning-experiment-2025010112345678 + + return nil +} + +// [END googlegenaisdk_tuning_job_get] diff --git a/genai/tuning/tuning_textgen_with_txt.go b/genai/tuning/tuning_textgen_with_txt.go new file mode 100644 index 0000000000..2f389e0b06 --- /dev/null +++ b/genai/tuning/tuning_textgen_with_txt.go @@ -0,0 +1,71 @@ +// 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 tuning shows how to use the GenAI SDK for tuning jobs. +package tuning + +// [START googlegenaisdk_tuning_textgen_with_txt] +import ( + "context" + "fmt" + "io" + + "google.golang.org/genai" +) + +// predictWithTunedEndpoint demonstrates how to send a text generation request +// to a tuned endpoint created from a tuning job. +func predictWithTunedEndpoint(w io.Writer, tuningJobName string) 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) + } + + // Retrieve the tuning job and its tuned model endpoint. + tuningJob, err := client.Tunings.Get(ctx, tuningJobName, nil) + if err != nil { + return fmt.Errorf("failed to get tuning job: %w", err) + } + + contents := []*genai.Content{ + { + Role: "user", + Parts: []*genai.Part{ + {Text: "Why is the sky blue?"}, + }, + }, + } + + // Send prediction request to the tuned endpoint. + resp, err := client.Models.GenerateContent(ctx, + tuningJob.TunedModel.Endpoint, + contents, + nil, + ) + if err != nil { + return fmt.Errorf("generate content failed: %w", err) + } + + fmt.Fprintln(w, resp.Text()) + // Example response: + // The sky is blue because ... + + return nil +} + +// [END googlegenaisdk_tuning_textgen_with_txt]