diff --git a/genai/package.json b/genai/package.json index 8713d4595e..d04ae788d4 100644 --- a/genai/package.json +++ b/genai/package.json @@ -23,6 +23,7 @@ "chai": "^4.5.0", "mocha": "^10.0.0", "sinon": "^18.0.0", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "proxyquire": "^2.1.3" } } diff --git a/genai/test/tuning-job-create.test.js b/genai/test/tuning-job-create.test.js new file mode 100644 index 0000000000..3785c8b977 --- /dev/null +++ b/genai/test/tuning-job-create.test.js @@ -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. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const proxyquire = require('proxyquire').noCallThru(); + +const projectId = process.env.CAIP_PROJECT_ID; + +describe('tuning-job-create', () => { + it('should create tuning job and return job name', async function () { + this.timeout(1000000); + const mockTuningJob = { + name: 'test-tuning-job', + experiment: 'test-experiment', + tunedModel: { + model: 'test-model', + endpoint: 'test-endpoint', + }, + }; + + class MockTunings { + async tune() { + return mockTuningJob; + } + async get() {} + } + + class MockGoogleGenAI { + constructor() { + this.tunings = new MockTunings(); + } + } + + const sample = proxyquire('../tuning/tuning-job-create.js', { + '@google/genai': {GoogleGenAI: MockGoogleGenAI}, + }); + + const response = await sample.createTuningJob(projectId); + + assert.strictEqual(response, 'test-tuning-job'); + }); +}); diff --git a/genai/test/tuning-job-get.test.js b/genai/test/tuning-job-get.test.js new file mode 100644 index 0000000000..06b136f66d --- /dev/null +++ b/genai/test/tuning-job-get.test.js @@ -0,0 +1,58 @@ +// 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. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const proxyquire = require('proxyquire').noCallThru(); + +const projectId = process.env.GOOGLE_CLOUD_PROJECT || 'test-project'; + +describe('tuning-job-get', () => { + it('should get tuning job and return job name', async function () { + this.timeout(1000000); + + const mockTuningJob = { + name: 'test-tuning-job', + experiment: 'test-experiment', + tunedModel: { + model: 'test-model', + endpoint: 'test-endpoint', + }, + }; + + class MockTunings { + async get({name}) { + if (name !== 'TestJobName') + throw new Error('Unexpected tuning job name'); + return mockTuningJob; + } + } + + class MockGoogleGenAI { + constructor() { + this.tunings = new MockTunings(); + } + } + + const sample = proxyquire('../tuning/tuning-job-get.js', { + '@google/genai': {GoogleGenAI: MockGoogleGenAI}, + }); + + const response = await sample.getTuningJob('TestJobName', projectId); + + assert.strictEqual(response, 'test-tuning-job'); + }); +}); diff --git a/genai/test/tuning-job-list.test.js b/genai/test/tuning-job-list.test.js new file mode 100644 index 0000000000..500e81d7e7 --- /dev/null +++ b/genai/test/tuning-job-list.test.js @@ -0,0 +1,28 @@ +// 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. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it} = require('mocha'); + +const projectId = process.env.CAIP_PROJECT_ID; +const sample = require('../tuning/tuning-job-list.js'); + +describe('tuning-job-list', () => { + it('should return tuning job list', async () => { + const output = await sample.listTuningJobs(projectId); + assert(output); + }); +}); diff --git a/genai/test/tuning-textgen-with-txt.test.js b/genai/test/tuning-textgen-with-txt.test.js new file mode 100644 index 0000000000..3cd9886553 --- /dev/null +++ b/genai/test/tuning-textgen-with-txt.test.js @@ -0,0 +1,67 @@ +// 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. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const proxyquire = require('proxyquire').noCallThru(); + +const projectId = process.env.CAIP_PROJECT_ID; + +describe('tuning-textgen-with-txt', () => { + it('should fetch tuning job and generate content', async function () { + this.timeout(1000000); + + const mockTuningJob = { + name: 'test-tuning-job', + experiment: 'test-experiment', + tunedModel: { + model: 'test-model', + endpoint: 'test-endpoint', + }, + }; + + const mockGenerateContentResult = { + text: 'Because it is hot and glowing!', + }; + + class MockTunings { + async get() { + return mockTuningJob; + } + } + + class MockModels { + async generateContent() { + return mockGenerateContentResult; + } + } + + class MockGoogleGenAI { + constructor() { + this.tunings = new MockTunings(); + this.models = new MockModels(); + } + } + + const sample = proxyquire('../tuning/tuning-textgen-with-txt.js', { + '@google/genai': {GoogleGenAI: MockGoogleGenAI}, + }); + + const response = await sample.generateContent(projectId); + + assert.strictEqual(response, 'Because it is hot and glowing!'); + }); +}); diff --git a/genai/tuning/tuning-job-create.js b/genai/tuning/tuning-job-create.js new file mode 100644 index 0000000000..a80eac85c7 --- /dev/null +++ b/genai/tuning/tuning-job-create.js @@ -0,0 +1,72 @@ +// 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. + +'use strict'; + +// [START googlegenaisdk_tuning_job_create] +const {GoogleGenAI} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function createTuningJob( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + let tuningJob = await client.tunings.tune({ + baseModel: 'gemini-2.5-flash', + trainingDataset: { + gcsUri: + 'gs://cloud-samples-data/ai-platform/generative_ai/gemini/text/sft_train_data.jsonl', + }, + config: { + tunedModelDisplayName: 'Example tuning job', + }, + }); + console.log('Created tuning job:', tuningJob); + + const runningStates = new Set(['JOB_STATE_PENDING', 'JOB_STATE_RUNNING']); + + while (runningStates.has(tuningJob.state)) { + console.log(`Job state: ${tuningJob.state}`); + tuningJob = await client.tunings.get({name: tuningJob.name}); + await sleep(60000); + } + + console.log(tuningJob.tunedModel.model); + console.log(tuningJob.tunedModel.endpoint); + console.log(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 tuningJob.name; +} +// [END googlegenaisdk_tuning_job_create] + +module.exports = { + createTuningJob, +}; diff --git a/genai/tuning/tuning-job-get.js b/genai/tuning/tuning-job-get.js new file mode 100644 index 0000000000..2c49ecd381 --- /dev/null +++ b/genai/tuning/tuning-job-get.js @@ -0,0 +1,52 @@ +// 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. + +'use strict'; + +// [START googlegenaisdk_tuning_job_get] +const {GoogleGenAI} = require('@google/genai'); +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; +const TUNING_JOB_NAME = 'TestJobName'; + +async function getTuningJob( + tuningJobName = TUNING_JOB_NAME, + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + // Get the tuning job and the tuned model. + const tuningJob = await client.tunings.get({name: tuningJobName}); + + console.log(tuningJob.tunedModel.model); + console.log(tuningJob.tunedModel.endpoint); + console.log(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 tuningJob.name; +} +// [END googlegenaisdk_tuning_job_get] + +module.exports = { + getTuningJob, +}; diff --git a/genai/tuning/tuning-job-list.js b/genai/tuning/tuning-job-list.js new file mode 100644 index 0000000000..e7b0910aba --- /dev/null +++ b/genai/tuning/tuning-job-list.js @@ -0,0 +1,48 @@ +// 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. + +'use strict'; + +// [START googlegenaisdk_tuning_job_list] +const {GoogleGenAI} = require('@google/genai'); +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function listTuningJobs( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const responses = await client.tunings.list(); + + for await (const item of responses) { + if (item.name && item.name.includes('/tuningJobs/')) { + console.log(item.name); + // Example response: + // projects/123456789012/locations/us-central1/tuningJobs/123456789012345 + } + } + + return responses; +} +// [END googlegenaisdk_tuning_job_list] + +module.exports = { + listTuningJobs, +}; diff --git a/genai/tuning/tuning-textgen-with-txt.js b/genai/tuning/tuning-textgen-with-txt.js new file mode 100644 index 0000000000..7bd6a8804f --- /dev/null +++ b/genai/tuning/tuning-textgen-with-txt.js @@ -0,0 +1,52 @@ +// 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. + +'use strict'; + +// [START googlegenaisdk_tuning_textgen_with_txt] +const {GoogleGenAI} = require('@google/genai'); +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; +const TUNING_JOB_NAME = 'TestJobName'; + +async function generateContent( + tuningJobName = TUNING_JOB_NAME, + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const tuningJob = await client.tunings.get({name: tuningJobName}); + + const content = 'Why lava is red?'; + + const response = await client.models.generateContent({ + model: tuningJob.tunedModel.endpoint, + content: content, + }); + + console.log(response.text); + // Example response: + // The lava is red because ... + return response.text; +} +// [END googlegenaisdk_tuning_textgen_with_txt] + +module.exports = { + generateContent, +};