Compare commits

...

44 Commits

Author SHA1 Message Date
Twisha Bansal
d3bf0840ae remove redundant lines 2026-02-11 13:37:44 +05:30
Twisha Bansal
42690d49c1 remove extra files 2026-02-11 13:36:30 +05:30
Twisha Bansal
45e393059a Merge branch 'main' into langchain-js-processing 2026-02-11 13:32:49 +05:30
Twisha Bansal
e88e29b602 add docs link 2026-02-09 12:31:33 +05:30
Twisha Bansal
3eb87217f0 Update js.md 2026-02-09 11:43:17 +05:30
Twisha Bansal
057cae426f update js docs according to python PR 2026-02-09 11:27:09 +05:30
Twisha Bansal
7b1f5ea7b9 Final fix 2026-02-05 23:34:20 +05:30
Twisha Bansal
860447d8c5 update deps 2026-02-05 23:15:39 +05:30
Twisha Bansal
b634c68396 fix docs 2026-02-05 22:59:08 +05:30
Twisha Bansal
49b53c83ce remove license header from docs 2026-02-05 12:14:54 +05:30
Twisha Bansal
c7e41262a0 gemini suggestion 2026-02-05 10:04:03 +05:30
Twisha Bansal
5e3b8d3672 Update docs/en/samples/pre_post_processing/js/langchain/agent.js
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-05 10:00:27 +05:30
Twisha Bansal
4ff4f258aa add test and doc page 2026-02-04 14:58:22 +05:30
Twisha Bansal
5fe9984661 add langchain js sample 2026-02-04 14:48:03 +05:30
Twisha Bansal
a212aedd19 more reliable tests 2026-02-03 18:30:33 +05:30
Twisha Bansal
9210e5555c unnecessary file change removal 2026-02-03 18:15:05 +05:30
Twisha Bansal
b43af71793 fix merge issues 2026-02-03 18:13:49 +05:30
Twisha Bansal
da1f463dd1 more reliable agent queries 2026-02-03 18:08:59 +05:30
Twisha Bansal
3265f7e3a6 better tests 2026-02-03 18:08:59 +05:30
Twisha Bansal
336743f747 add more test case + remove flaky test 2026-02-03 18:08:59 +05:30
Twisha Bansal
911069ae8d Fix tests 2026-02-03 18:08:58 +05:30
Twisha Bansal
cee59d52c3 update requirements file 2026-02-03 18:08:58 +05:30
Twisha Bansal
9517daba09 license fix 2026-02-03 18:08:57 +05:30
Twisha Bansal
3c61ee0597 add sample tests 2026-02-03 18:08:09 +05:30
Twisha Bansal
19271eb9ee docs: clarify that pre/post processing is an orchestration feature
Explicitly document that these capabilities are typically provided by orchestration frameworks (like LangChain, LangGraph) rather than the Toolbox SDK itself, but that Toolbox tools are designed to leverage them.
2026-02-03 18:08:09 +05:30
Twisha Bansal
3a150c77ca Highlight tool level processing 2026-02-03 18:08:09 +05:30
Twisha Bansal
ca6f31a192 fix import 2026-02-03 18:08:09 +05:30
Twisha Bansal
d7faf7700f logic fix 2026-02-03 18:08:09 +05:30
Twisha Bansal
37a60ea2a6 Update docs/en/samples/pre_post_processing/python/langchain/agent.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-03 18:08:09 +05:30
Twisha Bansal
8de16976ae license header 2026-02-03 18:08:08 +05:30
Twisha Bansal
49cb2f39f7 lint 2026-02-03 18:08:08 +05:30
Twisha Bansal
f169874e53 gemini code review 2026-02-03 18:08:08 +05:30
Twisha Bansal
db8c3a3c77 remove not needed files 2026-02-03 18:08:08 +05:30
Twisha Bansal
8b33b0c67f docs: add pre/post processing docs for langchain python 2026-02-03 18:08:08 +05:30
Twisha Bansal
35fa73516b rename file 2026-02-03 15:45:03 +05:30
Twisha Bansal
66df3bfd21 move file around fix 2026-02-03 15:40:49 +05:30
Twisha Bansal
73e0edc3cd move files around 2026-02-03 15:36:56 +05:30
Twisha Bansal
3f32a9aab6 fix paths 2026-02-03 15:29:18 +05:30
Twisha Bansal
28006fc9b2 move files around 2026-02-03 15:27:43 +05:30
Twisha Bansal
56c69131b4 Update run_tests.sh 2026-02-03 15:09:07 +05:30
Twisha Bansal
ad4a509340 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-03 14:16:47 +05:30
Twisha Bansal
d39acac96c Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-03 14:16:36 +05:30
Twisha Bansal
6df2ad28a9 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-03 14:15:54 +05:30
Twisha Bansal
8416378613 refactor: consolidate quickstart tests into universal runner 2026-02-03 14:11:54 +05:30
6 changed files with 1928 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,43 @@
---
title: "Javascript"
type: docs
weight: 2
description: >
How to add pre- and post- processing to your Agents using JS.
---
## Prerequisites
This tutorial assumes that you have set up Toolbox with a basic agent as described in the [local quickstart](../../getting-started/local_quickstart_js.md).
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" >}}
```
You can also use the `wrapModel` hook to intercept messages before and after model calls.
You can also use [node-style hooks](https://docs.langchain.com/oss/javascript/langchain/middleware/custom#node-style-hooks) to intercept messages at the agent and model level.
See the [LangChain Middleware documentation](https://docs.langchain.com/oss/javascript/langchain/middleware/custom#tool-call-monitoring) for details on these additional hook types.
{{% /tab %}}
{{< /tabpane >}}
## Results
The output should look similar to the following. Note that exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
```
AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.
AI: Error: Maximum stay duration is 14 days.
```

View File

@@ -0,0 +1,91 @@
// 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 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_KEYWORDS = [
"AI:",
"Loyalty Points",
"POLICY CHECK: Intercepting 'update-hotel'"
];
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();
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 missingKeywords = [];
for (const keyword of GOLDEN_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(", ")}]`
);
});
});

View File

@@ -0,0 +1,108 @@
import { ToolboxClient } from "@toolbox-sdk/core";
import { ChatVertexAI } from "@langchain/google-vertexai";
import { createAgent, createMiddleware, ToolMessage } from "langchain";
import { tool } from "@langchain/core/tools";
import { fileURLToPath } from "url";
import process from "process";
const systemPrompt = `
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
`;
const businessRulesMiddleware = createMiddleware({
name: "BusinessRules",
wrapToolCall: async (request, handler) => {
const toolName = request.toolCall.name;
const toolArgs = request.toolCall.args;
console.log(`POLICY CHECK: Intercepting '${toolName}' running with args ${JSON.stringify(toolArgs)}`);
if (toolName === "update-hotel" && toolArgs.checkin_date && toolArgs.checkout_date) {
try {
const start = new Date(toolArgs.checkin_date);
const end = new Date(toolArgs.checkout_date);
const duration = (end - start) / (1000 * 60 * 60 * 24); // days
if (duration > 14) {
console.log("BLOCKED: Stay too long");
return ToolMessage({content:'Error: Maximum stay duration is 14 days.', status:"error"})
}
} catch (e) {
// Ignore invalid dates
}
}
return handler(request);
}
});
const enrichmentMiddleware = createMiddleware({
name: "Enrichment",
wrapToolCall: async (request, handler) => {
const result = await handler(request);
const toolName = request.toolCall.name;
let content = result;
if (typeof result === 'object' && result !== null && result.content) {
content = result.content;
}
if (toolName === "book-hotel" && typeof content === 'string' && !content.includes("Error")) {
const loyaltyBonus = 500;
const enrichedContent = `Booking Confirmed!\n You earned ${loyaltyBonus} Loyalty Points with this stay.\n\nSystem Details: ${content}`;
if (typeof result === 'object' && result !== null) {
result.content = enrichedContent;
return result;
}
return enrichedContent;
}
return result;
}
});
const queries = [
"Book hotel with id 3.",
"Update my hotel with id 3 with checkin date 2025-01-18 and checkout date 2025-02-10"
];
async function main() {
const client = new ToolboxClient("http://127.0.0.1:5000");
const rawTools = await client.loadToolset("my-toolset");
const tools = rawTools
.map(t => tool(t, {
name: t.getName(),
description: t.getDescription(),
schema: t.getParamSchema()
}));
const model = new ChatVertexAI({
model: "gemini-2.5-flash",
temperature: 0,
});
const agent = createAgent({
model: model,
tools: tools,
systemPrompt: systemPrompt,
middleware: [businessRulesMiddleware, enrichmentMiddleware]
});
for (const query of queries) {
console.log(`\nUSER: '${query}'`);
const result = await agent.invoke({
messages: [
{ role: "user", content: query},
],
});
console.log("-".repeat(50));
console.log(`AI: ${result.messages[result.messages.length-1].content}`);
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main();
}
export { main };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
{
"name": "langchain",
"version": "1.0.0",
"description": "LangChain.js sample for pre/post processing",
"type": "module",
"main": "agent.js",
"scripts": {
"start": "node agent.js"
},
"dependencies": {
"@langchain/core": "^1.1.19",
"@langchain/google-vertexai": "^2.1.15",
"@toolbox-sdk/core": "^0.2.1",
"langchain": "^1.2.17",
"zod": "^3.23.8"
},
"author": "",
"license": "ISC"
}