diff --git a/ai/custom-func-ai-agent/AiVertex.js b/ai/custom-func-ai-agent/AiVertex.js new file mode 100644 index 000000000..aa58d0836 --- /dev/null +++ b/ai/custom-func-ai-agent/AiVertex.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. +*/ + +const LOCATION = PropertiesService.getScriptProperties().getProperty('LOCATION'); +const GEMINI_MODEL_ID = PropertiesService.getScriptProperties().getProperty('GEMINI_MODEL_ID'); +const REASONING_ENGINE_ID = PropertiesService.getScriptProperties().getProperty('REASONING_ENGINE_ID'); +const SERVICE_ACCOUNT_KEY = PropertiesService.getScriptProperties().getProperty('SERVICE_ACCOUNT_KEY'); + +const credentials = credentialsForVertexAI(); + +/** + * @param {string} statement The statement to fact-check. + */ +function requestLlmAuditorAdkAiAgent(statement) { + return UrlFetchApp.fetch( + `https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${credentials.projectId}/locations/${LOCATION}/reasoningEngines/${REASONING_ENGINE_ID}:streamQuery?alt=sse`, + { + method: 'post', + headers: { 'Authorization': `Bearer ${credentials.accessToken}` }, + contentType: 'application/json', + muteHttpExceptions: true, + payload: JSON.stringify({ + "class_method": "async_stream_query", + "input": { + "user_id": "google_sheets_custom_function_fact_check", + "message": statement, + } + }) + } + ).getContentText(); +} + +/** + * @param {string} prompt The Gemini prompt to use. + */ +function requestOutputFormatting(prompt) { + const response = UrlFetchApp.fetch( + `https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${credentials.projectId}/locations/${LOCATION}/publishers/google/models/${GEMINI_MODEL_ID}:generateContent`, + { + method: 'post', + headers: { 'Authorization': `Bearer ${credentials.accessToken}` }, + contentType: 'application/json', + muteHttpExceptions: true, + payload: JSON.stringify({ + "contents": [{ + "role": "user", + "parts": [{ "text": prompt }] + }], + "generationConfig": { "temperature": 0.1, "maxOutputTokens": 2048 }, + "safetySettings": [ + { + "category": "HARM_CATEGORY_HARASSMENT", + "threshold": "BLOCK_NONE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "threshold": "BLOCK_NONE" + }, + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "threshold": "BLOCK_NONE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "threshold": "BLOCK_NONE" + } + ] + }) + } + ); + return JSON.parse(response).candidates[0].content.parts[0].text +} + +/** + * Gets credentials required to call Vertex API using a Service Account. + * Requires use of Service Account Key stored with project. + * + * @return {!Object} Containing the Google Cloud project ID and the access token. + */ +function credentialsForVertexAI() { + const credentials = SERVICE_ACCOUNT_KEY; + if (!credentials) { + throw new Error("service_account_key script property must be set."); + } + + const parsedCredentials = JSON.parse(credentials); + + const service = OAuth2.createService("Vertex") + .setTokenUrl('https://oauth2.googleapis.com/token') + .setPrivateKey(parsedCredentials['private_key']) + .setIssuer(parsedCredentials['client_email']) + .setPropertyStore(PropertiesService.getScriptProperties()) + .setScope("https://www.googleapis.com/auth/cloud-platform"); + return { + projectId: parsedCredentials['project_id'], + accessToken: service.getAccessToken(), + } +} diff --git a/ai/custom-func-ai-agent/Code.js b/ai/custom-func-ai-agent/Code.js new file mode 100644 index 000000000..d18674f6a --- /dev/null +++ b/ai/custom-func-ai-agent/Code.js @@ -0,0 +1,36 @@ +/* +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. +*/ + +/** + * Passes a statement to fact-check and, optionally, output formatting instructions. + * + * @param {string} statement The statement to fact-check as a string or single cell + * reference (data ranges are not supported). + * @param {string} outputFormat The instructions as a string or single cell reference + * (data ranges are not supported). + * + * @return The generated and formatted verdict + * @customfunction + */ +function FACT_CHECK(statement, outputFormat) { + if (!outputFormat || outputFormat == "") { + outputFormat = 'Summarize it. Only keep the verdict result and main arguments. ' + + 'Do not reiterate the fact being checked. Remove all markdown. ' + + 'State the verdit result in a first paragraph in a few words and the rest of the summary in a second paragraph.'; + } + + return requestOutputFormatting(`Here is a fact checking result: ${requestLlmAuditorAdkAiAgent(statement)}.\n\n${outputFormat}`); +} diff --git a/ai/custom-func-ai-agent/README.md b/ai/custom-func-ai-agent/README.md new file mode 100644 index 000000000..43fe32494 --- /dev/null +++ b/ai/custom-func-ai-agent/README.md @@ -0,0 +1,38 @@ +# Google Sheets Custom Function relying on ADK AI Agent and Gemini model + +A [Vertex AI](https://cloud.google.com/vertex-ai) agent-powered **fact checker** custom function for Google Sheets to be used as a bound Apps Script project. + +![](./images/showcase.png) + +## Overview + +The **Google Sheets custom function** named `FACT_CHECK` integrates the sophisticated, multi-tool, multi-step reasoning capabilities of a **Vertex AI Agent Engine (ADK Agent)** directly into your Google Sheets spreadsheets. + +It operates as an end-to-end solution. It analyzes a statement, grounds its response using the latest web information, and returns the result in the format you need: + + * Usage: `=FACT_CHECK("Your statement here")` for a concise and summarized output. `=FACT_CHECK("Your statement here", "Your output formatting instructions here")` for a specific output format. + * Reasoning: [**LLM Auditor ADK AI Agent (Python sample)**](https://github.com/google/adk-samples/tree/main/python/agents/llm-auditor). + * Output formatting: [**Gemini model**](https://cloud.google.com/vertex-ai/generative-ai/docs/models). + +## Prerequisites + +* Google Cloud Project with billing enabled. + +## Set up your environment + +1. Configure the Google Cloud project + 1. Enable the Vertex AI API + 1. Create a Service Account and grant the role `Vertex AI User` + 1. Create a private key with type JSON. This will download the JSON file. +1. Setup, install, and deploy the LLM Auditor ADK AI Agent sample + 1. Use Vertex AI + 1. Use the same Google Cloud project + 1. Use the location `us-central1` + 1. Use the Vertex AI Agent Engine +1. Open an Apps Script project bound to a Google Sheets spreadsheet + 1. Add a Script Property. Enter `LOCATION` as the property name and `us-central1` as the value. + 1. Add a Script Property. Enter `GEMINI_MODEL_ID` as the property name and `gemini-2.5-flash-lite` as the value. + 1. Add a Script Property. Enter `REASONING_ENGINE_ID` as the property name and the ID of the deployed LLM Auditor ADK AI Agent as the value. + 1. Add a Script Property. Enter `SERVICE_ACCOUNT_KEY` as the property name and paste the JSON key from the service account as the value. + 1. Add OAuth2 v43 Apps Script Library using the ID `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF` + 1. Set the script files `Code.gs` and `AiVertex.gs` in the Apps Script project with the JS file contents in this project diff --git a/ai/custom-func-ai-agent/appsscript.json b/ai/custom-func-ai-agent/appsscript.json new file mode 100644 index 000000000..d1a41c7c1 --- /dev/null +++ b/ai/custom-func-ai-agent/appsscript.json @@ -0,0 +1,12 @@ +{ + "timeZone": "America/Los_Angeles", + "dependencies": { + "libraries": [{ + "userSymbol": "OAuth2", + "libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF", + "version": "43" + }] + }, + "exceptionLogging": "STACKDRIVER", + "runtimeVersion": "V8" +} \ No newline at end of file diff --git a/ai/custom-func-ai-agent/images/showcase.png b/ai/custom-func-ai-agent/images/showcase.png new file mode 100644 index 000000000..4b0314f15 Binary files /dev/null and b/ai/custom-func-ai-agent/images/showcase.png differ