mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-14 00:54:56 -05:00
Compare commits
44 Commits
feat/add-g
...
langchain-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3bf0840ae | ||
|
|
42690d49c1 | ||
|
|
45e393059a | ||
|
|
e88e29b602 | ||
|
|
3eb87217f0 | ||
|
|
057cae426f | ||
|
|
7b1f5ea7b9 | ||
|
|
860447d8c5 | ||
|
|
b634c68396 | ||
|
|
49b53c83ce | ||
|
|
c7e41262a0 | ||
|
|
5e3b8d3672 | ||
|
|
4ff4f258aa | ||
|
|
5fe9984661 | ||
|
|
a212aedd19 | ||
|
|
9210e5555c | ||
|
|
b43af71793 | ||
|
|
da1f463dd1 | ||
|
|
3265f7e3a6 | ||
|
|
336743f747 | ||
|
|
911069ae8d | ||
|
|
cee59d52c3 | ||
|
|
9517daba09 | ||
|
|
3c61ee0597 | ||
|
|
19271eb9ee | ||
|
|
3a150c77ca | ||
|
|
ca6f31a192 | ||
|
|
d7faf7700f | ||
|
|
37a60ea2a6 | ||
|
|
8de16976ae | ||
|
|
49cb2f39f7 | ||
|
|
f169874e53 | ||
|
|
db8c3a3c77 | ||
|
|
8b33b0c67f | ||
|
|
35fa73516b | ||
|
|
66df3bfd21 | ||
|
|
73e0edc3cd | ||
|
|
3f32a9aab6 | ||
|
|
28006fc9b2 | ||
|
|
56c69131b4 | ||
|
|
ad4a509340 | ||
|
|
d39acac96c | ||
|
|
6df2ad28a9 | ||
|
|
8416378613 |
@@ -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
|
||||||
43
docs/en/samples/pre_post_processing/js.md
Normal file
43
docs/en/samples/pre_post_processing/js.md
Normal 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.
|
||||||
|
```
|
||||||
91
docs/en/samples/pre_post_processing/js/agent.test.js
Normal file
91
docs/en/samples/pre_post_processing/js/agent.test.js
Normal 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(", ")}]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
108
docs/en/samples/pre_post_processing/js/langchain/agent.js
Normal file
108
docs/en/samples/pre_post_processing/js/langchain/agent.js
Normal 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 };
|
||||||
1610
docs/en/samples/pre_post_processing/js/langchain/package-lock.json
generated
Normal file
1610
docs/en/samples/pre_post_processing/js/langchain/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user