mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-19 03:14:29 -05:00
Compare commits
18 Commits
looker-dir
...
fix-param-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b875da3f | ||
|
|
fd22009322 | ||
|
|
d6af2907fd | ||
|
|
3684efd512 | ||
|
|
7a6d0c12e9 | ||
|
|
57b77bca09 | ||
|
|
276cf604a2 | ||
|
|
e8f3b9c8f2 | ||
|
|
77f7990a03 | ||
|
|
90ba07dfe0 | ||
|
|
e84a51b660 | ||
|
|
3e7d9b243d | ||
|
|
5a1559e1c8 | ||
|
|
5b107c53f3 | ||
|
|
4d51c2a61e | ||
|
|
3ceec96f8a | ||
|
|
06e01aecd1 | ||
|
|
af146cfb8a |
@@ -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: "go-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: "go"
|
||||
_IMAGE: "golang:1.25.1"
|
||||
_TARGET_ROOT: "docs/en/samples/pre_post_processing/go"
|
||||
_TABLE_NAME: "hotels_go_pre_post_processing"
|
||||
_SQL_FILE: ".ci/sample_tests/setup_hotels.sql"
|
||||
_AGENT_FILE_PATTERN: "agent.go"
|
||||
|
||||
options:
|
||||
logging: CLOUD_LOGGING_ONLY
|
||||
@@ -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
|
||||
4
.github/workflows/deploy_versioned_docs.yaml
vendored
4
.github/workflows/deploy_versioned_docs.yaml
vendored
@@ -35,7 +35,9 @@ jobs:
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Get Version from Release Tag
|
||||
run: echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
|
||||
run: echo "VERSION=${GITHUB_EVENT_RELEASE_TAG_NAME}" >> $GITHUB_ENV
|
||||
env:
|
||||
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3
|
||||
|
||||
132
.github/workflows/link_checker.yaml
vendored
Normal file
132
.github/workflows/link_checker.yaml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
name: Link Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
|
||||
jobs:
|
||||
link-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Identify Changed Files
|
||||
id: changed-files
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch origin main
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/main...HEAD -- '*.md')
|
||||
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
echo "No markdown files changed. Skipping checks."
|
||||
echo "HAS_CHANGES=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "--- Changed Files to Scan ---"
|
||||
echo "$CHANGED_FILES"
|
||||
echo "-----------------------------"
|
||||
|
||||
# FIX: Wrap filenames in quotes to handle spaces
|
||||
FILES_QUOTED=$(echo "$CHANGED_FILES" | sed 's/^/"/;s/$/"/' | tr '\n' ' ')
|
||||
|
||||
# Write to env using EOF pattern
|
||||
echo "CHECK_FILES<<EOF" >> $GITHUB_ENV
|
||||
echo "$FILES_QUOTED" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
echo "HAS_CHANGES=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
||||
- name: Restore lychee cache
|
||||
if: env.HAS_CHANGES == 'true'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
id: lychee-check
|
||||
if: env.HAS_CHANGES == 'true'
|
||||
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: >
|
||||
--quiet
|
||||
--no-progress
|
||||
--cache
|
||||
--max-cache-age 1d
|
||||
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
|
||||
${{ env.CHECK_FILES }}
|
||||
output: lychee-report.md
|
||||
format: markdown
|
||||
fail: true
|
||||
jobSummary: false
|
||||
debug: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Find comment
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: "## Link Resolution Note"
|
||||
|
||||
- name: Delete comment on success
|
||||
if: steps.lychee-check.outcome == 'success' && steps.find-comment.outputs.comment-id != ''
|
||||
run: |
|
||||
gh api \
|
||||
--method DELETE \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/issues/comments/${{ steps.find-comment.outputs.comment-id }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Prepare Report
|
||||
if: env.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
|
||||
run: |
|
||||
echo "## Link Resolution Note" > full-report.md
|
||||
|
||||
|
||||
echo "Local links and directory changes work differently on GitHub than on the docsite.You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> full-report.md
|
||||
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> full-report.md
|
||||
echo "" >> full-report.md
|
||||
sed -E '/(Redirect|Redirects per input)/d' lychee-report.md >> full-report.md
|
||||
|
||||
- name: Create PR Comment
|
||||
if: env.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: full-report.md
|
||||
edit-mode: replace
|
||||
|
||||
- name: Display Failure Report
|
||||
# Run this ONLY if the link checker failed
|
||||
if: steps.lychee-check.outcome == 'failure'
|
||||
run: |
|
||||
# We can now simply output the prepared file to the job summary
|
||||
cat full-report.md >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Fail the job
|
||||
exit 1
|
||||
68
.github/workflows/link_checker_workflow.yaml
vendored
68
.github/workflows/link_checker_workflow.yaml
vendored
@@ -1,68 +0,0 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
name: Link Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
|
||||
jobs:
|
||||
link-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
id: lychee-check
|
||||
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: >
|
||||
--quiet
|
||||
--no-progress
|
||||
--cache
|
||||
--max-cache-age 1d
|
||||
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
|
||||
README.md
|
||||
docs/
|
||||
output: lychee-report.md
|
||||
format: markdown
|
||||
fail: true
|
||||
jobSummary: false
|
||||
debug: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Display Failure Report
|
||||
# Run this ONLY if the link checker failed
|
||||
if: steps.lychee-check.outcome == 'failure'
|
||||
run: |
|
||||
echo "## Link Resolution Note" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Local links and directory changes work differently on GitHub than on the docsite." >> $GITHUB_STEP_SUMMARY
|
||||
echo "You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> $GITHUB_STEP_SUMMARY
|
||||
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "### Broken Links Found" >> $GITHUB_STEP_SUMMARY
|
||||
cat ./lychee-report.md >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
exit 1
|
||||
@@ -41,7 +41,7 @@
|
||||
"# Getting Started With MCP Toolbox\n",
|
||||
"\n",
|
||||
"This guide demonstrates how to quickly run\n",
|
||||
"[Toolbox](https://github.com/googleapis/genai-toolbox) end-to-end in Google\n",
|
||||
"[MCP Toolbox](https://github.com/googleapis/genai-toolbox) end-to-end in Google\n",
|
||||
"Colab using Python, PostgreSQL, and either [Google\n",
|
||||
"GenAI](https://pypi.org/project/google-genai/), [ADK](https://google.github.io/adk-docs/),\n",
|
||||
"[Langgraph](https://www.langchain.com/langgraph)\n",
|
||||
@@ -49,12 +49,12 @@
|
||||
"\n",
|
||||
"Within this Colab environment, you'll\n",
|
||||
"- Set up a `PostgreSQL database`.\n",
|
||||
"- Launch a Toolbox server.\n",
|
||||
"- Connect to Toolbox and develop a sample `Hotel Booking` application.\n",
|
||||
"- Launch an MCP Toolbox server.\n",
|
||||
"- Connect to MCP Toolbox and develop a sample `Hotel Booking` application.\n",
|
||||
"\n",
|
||||
"Here is the simplified flow of a Toolbox Application:\n",
|
||||
"Here is the simplified flow of a MCP Toolbox Application:\n",
|
||||
"\n",
|
||||
"<img src=\"https://services.google.com/fh/files/misc/toolbox_flow.png\" alt=\"Toolbox Flow\"/>\n",
|
||||
"<img src=\"https://services.google.com/fh/files/misc/toolbox_flow.png\" alt=\"MCP Toolbox Flow\"/>\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
@@ -208,12 +208,12 @@
|
||||
"id": "EPuheP8DIt3p"
|
||||
},
|
||||
"source": [
|
||||
"## Step 2: Install and configure Toolbox\n",
|
||||
"## Step 2: Install and configure MCP Toolbox\n",
|
||||
"\n",
|
||||
"In this section, we will\n",
|
||||
"1. Download the latest version of the toolbox binary.\n",
|
||||
"2. Create a toolbox config file.\n",
|
||||
"3. Start a toolbox server using the config file.\n",
|
||||
"1. Download the latest version of the MCP toolbox binary.\n",
|
||||
"2. Create an MCP toolbox config file.\n",
|
||||
"3. Start an MCP toolbox server using the config file.\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
@@ -223,7 +223,7 @@
|
||||
"id": "Bl1IeaqZbMYh"
|
||||
},
|
||||
"source": [
|
||||
"Download the [latest](https://github.com/googleapis/genai-toolbox/releases) version of Toolbox as a binary."
|
||||
"Download the [latest](https://github.com/googleapis/genai-toolbox/releases) version of MCP Toolbox as a binary."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -284,7 +284,7 @@
|
||||
"\n",
|
||||
"Our application will leverage these tools to interact with the hotels database.\n",
|
||||
"\n",
|
||||
"For detailed configuration options, please refer to the [Toolbox documentation](https://googleapis.github.io/genai-toolbox/getting-started/configure/).\n",
|
||||
"For detailed configuration options, please refer to the [MCP Toolbox documentation](https://googleapis.github.io/genai-toolbox/getting-started/configure/).\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
@@ -297,7 +297,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Create a tools file at runtime.\n",
|
||||
"# You can also upload a tools file and use that to run toolbox.\n",
|
||||
"# You can also upload a tools file and use that to run MCP toolbox.\n",
|
||||
"tools_file_name = \"tools.yml\"\n",
|
||||
"file_content = f\"\"\"\n",
|
||||
"kind: sources\n",
|
||||
@@ -417,7 +417,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Start a toolbox server\n",
|
||||
"# Start an MCP toolbox server\n",
|
||||
"! nohup {TOOLBOX_BINARY_PATH} --tools-file {TOOLS_FILE_PATH} -p {SERVER_PORT} > toolbox.log 2>&1 &"
|
||||
]
|
||||
},
|
||||
@@ -429,7 +429,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Check if toolbox is running\n",
|
||||
"# Check if MCP toolbox is running\n",
|
||||
"!sudo lsof -i :{SERVER_PORT}"
|
||||
]
|
||||
},
|
||||
@@ -439,10 +439,10 @@
|
||||
"id": "4yFH4JK7JEAv"
|
||||
},
|
||||
"source": [
|
||||
"## Step 3: Connect your agent to Toolbox\n",
|
||||
"## Step 3: Connect your agent to MCP Toolbox\n",
|
||||
"\n",
|
||||
"In this section, you will\n",
|
||||
"1. Establish a connection to the tools by creating a Toolbox client.\n",
|
||||
"1. Establish a connection to the tools by creating an MCP Toolbox client.\n",
|
||||
"2. Build an agent that leverages the tools and an LLM for Hotel Booking functionality.\n"
|
||||
]
|
||||
},
|
||||
@@ -495,7 +495,7 @@
|
||||
"id": "J46eLkFbNhWq"
|
||||
},
|
||||
"source": [
|
||||
"> You can either use LangGraph or LlamaIndex to develop a Toolbox based\n",
|
||||
"> You can either use LangGraph or LlamaIndex to develop an MCP Toolbox based\n",
|
||||
"> application. Run one of the sections below\n",
|
||||
"> - [Connect using Google GenAI](#scrollTo=Fv2-uT4mvYtp)\n",
|
||||
"> - [Connect using ADK](#scrollTo=QqRlWqvYNKSo)\n",
|
||||
@@ -618,7 +618,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the Toolbox Langchain package\n",
|
||||
"# Install the MCP Toolbox Langchain package\n",
|
||||
"!pip install toolbox-langchain --quiet\n",
|
||||
"!pip install langgraph --quiet\n",
|
||||
"\n",
|
||||
@@ -679,7 +679,7 @@
|
||||
" # model = ChatGoogleGenerativeAI(model=\"gemini-2.0-flash-001\")\n",
|
||||
" # model = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
|
||||
"\n",
|
||||
" # Load the tools from the Toolbox server\n",
|
||||
" # Load the tools from the MCP Toolbox server\n",
|
||||
" client = ToolboxClient(\"http://127.0.0.1:5000\")\n",
|
||||
" tools = await client.aload_toolset()\n",
|
||||
"\n",
|
||||
@@ -711,7 +711,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the Toolbox LlamaIndex package\n",
|
||||
"# Install the MCP Toolbox LlamaIndex package\n",
|
||||
"!pip install toolbox-llamaindex --quiet\n",
|
||||
"\n",
|
||||
"# Install the llamaindex llm package\n",
|
||||
@@ -783,7 +783,7 @@
|
||||
" # api_key=os.getenv(\"ANTHROPIC_API_KEY\")\n",
|
||||
" # )\n",
|
||||
"\n",
|
||||
" # Load the tools from the Toolbox server\n",
|
||||
" # Load the tools from the MCP Toolbox server\n",
|
||||
" client = ToolboxClient(\"http://127.0.0.1:5000\")\n",
|
||||
" tools = await client.aload_toolset()\n",
|
||||
"\n",
|
||||
@@ -821,7 +821,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the Toolbox Core package\n",
|
||||
"# Install the MCP Toolbox Core package\n",
|
||||
"!pip install toolbox-core --quiet\n",
|
||||
"\n",
|
||||
"# Install the Google GenAI package\n",
|
||||
@@ -999,7 +999,7 @@
|
||||
"id": "yatf9YoGclV9"
|
||||
},
|
||||
"source": [
|
||||
"Executing this will terminate the processes running on the database and Toolbox ports.\n",
|
||||
"Executing this will terminate the processes running on the database and MCP Toolbox ports.\n",
|
||||
"\n",
|
||||
"This is necessary before re-running the startup cells for these services to prevent `port already in use` errors."
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Python Quickstart (Local)"
|
||||
type: docs
|
||||
weight: 2
|
||||
description: >
|
||||
How to get started running Toolbox locally with [Python](https://github.com/googleapis/mcp-toolbox-sdk-python), PostgreSQL, and [Agent Development Kit](https://google.github.io/adk-docs/),
|
||||
How to get started running MCP Toolbox locally with [Python](https://github.com/googleapis/mcp-toolbox-sdk-python), PostgreSQL, and [Agent Development Kit](https://google.github.io/adk-docs/),
|
||||
[LangGraph](https://www.langchain.com/langgraph), [LlamaIndex](https://www.llamaindex.ai/) or [GoogleGenAI](https://pypi.org/project/google-genai/).
|
||||
---
|
||||
|
||||
@@ -32,14 +32,14 @@ This guide assumes you have already done the following:
|
||||
|
||||
{{< regionInclude "quickstart/shared/database_setup.md" "database_setup" >}}
|
||||
|
||||
## Step 2: Install and configure Toolbox
|
||||
## Step 2: Install and configure MCP Toolbox
|
||||
|
||||
{{< regionInclude "quickstart/shared/configure_toolbox.md" "configure_toolbox" >}}
|
||||
|
||||
## Step 3: Connect your agent to Toolbox
|
||||
## Step 3: Connect your agent to MCP Toolbox
|
||||
|
||||
In this section, we will write and run an agent that will load the Tools
|
||||
from Toolbox.
|
||||
from MCP Toolbox.
|
||||
|
||||
{{< notice tip>}}
|
||||
If you prefer to experiment within a Google Colab environment, you can connect
|
||||
@@ -113,7 +113,7 @@ pip install google-genai
|
||||
```
|
||||
<br/>
|
||||
|
||||
1. Update `my_agent/agent.py` with the following content to connect to Toolbox:
|
||||
1. Update `my_agent/agent.py` with the following content to connect to MCP Toolbox:
|
||||
```py
|
||||
{{< regionInclude "quickstart/python/adk/quickstart.py" "quickstart" >}}
|
||||
```
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Go Quickstart (Local)"
|
||||
type: docs
|
||||
weight: 4
|
||||
description: >
|
||||
How to get started running Toolbox locally with [Go](https://github.com/googleapis/mcp-toolbox-sdk-go), PostgreSQL, and orchestration frameworks such as [LangChain Go](https://tmc.github.io/langchaingo/docs/), [GenkitGo](https://genkit.dev/go/docs/get-started-go/), [Go GenAI](https://github.com/googleapis/go-genai) and [OpenAI Go](https://github.com/openai/openai-go).
|
||||
How to get started running MCP Toolbox locally with [Go](https://github.com/googleapis/mcp-toolbox-sdk-go), PostgreSQL, and orchestration frameworks such as [LangChain Go](https://tmc.github.io/langchaingo/docs/), [GenkitGo](https://genkit.dev/go/docs/get-started-go/), [Go GenAI](https://github.com/googleapis/go-genai) and [OpenAI Go](https://github.com/openai/openai-go).
|
||||
---
|
||||
|
||||
## Before you begin
|
||||
@@ -24,14 +24,14 @@ This guide assumes you have already done the following:
|
||||
|
||||
{{< regionInclude "quickstart/shared/database_setup.md" "database_setup" >}}
|
||||
|
||||
## Step 2: Install and configure Toolbox
|
||||
## Step 2: Install and configure MCP Toolbox
|
||||
|
||||
{{< regionInclude "quickstart/shared/configure_toolbox.md" "configure_toolbox" >}}
|
||||
|
||||
## Step 3: Connect your agent to Toolbox
|
||||
## Step 3: Connect your agent to MCP Toolbox
|
||||
|
||||
In this section, we will write and run an agent that will load the Tools
|
||||
from Toolbox.
|
||||
from MCP Toolbox.
|
||||
|
||||
1. Initialize a go module:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "JS Quickstart (Local)"
|
||||
type: docs
|
||||
weight: 3
|
||||
description: >
|
||||
How to get started running Toolbox locally with [JavaScript](https://github.com/googleapis/mcp-toolbox-sdk-js), PostgreSQL, and orchestration frameworks such as [LangChain](https://js.langchain.com/docs/introduction/), [GenkitJS](https://genkit.dev/docs/get-started/), [LlamaIndex](https://ts.llamaindex.ai/) and [GoogleGenAI](https://github.com/googleapis/js-genai).
|
||||
How to get started running MCP Toolbox locally with [JavaScript](https://github.com/googleapis/mcp-toolbox-sdk-js), PostgreSQL, and orchestration frameworks such as [LangChain](https://js.langchain.com/docs/introduction/), [GenkitJS](https://genkit.dev/docs/get-started/), [LlamaIndex](https://ts.llamaindex.ai/) and [GoogleGenAI](https://github.com/googleapis/js-genai).
|
||||
---
|
||||
|
||||
## Before you begin
|
||||
@@ -24,14 +24,14 @@ This guide assumes you have already done the following:
|
||||
|
||||
{{< regionInclude "quickstart/shared/database_setup.md" "database_setup" >}}
|
||||
|
||||
## Step 2: Install and configure Toolbox
|
||||
## Step 2: Install and configure MCP Toolbox
|
||||
|
||||
{{< regionInclude "quickstart/shared/configure_toolbox.md" "configure_toolbox" >}}
|
||||
|
||||
## Step 3: Connect your agent to Toolbox
|
||||
## Step 3: Connect your agent to MCP Toolbox
|
||||
|
||||
In this section, we will write and run an agent that will load the Tools
|
||||
from Toolbox.
|
||||
from MCP Toolbox.
|
||||
|
||||
1. (Optional) Initialize a Node.js project:
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// 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
|
||||
@@ -9,6 +11,7 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -3231,9 +3231,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
||||
@@ -133,3 +133,4 @@ instead of hardcoding your secrets into the configuration file.
|
||||
| user | string | true | Name of the Postgres user to connect as (e.g. "my-pg-user"). |
|
||||
| password | string | true | Password of the Postgres user (e.g. "my-password"). |
|
||||
| queryParams | map[string]string | false | Raw query to be added to the db connection string. |
|
||||
| queryExecMode | string | false | pgx query execution mode. Valid values: `cache_statement` (default), `cache_describe`, `describe_exec`, `exec`, `simple_protocol`. Useful with connection poolers that don't support prepared statement caching. |
|
||||
|
||||
@@ -4,8 +4,8 @@ linkTitle: "Redis"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
Redis is a in-memory data structure store.
|
||||
|
||||
Redis is a in-memory data structure store.
|
||||
|
||||
---
|
||||
|
||||
## About
|
||||
@@ -44,6 +44,9 @@ password: ${MY_AUTH_STRING} # Omit this field if you don't have a password.
|
||||
# database: 0
|
||||
# clusterEnabled: false
|
||||
# useGCPIAM: false
|
||||
# tls:
|
||||
# enabled: false
|
||||
# insecureSkipVerify: false
|
||||
```
|
||||
|
||||
{{< notice tip >}}
|
||||
@@ -61,7 +64,7 @@ Here is an example tools.yaml config with [AUTH][auth] enabled:
|
||||
```yaml
|
||||
kind: sources
|
||||
name: my-redis-cluster-instance
|
||||
type: memorystore-redis
|
||||
type: redis
|
||||
address:
|
||||
- 127.0.0.1:6379
|
||||
password: ${MY_AUTH_STRING}
|
||||
@@ -78,7 +81,7 @@ using IAM authentication:
|
||||
```yaml
|
||||
kind: sources
|
||||
name: my-redis-cluster-instance
|
||||
type: memorystore-redis
|
||||
type: redis
|
||||
address:
|
||||
- 127.0.0.1:6379
|
||||
useGCPIAM: true
|
||||
@@ -89,14 +92,16 @@ clusterEnabled: true
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|----------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| type | string | true | Must be "memorystore-redis". |
|
||||
| address | string | true | Primary endpoint for the Memorystore Redis instance to connect to. |
|
||||
| username | string | false | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank |
|
||||
| password | string | false | If you have [Redis AUTH][auth] enabled, specify the AUTH string here |
|
||||
| database | int | false | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`. |
|
||||
| clusterEnabled | bool | false | Set it to `true` if using a Redis Cluster instance. Defaults to `false`. |
|
||||
| useGCPIAM | string | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. |
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| type | string | true | Must be "redis". |
|
||||
| address | string | true | Primary endpoint for the Memorystore Redis instance to connect to. |
|
||||
| username | string | false | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank |
|
||||
| password | string | false | If you have [Redis AUTH][auth] enabled, specify the AUTH string here |
|
||||
| database | int | false | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`. |
|
||||
| tls.enabled | bool | false | Set it to `true` to enable TLS for the Redis connection. Defaults to `false`. |
|
||||
| tls.insecureSkipVerify | bool | false | Set it to `true` to skip TLS certificate verification. **Warning:** This is insecure and not recommended for production. Defaults to `false`. |
|
||||
| clusterEnabled | bool | false | Set it to `true` if using a Redis Cluster instance. Defaults to `false`. |
|
||||
| useGCPIAM | bool | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. |
|
||||
|
||||
[auth]: https://cloud.google.com/memorystore/docs/redis/about-redis-auth
|
||||
|
||||
@@ -50,5 +50,30 @@ It is helpful to understand how tool-level processing differs from other scopes:
|
||||
- **Model Level**: Intercepts individual calls to the LLM (prompts and responses). Unlike tool-level, this applies globally to all text sent/received, making it better for global PII redaction or token tracking.
|
||||
- **Agent Level**: Wraps the high-level execution loop (e.g., a "turn" in the conversation). Unlike tool-level, this envelopes the entire turn (user input to final response), making it suitable for session management or end-to-end auditing.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security & Guardrails
|
||||
|
||||
- **Principle of Least Privilege**: Ensure that tools run with the minimum necessary permissions. Middleware is an excellent place to enforce "read-only" modes or verify user identity before executing sensitive actions.
|
||||
- **Input Sanitization**: Actively strip potential PII (like credit card numbers or raw emails) from tool arguments before logging them.
|
||||
- **Prompt Injection Defense**: Use pre-processing hooks to scan user inputs for known jailbreak patterns or malicious directives before they reach the model or tools.
|
||||
|
||||
### Observability & Debugging
|
||||
|
||||
- **Structured Logging**: Instead of simple print statements, use structured JSON logging with correlation IDs. This allows you to trace a single user request through multiple agent turns and tool calls.
|
||||
- **Logging for Testability**: LLM responses are non-deterministic and may summarize away key details.
|
||||
- **Pattern**: Add explicit logging markers in your post-processing middleware (e.g., `logger.info("ACTION_SUCCESS: <id>")`).
|
||||
- **Benefit**: Your integration tests can grep logs for these stable markers to verify tool success, rather than painfully parsing variable natural language responses.
|
||||
|
||||
### Performance & Cost Optimization
|
||||
|
||||
- **Token Economy**: Tools often return verbose JSON. Use post-processing to strip unnecessary fields or summarize large datasets *before* returning the result to the LLM's context window. This saves tokens and reduces latency.
|
||||
- **Caching**: For read-heavy tools (like "search_knowledge_base"), implement caching middleware to return previous results for identical queries, saving both time and API costs.
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Graceful Degradation**: If a tool fails (e.g., API timeout), catch the exception in middleware and return a structured error message to the LLM (e.g., `Error: Database timeout, please try again`).
|
||||
- **Self-Correction**: Well-formatted error messages often allow the LLM to understand *why* a call failed and retry it with corrected parameters automatically.
|
||||
|
||||
|
||||
## Samples
|
||||
|
||||
42
docs/en/samples/pre_post_processing/go.md
Normal file
42
docs/en/samples/pre_post_processing/go.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "Go"
|
||||
type: docs
|
||||
weight: 3
|
||||
description: >
|
||||
How to add pre- and post- processing to your Agents using Go.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tutorial assumes that you have set up MCP Toolbox with a basic agent as described in the [local quickstart](../../getting-started/local_quickstart_go.md).
|
||||
|
||||
This guide demonstrates how to implement these patterns in your Toolbox applications.
|
||||
|
||||
## Implementation
|
||||
|
||||
{{< tabpane persist=header >}}
|
||||
{{% tab header="ADK" text=true %}}
|
||||
The following example demonstrates how to use the `beforeToolCallback` and `afterToolCallback` hooks in the ADK `LlmAgent` to implement pre and post processing logic.
|
||||
|
||||
```go
|
||||
{{< include "go/adk/agent.go" >}}
|
||||
```
|
||||
|
||||
You can also add model-level (`beforeModelCallback`, `afterModelCallback`) and agent-level (`beforeAgentCallback`, `afterAgentCallback`) hooks to intercept messages at different stages of the execution loop.
|
||||
|
||||
For more information, see the [ADK Callbacks documentation](https://google.github.io/adk-docs/callbacks/types-of-callbacks/).{{% /tab %}}
|
||||
{{< /tabpane >}}
|
||||
|
||||
## Results
|
||||
|
||||
The output should look similar to the following.
|
||||
|
||||
{{< notice note >}}
|
||||
The exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
|
||||
{{< /notice >}}
|
||||
|
||||
```
|
||||
AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.
|
||||
|
||||
AI: Error: Maximum stay duration is 14 days.
|
||||
```
|
||||
174
docs/en/samples/pre_post_processing/go/adk/agent.go
Normal file
174
docs/en/samples/pre_post_processing/go/adk/agent.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/mcp-toolbox-sdk-go/tbadk"
|
||||
"google.golang.org/adk/agent"
|
||||
"google.golang.org/adk/agent/llmagent"
|
||||
"google.golang.org/adk/model/gemini"
|
||||
"google.golang.org/adk/runner"
|
||||
"google.golang.org/adk/session"
|
||||
"google.golang.org/adk/tool"
|
||||
"google.golang.org/genai"
|
||||
)
|
||||
|
||||
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.`
|
||||
|
||||
var queries = []string{
|
||||
"Book hotel with id 3.",
|
||||
"Update my hotel with id 3 with checkin date 2025-01-04 and checkout date 2025-01-20",
|
||||
}
|
||||
|
||||
// Pre-processing
|
||||
func enforceBusinessRules(ctx tool.Context, tool tool.Tool, args map[string]any) (map[string]any, error) {
|
||||
|
||||
fmt.Printf("POLICY CHECK: Intercepting '%s'\n", tool.Name())
|
||||
if tool.Name() == "update-hotel" {
|
||||
checkinStr, okCheckin := args["checkin_date"].(string)
|
||||
checkoutStr, okCheckout := args["checkout_date"].(string)
|
||||
|
||||
if okCheckin && okCheckout {
|
||||
startDate, errStart := time.Parse("2006-01-02", checkinStr)
|
||||
endDate, errEnd := time.Parse("2006-01-02", checkoutStr)
|
||||
if errStart != nil || errEnd != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
duration := endDate.Sub(startDate).Hours() / 24
|
||||
if duration > 14 {
|
||||
fmt.Println("BLOCKED: Stay too long")
|
||||
return map[string]any{"Error": "Maximum stay duration is 14 days."}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Post-processing
|
||||
func enrichResponse(ctx tool.Context, tool tool.Tool, args, result map[string]any, err error) (map[string]any, error) {
|
||||
resultStr := fmt.Sprintf("%v", result)
|
||||
|
||||
if tool.Name() == "book-hotel" {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := result["Error"]; !ok && !strings.Contains(resultStr, "Error") {
|
||||
const loyaltyBonus = 500
|
||||
enrichedResult := fmt.Sprintf("Booking Confirmed!\n You earned %d Loyalty Points with this stay.\n\nSystem Details: %s", loyaltyBonus, resultStr)
|
||||
return map[string]any{"confirmation": enrichedResult}, nil
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
genaiKey := os.Getenv("GOOGLE_API_KEY")
|
||||
toolboxURL := "http://localhost:5000"
|
||||
ctx := context.Background()
|
||||
|
||||
toolboxClient, err := tbadk.NewToolboxClient(toolboxURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create MCP Toolbox client: %v", err)
|
||||
}
|
||||
|
||||
toolsetName := "my-toolset"
|
||||
mcpTools, err := toolboxClient.LoadToolset(toolsetName, ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load MCP toolset '%s': %v\nMake sure your Toolbox server is running.", toolsetName, err)
|
||||
}
|
||||
|
||||
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
|
||||
APIKey: genaiKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create model: %v", err)
|
||||
}
|
||||
|
||||
tools := make([]tool.Tool, len(mcpTools))
|
||||
for i := range mcpTools {
|
||||
tools[i] = &mcpTools[i]
|
||||
}
|
||||
llmagent, err := llmagent.New(llmagent.Config{
|
||||
Name: "hotel_assistant",
|
||||
Model: model,
|
||||
Description: "Agent to answer questions about hotels.",
|
||||
Instruction: systemPrompt,
|
||||
Tools: tools,
|
||||
// Add pre- and post- processing hooks
|
||||
BeforeToolCallbacks: []llmagent.BeforeToolCallback{enforceBusinessRules},
|
||||
AfterToolCallbacks: []llmagent.AfterToolCallback{enrichResponse},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create agent: %v", err)
|
||||
}
|
||||
|
||||
appName := "hotel_assistant"
|
||||
userID := "user-123"
|
||||
sessionService := session.InMemoryService()
|
||||
respSess, err := sessionService.Create(ctx, &session.CreateRequest{
|
||||
AppName: appName,
|
||||
UserID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create the session service: %v", err)
|
||||
}
|
||||
sess := respSess.Session
|
||||
|
||||
r, err := runner.New(runner.Config{
|
||||
AppName: appName,
|
||||
Agent: llmagent,
|
||||
SessionService: sessionService,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create runner: %v", err)
|
||||
}
|
||||
|
||||
for i, query := range queries {
|
||||
fmt.Printf("\n=== Query %d: %s ===\n", i+1, query)
|
||||
userMsg := genai.NewContentFromText(query, genai.RoleUser)
|
||||
streamingMode := agent.StreamingModeSSE
|
||||
|
||||
runIter := r.Run(ctx, userID, sess.ID(), userMsg, agent.RunConfig{
|
||||
StreamingMode: streamingMode,
|
||||
})
|
||||
|
||||
fmt.Print("AI: ")
|
||||
for event := range runIter {
|
||||
if event != nil && event.Content != nil {
|
||||
for _, p := range event.Content.Parts {
|
||||
fmt.Print(p.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n" + strings.Repeat("-", 80) + "\n")
|
||||
}
|
||||
}
|
||||
44
docs/en/samples/pre_post_processing/go/adk/go.mod
Normal file
44
docs/en/samples/pre_post_processing/go/adk/go.mod
Normal file
@@ -0,0 +1,44 @@
|
||||
module example.com/adk-agent
|
||||
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/googleapis/mcp-toolbox-sdk-go v0.5.1
|
||||
google.golang.org/adk v0.3.0
|
||||
google.golang.org/genai v1.43.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/safehtml v0.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/api v0.263.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
rsc.io/omap v1.2.0 // indirect
|
||||
rsc.io/ordered v1.1.1 // indirect
|
||||
)
|
||||
132
docs/en/samples/pre_post_processing/go/adk/go.sum
Normal file
132
docs/en/samples/pre_post_processing/go/adk/go.sum
Normal file
@@ -0,0 +1,132 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
|
||||
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k=
|
||||
cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=
|
||||
cloud.google.com/go/storage v1.59.2 h1:gmOAuG1opU8YvycMNpP+DvHfT9BfzzK5Cy+arP+Nocw=
|
||||
cloud.google.com/go/storage v1.59.2/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
|
||||
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8=
|
||||
github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
|
||||
github.com/googleapis/mcp-toolbox-sdk-go v0.5.1 h1:Jc7IUlVoitpkWK+21ccmzg+213Nv9lyN0tHXv16JPsQ=
|
||||
github.com/googleapis/mcp-toolbox-sdk-go v0.5.1/go.mod h1:wjOHkYUVD8TwLcAaSbubKj6kY8pfMVCEIxy2OzL4Fu0=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/adk v0.3.0 h1:gitgAKnET1F1+fFZc7VSAEo7cjK+D39mnRyqIRTzyzY=
|
||||
google.golang.org/adk v0.3.0/go.mod h1:iE1Kgc8JtYHiNxfdLa9dxcV4DqTn0D8q4eqhBi012Ak=
|
||||
google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk=
|
||||
google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8=
|
||||
google.golang.org/genai v1.43.0 h1:8vhqhzJNZu1U94e2m+KvDq/TUUjSmDrs1aKkvTa8SoM=
|
||||
google.golang.org/genai v1.43.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d h1:xXzuihhT3gL/ntduUZwHECzAn57E8dA6l8SOtYWdD8Q=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/omap v1.2.0 h1:c1M8jchnHbzmJALzGLclfH3xDWXrPxSUHXzH5C+8Kdw=
|
||||
rsc.io/omap v1.2.0/go.mod h1:C8pkI0AWexHopQtZX+qiUeJGzvc8HkdgnsWK4/mAa00=
|
||||
rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak=
|
||||
rsc.io/ordered v1.1.1/go.mod h1:evAi8739bWVBRG9aaufsjVc202+6okf8u2QeVL84BCM=
|
||||
78
docs/en/samples/pre_post_processing/go/agent_test.go
Normal file
78
docs/en/samples/pre_post_processing/go/agent_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQuickstartSample(t *testing.T) {
|
||||
framework := os.Getenv("ORCH_NAME")
|
||||
if framework == "" {
|
||||
t.Skip("Skipping test: ORCH_NAME environment variable is not set.")
|
||||
}
|
||||
|
||||
t.Logf("--- Testing: %s ---", framework)
|
||||
|
||||
if os.Getenv("GOOGLE_API_KEY") == "" {
|
||||
t.Skipf("Skipping test for %s: GOOGLE_API_KEY environment variable is not set.", framework)
|
||||
}
|
||||
|
||||
sampleDir := filepath.Join(".", framework)
|
||||
if _, err := os.Stat(sampleDir); os.IsNotExist(err) {
|
||||
t.Fatalf("Test setup failed: directory for framework '%s' not found.", framework)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "run", ".")
|
||||
cmd.Dir = sampleDir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
actualOutput := stdout.String()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Script execution failed with error: %v\n--- STDERR ---\n%s", err, stderr.String())
|
||||
}
|
||||
if len(actualOutput) == 0 {
|
||||
t.Fatal("Script ran successfully but produced no output.")
|
||||
}
|
||||
|
||||
goldenKeywords := []string{
|
||||
"AI:",
|
||||
"Loyalty Points",
|
||||
"POLICY CHECK: Intercepting 'update-hotel'",
|
||||
}
|
||||
|
||||
var missingKeywords []string
|
||||
outputLower := strings.ToLower(actualOutput)
|
||||
|
||||
for _, keyword := range goldenKeywords {
|
||||
kw := strings.TrimSpace(keyword)
|
||||
if kw != "" && !strings.Contains(outputLower, strings.ToLower(kw)) {
|
||||
missingKeywords = append(missingKeywords, kw)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingKeywords) > 0 {
|
||||
t.Fatalf("FAIL: The following keywords were missing from the output: [%s]", strings.Join(missingKeywords, ", "))
|
||||
}
|
||||
}
|
||||
47
docs/en/samples/pre_post_processing/js.md
Normal file
47
docs/en/samples/pre_post_processing/js.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
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 MCP 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 `wrapModelCall` 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.
|
||||
|
||||
{{< notice note >}}
|
||||
The exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
|
||||
{{< /notice >}}
|
||||
|
||||
```
|
||||
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(", ")}]`
|
||||
);
|
||||
});
|
||||
});
|
||||
109
docs/en/samples/pre_post_processing/js/langchain/agent.js
Normal file
109
docs/en/samples/pre_post_processing/js/langchain/agent.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { ToolboxClient } from "@toolbox-sdk/core";
|
||||
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
||||
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 GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || 'your-api-key'; // Replace it with your API key
|
||||
|
||||
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 ChatGoogleGenerativeAI({
|
||||
model: "gemini-2.5-flash",
|
||||
});
|
||||
|
||||
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 };
|
||||
1603
docs/en/samples/pre_post_processing/js/langchain/package-lock.json
generated
Normal file
1603
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,20 @@
|
||||
{
|
||||
"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.26",
|
||||
"@langchain/google-genai": "^2.1.19",
|
||||
"@langchain/google-vertexai": "^2.1.19",
|
||||
"@toolbox-sdk/core": "^0.2.1",
|
||||
"langchain": "^1.2.25",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -8,7 +8,7 @@ description: >
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tutorial assumes that you have set up Toolbox with a basic agent as described in the [local quickstart](../../getting-started/local_quickstart.md).
|
||||
This tutorial assumes that you have set up MCP Toolbox with a basic agent as described in the [local quickstart](../../getting-started/local_quickstart.md).
|
||||
|
||||
This guide demonstrates how to implement these patterns in your Toolbox applications.
|
||||
|
||||
@@ -31,7 +31,11 @@ You can also add model-level (`wrap_model`) and agent-level (`before_agent`, `af
|
||||
|
||||
## 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.
|
||||
The output should look similar to the following.
|
||||
|
||||
{{< notice note >}}
|
||||
The exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
|
||||
{{< /notice >}}
|
||||
|
||||
```
|
||||
AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.
|
||||
|
||||
@@ -87,8 +87,29 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
max-width: 50vw;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
background-color: transparent;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.resize-handle:hover,
|
||||
.resize-handle.active {
|
||||
background-color: var(--toolbox-blue);
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
@@ -626,10 +647,13 @@ body {
|
||||
.search-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-bottom: 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
#toolset-search-input {
|
||||
flex-grow: 1;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 20px 0 0 20px;
|
||||
@@ -637,6 +661,7 @@ body {
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-primary-gray);
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
116
internal/server/static/js/resize.js
Normal file
116
internal/server/static/js/resize.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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
|
||||
//
|
||||
// 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.
|
||||
|
||||
const STORAGE_KEY = 'toolbox-second-nav-width';
|
||||
const DEFAULT_WIDTH = 250;
|
||||
const MIN_WIDTH = 200;
|
||||
const MAX_WIDTH_PERCENT = 50;
|
||||
|
||||
/**
|
||||
* Creates and attaches a resize handle to the second navigation panel
|
||||
*/
|
||||
export function initializeResize() {
|
||||
const secondNav = document.querySelector('.second-nav');
|
||||
if (!secondNav) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create resize handle
|
||||
const resizeHandle = document.createElement('div');
|
||||
resizeHandle.className = 'resize-handle';
|
||||
resizeHandle.setAttribute('aria-label', 'Resize panel');
|
||||
secondNav.appendChild(resizeHandle);
|
||||
|
||||
// Load saved width or use default
|
||||
let initialWidth = DEFAULT_WIDTH;
|
||||
try {
|
||||
const savedWidth = localStorage.getItem(STORAGE_KEY);
|
||||
if (savedWidth) {
|
||||
const parsed = parseInt(savedWidth, 10);
|
||||
if (!isNaN(parsed) && parsed >= MIN_WIDTH) {
|
||||
initialWidth = parsed;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to load saved panel width:', e);
|
||||
}
|
||||
setPanelWidth(secondNav, initialWidth);
|
||||
|
||||
// Setup resize functionality
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const newWidth = startWidth + deltaX;
|
||||
const maxWidth = (window.innerWidth * MAX_WIDTH_PERCENT) / 100;
|
||||
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth));
|
||||
setPanelWidth(secondNav, clampedWidth);
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
|
||||
resizeHandle.classList.remove('active');
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
// Save width to localStorage
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, secondNav.offsetWidth.toString());
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to save panel width:', e);
|
||||
}
|
||||
};
|
||||
|
||||
resizeHandle.addEventListener('mousedown', (e) => {
|
||||
startX = e.clientX;
|
||||
startWidth = secondNav.offsetWidth;
|
||||
resizeHandle.classList.add('active');
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
e.preventDefault();
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
|
||||
// Handle window resize to enforce max width
|
||||
window.addEventListener('resize', () => {
|
||||
const currentWidth = secondNav.offsetWidth;
|
||||
const maxWidth = (window.innerWidth * MAX_WIDTH_PERCENT) / 100;
|
||||
|
||||
if (currentWidth > maxWidth) {
|
||||
setPanelWidth(secondNav, maxWidth);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, maxWidth.toString());
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to save panel width:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the width of the panel and updates flex property
|
||||
*/
|
||||
function setPanelWidth(panel, width) {
|
||||
panel.style.flex = `0 0 ${width}px`;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@
|
||||
<script type="module" src="/ui/js/tools.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<script type="module">
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolInstructions())
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolInstructions());
|
||||
|
||||
// Initialize resize functionality
|
||||
const { initializeResize } = await import('/ui/js/resize.js');
|
||||
initializeResize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -29,12 +29,16 @@
|
||||
<script type="module" src="/ui/js/toolsets.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<script type="module">
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolsetInstructions());
|
||||
|
||||
// Initialize resize functionality
|
||||
const { initializeResize } = await import('/ui/js/resize.js');
|
||||
initializeResize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"github.com/googleapis/genai-toolbox/internal/util/orderedmap"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -48,14 +49,15 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Host string `yaml:"host" validate:"required"`
|
||||
Port string `yaml:"port" validate:"required"`
|
||||
User string `yaml:"user" validate:"required"`
|
||||
Password string `yaml:"password" validate:"required"`
|
||||
Database string `yaml:"database" validate:"required"`
|
||||
QueryParams map[string]string `yaml:"queryParams"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Host string `yaml:"host" validate:"required"`
|
||||
Port string `yaml:"port" validate:"required"`
|
||||
User string `yaml:"user" validate:"required"`
|
||||
Password string `yaml:"password" validate:"required"`
|
||||
Database string `yaml:"database" validate:"required"`
|
||||
QueryParams map[string]string `yaml:"queryParams"`
|
||||
QueryExecMode string `yaml:"queryExecMode" validate:"omitempty,oneof=cache_statement cache_describe describe_exec exec simple_protocol"`
|
||||
}
|
||||
|
||||
func (r Config) SourceConfigType() string {
|
||||
@@ -63,7 +65,7 @@ func (r Config) SourceConfigType() string {
|
||||
}
|
||||
|
||||
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||
pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams)
|
||||
pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams, r.QueryExecMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create pool: %w", err)
|
||||
}
|
||||
@@ -126,7 +128,7 @@ func (s *Source) RunSQL(ctx context.Context, statement string, params []any) (an
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string) (*pgxpool.Pool, error) {
|
||||
func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string, queryExecMode string) (*pgxpool.Pool, error) {
|
||||
//nolint:all // Reassigned ctx
|
||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceType, name)
|
||||
defer span.End()
|
||||
@@ -150,7 +152,18 @@ func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name,
|
||||
Path: dbname,
|
||||
RawQuery: ConvertParamMapToRawQuery(queryParams),
|
||||
}
|
||||
pool, err := pgxpool.New(ctx, url.String())
|
||||
config, err := pgxpool.ParseConfig(url.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
|
||||
}
|
||||
|
||||
execMode, err := ParseQueryExecMode(queryExecMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ConnConfig.DefaultQueryExecMode = execMode
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create connection pool: %w", err)
|
||||
}
|
||||
@@ -165,3 +178,20 @@ func ConvertParamMapToRawQuery(queryParams map[string]string) string {
|
||||
}
|
||||
return strings.Join(queryArray, "&")
|
||||
}
|
||||
|
||||
func ParseQueryExecMode(queryExecMode string) (pgx.QueryExecMode, error) {
|
||||
switch queryExecMode {
|
||||
case "", "cache_statement":
|
||||
return pgx.QueryExecModeCacheStatement, nil
|
||||
case "cache_describe":
|
||||
return pgx.QueryExecModeCacheDescribe, nil
|
||||
case "describe_exec":
|
||||
return pgx.QueryExecModeDescribeExec, nil
|
||||
case "exec":
|
||||
return pgx.QueryExecModeExec, nil
|
||||
case "simple_protocol":
|
||||
return pgx.QueryExecModeSimpleProtocol, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid queryExecMode %q: must be one of %q, %q, %q, %q, or %q", queryExecMode, "cache_statement", "cache_describe", "describe_exec", "exec", "simple_protocol")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources/postgres"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
func TestParseFromYamlPostgres(t *testing.T) {
|
||||
@@ -88,6 +89,32 @@ func TestParseFromYamlPostgres(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "example with query exec mode",
|
||||
in: `
|
||||
kind: sources
|
||||
name: my-pg-instance
|
||||
type: postgres
|
||||
host: my-host
|
||||
port: my-port
|
||||
database: my_db
|
||||
user: my_user
|
||||
password: my_pass
|
||||
queryExecMode: simple_protocol
|
||||
`,
|
||||
want: map[string]sources.SourceConfig{
|
||||
"my-pg-instance": postgres.Config{
|
||||
Name: "my-pg-instance",
|
||||
Type: postgres.SourceType,
|
||||
Host: "my-host",
|
||||
Port: "my-port",
|
||||
Database: "my_db",
|
||||
User: "my_user",
|
||||
Password: "my_pass",
|
||||
QueryExecMode: "simple_protocol",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
@@ -137,6 +164,21 @@ func TestFailParseFromYaml(t *testing.T) {
|
||||
`,
|
||||
err: "error unmarshaling sources: unable to parse source \"my-pg-instance\" as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
|
||||
},
|
||||
{
|
||||
desc: "invalid query exec mode",
|
||||
in: `
|
||||
kind: sources
|
||||
name: my-pg-instance
|
||||
type: postgres
|
||||
host: my-host
|
||||
port: my-port
|
||||
database: my_db
|
||||
user: my_user
|
||||
password: my_pass
|
||||
queryExecMode: invalid_mode
|
||||
`,
|
||||
err: "error unmarshaling sources: unable to parse source \"my-pg-instance\" as \"postgres\": [6:16] Key: 'Config.QueryExecMode' Error:Field validation for 'QueryExecMode' failed on the 'oneof' tag\n 3 | name: my-pg-instance\n 4 | password: my_pass\n 5 | port: my-port\n> 6 | queryExecMode: invalid_mode\n ^\n 7 | type: postgres\n 8 | user: my_user",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
@@ -193,3 +235,32 @@ func TestConvertParamMapToRawQuery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQueryExecMode(t *testing.T) {
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want pgx.QueryExecMode
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "empty (default)", in: "", want: pgx.QueryExecModeCacheStatement},
|
||||
{desc: "cache_statement", in: "cache_statement", want: pgx.QueryExecModeCacheStatement},
|
||||
{desc: "cache_describe", in: "cache_describe", want: pgx.QueryExecModeCacheDescribe},
|
||||
{desc: "describe_exec", in: "describe_exec", want: pgx.QueryExecModeDescribeExec},
|
||||
{desc: "exec", in: "exec", want: pgx.QueryExecModeExec},
|
||||
{desc: "simple_protocol", in: "simple_protocol", want: pgx.QueryExecModeSimpleProtocol},
|
||||
{desc: "invalid mode", in: "invalid_mode", wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := postgres.ParseQueryExecMode(tc.in)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("parseQueryExecMode() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
if !tc.wantErr && got != tc.want {
|
||||
t.Errorf("parseQueryExecMode() = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -44,14 +45,20 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Address []string `yaml:"address" validate:"required"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Database int `yaml:"database"`
|
||||
UseGCPIAM bool `yaml:"useGCPIAM"`
|
||||
ClusterEnabled bool `yaml:"clusterEnabled"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Address []string `yaml:"address" validate:"required"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Database int `yaml:"database"`
|
||||
UseGCPIAM bool `yaml:"useGCPIAM"`
|
||||
ClusterEnabled bool `yaml:"clusterEnabled"`
|
||||
TLS TLSConfig `yaml:"tls"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
|
||||
}
|
||||
|
||||
func (r Config) SourceConfigType() string {
|
||||
@@ -91,6 +98,13 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if r.TLS.Enabled {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: r.TLS.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
var client RedisClient
|
||||
var err error
|
||||
if r.ClusterEnabled {
|
||||
@@ -104,6 +118,7 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
CredentialsProviderContext: authFn,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
err = clusterClient.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
|
||||
return shard.Ping(ctx).Err()
|
||||
@@ -125,6 +140,7 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
CredentialsProviderContext: authFn,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
_, err = standaloneClient.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
|
||||
@@ -63,6 +63,9 @@ func TestParseFromYamlRedis(t *testing.T) {
|
||||
database: 1
|
||||
useGCPIAM: true
|
||||
clusterEnabled: true
|
||||
tls:
|
||||
enabled: true
|
||||
insecureSkipVerify: true
|
||||
`,
|
||||
want: map[string]sources.SourceConfig{
|
||||
"my-redis-instance": redis.Config{
|
||||
@@ -73,6 +76,10 @@ func TestParseFromYamlRedis(t *testing.T) {
|
||||
Database: 1,
|
||||
ClusterEnabled: true,
|
||||
UseGCPIAM: true,
|
||||
TLS: redis.TLSConfig{
|
||||
Enabled: true,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -144,7 +144,7 @@ func ParseParams(ps Parameters, data map[string]any, claimsMap map[string]map[st
|
||||
// parse non auth-required parameter
|
||||
var ok bool
|
||||
v, ok = data[name]
|
||||
if !ok {
|
||||
if !ok || v == nil {
|
||||
v = p.GetDefault()
|
||||
// if the parameter is required and no value given, throw an error
|
||||
if CheckParamRequired(p.GetRequired(), v) {
|
||||
|
||||
@@ -2347,3 +2347,23 @@ func TestCheckParamRequired(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseParams_ExplicitNullForRequiredParam(t *testing.T) {
|
||||
// Define a required string parameter
|
||||
params := parameters.Parameters{
|
||||
parameters.NewStringParameter("required_param", "this is required"),
|
||||
}
|
||||
|
||||
// Input map with explicit nil
|
||||
input := map[string]any{
|
||||
"required_param": nil,
|
||||
}
|
||||
|
||||
// Call ParseParams
|
||||
_, err := parameters.ParseParams(params, input, nil)
|
||||
|
||||
// Expect an error because the parameter is required
|
||||
if err == nil {
|
||||
t.Errorf("ParseParams allowed explicit nil for required parameter, expected error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user