From 4ff4f258aae5815e5277a9566093ad246fac2a1a Mon Sep 17 00:00:00 2001 From: Twisha Bansal Date: Wed, 4 Feb 2026 14:58:22 +0530 Subject: [PATCH] add test and doc page --- .../js.integration.cloudbuild.yaml | 57 ++++++++++++ docs/en/samples/pre_post_processing/js.md | 31 +++++++ .../pre_post_processing/js/agent.test.js | 92 +++++++++++++++++++ .../pre_post_processing/js/langchain/agent.js | 4 +- 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 .ci/sample_tests/pre_post_processing/js.integration.cloudbuild.yaml create mode 100644 docs/en/samples/pre_post_processing/js.md create mode 100644 docs/en/samples/pre_post_processing/js/agent.test.js diff --git a/.ci/sample_tests/pre_post_processing/js.integration.cloudbuild.yaml b/.ci/sample_tests/pre_post_processing/js.integration.cloudbuild.yaml new file mode 100644 index 0000000000..b04e3bb1d1 --- /dev/null +++ b/.ci/sample_tests/pre_post_processing/js.integration.cloudbuild.yaml @@ -0,0 +1,57 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +steps: + - name: "${_IMAGE}" + id: "js-pre-post-processing-test" + entrypoint: "bash" + args: + - -c + - | + set -ex + chmod +x .ci/sample_tests/run_tests.sh + .ci/sample_tests/run_tests.sh + env: + - "CLOUD_SQL_INSTANCE=${_CLOUD_SQL_INSTANCE}" + - "GCP_PROJECT=${_GCP_PROJECT}" + - "DATABASE_NAME=${_DATABASE_NAME}" + - "DB_USER=${_DB_USER}" + - "TARGET_ROOT=${_TARGET_ROOT}" + - "TARGET_LANG=${_TARGET_LANG}" + - "TABLE_NAME=${_TABLE_NAME}" + - "SQL_FILE=${_SQL_FILE}" + - "AGENT_FILE_PATTERN=${_AGENT_FILE_PATTERN}" + secretEnv: ["TOOLS_YAML_CONTENT", "GOOGLE_API_KEY", "DB_PASSWORD"] + +availableSecrets: + secretManager: + - versionName: projects/${_GCP_PROJECT}/secrets/${_TOOLS_YAML_SECRET}/versions/5 + env: "TOOLS_YAML_CONTENT" + - versionName: projects/${_GCP_PROJECT_NUMBER}/secrets/${_API_KEY_SECRET}/versions/latest + env: "GOOGLE_API_KEY" + - versionName: projects/${_GCP_PROJECT}/secrets/${_DB_PASS_SECRET}/versions/latest + env: "DB_PASSWORD" + +timeout: 1200s + +substitutions: + _TARGET_LANG: "js" + _IMAGE: "node:22" + _TARGET_ROOT: "docs/en/samples/pre_post_processing/js" + _TABLE_NAME: "hotels_js_pre_post_processing" + _SQL_FILE: ".ci/sample_tests/setup_hotels.sql" + _AGENT_FILE_PATTERN: "agent.js" + +options: + logging: CLOUD_LOGGING_ONLY diff --git a/docs/en/samples/pre_post_processing/js.md b/docs/en/samples/pre_post_processing/js.md new file mode 100644 index 0000000000..c7e5c1b409 --- /dev/null +++ b/docs/en/samples/pre_post_processing/js.md @@ -0,0 +1,31 @@ +--- +title: "(JS) Pre and post processing" +type: docs +weight: 5 +description: > + How to add pre and post processing to your JS toolbox applications. +--- + +## Prerequisites + +This tutorial assumes that you have set up a basic toolbox application as described in the [local quickstart](../../getting-started/local_quickstart_js). + +This guide demonstrates how to implement these patterns in your Toolbox applications. + +## Implementation + +{{< tabpane persist=header >}} +{{% tab header="ADK" text=true %}} +Coming soon. +{{% /tab %}} +{{% tab header="Langchain" text=true %}} +The following example demonstrates how to use `ToolboxClient` with LangChain's middleware to implement pre and post processing for tool calls. + +```js +{{< include "js/langchain/agent.js" >}} +``` + +For more information, see the [LangChain Middleware documentation](https://js.langchain.com/docs/introduction/middleware). +You can also add model-level (`wrap_model`) and agent-level (`before_agent`, `after_agent`) hooks to intercept messages at different stages of the execution loop. See the [LangChain Middleware documentation](https://js.langchain.com/docs/introduction/middleware) for details on these additional hook types. +{{% /tab %}} +{{< /tabpane >}} diff --git a/docs/en/samples/pre_post_processing/js/agent.test.js b/docs/en/samples/pre_post_processing/js/agent.test.js new file mode 100644 index 0000000000..7b268e5900 --- /dev/null +++ b/docs/en/samples/pre_post_processing/js/agent.test.js @@ -0,0 +1,92 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { describe, test, before, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const ORCH_NAME = process.env.ORCH_NAME; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const orchDir = path.join(__dirname, ORCH_NAME); +const agentPath = path.join(orchDir, "agent.js"); + +const { main: runAgent } = await import(agentPath); + +const GOLDEN_FILE_PATH = path.resolve(__dirname, "../golden.txt"); + +describe(`${ORCH_NAME} Pre/Post Processing Agent`, () => { + let capturedOutput = []; + let capturedErrors = []; + let originalLog; + let originalError; + + before(() => { + originalLog = console.log; + originalError = console.error; + + console.log = (...args) => { + const msg = args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' '); + capturedOutput.push(msg); + }; + + console.error = (...args) => { + const msg = args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' '); + capturedErrors.push(msg); + }; + }); + + after(() => { + console.log = originalLog; + console.error = originalError; + }); + + test("runs without errors and outputContainsRequiredKeywords", async () => { + capturedOutput = []; + capturedErrors = []; + + await runAgent(); + if (capturedErrors.length > 0) { + console.error("Captured Stderr:", capturedErrors.join("\n")); + } + assert.equal( + capturedErrors.length, + 0, + `Script produced stderr: ${capturedErrors.join("\n")}` + ); + + const actualOutput = capturedOutput.join("\n"); + + assert.ok( + actualOutput.length > 0, + "Assertion Failed: Script ran successfully but produced no output." + ); + + const goldenFile = fs.readFileSync(GOLDEN_FILE_PATH, "utf8"); + const keywords = goldenFile.split("\n").filter((kw) => kw.trim() !== ""); + const missingKeywords = []; + + for (const keyword of keywords) { + if (!actualOutput.includes(keyword)) { + missingKeywords.push(keyword); + } + } + + assert.ok( + missingKeywords.length === 0, + `Assertion Failed: The following keywords were missing from the output: [${missingKeywords.join(", ")}]` + ); + }); +}); \ No newline at end of file diff --git a/docs/en/samples/pre_post_processing/js/langchain/agent.js b/docs/en/samples/pre_post_processing/js/langchain/agent.js index 6a02196a95..a3236e4948 100644 --- a/docs/en/samples/pre_post_processing/js/langchain/agent.js +++ b/docs/en/samples/pre_post_processing/js/langchain/agent.js @@ -159,4 +159,6 @@ async function main() { if (process.argv[1] === fileURLToPath(import.meta.url)) { main(); -} \ No newline at end of file +} + +export { main }; \ No newline at end of file