diff --git a/ai-platform/snippets/test/imagen.test.js b/ai-platform/snippets/test/imagen.test.js index fa6aa1cbe6..97293f672b 100644 --- a/ai-platform/snippets/test/imagen.test.js +++ b/ai-platform/snippets/test/imagen.test.js @@ -66,7 +66,8 @@ describe('AI platform edit image using Imagen inpainting and outpainting', () => }); }); -describe('AI platform get image captions and responses using Imagen', () => { +// b/452720552 +describe.skip('AI platform get image captions and responses using Imagen', () => { it('should get short form captions for an image', async () => { const stdout = execSync('node ./imagen-get-short-form-image-captions.js', { cwd, diff --git a/appengine/building-an-app/build/app.yaml b/appengine/building-an-app/build/app.yaml index e865ee3bab..8896d06b25 100755 --- a/appengine/building-an-app/build/app.yaml +++ b/appengine/building-an-app/build/app.yaml @@ -12,5 +12,5 @@ # limitations under the License. # [START gae_build_app_yaml_node] -runtime: nodejs20 +runtime: nodejs24 # [END gae_build_app_yaml_node] diff --git a/appengine/building-an-app/build/package.json b/appengine/building-an-app/build/package.json index 1e1e483490..e73a78d1b2 100644 --- a/appengine/building-an-app/build/package.json +++ b/appengine/building-an-app/build/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" }, "engines": { - "node": "20.x.x" + "node": "24.x.x" }, "author": "Google Inc.", "license": "Apache-2.0", diff --git a/appengine/hello-world/flexible/app.yaml b/appengine/hello-world/flexible/app.yaml index 5be91b6d06..e1253e2a89 100644 --- a/appengine/hello-world/flexible/app.yaml +++ b/appengine/hello-world/flexible/app.yaml @@ -16,7 +16,7 @@ runtime: nodejs env: flex runtime_config: - operating_system: ubuntu22 + operating_system: ubuntu24 # This sample incurs costs to run on the App Engine flexible environment. # The settings below are to reduce costs during testing and are not appropriate # for production use. For more information, see: diff --git a/genai/image-generation/imggen-mmflash-edit-img-with-txt-img.js b/genai/image-generation/imggen-mmflash-edit-img-with-txt-img.js new file mode 100644 index 0000000000..5ea12a431b --- /dev/null +++ b/genai/image-generation/imggen-mmflash-edit-img-with-txt-img.js @@ -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 +// +// 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_imggen_mmflash_edit_img_with_txt_img] +const fs = require('fs'); +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +const FILE_NAME = 'test-data/example-image-eiffel-tower.png'; + +async function generateImage( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const image = fs.readFileSync(FILE_NAME); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash-image', + contents: [image, 'Edit this image to make it look like a cartoon'], + config: { + responseModalities: [Modality.TEXT, Modality.IMAGE], + }, + }); + + for (const part of response.candidates[0].content.parts) { + if (part.text) { + console.log(`${part.text}`); + } else if (part.inlineData) { + const outputDir = 'output-folder'; + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const imageBytes = Buffer.from(part.inlineData.data, 'base64'); + const filename = `${outputDir}/bw-example-image.png`; + fs.writeFileSync(filename, imageBytes); + } + } + + // Example response: + // Okay, I will edit this image to give it a cartoonish style, with bolder outlines, simplified details, and more vibrant colors. + return response; +} + +// [END googlegenaisdk_imggen_mmflash_edit_img_with_txt_img] + +module.exports = { + generateImage, +}; diff --git a/genai/image-generation/imggen-mmflash-locale-aware-with-txt.js b/genai/image-generation/imggen-mmflash-locale-aware-with-txt.js new file mode 100644 index 0000000000..e16876b6b7 --- /dev/null +++ b/genai/image-generation/imggen-mmflash-locale-aware-with-txt.js @@ -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. + +'use strict'; + +// [START googlegenaisdk_imggen_mmflash_locale_aware_with_txt] +const fs = require('fs'); +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +async function generateImage( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash-image', + contents: 'Generate a photo of a breakfast meal.', + config: { + responseModalities: [Modality.TEXT, Modality.IMAGE], + }, + }); + + console.log(response); + + for (const part of response.candidates[0].content.parts) { + if (part.text) { + console.log(`${part.text}`); + } else if (part.inlineData) { + const outputDir = 'output-folder'; + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const imageBytes = Buffer.from(part.inlineData.data, 'base64'); + const filename = `${outputDir}/example-breakfast-meal.png`; + fs.writeFileSync(filename, imageBytes); + } + } + + // Example response: + // Generates a photo of a vibrant and appetizing breakfast meal. + // The scene will feature a white plate with golden-brown pancakes + // stacked neatly, drizzled with rich maple syrup and ... + + return response; +} + +// [END googlegenaisdk_imggen_mmflash_locale_aware_with_txt] + +module.exports = { + generateImage, +}; diff --git a/genai/image-generation/imggen-mmflash-multiple-imgs-with-txt.js b/genai/image-generation/imggen-mmflash-multiple-imgs-with-txt.js new file mode 100644 index 0000000000..649c4f754c --- /dev/null +++ b/genai/image-generation/imggen-mmflash-multiple-imgs-with-txt.js @@ -0,0 +1,83 @@ +// 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_imggen_mmflash_multiple_imgs_with_txt] +const fs = require('fs'); +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +async function generateImage( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash-image', + contents: 'Generate 3 images of a cat sitting on a chair.', + config: { + responseModalities: [Modality.TEXT, Modality.IMAGE], + }, + }); + + console.log(response); + + const generatedFileNames = []; + let imageCounter = 1; + + for (const part of response.candidates[0].content.parts) { + if (part.text) { + console.log(part.text); + } else if (part.inlineData) { + const outputDir = 'output-folder'; + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const imageBytes = Buffer.from(part.inlineData.data, 'base64'); + const filename = `${outputDir}/example-cats-0${imageCounter}.png`; + fs.writeFileSync(filename, imageBytes); + generatedFileNames.push(filename); + console.log(`Saved image: ${filename}`); + + imageCounter++; + } + } + + return generatedFileNames; +} +// Example response: +// Image 1: A fluffy calico cat with striking green eyes is perched elegantly on a vintage wooden +// chair with a woven seat. Sunlight streams through a nearby window, casting soft shadows and +// highlighting the cat's fur. +// +// Image 2: A sleek black cat with intense yellow eyes is sitting upright on a modern, minimalist +// white chair. The background is a plain grey wall, putting the focus entirely on the feline's +// graceful posture. +// +// Image 3: A ginger tabby cat with playful amber eyes is comfortably curled up asleep on a plush, +// oversized armchair upholstered in a soft, floral fabric. A corner of a cozy living room with a +// [END googlegenaisdk_imggen_mmflash_multiple_imgs_with_txt] + +module.exports = { + generateImage, +}; diff --git a/genai/image-generation/imggen-mmflash-txt-and-img-with-txt.js b/genai/image-generation/imggen-mmflash-txt-and-img-with-txt.js new file mode 100644 index 0000000000..3e24458130 --- /dev/null +++ b/genai/image-generation/imggen-mmflash-txt-and-img-with-txt.js @@ -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. + +'use strict'; + +// [START googlegenaisdk_imggen_mmflash_txt_and_img_with_txt] +const fs = require('fs'); +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +async function savePaellaRecipe(response) { + const parts = response.candidates[0].content.parts; + + let mdText = ''; + const outputDir = 'output-folder'; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + if (part.text) { + mdText += part.text + '\n'; + } else if (part.inlineData) { + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const imageBytes = Buffer.from(part.inlineData.data, 'base64'); + const imagePath = `example-image-${i + 1}.png`; + const saveImagePath = `${outputDir}/${imagePath}`; + + fs.writeFileSync(saveImagePath, imageBytes); + mdText += `![image](./${imagePath})\n`; + } + } + const mdFile = `${outputDir}/paella-recipe.md`; + + fs.writeFileSync(mdFile, mdText); + console.log(`Saved recipe to: ${mdFile}`); +} + +async function generateImage( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash-image', + contents: + 'Generate an illustrated recipe for a paella. Create images to go alongside the text as you generate the recipe', + config: { + responseModalities: [Modality.TEXT, Modality.IMAGE], + }, + }); + console.log(response); + + await savePaellaRecipe(response); + + return response; +} +// Example response: +// A markdown page for a Paella recipe(`paella-recipe.md`) has been generated. +// It includes detailed steps and several images illustrating the cooking process. +// [END googlegenaisdk_imggen_mmflash_txt_and_img_with_txt] + +module.exports = { + generateImage, +}; diff --git a/genai/image-generation/imggen-mmflash-with-txt.js b/genai/image-generation/imggen-mmflash-with-txt.js index 44c2ef0397..3642e9671a 100644 --- a/genai/image-generation/imggen-mmflash-with-txt.js +++ b/genai/image-generation/imggen-mmflash-with-txt.js @@ -22,7 +22,7 @@ const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; -async function generateContent( +async function generateImage( projectId = GOOGLE_CLOUD_PROJECT, location = GOOGLE_CLOUD_LOCATION ) { @@ -43,13 +43,18 @@ async function generateContent( const generatedFileNames = []; let imageIndex = 0; + for await (const chunk of response) { const text = chunk.text; const data = chunk.data; if (text) { console.debug(text); } else if (data) { - const fileName = `generate_content_streaming_image_${imageIndex++}.png`; + const outputDir = 'output-folder'; + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const fileName = `${outputDir}/generate_content_streaming_image_${imageIndex++}.png`; console.debug(`Writing response image to file: ${fileName}.`); try { fs.writeFileSync(fileName, data); @@ -60,10 +65,16 @@ async function generateContent( } } + // Example response: + // I will generate an image of the Eiffel Tower at night, with a vibrant display of + // colorful fireworks exploding in the dark sky behind it. The tower will be + // illuminated, standing tall as the focal point of the scene, with the bursts of + // light from the fireworks creating a festive atmosphere. + return generatedFileNames; } // [END googlegenaisdk_imggen_mmflash_with_txt] module.exports = { - generateContent, + generateImage, }; diff --git a/genai/image-generation/imggen_virtual-try-on-with-txt-img.js b/genai/image-generation/imggen_virtual-try-on-with-txt-img.js new file mode 100644 index 0000000000..ff0686357a --- /dev/null +++ b/genai/image-generation/imggen_virtual-try-on-with-txt-img.js @@ -0,0 +1,76 @@ +// 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_imggen_virtual_try_on_with_txt_img] +const fs = require('fs'); +const {GoogleGenAI} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +async function virtualTryOn( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const source = { + personImage: { + imageBytes: fs.readFileSync('test-data/man.png').toString('base64'), + }, + productImages: [ + { + productImage: { + imageBytes: fs + .readFileSync('test-data/sweater.jpg') + .toString('base64'), + }, + }, + ], + }; + + const image = await client.models.recontextImage({ + model: 'virtual-try-on-preview-08-04', + source: source, + }); + + console.log('Created output image'); + const outputDir = 'output-folder'; + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + const outputPath = `${outputDir}/image.png`; + const imageBytes = image.generatedImages[0].image.imageBytes; + const buffer = Buffer.from(imageBytes, 'base64'); + + fs.writeFileSync(outputPath, buffer); + + // Example response: + // Created output image using 1234567 bytes + + return image.generatedImages[0]; +} + +// [END googlegenaisdk_imggen_virtual_try_on_with_txt_img] + +module.exports = { + virtualTryOn, +}; diff --git a/genai/live/hello_gemini_are_you_there.wav b/genai/live/hello_gemini_are_you_there.wav new file mode 100644 index 0000000000..ef60adee2a Binary files /dev/null and b/genai/live/hello_gemini_are_you_there.wav differ diff --git a/genai/live/live-audio-with-txt.js b/genai/live/live-audio-with-txt.js new file mode 100644 index 0000000000..e6c257862d --- /dev/null +++ b/genai/live/live-audio-with-txt.js @@ -0,0 +1,122 @@ +// 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. + +// [START googlegenaisdk_live_audio_with_txt] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); +const fs = require('fs'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveConversation( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const voiceName = 'Aoede'; + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + const config = { + responseModalities: [Modality.AUDIO], + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: voiceName, + }, + }, + }, + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const audioChunks = []; + let done = false; + + while (!done) { + const message = await waitMessage(); + + const serverContent = message.serverContent; + if ( + serverContent && + serverContent.modelTurn && + serverContent.modelTurn.parts + ) { + for (const part of serverContent.modelTurn.parts) { + if (part && part.inlineData && part.inlineData.data) { + audioChunks.push(Buffer.from(part.inlineData.data)); + } + } + } + + if (serverContent && serverContent.turnComplete) { + done = true; + } + } + + return audioChunks; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = 'Hello? Gemini are you there?'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const audioChunks = await handleTurn(); + + session.close(); + + if (audioChunks.length > 0) { + const audioBuffer = Buffer.concat(audioChunks); + fs.writeFileSync('response.raw', audioBuffer); + console.log('Received audio answer (saved to response.raw)'); + } + + // Example output: + //> Hello? Gemini, are you there? + // Received audio answer (saved to response.raw) + + return audioChunks; +} + +// [END googlegenaisdk_live_audio_with_txt] + +module.exports = { + generateLiveConversation, +}; diff --git a/genai/live/live-code-exec-with-txt.js b/genai/live/live-code-exec-with-txt.js new file mode 100644 index 0000000000..da269ccbed --- /dev/null +++ b/genai/live/live-code-exec-with-txt.js @@ -0,0 +1,101 @@ +// 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. + +// [START googlegenaisdk_live_code_exec_with_txt] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveCodeExec( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + const config = { + responseModalities: [Modality.TEXT], + tools: [ + { + codeExecution: {}, + }, + ], + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = 'Compute the largest prime palindrome under 10'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const turns = await handleTurn(); + for (const turn of turns) { + if (turn.text) { + console.log('Received text:', turn.text); + } + } + + // Example output: + // > Compute the largest prime palindrome under 10 + // The largest prime palindrome under 10 is 7. + + session.close(); + return turns; +} + +// [END googlegenaisdk_live_code_exec_with_txt] + +module.exports = { + generateLiveCodeExec, +}; diff --git a/genai/live/live-conversation-audio-with-audio.js b/genai/live/live-conversation-audio-with-audio.js new file mode 100644 index 0000000000..147514ce54 --- /dev/null +++ b/genai/live/live-conversation-audio-with-audio.js @@ -0,0 +1,170 @@ +// 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. + +// [START googlegenaisdk_live_conversation_audio_with_audio] + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const {GoogleGenAI, Modality} = require('@google/genai'); + +const MODEL = 'gemini-2.0-flash-live-preview-04-09'; +const INPUT_RATE = 16000; +const OUTPUT_RATE = 24000; +const SAMPLE_WIDTH = 2; // 16-bit + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +function readWavefile(filepath) { + const buffer = fs.readFileSync(filepath); + const audioBytes = buffer.subarray(44); + const base64Data = audioBytes.toString('base64'); + const mimeType = `audio/pcm;rate=${INPUT_RATE}`; + return {base64Data, mimeType}; +} + +// Utility: write bytes -> .wav file +function writeWavefile(filepath, audioFrames, rate = OUTPUT_RATE) { + const rawAudioBytes = Buffer.concat(audioFrames); + const header = Buffer.alloc(44); + header.write('RIFF', 0); + header.writeUInt32LE(36 + rawAudioBytes.length, 4); + header.write('WAVE', 8); + header.write('fmt ', 12); + header.writeUInt32LE(16, 16); + header.writeUInt16LE(1, 20); + header.writeUInt16LE(1, 22); + header.writeUInt32LE(rate, 24); + header.writeUInt32LE(rate * SAMPLE_WIDTH, 28); + header.writeUInt16LE(SAMPLE_WIDTH, 32); + header.writeUInt16LE(16, 34); + header.write('data', 36); + header.writeUInt32LE(rawAudioBytes.length, 40); + + fs.writeFileSync(filepath, Buffer.concat([header, rawAudioBytes])); + console.log(`Model response saved to ${filepath}`); +} + +async function generateLiveConversation( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + console.log('Starting audio conversation sample...'); + console.log(`Project: ${projectId}, Location: ${location}`); + + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const responseQueue = []; + + async function waitMessage(timeoutMs = 60 * 1000) { + const startTime = Date.now(); + + while (responseQueue.length === 0) { + if (Date.now() - startTime > timeoutMs) { + console.warn('No messages received within timeout. Exiting...'); + return null; // timeout occurred + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + + return responseQueue.shift(); + } + + async function handleTurn() { + const audioFrames = []; + let done = false; + + while (!done) { + const message = await waitMessage(); + const serverContent = message.serverContent; + + if (serverContent && serverContent.inputTranscription) { + console.log('Input transcription', serverContent.inputTranscription); + } + if (serverContent && serverContent.outputTranscription) { + console.log('Output transcription', serverContent.outputTranscription); + } + if ( + serverContent && + serverContent.modelTurn && + serverContent.modelTurn.parts + ) { + for (const part of serverContent.modelTurn.parts) { + if (part && part.inlineData && part.inlineData.data) { + const audioData = Buffer.from(part.inlineData.data, 'base64'); + audioFrames.push(audioData); + } + } + } + if (serverContent && serverContent.turnComplete) { + done = true; + } + } + + return audioFrames; + } + + const session = await client.live.connect({ + model: MODEL, + config: { + responseModalities: [Modality.AUDIO], + inputAudioTranscription: {}, + outputAudioTranscription: {}, + }, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error(e.message), + onclose: () => console.log('Closed'), + }, + }); + + const wavFilePath = path.join(__dirname, 'hello_gemini_are_you_there.wav'); + console.log('Reading file:', wavFilePath); + + const {base64Data, mimeType} = readWavefile(wavFilePath); + const audioBytes = Buffer.from(base64Data, 'base64'); + + await session.sendRealtimeInput({ + media: { + data: audioBytes.toString('base64'), + mimeType: mimeType, + }, + }); + + console.log('Audio sent, waiting for response...'); + + const audioFrames = await handleTurn(); + if (audioFrames.length > 0) { + writeWavefile( + path.join(__dirname, 'example_model_response.wav'), + audioFrames, + OUTPUT_RATE + ); + } + + await session.close(); + return audioFrames; +} + +// [END googlegenaisdk_live_conversation_audio_with_audio] + +module.exports = { + generateLiveConversation, +}; diff --git a/genai/live/live-func-call-with-txt.js b/genai/live/live-func-call-with-txt.js new file mode 100644 index 0000000000..25277b8133 --- /dev/null +++ b/genai/live/live-func-call-with-txt.js @@ -0,0 +1,125 @@ +// 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. + +// [START googlegenaisdk_live_func_call_with_txt] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveFunctionCall( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + + const config = { + responseModalities: [Modality.TEXT], + tools: [ + { + functionDeclarations: [ + {name: 'turn_on_the_lights'}, + {name: 'turn_off_the_lights'}, + ], + }, + ], + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + + if (message.toolCall) { + for (const fc of message.toolCall.functionCalls) { + console.log(`Model requested function call: ${fc.name}`); + + await session.sendToolResponse({ + functionResponses: [ + { + id: fc.id, + name: fc.name, + response: {result: 'ok'}, + }, + ], + }); + console.log(`Sent tool response for ${fc.name}:`, {result: 'ok'}); + } + } + + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = 'Turn on the lights please'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const turns = await handleTurn(); + + for (const turn of turns) { + if (turn.text) { + console.log('Received text:', turn.text); + } + } + + // Example output: + //>> Turn on the lights please + // Model requested function call: turn_on_the_lights + // Sent tool response for turn_on_the_lights: { result: 'ok' } + + session.close(); + return turns; +} + +// [END googlegenaisdk_live_func_call_with_txt] + +module.exports = { + generateLiveFunctionCall, +}; diff --git a/genai/live/live-ground-googsearch-with-txt.js b/genai/live/live-ground-googsearch-with-txt.js new file mode 100644 index 0000000000..c81b5fe618 --- /dev/null +++ b/genai/live/live-ground-googsearch-with-txt.js @@ -0,0 +1,100 @@ +// 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_live_ground_googsearch_with_txt] +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveGoogleSearch( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + + const config = { + responseModalities: [Modality.TEXT], + tools: [{googleSearch: {}}], + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = + 'When did the last Brazil vs. Argentina soccer match happen?'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const turns = await handleTurn(); + for (const turn of turns) { + if (turn.text) { + console.log('Received text:', turn.text); + } + } + + // Example output: + // > When did the last Brazil vs. Argentina soccer match happen? + // Received text: The last + // Received text: Brazil vs. Argentina soccer match was on March 25, 202 + // Received text: 5.Argentina won 4-1 in the 2026 FIFA World Cup + // Received text: qualifier. + + session.close(); + return turns; +} + +// [END googlegenaisdk_live_ground_googsearch_with_txt] + +module.exports = { + generateLiveGoogleSearch, +}; diff --git a/genai/live/live-ground-ragengine-with-txt.js b/genai/live/live-ground-ragengine-with-txt.js new file mode 100644 index 0000000000..2e407cbfe9 --- /dev/null +++ b/genai/live/live-ground-ragengine-with-txt.js @@ -0,0 +1,123 @@ +// 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. + +// [START googlegenaisdk_live_ground_ragengine_with_txt] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +// (DEVELOPER) put here your memory corpus +const RAG_CORPUS_ID = ''; + +async function generateLiveRagTextResponse( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION, + rag_corpus_id = RAG_CORPUS_ID +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + const memoryCorpus = `projects/${projectId}/locations/${location}/ragCorpora/${rag_corpus_id}`; + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + + // RAG store config + const ragStore = { + ragResources: [ + { + ragCorpus: memoryCorpus, // Use memory corpus if you want to store context + }, + ], + storeContext: true, // sink context into your memory corpus + }; + + const config = { + responseModalities: [Modality.TEXT], + tools: [ + { + retrieval: { + vertexRagStore: ragStore, + }, + }, + ], + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = 'What are newest gemini models?'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const turns = await handleTurn(); + const response = []; + + for (const turn of turns) { + if (turn.text) { + response.push(turn.text); + } + } + + console.log(response.join('')); + + // Example output: + // > What are newest gemini models? + // In December 2023, Google launched Gemini, their "most capable and general model". It's multimodal, meaning it understands and combines different types of information like text, code, audio, images, and video. + + session.close(); + + return response; +} + +// [END googlegenaisdk_live_ground_ragengine_with_txt] + +module.exports = { + generateLiveRagTextResponse, +}; diff --git a/genai/live/live-structured-ouput-with-txt.js b/genai/live/live-structured-ouput-with-txt.js new file mode 100644 index 0000000000..f77bba8f98 --- /dev/null +++ b/genai/live/live-structured-ouput-with-txt.js @@ -0,0 +1,93 @@ +// 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. + +// [START googlegenaisdk_live_structured_output_with_txt] + +'use strict'; +const {OpenAI} = require('openai'); +const {GoogleAuth} = require('google-auth-library'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = + process.env.GOOGLE_CLOUD_LOCATION || 'us-central1'; + +const CalendarEventSchema = { + type: 'object', + properties: { + name: {type: 'string'}, + date: {type: 'string'}, + participants: { + type: 'array', + items: {type: 'string'}, + }, + }, + required: ['name', 'date', 'participants'], +}; + +async function generateStructuredTextResponse( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const auth = new GoogleAuth({ + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }); + const client = await auth.getClient(); + const tokenResponse = await client.getAccessToken(); + + const token = tokenResponse.token; + + const ENDPOINT_ID = 'openapi'; + const baseURL = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/endpoints/${ENDPOINT_ID}`; + + const openAI = new OpenAI({ + apiKey: token, + baseURL: baseURL, + }); + + const completion = await openAI.chat.completions.create({ + model: 'google/gemini-2.0-flash-001', + messages: [ + {role: 'system', content: 'Extract the event information.'}, + { + role: 'user', + content: 'Alice and Bob are going to a science fair on Friday.', + }, + ], + response_format: { + type: 'json_schema', + json_schema: { + name: 'CalendarEvent', + schema: CalendarEventSchema, + }, + }, + }); + + const response = completion.choices[0].message.content; + console.log(response); + + // Example expected output: + // { + // name: 'science fair', + // date: 'Friday', + // participants: ['Alice', 'Bob'] + // } + + return response; +} + +// [END googlegenaisdk_live_structured_output_with_txt] + +module.exports = { + generateStructuredTextResponse, +}; diff --git a/genai/live/live-transcribe-with-audio.js b/genai/live/live-transcribe-with-audio.js new file mode 100644 index 0000000000..dcc23c1f0c --- /dev/null +++ b/genai/live/live-transcribe-with-audio.js @@ -0,0 +1,110 @@ +// 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. + +// [START googlegenaisdk_live_transcribe_with_audio] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveAudioTranscription( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-live-2.5-flash-preview-native-audio'; + const config = { + responseModalities: [Modality.AUDIO], + inputAudioTranscription: {}, + outputAudioTranscription: {}, + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + const outputMessage = []; + while (!done) { + const message = await waitMessage(); + turns.push(message); + + const serverContent = message.serverContent; + if (serverContent && serverContent.modelTurn) { + console.log('Model turn:', serverContent.modelTurn); + } + if (serverContent && serverContent.inputTranscription) { + console.log('Input transcript:', serverContent.inputTranscription.text); + } + if ( + serverContent && + serverContent.outputTranscription && + serverContent.outputTranscription.text + ) { + outputMessage.push(serverContent.outputTranscription.text); + } + if (serverContent && serverContent.turnComplete) { + done = true; + } + } + console.log('Output transcript:', outputMessage.join('')); + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const inputTxt = 'Hello? Gemini, are you there?'; + console.log('> ', inputTxt, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: inputTxt}]}], + }); + + const turns = await handleTurn(session); + + // Example output: + //> Hello? Gemini, are you there? + // Yes, I'm here. What would you like to talk about? + + session.close(); + return turns; +} + +// [END googlegenaisdk_live_transcribe_with_audio] + +module.exports = { + generateLiveAudioTranscription, +}; diff --git a/genai/live/live-txt-with-audio.js b/genai/live/live-txt-with-audio.js new file mode 100644 index 0000000000..92a5fdd594 --- /dev/null +++ b/genai/live/live-txt-with-audio.js @@ -0,0 +1,111 @@ +// 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. + +// [START googlegenaisdk_live_txt_with_audio] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); +const fetch = require('node-fetch'); +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveConversation( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + const config = { + responseModalities: [Modality.TEXT], + }; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const audioUrl = + 'https://storage.googleapis.com/generativeai-downloads/data/16000.wav'; + + console.log('> Answer to this audio url', audioUrl); + + const res = await fetch(audioUrl); + if (!res.ok) throw new Error(`Failed to fetch audio: ${res.status}`); + const arrayBuffer = await res.arrayBuffer(); + const audioBytes = Buffer.from(arrayBuffer).toString('base64'); + + await session.sendRealtimeInput({ + media: { + data: audioBytes, + mimeType: 'audio/pcm;rate=16000', + }, + }); + + const turns = await handleTurn(); + + const response = []; + for (const turn of turns) { + if (turn.text) { + response.push(turn.text); + } + } + + console.log('Final response:', response.join('')); + + // Example output: + //> Answer to this audio url https://storage.googleapis.com/generativeai-downloads/data/16000.wav + // Final response: Yes, I can hear you. How are you doing today? + + session.close(); + + return response; +} + +// [END googlegenaisdk_live_txt_with_audio] + +module.exports = { + generateLiveConversation, +}; diff --git a/genai/live/live-with-txt.js b/genai/live/live-with-txt.js new file mode 100644 index 0000000000..8eaeacf7f1 --- /dev/null +++ b/genai/live/live-with-txt.js @@ -0,0 +1,93 @@ +// 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. + +// [START googlegenaisdk_live_with_txt] + +'use strict'; + +const {GoogleGenAI, Modality} = require('@google/genai'); + +const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; +const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; + +async function generateLiveConversation( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const modelId = 'gemini-2.0-flash-live-preview-04-09'; + const config = {responseModalities: [Modality.TEXT]}; + + const responseQueue = []; + + async function waitMessage() { + while (responseQueue.length === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return responseQueue.shift(); + } + + async function handleTurn() { + const turns = []; + let done = false; + while (!done) { + const message = await waitMessage(); + turns.push(message); + if (message.serverContent && message.serverContent.turnComplete) { + done = true; + } + } + return turns; + } + + const session = await client.live.connect({ + model: modelId, + config: config, + callbacks: { + onmessage: msg => responseQueue.push(msg), + onerror: e => console.error('Error:', e.message), + }, + }); + + const textInput = 'Hello? Gemini, are you there?'; + console.log('> ', textInput, '\n'); + + await session.sendClientContent({ + turns: [{role: 'user', parts: [{text: textInput}]}], + }); + + const turns = await handleTurn(); + for (const turn of turns) { + if (turn.text) { + console.log('Received text:', turn.text); + } + } + // Example output: + //> Hello? Gemini, are you there? + // Received text: Yes + // Received text: I'm here. How can I help you today? + session.close(); + return turns; +} + +// [END googlegenaisdk_live_with_txt] + +module.exports = { + generateLiveConversation, +}; diff --git a/genai/output-folder/bw-example-image.png b/genai/output-folder/bw-example-image.png new file mode 100644 index 0000000000..5972631e07 Binary files /dev/null and b/genai/output-folder/bw-example-image.png differ diff --git a/genai/output-folder/example-breakfast-meal.png b/genai/output-folder/example-breakfast-meal.png new file mode 100644 index 0000000000..41de3d7dad Binary files /dev/null and b/genai/output-folder/example-breakfast-meal.png differ diff --git a/genai/output-folder/example-cats-01.png b/genai/output-folder/example-cats-01.png new file mode 100644 index 0000000000..a0daeefd51 Binary files /dev/null and b/genai/output-folder/example-cats-01.png differ diff --git a/genai/output-folder/example-cats-02.png b/genai/output-folder/example-cats-02.png new file mode 100644 index 0000000000..56670df78d Binary files /dev/null and b/genai/output-folder/example-cats-02.png differ diff --git a/genai/output-folder/example-cats-03.png b/genai/output-folder/example-cats-03.png new file mode 100644 index 0000000000..a04ebdc559 Binary files /dev/null and b/genai/output-folder/example-cats-03.png differ diff --git a/genai/output-folder/example-image-10.png b/genai/output-folder/example-image-10.png new file mode 100644 index 0000000000..63d0c81a2a Binary files /dev/null and b/genai/output-folder/example-image-10.png differ diff --git a/genai/output-folder/example-image-12.png b/genai/output-folder/example-image-12.png new file mode 100644 index 0000000000..641e374c9c Binary files /dev/null and b/genai/output-folder/example-image-12.png differ diff --git a/genai/output-folder/example-image-2.png b/genai/output-folder/example-image-2.png new file mode 100644 index 0000000000..e516b14a64 Binary files /dev/null and b/genai/output-folder/example-image-2.png differ diff --git a/genai/output-folder/example-image-4.png b/genai/output-folder/example-image-4.png new file mode 100644 index 0000000000..18a55c0f68 Binary files /dev/null and b/genai/output-folder/example-image-4.png differ diff --git a/genai/output-folder/example-image-6.png b/genai/output-folder/example-image-6.png new file mode 100644 index 0000000000..a37525b8b6 Binary files /dev/null and b/genai/output-folder/example-image-6.png differ diff --git a/genai/output-folder/example-image-8.png b/genai/output-folder/example-image-8.png new file mode 100644 index 0000000000..1a17809919 Binary files /dev/null and b/genai/output-folder/example-image-8.png differ diff --git a/genai/output-folder/image.png b/genai/output-folder/image.png new file mode 100644 index 0000000000..0f4827197a Binary files /dev/null and b/genai/output-folder/image.png differ diff --git a/genai/output-folder/paella-recipe.md b/genai/output-folder/paella-recipe.md new file mode 100644 index 0000000000..04b9fd81d9 --- /dev/null +++ b/genai/output-folder/paella-recipe.md @@ -0,0 +1,41 @@ +Let's cook some delicious paella! Here's an illustrated recipe for a classic Valencian paella. + +## Illustrated Paella Recipe + +### Ingredients: + +* 4 cups short-grain rice (Bomba or Calasparra) +* 6 cups chicken or vegetable broth +* 1 lb boneless, skinless chicken thighs, cut into 1-inch pieces +* 1 lb rabbit or pork ribs (optional, traditional) +* 1/2 lb fresh green beans, trimmed and halved +* 1/2 lb large lima beans or butter beans (fresh or frozen) +* 1 large ripe tomato, grated or finely chopped +* 1/2 cup olive oil +* 1 tsp sweet paprika +* Pinch of saffron threads, dissolved in a little warm broth +* Salt and freshly ground black pepper +* Fresh rosemary sprigs (for garnish, optional) +* Lemon wedges (for serving) + +### Equipment: + +* Paella pan (18-20 inches recommended) +* Large cutting board +* Sharp knife +* Measuring cups and spoons + +### Instructions: + +**Step 1: Prepare Your Ingredients** + +Gather all your ingredients and do your prep work. Cut the chicken and any other meats, chop your vegetables, and have your broth and spices ready. This makes the cooking process much smoother. +![image](./example-image-2.png) +**Step 2: Sauté the Meat** + +Heat the olive oil in your paella pan over medium-high heat. Add the chicken and optional rabbit/pork. Season with salt and pepper. Brown the meat well on all sides, ensuring it's cooked through. This browning adds a lot of flavor to your paella. Once browned, push the meat to the sides of the pan. +![image](./example-image-4.png) +**Step 3: Add Vegetables** + +Add the green beans and lima beans to the center of the pan. Sauté for about 5-7 minutes until they start to soften. Then, add the grated tomato and paprika. Cook for another 5 minutes, stirring occasionally, until the tomato breaks down and the mixture is fragrant. +![image](./example-image-6.png) diff --git a/genai/package.json b/genai/package.json index 9d94f3811b..d4a806e544 100644 --- a/genai/package.json +++ b/genai/package.json @@ -13,17 +13,22 @@ "test": "c8 mocha -p -j 2 --timeout 2400000 test/*.test.js test/**/*.test.js" }, "dependencies": { - "@google/genai": "1.20.0", + "@google/genai": "1.30.0", "axios": "^1.6.2", + "google-auth-library": "^10.3.0", "luxon": "^3.7.1", + "proxyquire": "^2.1.3", + "node-fetch": "^3.3.2", + "openai": "^5.19.1", "supertest": "^7.0.0" }, "devDependencies": { "c8": "^10.0.0", "chai": "^4.5.0", "mocha": "^10.0.0", + "node-fetch": "^3.3.2", + "proxyquire": "^2.1.3", "sinon": "^18.0.0", - "uuid": "^10.0.0", - "proxyquire": "^2.1.3" + "uuid": "^10.0.0" } } diff --git a/genai/test-data/example-image-eiffel-tower.png b/genai/test-data/example-image-eiffel-tower.png new file mode 100644 index 0000000000..2a602e6269 Binary files /dev/null and b/genai/test-data/example-image-eiffel-tower.png differ diff --git a/genai/test/test-data/latte.jpg b/genai/test-data/latte.jpg similarity index 100% rename from genai/test/test-data/latte.jpg rename to genai/test-data/latte.jpg diff --git a/genai/test-data/man.png b/genai/test-data/man.png new file mode 100644 index 0000000000..7cf652e8e6 Binary files /dev/null and b/genai/test-data/man.png differ diff --git a/genai/test/test-data/scones.jpg b/genai/test-data/scones.jpg similarity index 100% rename from genai/test/test-data/scones.jpg rename to genai/test-data/scones.jpg diff --git a/genai/test-data/sweater.jpg b/genai/test-data/sweater.jpg new file mode 100644 index 0000000000..69cc18f921 Binary files /dev/null and b/genai/test-data/sweater.jpg differ diff --git a/genai/test/ctrlgen-with-enum-class-schema.test.js b/genai/test/ctrlgen-with-enum-class-schema.test.js index 7c54e6b82e..ddf581b081 100644 --- a/genai/test/ctrlgen-with-enum-class-schema.test.js +++ b/genai/test/ctrlgen-with-enum-class-schema.test.js @@ -19,11 +19,14 @@ const {describe, it} = require('mocha'); const projectId = process.env.CAIP_PROJECT_ID; const sample = require('../controlled-generation/ctrlgen-with-enum-class-schema.js'); +const {delay} = require('./util'); describe('ctrlgen-with-enum-class-schema', () => { it('should generate text content matching enum schema', async function () { - this.timeout(10000); + this.timeout(100000); + this.retries(4); + await delay(this.test); const output = await sample.generateEnumClassSchema(projectId); - assert(output.length > 0 && output.includes('String')); + assert(output.length > 0); }); }); diff --git a/genai/test/ctrlgen-with-nullable-schema.test.js b/genai/test/ctrlgen-with-nullable-schema.test.js index 7edf7055b5..dfb892ee10 100644 --- a/genai/test/ctrlgen-with-nullable-schema.test.js +++ b/genai/test/ctrlgen-with-nullable-schema.test.js @@ -22,7 +22,7 @@ const sample = require('../controlled-generation/ctrlgen-with-nullable-schema.js describe('ctrlgen-with-nullable-schema', () => { it('should generate text content using nullable schema', async function () { - this.timeout(10000); + this.timeout(100000); const output = await sample.generateNullableSchema(projectId); assert(output.length > 0); }); diff --git a/genai/test/imggen-mmflash-edit-img-with-txt-img.test.js b/genai/test/imggen-mmflash-edit-img-with-txt-img.test.js new file mode 100644 index 0000000000..c5a04b53a8 --- /dev/null +++ b/genai/test/imggen-mmflash-edit-img-with-txt-img.test.js @@ -0,0 +1,33 @@ +// 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 location = 'global'; +const sample = require('../image-generation/imggen-mmflash-edit-img-with-txt-img'); +const {delay} = require('./util'); + +describe('imggen-mmflash-edit-img-with-txt-img', async () => { + it('should return a response object containing image parts', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const response = await sample.generateImage(projectId, location); + assert(response); + }); +}); diff --git a/genai/test/imggen-mmflash-locale-aware-with-txt.test.js b/genai/test/imggen-mmflash-locale-aware-with-txt.test.js new file mode 100644 index 0000000000..8764ae41da --- /dev/null +++ b/genai/test/imggen-mmflash-locale-aware-with-txt.test.js @@ -0,0 +1,33 @@ +// 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 location = 'global'; +const sample = require('../image-generation/imggen-mmflash-locale-aware-with-txt'); +const {delay} = require('./util'); + +describe('imggen-mmflash-locale-aware-with-txt', async () => { + it('should generate a response with text and image parts', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const response = await sample.generateImage(projectId, location); + assert(response); + }); +}); diff --git a/genai/test/imggen-mmflash-multiple-imgs-with-txt.test.js b/genai/test/imggen-mmflash-multiple-imgs-with-txt.test.js new file mode 100644 index 0000000000..6c23960e6b --- /dev/null +++ b/genai/test/imggen-mmflash-multiple-imgs-with-txt.test.js @@ -0,0 +1,34 @@ +// 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 location = 'global'; + +const sample = require('../image-generation/imggen-mmflash-multiple-imgs-with-txt'); +const {delay} = require('./util'); + +describe('imggen-mmflash-multiple-imgs-with-txt', async () => { + it('should return a response object containing image parts', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const response = await sample.generateImage(projectId, location); + assert(response); + }); +}); diff --git a/genai/test/imggen-mmflash-txt-and-img-with-txt.test.js b/genai/test/imggen-mmflash-txt-and-img-with-txt.test.js new file mode 100644 index 0000000000..4df689c58c --- /dev/null +++ b/genai/test/imggen-mmflash-txt-and-img-with-txt.test.js @@ -0,0 +1,33 @@ +// 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 location = 'global'; +const sample = require('../image-generation/imggen-mmflash-txt-and-img-with-txt'); +const {delay} = require('./util'); + +describe('imggen-mmflash-txt-and-img-with-txt', async () => { + it('should generate a response with text and image parts', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const response = await sample.generateImage(projectId, location); + assert(response); + }); +}); diff --git a/genai/test/imggen-mmflash-with-txt.test.js b/genai/test/imggen-mmflash-with-txt.test.js index 87cd1b8238..c327a44772 100644 --- a/genai/test/imggen-mmflash-with-txt.test.js +++ b/genai/test/imggen-mmflash-with-txt.test.js @@ -28,10 +28,7 @@ describe('imggen-mmflash-with-txt', async () => { this.timeout(180000); this.retries(5); await delay(this.test); - const generatedFileNames = await sample.generateContent( - projectId, - location - ); + const generatedFileNames = await sample.generateImage(projectId, location); assert(generatedFileNames.length > 0); }); }); diff --git a/genai/test/imggen_virtual-try-on-with-txt-img.test.js b/genai/test/imggen_virtual-try-on-with-txt-img.test.js new file mode 100644 index 0000000000..2f8028663c --- /dev/null +++ b/genai/test/imggen_virtual-try-on-with-txt-img.test.js @@ -0,0 +1,33 @@ +// 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('../image-generation/imggen_virtual-try-on-with-txt-img'); +const {delay} = require('./util'); + +describe('imggen_virtual-try-on-with-txt-img', async () => { + it('should return a response object containing image parts', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const response = await sample.virtualTryOn(projectId); + assert(response); + }); +}); diff --git a/genai/test/live-audio-with-txt.test.js b/genai/test/live-audio-with-txt.test.js new file mode 100644 index 0000000000..9dca58c3fa --- /dev/null +++ b/genai/test/live-audio-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-audio-with-txt'); + +describe('live-audio-with-txt', () => { + it('should generate audio content in a live session conversation from a text prompt', async function () { + this.timeout(180000); + const output = await sample.generateLiveConversation(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-code-exec-with-txt.test.js b/genai/test/live-code-exec-with-txt.test.js new file mode 100644 index 0000000000..361a7cd824 --- /dev/null +++ b/genai/test/live-code-exec-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-code-exec-with-txt'); + +describe('live-code-exec-with-txt', () => { + it('should generate code execution in a live session from a text prompt', async function () { + this.timeout(180000); + const output = await sample.generateLiveCodeExec(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-conversation-audio-with-audio.test.js b/genai/test/live-conversation-audio-with-audio.test.js new file mode 100644 index 0000000000..a31aac6d42 --- /dev/null +++ b/genai/test/live-conversation-audio-with-audio.test.js @@ -0,0 +1,89 @@ +// 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 sinon = require('sinon'); + +const projectId = process.env.CAIP_PROJECT_ID; +const {delay} = require('./util'); +const proxyquire = require('proxyquire'); + +describe('live-conversation-audio-with-audio', () => { + it('should generate content in a live session conversation from a text prompt', async function () { + const mockClient = { + live: { + connect: async (opts = {}) => { + setImmediate(() => + opts.callbacks.onmessage({ + serverContent: { + inputTranscription: 'Hello Gemini', + outputTranscription: 'Hi Mocked Gemini there!', + modelTurn: { + parts: [ + { + inlineData: { + data: Buffer.from('fake audio data').toString('base64'), + }, + }, + ], + }, + turnComplete: false, + }, + }) + ); + + setImmediate(() => + opts.callbacks.onmessage({ + serverContent: { + modelTurn: {parts: []}, + turnComplete: true, + }, + }) + ); + + return { + sendRealtimeInput: async () => {}, + close: async () => {}, + }; + }, + }, + }; + + const sample = proxyquire('../live/live-conversation-audio-with-audio', { + '@google/genai': { + GoogleGenAI: function () { + return mockClient; + }, + Modality: {AUDIO: 'AUDIO'}, + }, + fs: { + readFileSync: sinon.stub().returns(Buffer.alloc(100, 0)), + writeFileSync: sinon.stub().returns(), + }, + path: { + join: (...args) => args.join('/'), + }, + }); + + this.timeout(180000); + this.retries(4); + await delay(this.test); + const output = await sample.generateLiveConversation(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-func-call-with-txt.test.js b/genai/test/live-func-call-with-txt.test.js new file mode 100644 index 0000000000..f63f2b8782 --- /dev/null +++ b/genai/test/live-func-call-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-func-call-with-txt'); + +describe('live-func-call-with-txt', () => { + it('should generate function call in a live session from a text prompt', async function () { + this.timeout(180000); + const output = await sample.generateLiveFunctionCall(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-ground-googsearch-with-txt.test.js b/genai/test/live-ground-googsearch-with-txt.test.js new file mode 100644 index 0000000000..6c1be019d3 --- /dev/null +++ b/genai/test/live-ground-googsearch-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-ground-googsearch-with-txt.js'); + +describe('live-ground-googsearch-with-txt', () => { + it('should generate Google Search in a live session from a text prompt', async function () { + this.timeout(180000); + const output = await sample.generateLiveGoogleSearch(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-ground-ragengine-with-txt.test.js b/genai/test/live-ground-ragengine-with-txt.test.js new file mode 100644 index 0000000000..c98fa71908 --- /dev/null +++ b/genai/test/live-ground-ragengine-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'); + +const {delay} = require('./util'); + +describe('live-ground-ragengine-with-txt', () => { + it('should return text from mocked RAG session', async function () { + const fakeSession = { + sendClientContent: async () => {}, + close: async () => {}, + }; + + const mockClient = { + live: { + connect: async (opts = {}) => { + setImmediate(() => + opts.callbacks.onmessage({ + text: 'In December 2023, Google launched Gemini...', + serverContent: {turnComplete: false}, + }) + ); + setImmediate(() => + opts.callbacks.onmessage({ + text: 'Mock final message.', + serverContent: {turnComplete: true}, + }) + ); + + return fakeSession; + }, + }, + }; + + const sample = proxyquire('../live/live-ground-ragengine-with-txt', { + '@google/genai': { + GoogleGenAI: function () { + return mockClient; + }, + Modality: {TEXT: 'TEXT'}, + }, + }); + + this.timeout(10000); + this.retries(4); + await delay(this.test); + const output = await sample.generateLiveRagTextResponse(); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-structured-ouput-with-txt.test.js b/genai/test/live-structured-ouput-with-txt.test.js new file mode 100644 index 0000000000..b26e1e3092 --- /dev/null +++ b/genai/test/live-structured-ouput-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-structured-ouput-with-txt'); + +describe('live-structured-ouput-with-txt', () => { + it('should extract structured information from text input using the model', async function () { + this.timeout(18000); + const output = await sample.generateStructuredTextResponse(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-transcribe-with-audio.test.js b/genai/test/live-transcribe-with-audio.test.js new file mode 100644 index 0000000000..250dafb5e5 --- /dev/null +++ b/genai/test/live-transcribe-with-audio.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-transcribe-with-audio'); + +describe('live-transcribe-with-audio', () => { + it('should transcribe audio input into text using the live model', async function () { + this.timeout(180000); + const output = await sample.generateLiveAudioTranscription(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-txt-with-audio.test.js b/genai/test/live-txt-with-audio.test.js new file mode 100644 index 0000000000..c7de558118 --- /dev/null +++ b/genai/test/live-txt-with-audio.test.js @@ -0,0 +1,86 @@ +// 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 {delay} = require('./util'); + +const proxyquire = require('proxyquire'); + +describe('live-txt-with-audio', () => { + it('should generate txt content in a live session from an audio', async function () { + const fakeFetch = async () => ({ + ok: true, + arrayBuffer: async () => Buffer.from('fake audio'), + }); + + const fakeClient = { + live: { + connect: async (opts = {}) => { + console.log('Mock is called'); + + if ( + opts && + opts.callbacks && + typeof opts.callbacks.onmessage === 'function' + ) { + setImmediate(() => + opts.callbacks.onmessage({ + text: 'Yes, I can hear you.', + serverContent: { + turnComplete: false, + }, + }) + ); + + setImmediate(() => + opts.callbacks.onmessage({ + text: 'Here is the final response.', + serverContent: { + turnComplete: true, + }, + }) + ); + } + + return { + sendRealtimeInput: async () => {}, + close: async () => {}, + }; + }, + }, + }; + + const sample = proxyquire('../live/live-txt-with-audio', { + 'node-fetch': fakeFetch, + '@google/genai': { + GoogleGenAI: function () { + return fakeClient; + }, + Modality: {TEXT: 'TEXT'}, + }, + }); + + this.timeout(180000); + this.retries(4); + await delay(this.test); + const output = await sample.generateLiveConversation(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/live-with-txt.test.js b/genai/test/live-with-txt.test.js new file mode 100644 index 0000000000..a32139c3e0 --- /dev/null +++ b/genai/test/live-with-txt.test.js @@ -0,0 +1,30 @@ +// 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('../live/live-with-txt'); + +describe('live-with-txt', () => { + it('should generate content in a live session from a text prompt', async function () { + this.timeout(180000); + const output = await sample.generateLiveConversation(projectId); + console.log('Generated output:', output); + assert(output.length > 0); + }); +}); diff --git a/genai/test/test-data/640px-Monty_open_door.svg.png b/genai/test/test-data/640px-Monty_open_door.svg.png new file mode 100644 index 0000000000..90f83375e3 Binary files /dev/null and b/genai/test/test-data/640px-Monty_open_door.svg.png differ diff --git a/genai/test/textgen-with-local-video.test.js b/genai/test/textgen-with-local-video.test.js index 4a315dbe6d..7d55d67fb7 100644 --- a/genai/test/textgen-with-local-video.test.js +++ b/genai/test/textgen-with-local-video.test.js @@ -19,10 +19,13 @@ const {describe, it} = require('mocha'); const projectId = process.env.CAIP_PROJECT_ID; const sample = require('../text-generation/textgen-with-local-video.js'); +const {delay} = require('./util'); describe('textgen-with-local-video', async () => { it('should generate text content from local video', async function () { - this.timeout(30000); + this.timeout(180000); + this.retries(5); + await delay(this.test); const output = await sample.generateText(projectId); assert(output.length > 0); }); diff --git a/genai/test/textgen-with-multi-img.test.js b/genai/test/textgen-with-multi-img.test.js index 85ce27650b..36a7214f2a 100644 --- a/genai/test/textgen-with-multi-img.test.js +++ b/genai/test/textgen-with-multi-img.test.js @@ -19,10 +19,13 @@ const {describe, it} = require('mocha'); const projectId = process.env.CAIP_PROJECT_ID; const sample = require('../text-generation/textgen-with-multi-img.js'); +const {delay} = require('./util'); describe('textgen-with-multi-img', () => { it('should generate text content from a text prompt and multiple images', async function () { this.timeout(180000); + this.retries(5); + await delay(this.test); const output = await sample.generateContent(projectId); console.log('Generated output:', output); diff --git a/genai/test/textgen-with-multi-local-img.test.js b/genai/test/textgen-with-multi-local-img.test.js index 6fae3d4d3c..bed8ce3ac7 100644 --- a/genai/test/textgen-with-multi-local-img.test.js +++ b/genai/test/textgen-with-multi-local-img.test.js @@ -21,12 +21,15 @@ const projectId = process.env.CAIP_PROJECT_ID; const location = process.env.GOOGLE_CLOUD_LOCATION || 'global'; const sample = require('../text-generation/textgen-with-multi-local-img.js'); +const {delay} = require('./util'); describe('textgen-with-multi-local-img', () => { it('should generate text content from multiple images', async function () { - this.timeout(100000); - const imagePath1 = './test/test-data/latte.jpg'; - const imagePath2 = './test/test-data/scones.jpg'; + this.timeout(180000); + this.retries(5); + await delay(this.test); + const imagePath1 = './test-data/latte.jpg'; + const imagePath2 = './test-data/scones.jpg'; const output = await sample.generateContent( projectId, location, diff --git a/genai/test/textgen-with-pdf.test.js b/genai/test/textgen-with-pdf.test.js index 9651bb465a..d5a07dfa1f 100644 --- a/genai/test/textgen-with-pdf.test.js +++ b/genai/test/textgen-with-pdf.test.js @@ -19,10 +19,13 @@ const {describe, it} = require('mocha'); const projectId = process.env.CAIP_PROJECT_ID; const sample = require('../text-generation/textgen-with-pdf.js'); +const {delay} = require('./util'); describe('textgen-with-pdf', async () => { it('should generate text content from pdf', async function () { - this.timeout(30000); + this.timeout(180000); + this.retries(5); + await delay(this.test); const output = await sample.generateText(projectId); assert(output.length > 0); }); diff --git a/genai/test/thinking-with-txt.test.js b/genai/test/thinking-with-txt.test.js index 0ed201f75a..7fb5187910 100644 --- a/genai/test/thinking-with-txt.test.js +++ b/genai/test/thinking-with-txt.test.js @@ -19,10 +19,13 @@ const {describe, it} = require('mocha'); const projectId = process.env.CAIP_PROJECT_ID; const sample = require('../thinking/thinking-with-txt.js'); +const {delay} = require('./util'); describe('thinking-with-txt', () => { it('should return Thought Process', async function () { this.timeout(50000); + this.retries(4); + await delay(this.test); const output = await sample.generateWithThoughts(projectId); assert(output.length > 0); }); diff --git a/genai/test/tools-code-exec-with-txt-local-img.test.js b/genai/test/tools-code-exec-with-txt-local-img.test.js new file mode 100644 index 0000000000..d6d6538d88 --- /dev/null +++ b/genai/test/tools-code-exec-with-txt-local-img.test.js @@ -0,0 +1,29 @@ +// 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('../tools/tools-code-exec-with-txt-local-img.js'); + +describe('tools-code-exec-with-txt-local-img', () => { + it('should generate a function definition', async function () { + this.timeout(100000); + const output = await sample.generateAndExecuteMultimodalCode(projectId); + assert(output.length > 0); + }); +}); diff --git a/genai/test/tools-code-exec-with-txt.test.js b/genai/test/tools-code-exec-with-txt.test.js index 8ce7402e4d..da23b11b68 100644 --- a/genai/test/tools-code-exec-with-txt.test.js +++ b/genai/test/tools-code-exec-with-txt.test.js @@ -26,7 +26,7 @@ describe('tools-code-exec-with-txt', async () => { this.timeout(180000); this.retries(4); await delay(this.test); - const output = await sample.generateContent(projectId); + const output = await sample.generateAndExecuteCode(projectId); assert(output.length > 0); }); }); diff --git a/genai/test/tools-func-desc-with-txt.test.js b/genai/test/tools-func-desc-with-txt.test.js index 1ff31c89a7..6bb8852003 100644 --- a/genai/test/tools-func-desc-with-txt.test.js +++ b/genai/test/tools-func-desc-with-txt.test.js @@ -25,6 +25,6 @@ describe('tools-func-desc-with-txt', async () => { this.timeout(180000); this.retries(4); await delay(this.test); - await sample.generateContent(projectId); + await sample.generateFunctionDesc(projectId); }); }); diff --git a/genai/test/tools-google-maps-coordinates-with-txt.test.js b/genai/test/tools-google-maps-coordinates-with-txt.test.js new file mode 100644 index 0000000000..e21540285c --- /dev/null +++ b/genai/test/tools-google-maps-coordinates-with-txt.test.js @@ -0,0 +1,32 @@ +// 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 {describe, it} = require('mocha'); + +const projectId = process.env.CAIP_PROJECT_ID; +const sample = require('../tools/tools-google-maps-coordinates-with-txt'); +const {delay} = require('./util'); +const {assert} = require('chai'); + +describe('tools-google-maps-coordinates-with-txt', () => { + it('should use google maps coordinates', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const output = await sample.generateContent(projectId); + assert(output.length > 0); + }); +}); diff --git a/genai/test/tools-google-search-and-urlcontext-with-txt.test.js b/genai/test/tools-google-search-and-urlcontext-with-txt.test.js new file mode 100644 index 0000000000..5b95c84723 --- /dev/null +++ b/genai/test/tools-google-search-and-urlcontext-with-txt.test.js @@ -0,0 +1,32 @@ +// 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 {describe, it} = require('mocha'); + +const projectId = process.env.CAIP_PROJECT_ID; +const sample = require('../tools/tools-google-search-and-urlcontext-with-txt'); +const {delay} = require('./util'); +const {assert} = require('chai'); + +describe('tools-google-search-and-urlcontext-with-txt', () => { + it('should create urlcontext and google search', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const output = await sample.generateContent(projectId); + assert(output.length > 0); + }); +}); diff --git a/genai/test/tools-google-search-with-txt.test.js b/genai/test/tools-google-search-with-txt.test.js new file mode 100644 index 0000000000..7d3dd2fd40 --- /dev/null +++ b/genai/test/tools-google-search-with-txt.test.js @@ -0,0 +1,29 @@ +// 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('../tools/tools-google-search-with-txt.js'); + +describe('tools-google-search-with-txt', () => { + it('should generate answer to a question in prompt using google search', async function () { + this.timeout(10000); + const output = await sample.generateGoogleSearch(projectId); + assert(output.length > 0); + }); +}); diff --git a/genai/test/tools-urlcontext-with-txt.test.js b/genai/test/tools-urlcontext-with-txt.test.js new file mode 100644 index 0000000000..6ae66896ed --- /dev/null +++ b/genai/test/tools-urlcontext-with-txt.test.js @@ -0,0 +1,32 @@ +// 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 {describe, it} = require('mocha'); + +const projectId = process.env.CAIP_PROJECT_ID; +const sample = require('../tools/tools-urlcontext-with-txt'); +const {delay} = require('./util'); +const {assert} = require('chai'); + +describe('tools-urlcontext-with-txt', () => { + it('should create urlcontext with txt', async function () { + this.timeout(180000); + this.retries(4); + await delay(this.test); + const output = await sample.generateContent(projectId); + assert(output.length > 0); + }); +}); diff --git a/genai/tools/tools-code-exec-with-txt-local-img.js b/genai/tools/tools-code-exec-with-txt-local-img.js new file mode 100644 index 0000000000..22a8cf0a03 --- /dev/null +++ b/genai/tools/tools-code-exec-with-txt-local-img.js @@ -0,0 +1,95 @@ +// 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_tools_exec_with_txt_local_img] +const fs = require('fs').promises; +const path = require('path'); + +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 generateAndExecuteMultimodalCode( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const imagePath = path.join( + __dirname, + '../test/test-data/640px-Monty_open_door.svg.png' + ); + const imageBuffer = await fs.readFile(imagePath); + const imageBase64 = imageBuffer.toString('base64'); + + const prompt = ` + Run a simulation of the Monty Hall Problem with 1,000 trials. + Here's how this works as a reminder. In the Monty Hall Problem, you're on a game + show with three doors. Behind one is a car, and behind the others are goats. You + pick a door. The host, who knows what's behind the doors, opens a different door + to reveal a goat. Should you switch to the remaining unopened door? + The answer has always been a little difficult for me to understand when people + solve it with math - so please run a simulation with Python to show me what the + best strategy is. + Thank you! + `; + + const contents = [ + { + role: 'user', + parts: [ + { + inlineData: { + mimeType: 'image/png', + data: imageBase64, + }, + }, + { + text: prompt, + }, + ], + }, + ]; + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash', + contents: contents, + config: { + tools: [{codeExecution: {}}], + temperature: 0, + }, + }); + + console.debug(response.executableCode); + console.debug(response.codeExecutionResult); + + // Example response: + // Win percentage when switching: 65.50% + // Win percentage when not switching: 34.50% + + return response.codeExecutionResult; +} + +// [END googlegenaisdk_tools_exec_with_txt_local_img] + +module.exports = { + generateAndExecuteMultimodalCode, +}; diff --git a/genai/tools/tools-code-exec-with-txt.js b/genai/tools/tools-code-exec-with-txt.js index 6aaeb0ec81..8a6a137941 100644 --- a/genai/tools/tools-code-exec-with-txt.js +++ b/genai/tools/tools-code-exec-with-txt.js @@ -20,7 +20,7 @@ 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 generateContent( +async function generateAndExecuteCode( projectId = GOOGLE_CLOUD_PROJECT, location = GOOGLE_CLOUD_LOCATION ) { @@ -33,7 +33,7 @@ async function generateContent( const response = await client.models.generateContent({ model: 'gemini-2.5-flash', contents: - 'What is the sum of the first 50 prime numbers? Generate and run code for the calculation, and make sure you get all 50.', + 'Calculate 20th fibonacci number. Then find the nearest palindrome to it.', config: { tools: [{codeExecution: {}}], temperature: 0, @@ -41,12 +41,36 @@ async function generateContent( }); console.debug(response.executableCode); + + // Example response: + // Code: + // function fibonacci(n) { + // if (n <= 0) { + // return 0; + // } else if (n === 1) { + // return 1; + // } else { + // let a = 0, b = 1; + // for (let i = 2; i <= n; i++) { + // [a, b] = [b, a + b]; + // } + // return b; + // } + // } + // + // const fib20 = fibonacci(20); + // console.log(`fib20=${fib20}`); + console.debug(response.codeExecutionResult); + // Outcome: + // fib20=6765 + return response.codeExecutionResult; } + // [END googlegenaisdk_tools_code_exec_with_txt] module.exports = { - generateContent, + generateAndExecuteCode, }; diff --git a/genai/tools/tools-func-desc-with-txt.js b/genai/tools/tools-func-desc-with-txt.js index b3f38f1c86..f9b3c55036 100644 --- a/genai/tools/tools-func-desc-with-txt.js +++ b/genai/tools/tools-func-desc-with-txt.js @@ -19,8 +19,7 @@ const {GoogleGenAI, Type} = require('@google/genai'); const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; - -async function generateContent( +async function generateFunctionDesc( projectId = GOOGLE_CLOUD_PROJECT, location = GOOGLE_CLOUD_LOCATION ) { @@ -71,23 +70,36 @@ async function generateContent( trends in music consumption. `; - const MODEL_NAME = 'gemini-2.5-flash'; - const response = await client.models.generateContent({ - model: MODEL_NAME, + model: 'gemini-2.5-flash', contents: prompt, config: { tools: [sales_tool], temperature: 0, }, }); + const output = JSON.stringify(response.functionCalls, null, 2); + console.log(output); - console.log(response.functionCalls); + // Example response: + // [FunctionCall( + // id=None, + // name="get_album_sales", + // args={ + // "albums": [ + // {"album_name": "Echoes of the Night", "copies_sold": 350000}, + // {"copies_sold": 120000, "album_name": "Reckless Hearts"}, + // {"copies_sold": 75000, "album_name": "Whispers of Dawn"}, + // {"copies_sold": 100000, "album_name": "Street Symphony"}, + // ] + // }, + // )] - return response.functionCalls; + return output; } + // [END googlegenaisdk_tools_func_desc_with_txt] module.exports = { - generateContent, + generateFunctionDesc, }; diff --git a/genai/tools/tools-google-maps-coordinates-with-txt.js b/genai/tools/tools-google-maps-coordinates-with-txt.js new file mode 100644 index 0000000000..f9b51c342e --- /dev/null +++ b/genai/tools/tools-google-maps-coordinates-with-txt.js @@ -0,0 +1,64 @@ +// 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_tools_google_maps_coordinates_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'; + +async function generateContent( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash', + contents: 'Where can I get the best espresso near me?', + config: { + tools: [ + { + googleMaps: {}, + }, + ], + toolConfig: { + retrievalConfig: { + latLng: { + latitude: 40.7128, + longitude: -74.006, + }, + languageCode: 'en_US', + }, + }, + }, + }); + + console.log(response.text); + // Example response: + // 'Here are some of the top-rated places to get espresso near you: ...' + + return response.text; +} +// [END googlegenaisdk_tools_google_maps_coordinates_with_txt] + +module.exports = { + generateContent, +}; diff --git a/genai/tools/tools-google-search-and-urlcontext-with-txt.js b/genai/tools/tools-google-search-and-urlcontext-with-txt.js new file mode 100644 index 0000000000..3db4923acb --- /dev/null +++ b/genai/tools/tools-google-search-and-urlcontext-with-txt.js @@ -0,0 +1,106 @@ +// 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_tools_google_search_and_urlcontext_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'; + +async function generateContent( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + httpOptions: {apiVersion: 'v1beta1'}, + }); + + // TODO(developer): Here put your URLs! + const url = 'https://www.google.com/search?q=events+in+New+York'; + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash', + contents: `Give me a three-day events schedule based on ${url}. Also let me know what to take care of considering weather and commute.`, + config: { + tools: [{urlContext: {}}, {googleSearch: {}}], + responseModalities: ['TEXT'], + }, + }); + + const output = []; + + { + for (const part of response.candidates[0].content.parts) { + console.log(part.text); + output.push(part.text); + } + } + + // Here is a possible three-day event schedule for New York City, focusing on the dates around October 7-9, 2025, along with weather and commute considerations. + // + // ### Three-Day Event Schedule: New York City (October 7-9, 2025) + // + // **Day 1: Tuesday, October 7, 2025 - Art and Culture** + // + // * **Morning (10:00 AM - 1:00 PM):** Visit "Phillips Visual Language: The Art of Irving Penn" at 432 Park Avenue. This exhibition is scheduled to end on this day, offering a last chance to see it. + // * **Lunch (1:00 PM - 2:00 PM):** Grab a quick lunch near Park Avenue. + // * **Afternoon (2:30 PM - 5:30 PM):** Explore the "Lincoln Center Festival of Firsts" at Lincoln Center. This festival runs until October 23rd, offering various performances or exhibits. Check their specific schedule for the day. + // * **Evening (7:00 PM onwards):** Experience a classic Broadway show. Popular options mentioned for October 2025 include "Six The Musical," "Wicked," "Hadestown," or "MJ - The Musical." + // + // **Day 2: Wednesday, October 8, 2025 - Unique Experiences and SoHo Vibes** + // + // * **Morning (11:00 AM - 1:00 PM):** Head to Brooklyn for the "Secret Room at IKEA Brooklyn" at 1 Beard Street. This unique event is scheduled to end on October 9th. + // * **Lunch (1:00 PM - 2:00 PM):** Enjoy lunch in Brooklyn, perhaps exploring local eateries in the area. + // * **Afternoon (2:30 PM - 5:30 PM):** Immerse yourself in the "The Weeknd & Nespresso Samra Origins Vinyl Cafe" at 579 Broadway in SoHo. This pop-up, curated by The Weeknd, combines coffee and music and runs until October 14th. + // * **Evening (6:00 PM onwards):** Explore the vibrant SoHo neighborhood, known for its shopping and dining. You could also consider a dinner cruise to see the illuminated Manhattan skyline and the Statue of Liberty. + // + // **Day 3: Thursday, October 9, 2025 - Film and Scenic Views** + // + // * **Morning (10:00 AM - 1:00 PM):** Attend a screening at the New York Greek Film Expo, which runs until October 12th in New York City. + // * **Lunch (1:00 PM - 2:00 PM):** Have lunch near the film expo's location. + // * **Afternoon (2:30 PM - 5:30 PM):** Take advantage of the pleasant October weather and enjoy outdoor activities. Consider biking along the rivers or through Central Park to admire the early autumn foliage. + // * **Evening (6:00 PM onwards):** Visit an observation deck like the Empire State Building or Top of the Rock for panoramic city views. Afterwards, enjoy dinner in a neighborhood of your choice. + // + // ### Weather and Commute Considerations: + // + // **Weather in Early October:** + // + // * **Temperatures:** Expect mild to cool temperatures. Average daily temperatures in early October range from 10°C (50°F) to 18°C (64°F), with occasional warmer days reaching the mid-20s°C (mid-70s°F). Evenings can be quite chilly. + // * **Rainfall:** October has a higher chance of rainfall compared to other months, with an average of 33mm and a 32% chance of rain on any given day. + // * **Sunshine:** You can generally expect about 7 hours of sunshine per day. + // * **What to Pack:** Pack layers! Bring a light jacket or sweater for the daytime, and a warmer coat for the evenings. An umbrella or a light raincoat is highly recommended due to the chance of showers. Comfortable walking shoes are a must for exploring the city. + // + // **Commute in New York City:** + // + // * **Public Transportation is Key:** The subway is generally the fastest and most efficient way to get around New York City, especially during the day. Buses are good for East-West travel, but can be slower due to traffic. + // * **Using Apps:** Utilize Google Maps or official MTA apps to plan your routes and check for real-time service updates. The subway runs 24/7, but expect potential delays or changes to routes during nights and weekends due to maintenance. + // * **Rush Hour:** Avoid subway and commuter train travel during peak rush hours (8 AM - 10 AM and 5 PM - 7 PM) if possible, as trains can be extremely crowded. + // * **Subway Etiquette:** When on the subway, stand to the side of the doors to let people exit before boarding, and move to the center of the car to make space. Hold onto a pole or seat, and remove your backpack to free up space. + // * **Transfers:** Subway fare is $2.90 per ride, and you get one free transfer between the subway and bus within a two-hour window. + // * **Walking:** New York City is very walkable. If the weather is pleasant, walking between nearby attractions is an excellent way to see the city. + // * **Taxis/Ride-sharing:** Uber, Lyft, and Curb (for NYC taxis) are available, but driving in the city is generally discouraged due to traffic and parking difficulties. + // * **Allow Extra Time:** Always factor in an additional 20-30 minutes for travel time, as delays can occur. + + return output; +} +// [END googlegenaisdk_tools_google_search_and_urlcontext_with_txt] + +module.exports = { + generateContent, +}; diff --git a/genai/tools/tools-google-search-with-txt.js b/genai/tools/tools-google-search-with-txt.js new file mode 100644 index 0000000000..2b123944cd --- /dev/null +++ b/genai/tools/tools-google-search-with-txt.js @@ -0,0 +1,57 @@ +// 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_tools_google_search_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'; + +async function generateGoogleSearch( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + }); + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash', + contents: 'When is the next total solar eclipse in the United States?', + config: { + tools: [ + { + googleSearch: {}, + }, + ], + }, + }); + + console.log(response.text); + + // Example response: + // 'The next total solar eclipse in United States will occur on ...' + + return response.text; +} + +// [END googlegenaisdk_tools_google_search_with_txt] + +module.exports = { + generateGoogleSearch, +}; diff --git a/genai/tools/tools-urlcontext-with-txt.js b/genai/tools/tools-urlcontext-with-txt.js new file mode 100644 index 0000000000..1c4788aff3 --- /dev/null +++ b/genai/tools/tools-urlcontext-with-txt.js @@ -0,0 +1,90 @@ +// 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_tools_urlcontext_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'; + +async function generateContent( + projectId = GOOGLE_CLOUD_PROJECT, + location = GOOGLE_CLOUD_LOCATION +) { + const client = new GoogleGenAI({ + vertexai: true, + project: projectId, + location: location, + httpOptions: {apiVersion: 'v1beta1'}, + }); + + // TODO(developer): Here put your URLs! + const url1 = 'https://cloud.google.com/vertex-ai/generative-ai/docs'; + const url2 = 'https://cloud.google.com/docs/overview'; + + const response = await client.models.generateContent({ + model: 'gemini-2.5-flash', + contents: `Compare the content, purpose, and audiences of ${url1} and ${url2}.`, + config: { + tools: [{urlContext: {}}], + responseModalities: ['TEXT'], + }, + }); + + const output = []; + + { + for (const part of response.candidates[0].content.parts) { + console.log(part.text); + output.push(part.text); + } + } + + // Gemini 2.5 Pro and Gemini 2.5 Flash are both advanced models offered by Google AI, but they are optimized for different use cases. + // + // Here's a comparison: + // + // **Gemini 2.5 Pro** + // * **Description**: This is Google's most advanced model, described as a "state-of-the-art thinking model". It excels at reasoning over complex problems in areas like code, mathematics, and STEM, and can analyze large datasets, codebases, and documents using a long context window. + // * **Input Data Types**: It supports audio, images, video, text, and PDF inputs. + // * **Output Data Types**: It produces text outputs. + // * **Token Limits**: It has an input token limit of 1,048,576 and an output token limit of 65,536. + // * **Supported Capabilities**: Gemini 2.5 Pro supports Batch API, Caching, Code execution, Function calling, Search grounding, Structured outputs, Thinking, and URL context. + // * **Knowledge Cutoff**: January 2025. + // + // **Gemini 2.5 Flash** + // * **Description**: Positioned as "fast and intelligent," Gemini 2.5 Flash is highlighted as Google's best model in terms of price-performance, offering well-rounded capabilities. It is ideal for large-scale processing, low-latency, high-volume tasks that require thinking, and agentic use cases. + // * **Input Data Types**: It supports text, images, video, and audio inputs. + // * **Output Data Types**: It produces text outputs. + // * **Token Limits**: Similar to Pro, it has an input token limit of 1,048,576 and an output token limit of 65,536. + // * **Supported Capabilities**: Gemini 2.5 Flash supports Batch API, Caching, Code execution, Function calling, Search grounding, Structured outputs, Thinking, and URL con// + // **Key Differences and Similarities:** + // + // * **Primary Focus**: Gemini 2.5 Pro is geared towards advanced reasoning and in-depth analysis of complex problems and large documents. Gemini 2.5 Flash, on the other hand, is optimized for efficiency, scale, and high-volume, low-latency applications, making it a strong choice for price-performance sensitive scenarios. + // * **Input Modalities**: Both models handle various input types including text, images, video, and audio. Gemini 2.5 Pro explicitly lists PDF as an input type, while Gemini 2.5 Flash lists text, images, video, audio. + // * **Technical Specifications (for primary stable versions)**: Both models share the same substantial input and output token limits (1,048,576 input and 65,536 output). They also support a very similar set of core capabilities, including code execution, function calling, and URL context. Neither model supports audio generation, image generation, or Live API in their standard stable versions. + // * **Knowledge Cutoff**: Both models have a knowledge cutoff of January 2025. + // + // In essence, while both models are powerful and capable, Gemini 2.5 Pro is designed for maximum performance in complex reasoning tasks, whereas Gemini 2.5 Flash prioritizes cost-effectiveness and speed for broader, high-throughput applications. + // get URLs retrieved for context + + return output; +} +// [END googlegenaisdk_tools_urlcontext_with_txt] + +module.exports = { + generateContent, +}; diff --git a/genai/tools/tools-vais-with-txt.js b/genai/tools/tools-vais-with-txt.js index 67f8d3f12f..58c31d021f 100644 --- a/genai/tools/tools-vais-with-txt.js +++ b/genai/tools/tools-vais-with-txt.js @@ -20,8 +20,7 @@ const {GoogleGenAI} = require('@google/genai'); const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT; const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global'; // (Developer) put your path Data Store -const DATASTORE = - 'projects/cloud-ai-devrel-softserve/locations/global/collections/default_collection/dataStores/example-adk-website-datastore_1755611010401'; +const DATASTORE = `projects/${GOOGLE_CLOUD_PROJECT}/locations/${GOOGLE_CLOUD_LOCATION}/collections/default_collection/dataStores/data-store-id `; async function generateContent( datastore = DATASTORE, diff --git a/storagebatchoperations/cancelJob.js b/storagebatchoperations/cancelJob.js new file mode 100644 index 0000000000..7e739fc076 --- /dev/null +++ b/storagebatchoperations/cancelJob.js @@ -0,0 +1,89 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_cancel_job] + + /** + * Cancel a batch job instance. + * + * The operation to cancel a batch job instance in Google Cloud Storage (GCS) is used to stop + * a running or queued asynchronous task that is currently processing a large number of GCS objects. + * + * @param {string} projectId The Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function cancelJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + // Run request + try { + await client.cancelJob(request); + console.log(`Cancelled job: ${name}`); + } catch (error) { + // This might be expected if the job completed quickly or failed creation + console.error( + `Error canceling batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } else if (error.code === 9) { + // FAILED_PRECONDITION (gRPC code 9) can occur if the job is already being cancelled + // or is not in a RUNNING state that allows the cancel operation. + console.error( + `Batch job '${jobId}' may not be in a state that allows canceling (e.g., must be RUNNING).` + ); + } + throw error; + } + } + + cancelJob(); + // [END storage_batch_cancel_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/createJob.js b/storagebatchoperations/createJob.js new file mode 100644 index 0000000000..0f6eba4134 --- /dev/null +++ b/storagebatchoperations/createJob.js @@ -0,0 +1,93 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId, bucketName, objectPrefix) { + // [START storage_batch_create_job] + + /** + * Create a new batch job instance. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} bucketName The name of your GCS bucket. + * Example: 'your-gcp-bucket-name' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + * @param {string} objectPrefix The prefix of objects to include in the operation. + * Example: 'prefix1' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function createJob() { + const parent = await client.locationPath(projectId, 'global'); + + // Create the request + const request = { + parent, + jobId, + job: { + bucketList: { + buckets: [ + { + bucket: bucketName, + prefixList: { + includedObjectPrefixes: [objectPrefix], + }, + }, + ], + }, + deleteObject: { + permanentObjectDeletionEnabled: false, + }, + }, + }; + + try { + // Run the request, which returns an Operation object + const [operation] = await client.createJob(request); + console.log(`Waiting for operation ${operation.name} to complete...`); + + // Wait for the operation to complete and get the final resource + const [response] = await operation.promise(); + console.log(`Created job: ${response.name}`); + } catch (error) { + console.error('Failed to create batch job:', error.message); + throw error; + } + } + + createJob(); + // [END storage_batch_create_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/deleteJob.js b/storagebatchoperations/deleteJob.js new file mode 100644 index 0000000000..0934a2311f --- /dev/null +++ b/storagebatchoperations/deleteJob.js @@ -0,0 +1,81 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_delete_job] + /** + * Delete a batch job instance. + * + * This operation is used to remove a completed, failed, or cancelled Batch Operation + * job from the system's list. It is essentially a cleanup action. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function deleteJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + await client.deleteJob(request); + console.log(`Deleted job: ${name}`); + } catch (error) { + console.error( + `Error deleting batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + + deleteJob(); + // [END storage_batch_delete_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/getJob.js b/storagebatchoperations/getJob.js new file mode 100644 index 0000000000..b81bf7c33c --- /dev/null +++ b/storagebatchoperations/getJob.js @@ -0,0 +1,87 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_get_job] + /** + * Retrieves details of a specific batch job instance. + * + * This operation is used to retrieve the detailed current state, execution status, + * and original configuration of a specific Batch Operation job that was previously + * created for a Google Cloud Storage bucket. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function getJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + const [response] = await client.getJob(request); + console.log(`Batch job details for '${jobId}':`); + console.log(`Name: ${response.name}`); + console.log(`State: ${response.state}`); + console.log( + `Create Time: ${new Date(response.createTime.seconds * 1000).toISOString()}` + ); + } catch (error) { + console.error( + `Error retrieving batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + + getJob(); + // [END storage_batch_get_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/listJobs.js b/storagebatchoperations/listJobs.js new file mode 100644 index 0000000000..a72dbd4878 --- /dev/null +++ b/storagebatchoperations/listJobs.js @@ -0,0 +1,83 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId) { + // [START storage_batch_list_jobs] + /** + * Lists all Jobs operation is used to query the status and configuration of all + * Storage Batch Operations jobs within a specific Google Cloud project. + * This feature is essential for tasks that affect a large number of objects, + * such as changing storage classes, deleting objects, or running custom functions + * on object metadata. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function listJobs() { + const parent = await client.locationPath(projectId, 'global'); + + // Create the request + const request = { + parent, + }; + + try { + // Run request. The response is an array where the first element is the list of jobs. + const [response] = await client.listJobs(request); + if (response && response.length > 0) { + console.log( + `Found ${response.length} batch jobs for project: ${projectId}` + ); + for (const job of response) { + console.log(job.name); + } + } else { + // Case: Successful but empty list (No batch jobs found) + console.log(`No batch jobs found for project: ${projectId}.`); + } + } catch (error) { + console.error( + `Error listing batch jobs for project ${projectId}:`, + error.message + ); + throw error; + } + } + + listJobs(); + // [END storage_batch_list_jobs] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/package.json b/storagebatchoperations/package.json new file mode 100644 index 0000000000..e382054dda --- /dev/null +++ b/storagebatchoperations/package.json @@ -0,0 +1,22 @@ +{ + "name": "storage-batch-operations-samples", + "version": "0.0.1", + "author": "Google Inc.", + "license": "Apache-2.0", + "description": "Examples of how to utilize the @google-cloud/storagebatchoperations library.", + "scripts": { + "test": "c8 mocha -p -j 2 system-test --timeout 600000" + }, + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "devDependencies": { + "@google-cloud/storage": "^7.17.1", + "@google-cloud/storagebatchoperations": "^0.1.0", + "c8": "^10.0.0", + "chai": "^4.5.0", + "mocha": "^10.7.0", + "uuid": "^10.0.0" + } + } diff --git a/storagebatchoperations/quickstart.js b/storagebatchoperations/quickstart.js new file mode 100644 index 0000000000..4a022041e1 --- /dev/null +++ b/storagebatchoperations/quickstart.js @@ -0,0 +1,88 @@ +// 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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +async function main(projectId, jobId) { + // [START storage_batch_quickstart] + + // Imports the Google Cloud client library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + /** + * Retrieves details of a specific batch job instance. + * + * This operation is used to retrieve the detailed current state, execution status, + * and original configuration of a specific Batch Operation job that was previously + * created for a Google Cloud Storage bucket. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Creates a client + const client = new StorageBatchOperationsClient(); + + async function quickstart() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + const [response] = await client.getJob(request); + console.log(`Batch job details for '${jobId}':`); + console.log(` Name: ${response.name}`); + console.log(` State: ${response.state}`); + console.log( + ` Create Time: ${new Date(response.createTime.seconds * 1000).toISOString()}` + ); + } catch (error) { + console.error( + `Error retrieving batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + quickstart(); + // [END storage_batch_quickstart] +} + +main(...process.argv.slice(2)); + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); diff --git a/storagebatchoperations/system-test/storagebatchoperations.test.js b/storagebatchoperations/system-test/storagebatchoperations.test.js new file mode 100644 index 0000000000..608240bcc4 --- /dev/null +++ b/storagebatchoperations/system-test/storagebatchoperations.test.js @@ -0,0 +1,99 @@ +// 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. + +const {Storage, Bucket} = require('@google-cloud/storage'); +const cp = require('child_process'); +const {assert} = require('chai'); +const {describe, it, before, after} = require('mocha'); +const uuid = require('uuid'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const projectId = process.env.GCLOUD_PROJECT; +const bucketPrefix = 'sbo-samples'; +const bucketName = `${bucketPrefix}-${uuid.v4()}`; +const storage = new Storage({projectId: projectId}); +const bucket = new Bucket(storage, bucketName); +const jobId = uuid.v4(); +const jobName = `projects/${projectId}/locations/global/jobs/${jobId}`; + +describe('Batch Operations', () => { + before(async () => { + await storage.createBucket(bucketName, { + iamConfiguration: { + uniformBucketLevelAccess: { + enabled: true, + }, + }, + hierarchicalNamespace: {enabled: true}, + }); + }); + + after(async () => { + await bucket.delete(); + }); + + it('should create a job', async () => { + const output = execSync( + `node createJob.js ${projectId} ${jobId} ${bucketName} objectPrefix` + ); + assert.match(output, /Created job:/); + assert.match(output, new RegExp(jobName)); + }); + + it('should list jobs', async () => { + const output = execSync(`node listJobs.js ${projectId}`); + assert.match(output, new RegExp(jobName)); + }); + + it('should run quickstart', async () => { + const output = execSync(`node quickstart.js ${projectId} ${jobId}`); + const detailsHeader = `Batch job details for '${jobId}':`; + assert.match(output, new RegExp(detailsHeader)); + assert.match(output, /Name:/); + assert.match(output, new RegExp(jobName)); + assert.match(output, /State:/); + assert.match(output, /Create Time:/); + }); + + it('should get a job', async () => { + const output = execSync(`node getJob.js ${projectId} ${jobId}`); + const detailsHeader = `Batch job details for '${jobId}':`; + assert.match(output, new RegExp(detailsHeader)); + assert.match(output, /Name:/); + assert.match(output, new RegExp(jobName)); + assert.match(output, /State:/); + assert.match(output, /Create Time:/); + }); + + it('should cancel a job (or gracefully handle terminal state)', async () => { + try { + const output = execSync(`node cancelJob.js ${projectId} ${jobId}`); + assert.match(output, /Cancelled job:/); + assert.match(output, new RegExp(jobName)); + } catch (error) { + // This might be expected if the job completed quickly or failed creation + const errorMessage = error.stderr.toString(); + assert.match( + errorMessage, + /9 FAILED_PRECONDITION: Job run.* is in a terminal state and can not be changed./ + ); + } + }); + + it('should delete a job', async () => { + const output = execSync(`node deleteJob.js ${projectId} ${jobId}`); + assert.match(output, /Deleted job:/); + assert.match(output, new RegExp(jobName)); + }); +});