Compare commits

..

29 Commits

Author SHA1 Message Date
duwenxin99
0fb1235cb0 draft 2025-06-12 16:38:16 -04:00
Wenxin Du
054ec198b9 feat: Add Valkey Source and Tool (#532) 2025-06-11 23:17:32 -04:00
Wenxin Du
f0aef29b0c feat: Add Redis Source and Tool (#519)
1. Added Redis Source and Tool
2. Moved some integration test helpers from tools.go to common.go
3. Make auth integration test want an input variable
2025-06-11 22:47:27 -04:00
Yuan
075dfa47e1 feat(tools/spanner): add templateParameters field for spanner (#691)
Add `templateParameters` to support non-filter parameters and DDL
statements.

Added a new argument `ignoreDdl` at integration test. Admin client is
needed to execute ddl statement in spanner. Toolbox does not use admin
client.

Part of #535
2025-06-11 23:54:13 +00:00
Mend Renovate
ad62d14cd5 chore(deps): update module cloud.google.com/go/cloudsqlconn to v1.17.2 (#694)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[cloud.google.com/go/cloudsqlconn](https://redirect.github.com/googlecloudplatform/cloud-sql-go-connector)
| `v1.17.1` -> `v1.17.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fcloudsqlconn/v1.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/cloud.google.com%2fgo%2fcloudsqlconn/v1.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/cloud.google.com%2fgo%2fcloudsqlconn/v1.17.1/v1.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fcloudsqlconn/v1.17.1/v1.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>googlecloudplatform/cloud-sql-go-connector
(cloud.google.com/go/cloudsqlconn)</summary>

###
[`v1.17.2`](https://redirect.github.com/GoogleCloudPlatform/cloud-sql-go-connector/releases/tag/v1.17.2)

[Compare
Source](https://redirect.github.com/googlecloudplatform/cloud-sql-go-connector/compare/v1.17.1...v1.17.2)

##### Bug Fixes

- reduce the number of memory allocations and the latency overhead.
([#&#8203;983](https://redirect.github.com/GoogleCloudPlatform/cloud-sql-go-connector/issues/983))
([cb641f2](cb641f223e))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-11 21:40:22 +00:00
Wenxin Du
5d183b0efe ci: Enable label-controlled docs preview (#516)
Allow external contributors to preview doc changes
2025-06-11 17:33:36 -04:00
Mend Renovate
904d04bc45 chore(deps): update module cloud.google.com/go/alloydbconn to v1.15.3 (#698)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[cloud.google.com/go/alloydbconn](https://redirect.github.com/googlecloudplatform/alloydb-go-connector)
| `v1.15.2` -> `v1.15.3` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2falloydbconn/v1.15.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/cloud.google.com%2fgo%2falloydbconn/v1.15.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/cloud.google.com%2fgo%2falloydbconn/v1.15.2/v1.15.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2falloydbconn/v1.15.2/v1.15.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>googlecloudplatform/alloydb-go-connector
(cloud.google.com/go/alloydbconn)</summary>

###
[`v1.15.3`](https://redirect.github.com/GoogleCloudPlatform/alloydb-go-connector/releases/tag/v1.15.3)

[Compare
Source](https://redirect.github.com/googlecloudplatform/alloydb-go-connector/compare/v1.15.2...v1.15.3)

##### Bug Fixes

- reduce the number of memory allocations and latency overhead
([#&#8203;686](https://redirect.github.com/GoogleCloudPlatform/alloydb-go-connector/issues/686))
([274ce04](274ce04953))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-06-11 14:20:50 -07:00
AlexTalreja
75e254c0a4 feat(tools/sqlitesql): add templateParameters field for sqlitesql (#687)
Add templateParameters to support non-filter parameters and DDL
statements.

Part of https://github.com/googleapis/genai-toolbox/issues/535
2025-06-10 13:08:52 -07:00
Yuan
850b32c5b0 docs(tools/alloydbainl): update broken links (#688)
Fixes #631
2025-06-10 11:25:12 -07:00
Anubhav Dhawan
927ef3c508 docs: Add toolbox-core examples to quickstart guide (#642)
## Description
This PR enhances our documentation by integrating `toolbox-core` code
snippets into the quickstart examples on the docsite.

## Context
Previously, our client-facing documentation primarily showcased agent
implementations using third-party frameworks like LangChain, LlamaIndex,
and ADK. This created a documentation gap, as there were no baseline
examples demonstrating how to use our foundational `toolbox-core` SDK
directly. Users who wanted to build a custom integration without a
specific framework had no direct reference in the guides.

## Changes
This PR introduces a new "Core" tab to all relevant code examples within
the auth services and local quickstart (with BigQuery) documentations.

---------

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-10 18:30:39 +05:30
Yuan
15d3c45159 ci: update generate release table shell script (#685) 2025-06-09 21:32:51 -07:00
release-please[bot]
714d990c34 chore(main): release 0.7.0 (#640)
🤖 I have created a release *beep* *boop*
---


##
[0.7.0](https://github.com/googleapis/genai-toolbox/compare/v0.6.0...v0.7.0)
(2025-06-10)


### Features

* Add templateParameters field for mssqlsql
([#671](https://github.com/googleapis/genai-toolbox/issues/671))
([b81fc6a](b81fc6aa6c))
* Add templateParameters field for mysqlsql
([#663](https://github.com/googleapis/genai-toolbox/issues/663))
([0a08d2c](0a08d2c15d))
* **metrics:** Add user agent for prebuilt tools
([#669](https://github.com/googleapis/genai-toolbox/issues/669))
([29aa0a7](29aa0a70da))
* **tools/postgressql:** Add templateParameters field
([#615](https://github.com/googleapis/genai-toolbox/issues/615))
([b763469](b76346993f))


### Bug Fixes

* Improve versionString
([#658](https://github.com/googleapis/genai-toolbox/issues/658))
([cf96f4c](cf96f4c249))
* **server/stdio:** Notifications should not return a response
([#638](https://github.com/googleapis/genai-toolbox/issues/638))
([69d047a](69d047af46))
* **tools/mysqlsql:** Handled the null value for string case in mysqlsql
tools ([#641](https://github.com/googleapis/genai-toolbox/issues/641))
([ef94648](ef94648455))
* Update path library
([#678](https://github.com/googleapis/genai-toolbox/issues/678))
([4998f82](4998f82852)),
closes [#662](https://github.com/googleapis/genai-toolbox/issues/662)

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
2025-06-09 17:15:26 -07:00
Yuan
e6c2fb324b ci: add extra files in release please config (#684)
Adding extra files for release please automatic files version updates.

removed "docs/en/how-to/deploy_gke.md" since it was updated to use the
`latest` version by default.
2025-06-09 23:13:55 +00:00
Kurtis Van Gent
cf96f4c249 fix: improve versionString (#658)
Reduces complexity of the version string calculation and fixes the ci/cd
pipeline to pass the correct parameters.

---------

Co-authored-by: Averi Kitsch <akitsch@google.com>
Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-09 15:31:42 -07:00
Mend Renovate
8569c6b59f chore(deps): update module modernc.org/sqlite to v1.38.0 (#681)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | `v1.37.1` ->
`v1.38.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/modernc.org%2fsqlite/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/modernc.org%2fsqlite/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/modernc.org%2fsqlite/v1.37.1/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/modernc.org%2fsqlite/v1.37.1/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>cznic/sqlite (modernc.org/sqlite)</summary>

###
[`v1.38.0`](https://gitlab.com/cznic/sqlite/compare/v1.37.1...v1.38.0)

[Compare
Source](https://gitlab.com/cznic/sqlite/compare/v1.37.1...v1.38.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-09 22:04:16 +00:00
Yuan
44d41a4888 chore: add .exe suffix to windows binary (#682) 2025-06-09 13:52:28 -07:00
Yuan
4998f82852 fix: update path library (#678)
Using filepath.Join for embed filenames will not respect Windows
filepaths. https://github.com/golang/go/issues/44305

Fix #662
2025-06-09 10:00:26 -07:00
Yuan
b81fc6aa6c feat: add templateParameters field for mssqlsql (#671)
Add `templateParameters to support non-filter parameters and DDL
statements.

Part of https://github.com/googleapis/genai-toolbox/issues/535
2025-06-06 13:18:37 -07:00
Averi Kitsch
29aa0a70da feat(metrics): add user agent for prebuilt tools (#669)
Append "+prebuilt.<source>" to the user agent for tracking the prebuilt
tools usage. This is set before the server is initialized and the user
agent is set on the client.

Using the period to stay consistent with our other appended metadata
like "binary.linux"
2025-06-06 09:49:25 -07:00
Yuan
5638ef520a chore: fix typos in tests (#672) 2025-06-06 14:43:57 +00:00
AlexTalreja
2f42de9507 docs: add templateParameters to postgres documentation (#657) 2025-06-05 17:33:58 -07:00
Yuan
0a08d2c15d feat: add templateParameters field for mysqlsql (#663)
Add `templateParameters to support non-filter parameters and DDL
statements.

Part of #535
2025-06-05 21:37:30 +00:00
Yuan
71250e1ced test: add templateParam postgres-sql tests to other pg source (#664)
Add the template parameters integration test to `postgres` source and
`cloudsql-postgres` source.
2025-06-05 21:33:35 +00:00
Mend Renovate
702dbc355b chore(deps): update dependency go to v1.24.4 (#666)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | patch |
`1.24.3` -> `1.24.4` |

---

### Release Notes

<details>
<summary>golang/go (go)</summary>

###
[`v1.24.4`](https://redirect.github.com/golang/go/compare/go1.24.3...go1.24.4)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-06-05 14:23:09 -07:00
Kurtis Van Gent
ef0cbdb4bf docs: fix SQL capitalization (#667) 2025-06-05 20:01:17 +00:00
Yuan
518a0e4c70 refactor(tools/http): dedup to use GetParams (#659) 2025-06-05 10:54:08 -07:00
Yuan
d7ba2736eb refactor: concatenate template parameters and parameters (#660)
Refactor this to dedup from other tools.
2025-06-05 09:01:45 -07:00
Yuan
33ae70ec02 docs: add llms-full.txt (#656)
Generating a `/llms-full.txt` to provide LLM-friendly content (that
doesn't require navigation). For more information, checkout
https://llmstxt.org/.


Preview:
https://googleapis.github.io/genai-toolbox/previews/PR-656/llms-full.txt
2025-06-04 16:00:48 -07:00
Kurtis Van Gent
1c9ad5ea24 refactor: implement dynamic source registration (#614)
This commit refactors the source configuration and loading mechanism to
use a dynamic registration pattern. Each source package now registers
itself with a central registry via its init() function.

The server configuration code uses this registry to decode and
initialize sources, decoupling it from specific source implementations
and simplifying the addition of new sources.

Key changes:
- Introduced `sources.Register()` and `newConfig()` constructor in each
source package.
- Moved source package imports to `cmd/root.go` as blank imports to
trigger `init()` functions for self-registration.
- Removed direct imports of specific source packages from
`internal/server/config.go`.
- Renamed `SourceKind` constants to `Kind` within each source package.
- Updated tests to use the new `Kind` constants and reflect registration
changes.

---------

Co-authored-by: Yuan Teoh <yuanteoh@google.com>
2025-06-04 14:23:57 -07:00
111 changed files with 3579 additions and 621 deletions

View File

@@ -17,7 +17,7 @@ steps:
waitFor: ['-']
script: |
#!/usr/bin/env bash
docker buildx build --build-arg METADATA_TAGS=$(git rev-parse HEAD) -t ${_DOCKER_URI}:$REF_NAME .
docker buildx build --build-arg COMMIT_SHA=$(git rev-parse HEAD) -t ${_DOCKER_URI}:$REF_NAME .
- id: "push-docker"
name: "gcr.io/cloud-builders/docker"
@@ -50,7 +50,7 @@ steps:
script: |
#!/usr/bin/env bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.linux.amd64.$REF_NAME" -o toolbox.linux.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.linux.amd64
- id: "store-linux-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -72,7 +72,7 @@ steps:
script: |
#!/usr/bin/env bash
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.darwin.arm64.$REF_NAME" -o toolbox.darwin.arm64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.darwin.arm64
- id: "store-darwin-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -94,7 +94,7 @@ steps:
script: |
#!/usr/bin/env bash
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.darwin.amd64.$REF_NAME" -o toolbox.darwin.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.darwin.amd64
- id: "store-darwin-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -116,7 +116,7 @@ steps:
script: |
#!/usr/bin/env bash
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.windows.amd64.$REF_NAME" -o toolbox.windows.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.windows.amd64
- id: "store-windows-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -124,7 +124,7 @@ steps:
- "build-windows-amd64"
script: |
#!/usr/bin/env bash
gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$REF_NAME/windows/amd64/toolbox
gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$REF_NAME/windows/amd64/toolbox.exe
options:
automapSubstitutions: true

View File

@@ -36,7 +36,12 @@ do
ARCH=$(echo "$file_key" | cut -d '.' -f 2)
# Get release URL
URL="https://storage.googleapis.com/genai-toolbox/$VERSION/$OS/$ARCH/toolbox"
if [ "$OS" = 'windows' ];
then
URL="https://storage.googleapis.com/genai-toolbox/$VERSION/$OS/$ARCH/toolbox.exe"
else
URL="https://storage.googleapis.com/genai-toolbox/$VERSION/$OS/$ARCH/toolbox"
fi
curl "$URL" --fail --output toolbox || exit 1

View File

@@ -33,7 +33,7 @@ steps:
- name: "go"
path: "/gopath"
script: |
go test -c -race ./tests/...
go test -c -cover -coverprofile=coverage.out -race ./tests/...
- id: "cloud-sql-pg"
name: golang:1
@@ -75,7 +75,7 @@ steps:
args:
- -c
- |
./alloydbpg.test -test.v
./alloydbpg.test -test.v -test.coverprofile=coverage.out
- id: "alloydb-ai-nl"
name: golang:1
@@ -332,6 +332,39 @@ steps:
- -c
- |
./couchbase.test -test.v
- id: "redis"
name : golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
- "GOPATH=/gopath"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv: ["REDIS_ADDRESS", "REDIS_PASS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
args:
- -c
- |
./redis.test -test.v
- id: "valkey"
name : golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
- "GOPATH=/gopath"
- "VALKEY_DATABASE=$_VALKEY_DATABASE"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv: ["VALKEY_ADDRESS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
args:
- -c
- |
./valkey.test -test.v
availableSecrets:
@@ -380,6 +413,12 @@ availableSecrets:
env: COUCHBASE_USER
- versionName: projects/$PROJECT_ID/secrets/couchbase_pass/versions/latest
env: COUCHBASE_PASS
- versionName: projects/$PROJECT_ID/secrets/memorystore_redis_address/versions/latest
env: REDIS_ADDRESS
- versionName: projects/$PROJECT_ID/secrets/memorystore_redis_pass/versions/latest
env: REDIS_PASS
- versionName: projects/$PROJECT_ID/secrets/memorystore_valkey_address/versions/latest
env: VALKEY_ADDRESS
options:
@@ -411,4 +450,4 @@ substitutions:
_MSSQL_PORT: "1433"
_DGRAPHURL: "https://play.dgraph.io"
_COUCHBASE_BUCKET: "couchbase-bucket"
_COUCHBASE_SCOPE: "couchbase-scope"
_COUCHBASE_SCOPE: "couchbase-scope"

View File

@@ -18,7 +18,7 @@ steps:
script: |
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
docker buildx build --build-arg METADATA_TAGS=$(git rev-parse HEAD) -t ${_DOCKER_URI}:$VERSION -t ${_DOCKER_URI}:latest .
docker buildx build --build-arg BUILD_TYPE=container.release --build-arg COMMIT_SHA=$(git rev-parse HEAD) -t ${_DOCKER_URI}:$VERSION -t ${_DOCKER_URI}:latest .
- id: "push-docker"
name: "gcr.io/cloud-builders/docker"
@@ -56,7 +56,7 @@ steps:
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.linux.amd64.$VERSION.$(git rev-parse HEAD)" -o toolbox.linux.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.linux.amd64
- id: "store-linux-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -80,7 +80,7 @@ steps:
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.darwin.arm64.$VERSION.$(git rev-parse HEAD)" -o toolbox.darwin.arm64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.darwin.arm64
- id: "store-darwin-arm64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -104,7 +104,7 @@ steps:
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.darwin.amd64.$VERSION.$(git rev-parse HEAD)" -o toolbox.darwin.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.darwin.amd64
- id: "store-darwin-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -128,7 +128,7 @@ steps:
#!/usr/bin/env bash
export VERSION=$(cat ./cmd/version.txt)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=binary.windows.amd64.$VERSION.$(git rev-parse HEAD)" -o toolbox.windows.amd64
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse HEAD)" -o toolbox.windows.amd64
- id: "store-windows-amd64"
name: "gcr.io/cloud-builders/gcloud:latest"
@@ -137,7 +137,7 @@ steps:
script: |
#!/usr/bin/env bash
export VERSION=v$(cat ./cmd/version.txt)
gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$VERSION/windows/amd64/toolbox
gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$VERSION/windows/amd64/toolbox.exe
options:
automapSubstitutions: true

4
.github/labels.yaml vendored
View File

@@ -69,6 +69,10 @@
color: 3DED97
description: Label to trigger Github Action tests.
- name: 'docs: deploy-preview'
color: BFDADC
description: Label to trigger Github Action docs preview.
- name: 'status: contribution welcome'
color: 8befd7
description: Status - Contributions welcome.

View File

@@ -21,9 +21,15 @@ extraFiles: [
"docs/en/getting-started/introduction/_index.md",
"docs/en/getting-started/local_quickstart.md",
"docs/en/getting-started/mcp_quickstart/_index.md",
"docs/en/how-to/deploy_gke.md",
"docs/en/samples/bigquery/local_quickstart.md",
"docs/en/samples/bigquery/mcp_quickstart.md",
"docs/en/samples/bigquery/mcp_quickstart/_index.md",
"docs/en/getting-started/colab_quickstart.ipynb",
"docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb",
"docs/en/how-to/connect-ide/bigquery_mcp.md",
"docs/en/how-to/connect-ide/spanner_mcp.md",
"docs/en/how-to/connect-ide/alloydb_pg_mcp.md",
"docs/en/how-to/connect-ide/cloud_sql_mysql_mcp.md",
"docs/en/how-to/connect-ide/cloud_sql_pg_mcp.md",
"docs/en/how-to/connect-ide/postgres_mcp.md",
"docs/en/how-to/connect-ide/cloud_sql_mssql_mcp.md",
]

View File

@@ -17,7 +17,7 @@ name: "docs"
permissions:
contents: write
pull-requests: write
# This Workflow depends on 'github.event.number',
# not compatible with branch or manual triggers.
on:
@@ -27,9 +27,19 @@ on:
- 'docs/**'
- 'github/workflows/docs**'
- '.hugo/**'
pull_request_target:
types: [labeled]
paths:
- 'docs/**'
- 'github/workflows/docs**'
- '.hugo/**'
jobs:
preview:
# run job on proper workflow event triggers (skip job for pull_request event
# from forks and only run pull_request_target for "docs: deploy-preview"
# label)
if: "${{ (github.event.action != 'labeled' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) || github.event.label.name == 'docs: deploy-preview' }}"
runs-on: ubuntu-24.04
defaults:
run:
@@ -70,8 +80,6 @@ jobs:
HUGO_RELATIVEURLS: false
- name: Deploy
# If run from a fork, GitHub write operations will fail.
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -80,8 +88,6 @@ jobs:
commit_message: "stage: PR-${{ github.event.number }}: ${{ github.event.head_commit.message }}"
- name: Comment
# If run from a fork, GitHub write operations will fail.
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |

View File

@@ -51,11 +51,16 @@ enableRobotsTXT = true
unsafe= true
[outputFormats]
[outputFormats.TXT]
[outputFormats.LLMS]
mediaType = "text/plain"
baseName = "llms"
isPlainText = true
root = true
[outputFormats.LLMS-FULL]
mediaType = "text/plain"
baseName = "llms-full"
isPlainText = true
root = true
[outputs]
home = ["HTML", "TXT"]
home = ["HTML", "RSS", "LLMS", "LLMS-FULL"]

View File

@@ -0,0 +1,14 @@
{{ .Site.Params.description }}
{{ range .Site.Sections }}
# {{ .Title }}
{{ .Description }}
{{ range .Pages }}
# {{ .Title }}
{{ .Description }}
{{ .RawContent }}
{{ range .Pages }}
# {{ .Title }}
{{ .Description }}
{{ .RawContent }}
{{end }}{{ end }}{{ end }}

View File

@@ -1,5 +1,23 @@
# Changelog
## [0.7.0](https://github.com/googleapis/genai-toolbox/compare/v0.6.0...v0.7.0) (2025-06-10)
### Features
* Add templateParameters field for mssqlsql ([#671](https://github.com/googleapis/genai-toolbox/issues/671)) ([b81fc6a](https://github.com/googleapis/genai-toolbox/commit/b81fc6aa6ccdfbc15676fee4d87041d9ad9682fa))
* Add templateParameters field for mysqlsql ([#663](https://github.com/googleapis/genai-toolbox/issues/663)) ([0a08d2c](https://github.com/googleapis/genai-toolbox/commit/0a08d2c15dcbec18bb556f4dc49792ba0c69db46))
* **metrics:** Add user agent for prebuilt tools ([#669](https://github.com/googleapis/genai-toolbox/issues/669)) ([29aa0a7](https://github.com/googleapis/genai-toolbox/commit/29aa0a70da3c2eb409a38993b3782da8bec7cb85))
* **tools/postgressql:** Add templateParameters field ([#615](https://github.com/googleapis/genai-toolbox/issues/615)) ([b763469](https://github.com/googleapis/genai-toolbox/commit/b76346993f298b4f7493a51405d0a287bacce05f))
### Bug Fixes
* Improve versionString ([#658](https://github.com/googleapis/genai-toolbox/issues/658)) ([cf96f4c](https://github.com/googleapis/genai-toolbox/commit/cf96f4c249f0692e3eb19fc56c794ca6a3079307))
* **server/stdio:** Notifications should not return a response ([#638](https://github.com/googleapis/genai-toolbox/issues/638)) ([69d047a](https://github.com/googleapis/genai-toolbox/commit/69d047af46f1ec00f236db8a978a7a7627217fd2))
* **tools/mysqlsql:** Handled the null value for string case in mysqlsql tools ([#641](https://github.com/googleapis/genai-toolbox/issues/641)) ([ef94648](https://github.com/googleapis/genai-toolbox/commit/ef94648455c3b20adda4f8cff47e70ddccac8c06))
* Update path library ([#678](https://github.com/googleapis/genai-toolbox/issues/678)) ([4998f82](https://github.com/googleapis/genai-toolbox/commit/4998f8285287b5daddd0043540f2cf871e256db5)), closes [#662](https://github.com/googleapis/genai-toolbox/issues/662)
## [0.6.0](https://github.com/googleapis/genai-toolbox/compare/v0.5.0...v0.6.0) (2025-05-28)

View File

@@ -80,6 +80,44 @@
automatically. For external contributors, ask the Toolbox
maintainers to trigger the testing workflows on your PR.
## Developing Documentation
Follow these steps to run a Hugo server for local preview:
1. [Install Hugo](https://gohugo.io/installation/macos/) version 0.146.0+.
1. Move into the `.hugo` directory
```bash
cd .hugo
```
1. Install dependencies
```bash
npm ci
```
1. Run the server
```bash
hugo server
```
### PR documentation preview
- For contributors:
- Ask a repo owner to run the preview deployment workflow on your PR. The preview link
will be commented under your PR automatically.
- For maintainers:
- Inspect the proposed changes in the PR and ensure that it does not contain
malicious code changes. You should be especially alert to any proposed changes in the
`.github/workflows/` directory that affect workflow files.
- After you make sure the changes are safe, apply the `docs: deploy-preview`
label to the PR to deploy documentation preview.
## Compile the app locally
### Compile Toolbox binary
@@ -130,27 +168,6 @@
docker run -d toolbox:dev
```
## Developing Documentation
1. [Install Hugo](https://gohugo.io/installation/macos/) version 0.146.0+.
1. Move into the `.hugo` directory
```bash
cd .hugo
```
1. Install dependencies
```bash
npm ci
```
1. Run the server
```bash
hugo server
```
## Developing Toolbox SDKs
Please refer to the [SDK developer guide](https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/DEVELOPER.md)

View File

@@ -20,11 +20,12 @@ COPY . .
ARG TARGETOS
ARG TARGETARCH
ARG METADATA_TAGS=dev
ARG BUILD_TYPE="container.dev"
ARG COMMIT_SHA=""
RUN go get ./...
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.metadataString=container.${METADATA_TAGS}"
go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=container.${BUILD_TYPE} -X github.com/googleapis/genai-toolbox/cmd.commitSha=${COMMIT_SHA}"
# Final Stage
FROM gcr.io/distroless/static:nonroot

View File

@@ -99,7 +99,7 @@ To install Toolbox as a binary:
<!-- {x-release-please-start-version} -->
```sh
# see releases page for other versions
export VERSION=0.6.0
export VERSION=0.7.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -112,7 +112,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.6.0
export VERSION=0.7.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -125,7 +125,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.6.0
go install github.com/googleapis/genai-toolbox@v0.7.0
```
<!-- {x-release-please-end} -->

View File

@@ -22,6 +22,7 @@ import (
"os"
"os/signal"
"regexp"
"runtime"
"strings"
"syscall"
"time"
@@ -52,19 +53,43 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/neo4j"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgresexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgressql"
_ "github.com/googleapis/genai-toolbox/internal/tools/redis"
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner"
_ "github.com/googleapis/genai-toolbox/internal/tools/spannerexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlitesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/valkey"
"github.com/spf13/cobra"
_ "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg"
_ "github.com/googleapis/genai-toolbox/internal/sources/bigquery"
_ "github.com/googleapis/genai-toolbox/internal/sources/bigtable"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg"
_ "github.com/googleapis/genai-toolbox/internal/sources/couchbase"
_ "github.com/googleapis/genai-toolbox/internal/sources/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/sources/http"
_ "github.com/googleapis/genai-toolbox/internal/sources/mssql"
_ "github.com/googleapis/genai-toolbox/internal/sources/mysql"
_ "github.com/googleapis/genai-toolbox/internal/sources/neo4j"
_ "github.com/googleapis/genai-toolbox/internal/sources/postgres"
_ "github.com/googleapis/genai-toolbox/internal/sources/redis"
_ "github.com/googleapis/genai-toolbox/internal/sources/spanner"
_ "github.com/googleapis/genai-toolbox/internal/sources/sqlite"
_ "github.com/googleapis/genai-toolbox/internal/sources/valkey"
)
var (
// versionString indicates the version of this library.
//go:embed version.txt
// versionString stores the full semantic version, including build metadata.
versionString string
// versionNum indicates the numerical part fo the version
//go:embed version.txt
versionNum string
// metadataString indicates additional build or distribution metadata.
metadataString string
buildType string = "dev" // should be one of "dev", "binary", or "container"
// commitSha is the git commit it was built from
commitSha string
)
func init() {
@@ -73,10 +98,11 @@ func init() {
// semanticVersion returns the version of the CLI including a compile-time metadata.
func semanticVersion() string {
v := strings.TrimSpace(versionString)
if metadataString != "" {
v += "+" + metadataString
metadataStrings := []string{buildType, runtime.GOOS, runtime.GOARCH}
if commitSha != "" {
metadataStrings = append(metadataStrings, commitSha)
}
v := strings.TrimSpace(versionNum) + "+" + strings.Join(metadataStrings, ".")
return v
}
@@ -289,6 +315,8 @@ func run(cmd *Command) error {
}
logMsg := fmt.Sprint("Using prebuilt tool configuration for ", cmd.prebuiltConfig)
cmd.logger.InfoContext(ctx, logMsg)
// Append prebuilt.source to Version string for the User Agent
cmd.cfg.Version += "+prebuilt." + cmd.prebuiltConfig
} else {
// Set default value of tools-file flag to tools.yaml
if cmd.tools_file == "" {

View File

@@ -18,6 +18,7 @@ import (
"bytes"
_ "embed"
"os"
"runtime"
"strings"
"testing"
@@ -37,7 +38,9 @@ import (
func withDefaults(c server.ServerConfig) server.ServerConfig {
data, _ := os.ReadFile("version.txt")
c.Version = strings.TrimSpace(string(data))
version := strings.TrimSpace(string(data)) // Preserving 'data', new var for clarity
c.Version = version + "+" + strings.Join([]string{"dev", runtime.GOOS, runtime.GOARCH}, ".")
if c.Address == "" {
c.Address = "127.0.0.1"
}

View File

@@ -1 +1 @@
0.6.0
0.7.0

View File

@@ -222,7 +222,7 @@
},
"outputs": [],
"source": [
"version = \"0.6.0\" # x-release-please-version\n",
"version = \"0.7.0\" # x-release-please-version\n",
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
"\n",
"# Make the binary executable\n",

View File

@@ -73,7 +73,7 @@ To install Toolbox as a binary:
```sh
# see releases page for other versions
export VERSION=0.6.0
export VERSION=0.7.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -84,7 +84,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.6.0
export VERSION=0.7.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -95,7 +95,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.6.0
go install github.com/googleapis/genai-toolbox@v0.7.0
```
{{% /tab %}}

View File

@@ -138,7 +138,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -50,19 +50,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -45,19 +45,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -3,7 +3,7 @@ title: "Cloud SQL for SQL Server using MCP"
type: docs
weight: 2
description: >
Connect your IDE to Cloud SQl for SQL Server using Toolbox.
Connect your IDE to Cloud SQL for SQL Server using Toolbox.
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like Cloud SQL. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a Cloud SQL for SQL Server instance:
@@ -50,19 +50,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
@@ -266,4 +266,4 @@ The following tools are available to the LLM:
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs will adapt to the tools available, so this shouldn't affect most users.
{{< /notice >}}
{{< /notice >}}

View File

@@ -3,7 +3,7 @@ title: "Cloud SQL for MySQL using MCP"
type: docs
weight: 2
description: >
Connect your IDE to Cloud SQl for MySQL using Toolbox.
Connect your IDE to Cloud SQL for MySQL using Toolbox.
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like Cloud SQL. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a Cloud SQL for MySQL instance:
@@ -50,19 +50,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
@@ -260,4 +260,4 @@ The following tools are available to the LLM:
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs will adapt to the tools available, so this shouldn't affect most users.
{{< /notice >}}
{{< /notice >}}

View File

@@ -3,7 +3,7 @@ title: "Cloud SQL for Postgres using MCP"
type: docs
weight: 2
description: >
Connect your IDE to Cloud SQl for Postgres using Toolbox.
Connect your IDE to Cloud SQL for Postgres using Toolbox.
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like Cloud SQL. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a Cloud SQL for Postgres instance:
@@ -50,19 +50,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
@@ -261,4 +261,4 @@ The following tools are available to the LLM:
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs will adapt to the tools available, so this shouldn't affect most users.
{{< /notice >}}
{{< /notice >}}

View File

@@ -44,19 +44,19 @@ This guide can be used with [AlloyDB Omni](https://cloud.google.com/alloydb/omni
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -47,19 +47,19 @@ description: >
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -62,52 +62,104 @@ token you will provide a function (that returns an id). This function is called
when the tool is invoked. This allows you to cache and refresh the ID token as
needed.
The primary method for providing these getters is via the `auth_token_getters` parameter when loading tools, or the `add_auth_token_getter`() / `add_auth_token_getters()` methods on a loaded tool object.
### Specifying tokens during load
{{< tabpane persist=header >}}
{{< tab header="LangChain" lang="Python" >}}
{{< tab header="Core" lang="Python" >}}
import asyncio
from toolbox_core import ToolboxClient
async def get_auth_token():
# ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
# This example just returns a placeholder. Replace with your actual token retrieval.
return "YOUR_ID_TOKEN" # Placeholder
# for a single tool use
async def main():
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
auth_tool = await toolbox.load_tool(
"get_sensitive_data",
auth_token_getters={"my_auth_app_1": get_auth_token}
)
result = await auth_tool(param="value")
print(result)
authorized_tool = toolbox.load_tool("my-tool-name", auth_tokens={"my_auth": get_auth_token})
if __name__ == "__main__":
asyncio.run(main())
{{< /tab >}}
{{< tab header="LangChain" lang="Python" >}}
import asyncio
from toolbox_langchain import ToolboxClient
# for a toolset use
async def get_auth_token():
# ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
# This example just returns a placeholder. Replace with your actual token retrieval.
return "YOUR_ID_TOKEN" # Placeholder
authorized_tools = toolbox.load_toolset("my-toolset-name", auth_tokens={"my_auth": get_auth_token})
async def main():
toolbox = ToolboxClient("http://127.0.0.1:5000")
auth_tool = await toolbox.aload_tool(
"get_sensitive_data",
auth_token_getters={"my_auth_app_1": get_auth_token}
)
result = await auth_tool.ainvoke({"param": "value"})
print(result)
if __name__ == "__main__":
asyncio.run(main())
{{< /tab >}}
{{< tab header="Llamaindex" lang="Python" >}}
import asyncio
from toolbox_llamaindex import ToolboxClient
async def get_auth_token():
# ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
# This example just returns a placeholder. Replace with your actual token retrieval.
return "YOUR_ID_TOKEN" # Placeholder
# for a single tool use
async def main():
toolbox = ToolboxClient("http://127.0.0.1:5000")
authorized_tool = toolbox.load_tool("my-tool-name", auth_tokens={"my_auth": get_auth_token})
auth_tool = await toolbox.aload_tool(
"get_sensitive_data",
auth_token_getters={"my_auth_app_1": get_auth_token}
)
# result = await auth_tool.acall(param="value")
# print(result.content)
# for a toolset use
authorized_tools = toolbox.load_toolset("my-toolset-name", auth_tokens={"my_auth": get_auth_token})
{{< /tab >}}
if __name__ == "__main__":
asyncio.run(main()){{< /tab >}}
{{< /tabpane >}}
### Specifying tokens for existing tools
{{< tabpane persist=header >}}
{{< tab header="Core" lang="Python" >}}
tools = await toolbox.load_toolset()
# for a single token
authorized_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token)
# OR, if multiple tokens are needed
authorized_tool = tools[0].add_auth_token_getters({
"my_auth1": get_auth1_token,
"my_auth2": get_auth2_token,
})
{{< /tab >}}
{{< tab header="LangChain" lang="Python" >}}
tools = toolbox.load_toolset()
# for a single token
auth_tools = [tool.add_auth_token("my_auth", get_auth_token) for tool in tools]
authorized_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token)
# OR, if multiple tokens are needed
authorized_tool = tools[0].add_auth_tokens({
authorized_tool = tools[0].add_auth_token_getters({
"my_auth1": get_auth1_token,
"my_auth2": get_auth2_token,
})
@@ -117,11 +169,11 @@ tools = toolbox.load_toolset()
# for a single token
auth_tools = [tool.add_auth_token("my_auth", get_auth_token) for tool in tools]
authorized_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token)
# OR, if multiple tokens are needed
authorized_tool = tools[0].add_auth_tokens({
authorized_tool = tools[0].add_auth_token_getters({
"my_auth1": get_auth1_token,
"my_auth2": get_auth2_token,
})

View File

@@ -0,0 +1,90 @@
---
title: "Redis"
linkTitle: "Redis"
type: docs
weight: 1
description: >
Redis is an open-source, in-memory data structure store.
---
## About
Redis is an open-source, in-memory data structure store, used as a database, cache, and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries.
If you are new to Redis, you can find installation and getting started guides on the [official Redis website](https://redis.io/docs/getting-started/).
## Requirements
### Redis
[AUTH string][auth] is a password for connection to Redis. If you have the `requirepass` directive set in your Redis configuration, incoming client connections must authenticate in order to connect.
Specify your AUTH string in the password field:
```yaml
sources:
my-redis-instance:
kind: redis
address:
- 127.0.0.1
username: ${MY_USER_NAME}
password: ${MY_AUTH_STRING} # Omit this field if you don't have a password.
# database: 0
# clusterEnabled: false
# useGCPIAM: false
```
{{< notice tip >}}
Use environment variable replacement with the format ${ENV_NAME}
instead of hardcoding your secrets into the configuration file.
{{< /notice >}}
### Memorystore For Redis
Memorystore standalone instances support authentication using an [AUTH][auth]
string.
Here is an example tools.yaml config with [AUTH][auth] enabled:
```yaml
sources:
my-redis-cluster-instance:
kind: memorystore-redis
address:
- 127.0.0.1
password: ${MY_AUTH_STRING}
# useGCPIAM: false
# clusterEnabled: false
```
Memorystore Redis Cluster supports IAM authentication instead. Grant your account the
required [IAM role][iam] and make sure to set `useGCPIAM` to `true`.
Here is an example tools.yaml config for Memorystore Redis Cluster instances
using IAM authentication:
```yaml
sources:
my-redis-cluster-instance:
kind: memorystore-redis
address: 127.0.0.1
useGCPIAM: true
clusterEnabled: true
```
[iam]: https://cloud.google.com/memorystore/docs/cluster/about-iam-auth
## Reference
| **field** | **type** | **required** | **description** |
|----------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------|
| kind | 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`. |
[auth]: https://cloud.google.com/memorystore/docs/redis/about-redis-auth

View File

@@ -0,0 +1,64 @@
---
title: "Valkey"
linkTitle: "Valkey"
type: docs
weight: 1
description: >
Valkey is an open-source, in-memory data structure store, forked from Redis.
---
## About
Valkey is an open-source, in-memory data structure store that originated as a fork of Redis. It's designed to be used as a database, cache, and message broker, supporting a wide range of data structures like strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries.
If you're new to Valkey, you can find installation and getting started guides on the [official Valkey website](https://valkey.io/docs/getting-started/).
## Example
```yaml
sources:
my-valkey-instance:
kind: valkey
address:
- 127.0.0.1
username: ${YOUR_USERNAME}
password: ${YOUR_PASSWORD}
# database: 0
# useGCPIAM: false
# disableCache: false
```
{{< notice tip >}}
Use environment variable replacement with the format ${ENV_NAME}
instead of hardcoding your secrets into the configuration file.
{{< /notice >}}
### IAM Authentication
If you are using GCP's Memorystore for Valkey, you can connect using IAM
authentication. Grant your account the required [IAM role][iam] and set
`useGCPIAM` to `true`:
```yaml
sources:
my-valkey-instance:
kind: valkey
address:
- 127.0.0.1
useGCPIAM: true
```
[iam]: https://cloud.google.com/memorystore/docs/valkey/about-iam-auth
## Reference
| **field** | **type** | **required** | **description** |
|--------------|:--------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "valkey". |
| address | []string | true | Endpoints for the Valkey 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 Valkey, leave this field blank |
| password | string | false | Password for the Valkey instance |
| database | int | false | The Valkey database to connect to. Not applicable for cluster enabled instances. The default database is `0`. |
| useGCPIAM | bool | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. |
| disableCache | bool | false | Set it to `true` if you want to enable client-side caching. Defaults to `false`. |

View File

@@ -141,6 +141,58 @@ specific claims within the user's ID token.
| name | string | true | Name of the [authServices](../authservices) used to verify the OIDC auth token. |
| field | string | true | Claim field decoded from the OIDC token used to auto-populate this parameter. |
### Template Parameters
Template parameters types include `string`, `integer`, `float`, `boolean` types. In
most cases, the description will be provided to the LLM as context on specifying
the parameter. Template parameters will be inserted into the SQL statement before
executing the prepared statement. They will be inserted without quotes, so to
insert a string using template parameters, quotes must be explicitly added within
the string.
Template parameter arrays can also be used similarly to basic parameters, and array
items must be strings. Once inserted into the SQL statement, the outer layer of quotes
will be removed. Therefore to insert strings into the SQL statement, a set of quotes
must be explicitly added within the string.
{{< notice warning >}}
Because template parameters can directly replace identifiers, column names, and table names, they are prone to SQL injections. Basic parameters are preferred for performance and safety reasons.
{{< /notice >}}
```yaml
tools:
select_columns_from_table:
kind: postgres-sql
source: my-pg-instance
statement: |
SELECT {{array .columnNames}} FROM {{.tableName}}
description: |
Use this tool to list all information from a specific table.
Example:
{{
"tableName": "flights",
"columnNames": ["id", "name"]
}}
templateParameters:
- name: tableName
type: string
description: Table to select from
- name: columnNames
type: array
description: The columns to select
items:
name: column
type: string
description: Name of a column to select
```
| **field** | **type** | **required** | **description** |
|-------------|:----------------:|:-------------:|-------------------------------------------------------------------------------------|
| name | string | true | Name of the template parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
| description | string | true | Natural language description of the template parameter to describe it to the agent. |
| items | parameter object |true (if array)| Specify a Parameter object for the type of the values in the array (string only). |
## Authorized Invocations
You can require an authorization check for any Tool invocation request by

View File

@@ -30,8 +30,7 @@ database layer.
## Requirements
{{< notice tip >}} AlloyDB AI natural language is currently in gated public
preview. For more information on availability and limitations, please see
[AlloyDB AI natural language
overview](https://cloud.google.com/alloydb/docs/natural-language-questions-overview)
[AlloyDB AI natural language overview](https://cloud.google.com/alloydb/docs/ai/natural-language-overview)
{{< /notice >}}
To enable AlloyDB AI natural language for your AlloyDB cluster, please follow
@@ -39,8 +38,8 @@ the steps listed in the [Generate SQL queries that answer natural language
questions][alloydb-ai-gen-nl], including enabling the extension and configuring
context for your application.
[alloydb-ai-nl-overview]: https://cloud.google.com/alloydb/docs/natural-language-questions-overview
[alloydb-ai-gen-nl]: https://cloud.google.com/alloydb/docs/alloydb/docs/ai/generate-queries-natural-language
[alloydb-ai-nl-overview]: https://cloud.google.com/alloydb/docs/ai/natural-language-overview
[alloydb-ai-gen-nl]: https://cloud.google.com/alloydb/docs/ai/generate-sql-queries-natural-language
## Configuration
@@ -70,7 +69,7 @@ Parameters](../tools/#array-parameters) or Bound Parameters to provide secure
access to queries generated using natural language, as these parameters are not
visible to the LLM.
[alloydb-psv]: https://cloud.google.com/alloydb/docs/ai/use-psvs#parameterized_secure_views
[alloydb-psv]: https://cloud.google.com/alloydb/docs/parameterized-secure-views-overview
## Example

View File

@@ -19,7 +19,10 @@ database. It's compatible with any of the following sources:
The specified SQL statement is executed as a [prepared statement][pg-prepare],
and specified parameters will inserted according to their position: e.g. `1`
will be the first parameter specified, `$@` will be the second parameter, and so
on.
on. If template parameters are included, they will be resolved before execution
of the prepared statement.
## Example
> **Note:** This tool uses parameterized queries to prevent SQL injections.
> Query parameters can be used as substitutes for arbitrary expressions.
@@ -28,8 +31,6 @@ on.
[pg-prepare]: https://www.postgresql.org/docs/current/sql-prepare.html
## Example
```yaml
tools:
search_flights_by_number:
@@ -68,12 +69,39 @@ tools:
description: 1 to 4 digit number
```
### Example with Template Parameters
> **Note:** This tool allows direct modifications to the SQL statement, including identifiers, column names,
> and table names. **This makes it more vulnerable to SQL injections**. Using basic parameters
> only (see above) is recommended for performance and safety reasons. For more details, please
> check [templateParameters](_index#template-parameters).
```yaml
tools:
list_table:
kind: postgres-sql
source: my-pg-instance
statement: |
SELECT * FROM {{.tableName}}
description: |
Use this tool to list all information from a specific table.
Example:
{{
"tableName": "flights",
}}
templateParameters:
- name: tableName
type: string
description: Table to select from
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "postgres-sql". |
| source | string | true | Name of the source the SQL should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | SQL statement to execute on. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| **field** | **type** | **required** | **description** |
|---------------------|:---------------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "postgres-sql". |
| source | string | true | Name of the source the SQL should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | SQL statement to execute on. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

@@ -0,0 +1,57 @@
---
title: "redis"
type: docs
weight: 1
description: >
A "redis" tool executes a set of pre-defined Redis commands against a Redis instance.
---
## About
A redis tool executes a series of pre-defined Redis commands against a
Redis source.
The specified Redis commands are executed sequentially. Each command is
represented as a string list, where the first element is the command name (e.g., SET,
GET, HGETALL) and subsequent elements are its arguments.
### Dynamic Command Parameters
Command arguments can be templated using the `$variableName` annotation. The
array type parameters will be expanded once into multiple arguments. Take the
following config for example:
```yaml
commands:
- [SADD, userNames, $userNames] # Array will be flattened into multiple arguments.
parameters:
- name: userNames
type: array
description: The user names to be set.
```
If the input is an array of strings `["Alice", "Sid", "Bob"]`, The final command
to be executed after argument expansion will be `[SADD, userNames, Alice, Sid, Bob]`.
## Example
```yaml
tools:
user_data_tool:
kind: redis
source: my-redis-instance
description: |
Use this tool to interact with user data stored in Redis.
It can set, retrieve, and delete user-specific information.
commands:
- [SADD, userNames, $userNames] # Array will be flattened into multiple arguments.
- [GET, $userId]
parameters:
- name: userId
type: string
description: The unique identifier for the user.
- name: userNames
type: array
description: The user names to be set.
```

View File

@@ -0,0 +1,56 @@
---
title: "valkey"
type: docs
weight: 1
description: >
A "valkey" tool executes a set of pre-defined Valkey commands against a Memorystore for Valkey instance.
---
## About
A valkey tool executes a series of pre-defined Valkey commands against a
Memorystore for Valkey instance.
The specified Valkey commands are executed sequentially. Each command is
represented as a string array, where the first element is the command name (e.g., SET,
GET, HGETALL) and subsequent elements are its arguments.
### Dynamic Command Parameters
Command arguments can be templated using the `$variableName` annotation. The
array type parameters will be expanded once into multiple arguments. Take the
following config for example:
```yaml
commands:
- [SADD, userNames, $userNames] # Array will be flattened into multiple arguments.
parameters:
- name: userNames
type: array
description: The user names to be set.
```
If the input is an array of strings `["Alice", "Sid", "Bob"]`, The final command
to be executed after argument expansion will be `[SADD, userNames, Alice, Sid, Bob]`.
## Example
```yaml
tools:
user_data_tool:
kind: valkey
source: my-valkey-instance
description: |
Use this tool to interact with user data stored in Valkey.
It can set, retrieve, and delete user-specific information.
commands:
- [SADD, userNames, $userNames] # Array will be flattened into multiple arguments.
- [GET, $userId]
parameters:
- name: userId
type: string
description: The unique identifier for the user.
- name: userNames
type: array
description: The user names to be set.
```

View File

@@ -220,7 +220,7 @@
},
"outputs": [],
"source": [
"version = \"0.6.0\" # x-release-please-version\n",
"version = \"0.7.0\" # x-release-please-version\n",
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
"\n",
"# Make the binary executable\n",

View File

@@ -32,6 +32,13 @@ This guide assumes you have already done the following:
```
1. Completed setup for usage with an LLM model such as
{{< tabpane text=true persist=header >}}
{{% tab header="Core" lang="en" %}}
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup) package.
- [langchain-google-genai](https://python.langchain.com/docs/integrations/chat/google_generative_ai/#setup) package.
- [langchain-anthropic](https://python.langchain.com/docs/integrations/chat/anthropic/#setup) package.
{{% /tab %}}
{{% tab header="LangChain" lang="en" %}}
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup) package.
@@ -131,7 +138,7 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
@@ -246,6 +253,10 @@ you can connect to a
1. In a new terminal, install the SDK package.
{{< tabpane persist=header >}}
{{< tab header="Core" lang="bash" >}}
pip install toolbox-core
{{< /tab >}}
{{< tab header="Langchain" lang="bash" >}}
pip install toolbox-langchain
@@ -262,8 +273,15 @@ pip install google-adk
{{< /tabpane >}}
1. Install other required dependencies:
{{< tabpane persist=header >}}
{{< tab header="Core" lang="bash" >}}
# TODO(developer): replace with correct package if needed
pip install langgraph langchain-google-vertexai
# pip install langchain-google-genai
# pip install langchain-anthropic
{{< /tab >}}
{{< tab header="Langchain" lang="bash" >}}
# TODO(developer): replace with correct package if needed
@@ -285,8 +303,120 @@ pip install toolbox-core
1. Create a new file named `hotel_agent.py` and copy the following
code to create an agent:
{{< tabpane persist=header >}}
{{< tab header="Core" lang="python" >}}
import asyncio
from google import genai
from google.genai.types import (
Content,
FunctionDeclaration,
GenerateContentConfig,
Part,
Tool,
)
from toolbox_core import ToolboxClient
prompt = """
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 id 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.
"""
queries = [
"Find hotels in Basel with Basel in it's name.",
"Please book the hotel Hilton Basel for me.",
"This is too expensive. Please cancel it.",
"Please book Hyatt Regency for me",
"My check in dates for my booking would be from April 10, 2024 to April 19, 2024.",
]
async def run_application():
async with ToolboxClient("http://127.0.0.1:5000") as toolbox_client:
# The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use
# integration. While this example uses Google's genai client, these callables can be adapted for
# various function-calling or agent frameworks. For easier integration with supported frameworks
# (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the
# provided wrapper packages, which handle framework-specific boilerplate.
toolbox_tools = await toolbox_client.load_toolset("my-toolset")
genai_client = genai.Client(
vertexai=True, project="project-id", location="us-central1"
)
genai_tools = [
Tool(
function_declarations=[
FunctionDeclaration.from_callable_with_api_option(callable=tool)
]
)
for tool in toolbox_tools
]
history = []
for query in queries:
user_prompt_content = Content(
role="user",
parts=[Part.from_text(text=query)],
)
history.append(user_prompt_content)
response = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
system_instruction=prompt,
tools=genai_tools,
),
)
history.append(response.candidates[0].content)
function_response_parts = []
for function_call in response.function_calls:
fn_name = function_call.name
# The tools are sorted alphabetically
if fn_name == "search-hotels-by-name":
function_result = await toolbox_tools[3](**function_call.args)
elif fn_name == "search-hotels-by-location":
function_result = await toolbox_tools[2](**function_call.args)
elif fn_name == "book-hotel":
function_result = await toolbox_tools[0](**function_call.args)
elif fn_name == "update-hotel":
function_result = await toolbox_tools[4](**function_call.args)
elif fn_name == "cancel-hotel":
function_result = await toolbox_tools[1](**function_call.args)
else:
raise ValueError("Function name not present.")
function_response = {"result": function_result}
function_response_part = Part.from_function_response(
name=function_call.name,
response=function_response,
)
function_response_parts.append(function_response_part)
if function_response_parts:
tool_response_content = Content(role="tool", parts=function_response_parts)
history.append(tool_response_content)
response2 = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
tools=genai_tools,
),
)
final_model_response_content = response2.candidates[0].content
history.append(final_model_response_content)
print(response2.text)
asyncio.run(run_application())
{{< /tab >}}
{{< tab header="LangChain" lang="python" >}}
import asyncio
from langgraph.prebuilt import create_react_agent
# TODO(developer): replace this with another import if needed
from langchain_google_vertexai import ChatVertexAI
@@ -313,25 +443,25 @@ queries = [
"My check in dates would be from April 10, 2024 to April 19, 2024.",
]
def main():
async def main():
# TODO(developer): replace this with another model if needed
model = ChatVertexAI(model_name="gemini-2.0-flash-001")
# model = ChatGoogleGenerativeAI(model="gemini-2.0-flash-001")
# model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
# Load the tools from the Toolbox server
async with ToolboxClient("http://127.0.0.1:5000") as client:
tools = client.load_toolset()
client = ToolboxClient("http://127.0.0.1:5000")
tools = await client.aload_toolset()
agent = create_react_agent(model, tools, checkpointer=MemorySaver())
agent = create_react_agent(model, tools, checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "thread-1"}}
for query in queries:
inputs = {"messages": [("user", prompt + query)]}
response = agent.invoke(inputs, stream_mode="values", config=config)
print(response["messages"][-1].content)
config = {"configurable": {"thread_id": "thread-1"}}
for query in queries:
inputs = {"messages": [("user", prompt + query)]}
response = await agent.ainvoke(inputs, stream_mode="values", config=config)
print(response["messages"][-1].content)
main()
asyncio.run(main())
{{< /tab >}}
{{< tab header="LlamaIndex" lang="python" >}}
import asyncio
@@ -380,19 +510,19 @@ async def main():
# )
# Load the tools from the Toolbox server
async with ToolboxClient("http://127.0.0.1:5000") as client:
tools = client.load_toolset()
client = ToolboxClient("http://127.0.0.1:5000")
tools = await client.aload_toolset()
agent = AgentWorkflow.from_tools_or_functions(
tools,
llm=llm,
system_prompt=prompt,
)
ctx = Context(agent)
for query in queries:
response = await agent.run(user_msg=query, ctx=ctx)
print(f"---- {query} ----")
print(str(response))
agent = AgentWorkflow.from_tools_or_functions(
tools,
llm=llm,
system_prompt=prompt,
)
ctx = Context(agent)
for query in queries:
response = await agent.arun(user_msg=query, ctx=ctx)
print(f"---- {query} ----")
print(str(response))
asyncio.run(main())
{{< /tab >}}
@@ -479,6 +609,9 @@ with ToolboxSyncClient("http://127.0.0.1:5000") as toolbox_client:
{{< /tabpane >}}
{{< tabpane text=true persist=header >}}
{{% tab header="Core" lang="en" %}}
To learn more about the Core SDK, check out the [Toolbox Core SDK documentation.](https://github.com/googleapis/genai-toolbox/tree/main/sdks/toolbox-core)
{{% /tab %}}
{{% tab header="Langchain" lang="en" %}}
To learn more about Agents in LangChain, check out the [LangGraph Agent documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent)
{{% /tab %}}

View File

@@ -84,7 +84,7 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.6.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

46
go.mod
View File

@@ -2,20 +2,20 @@ module github.com/googleapis/genai-toolbox
go 1.23.8
toolchain go1.24.3
toolchain go1.24.4
require (
cloud.google.com/go/alloydbconn v1.15.2
cloud.google.com/go/alloydbconn v1.15.3
cloud.google.com/go/bigquery v1.69.0
cloud.google.com/go/bigtable v1.37.0
cloud.google.com/go/cloudsqlconn v1.17.1
cloud.google.com/go/cloudsqlconn v1.17.2
cloud.google.com/go/spanner v1.82.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0
github.com/couchbase/gocb/v2 v2.10.0
github.com/couchbase/tools-common/http v1.0.9
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/httplog/v3 v3.0.0
github.com/go-chi/httplog/v2 v2.1.1
github.com/go-chi/render v1.0.3
github.com/go-playground/validator/v10 v10.26.0
github.com/go-sql-driver/mysql v1.9.2
@@ -23,9 +23,12 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.5
github.com/json-iterator/go v1.1.12
github.com/microsoft/go-mssqldb v1.8.2
github.com/neo4j/neo4j-go-driver/v5 v5.28.1
github.com/redis/go-redis/v9 v9.9.0
github.com/spf13/cobra v1.9.1
github.com/valkey-io/valkey-go v1.0.61
go.opentelemetry.io/contrib/propagators/autoprop v0.61.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0
@@ -36,16 +39,16 @@ require (
go.opentelemetry.io/otel/trace v1.36.0
golang.org/x/oauth2 v0.30.0
google.golang.org/api v0.236.0
modernc.org/sqlite v1.37.1
modernc.org/sqlite v1.38.0
)
require golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
require (
cel.dev/expr v0.20.0 // indirect
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.0 // indirect
cloud.google.com/go/alloydb v1.15.2 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/alloydb v1.16.1 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
@@ -60,12 +63,13 @@ require (
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/couchbase/gocbcore/v10 v10.7.0 // indirect
github.com/couchbase/gocbcoreps v0.1.3 // indirect
github.com/couchbase/goprotostellar v1.0.2 // indirect
github.com/couchbase/tools-common/errors v1.0.0 // indirect
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@@ -96,6 +100,8 @@ require (
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@@ -108,7 +114,7 @@ require (
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.36.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.36.0 // indirect
@@ -118,21 +124,21 @@ require (
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

100
go.sum
View File

@@ -1,5 +1,5 @@
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -53,10 +53,10 @@ cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9j
cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=
cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k=
cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=
cloud.google.com/go/alloydb v1.15.2 h1:wW1uQy39jkK9O5KApw1TJlQG592aJ4AuegT7sJ9bmRM=
cloud.google.com/go/alloydb v1.15.2/go.mod h1:gWJaNDqS51UTSFFRf+VdO9FyvCxmPNic05rmDX8GP5M=
cloud.google.com/go/alloydbconn v1.15.2 h1:mAYPxiMXSrbVmLNnpF5QHT3sYcbQ4Ohibb7AYReIoQw=
cloud.google.com/go/alloydbconn v1.15.2/go.mod h1:EjJMii4lmPi/wxLThjQ/uMJFNUCqLAAjQINNcuFX7eY=
cloud.google.com/go/alloydb v1.16.1 h1:pW4D0O2jAfAjoOEI1bgChPwMHWE8X8BjwSO0tfWkWvk=
cloud.google.com/go/alloydb v1.16.1/go.mod h1:zeZuGJ5mEaQE70FMXEvZIp5hQLR9yrGnHo1YUOncWRY=
cloud.google.com/go/alloydbconn v1.15.3 h1:j0Y0+LpVjdyUguX0uwsaeTtq4tQUZiFvsO52AH+yusY=
cloud.google.com/go/alloydbconn v1.15.3/go.mod h1:9yrNzUeMr3wR/D4gTJrh5ph2VDW/19tAMV7TlNuyRfM=
cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=
@@ -105,8 +105,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
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/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
@@ -167,8 +167,8 @@ cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb
cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=
cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=
cloud.google.com/go/cloudsqlconn v1.17.1 h1:jnBDOtliNASkHivU+gIs8Hk9govggDD0UYO1e/MnrYg=
cloud.google.com/go/cloudsqlconn v1.17.1/go.mod h1:jfz0XbsYVQAfwbK4hFCjpgPnyQyA7sOektt2t2hXw3o=
cloud.google.com/go/cloudsqlconn v1.17.2 h1:SxSt6ujMxK1KyxKAI2Z5raT2n3geN7ipu6bA8f7iR7E=
cloud.google.com/go/cloudsqlconn v1.17.2/go.mod h1:l7NymuoD+hycOo+92SJEyETPtE05oRG4oXjcH3swftw=
cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=
@@ -677,6 +677,10 @@ github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -704,8 +708,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/couchbase/gocb/v2 v2.10.0 h1:NNxZ4okToU1Ylqp6F8tE41CEJQPhb2WjufryAkeubOk=
github.com/couchbase/gocb/v2 v2.10.0/go.mod h1:OSbMfQkP7ltbKiDZhsT2mGDhkQNmvGXxptKcxAUJQ2Y=
github.com/couchbase/gocbcore/v10 v10.7.0 h1:lAEi0PNeEGKOu8pWrPUdtLOT2oGr1J/UTdGHVPC3r/0=
@@ -728,6 +732,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -764,8 +770,8 @@ github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrP
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/httplog/v3 v3.0.0 h1:9wa4p9+s2YGDBdpWETd2kK5D/ApQg90TnwdYhgyBdgk=
github.com/go-chi/httplog/v3 v3.0.0/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w=
github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk=
github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
@@ -879,6 +885,7 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -971,6 +978,8 @@ github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@@ -1009,10 +1018,17 @@ github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkR
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/neo4j/neo4j-go-driver/v5 v5.28.1 h1:RKWQW7wTgYAY2fU9S+9LaJ9OwRPbRc0I17tlT7nDmAY=
github.com/neo4j/neo4j-go-driver/v5 v5.28.1/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
@@ -1035,6 +1051,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -1074,6 +1092,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valkey-io/valkey-go v1.0.61 h1:uz7gxSs4dKqLfaa8xKFo8wHaCWYSCD3lMhVL0OJifZA=
github.com/valkey-io/valkey-go v1.0.61/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1102,8 +1122,8 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/propagators/autoprop v0.61.0 h1:cxOVDJ30qfzV27G5p9WMtJUB/3cXC0iL+u9EV1fSOws=
@@ -1156,8 +1176,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1217,8 +1237,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1276,8 +1296,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1325,8 +1345,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1432,16 +1452,16 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1729,10 +1749,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1774,8 +1794,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1831,8 +1851,8 @@ modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJD
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
@@ -1843,8 +1863,8 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@@ -1862,8 +1882,8 @@ modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=

View File

@@ -17,7 +17,7 @@ package prebuiltconfigs
import (
"embed"
"fmt"
"path/filepath"
"path"
"strings"
)
@@ -66,7 +66,7 @@ func loadPrebuiltToolYAMLs() (map[string][]byte, []string, error) {
for _, entry := range entries {
lowerName := strings.ToLower(entry.Name())
if !entry.IsDir() && (strings.HasSuffix(lowerName, ".yaml")) {
filePathInFS := filepath.Join("tools", entry.Name())
filePathInFS := path.Join("tools", entry.Name())
content, err := prebuiltConfigsFS.ReadFile(filePathInFS)
if err != nil {
errMsg := fmt.Errorf("failed to read a prebuilt tool %w", err)

View File

@@ -22,21 +22,6 @@ import (
"github.com/googleapis/genai-toolbox/internal/auth"
"github.com/googleapis/genai-toolbox/internal/auth/google"
"github.com/googleapis/genai-toolbox/internal/sources"
alloydbpgsrc "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg"
bigquerysrc "github.com/googleapis/genai-toolbox/internal/sources/bigquery"
bigtablesrc "github.com/googleapis/genai-toolbox/internal/sources/bigtable"
cloudsqlmssqlsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql"
cloudsqlmysqlsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql"
cloudsqlpgsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg"
couchbasesrc "github.com/googleapis/genai-toolbox/internal/sources/couchbase"
dgraphsrc "github.com/googleapis/genai-toolbox/internal/sources/dgraph"
httpsrc "github.com/googleapis/genai-toolbox/internal/sources/http"
mssqlsrc "github.com/googleapis/genai-toolbox/internal/sources/mssql"
mysqlsrc "github.com/googleapis/genai-toolbox/internal/sources/mysql"
neo4jsrc "github.com/googleapis/genai-toolbox/internal/sources/neo4j"
postgressrc "github.com/googleapis/genai-toolbox/internal/sources/postgres"
spannersrc "github.com/googleapis/genai-toolbox/internal/sources/spanner"
sqlitesrc "github.com/googleapis/genai-toolbox/internal/sources/sqlite"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/util"
)
@@ -145,108 +130,23 @@ func (c *SourceConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interf
kind, ok := v["kind"]
if !ok {
return fmt.Errorf("missing 'kind' field for %q", name)
return fmt.Errorf("missing 'kind' field for source %q", name)
}
kindStr, ok := kind.(string)
if !ok {
return fmt.Errorf("invalid 'kind' field for source %q (must be a string)", name)
}
dec, err := util.NewStrictDecoder(v)
yamlDecoder, err := util.NewStrictDecoder(v)
if err != nil {
return fmt.Errorf("error creating decoder: %w", err)
}
switch kind {
case alloydbpgsrc.SourceKind:
actual := alloydbpgsrc.Config{Name: name, IPType: "public"}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case bigtablesrc.SourceKind:
actual := bigtablesrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlpgsrc.SourceKind:
actual := cloudsqlpgsrc.Config{Name: name, IPType: "public"}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case postgressrc.SourceKind:
actual := postgressrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlmysqlsrc.SourceKind:
actual := cloudsqlmysqlsrc.Config{Name: name, IPType: "public"}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mysqlsrc.SourceKind:
actual := mysqlsrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case spannersrc.SourceKind:
actual := spannersrc.Config{Name: name, Dialect: "googlesql"}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case neo4jsrc.SourceKind:
actual := neo4jsrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlmssqlsrc.SourceKind:
actual := cloudsqlmssqlsrc.Config{Name: name, IPType: "public"}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mssqlsrc.SourceKind:
actual := mssqlsrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case dgraphsrc.SourceKind:
actual := dgraphsrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case httpsrc.SourceKind:
actual := httpsrc.DefaultConfig(name)
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case bigquerysrc.SourceKind:
actual := bigquerysrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case sqlitesrc.SourceKind:
actual := sqlitesrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case couchbasesrc.SourceKind:
actual := couchbasesrc.Config{Name: name}
if err := dec.DecodeContext(ctx, &actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
default:
return fmt.Errorf("%q is not a valid kind of data source", kind)
return fmt.Errorf("error creating YAML decoder for source %q: %w", name, err)
}
sourceConfig, err := sources.DecodeConfig(ctx, kindStr, name, yamlDecoder)
if err != nil {
return err
}
(*c)[name] = sourceConfig
}
return nil
}

View File

@@ -26,7 +26,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httplog/v3"
"github.com/go-chi/httplog/v2"
"github.com/googleapis/genai-toolbox/internal/auth"
"github.com/googleapis/genai-toolbox/internal/log"
"github.com/googleapis/genai-toolbox/internal/sources"

View File

@@ -21,6 +21,7 @@ import (
"strings"
"cloud.google.com/go/alloydbconn"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"github.com/jackc/pgx/v5/pgxpool"
@@ -32,6 +33,20 @@ const SourceKind string = "alloydb-postgres"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, IPType: "public"} // Default IPType
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -159,7 +159,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"alloydb-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
err: "unable to parse source \"my-pg-instance\" as \"alloydb-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
@@ -176,7 +176,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"alloydb-postgres\": [3:1] unknown field \"foo\"\n 1 | cluster: my-cluster\n 2 | database: my_db\n> 3 | foo: bar\n ^\n 4 | instance: my-instance\n 5 | kind: alloydb-postgres\n 6 | password: my_pass\n 7 | ",
err: "unable to parse source \"my-pg-instance\" as \"alloydb-postgres\": [3:1] unknown field \"foo\"\n 1 | cluster: my-cluster\n 2 | database: my_db\n> 3 | foo: bar\n ^\n 4 | instance: my-instance\n 5 | kind: alloydb-postgres\n 6 | password: my_pass\n 7 | ",
},
{
desc: "missing required field",
@@ -191,7 +191,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"alloydb-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
err: "unable to parse source \"my-pg-instance\" as \"alloydb-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -19,12 +19,12 @@ import (
"fmt"
bigqueryapi "cloud.google.com/go/bigquery"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
const SourceKind string = "bigquery"
@@ -32,6 +32,20 @@ const SourceKind string = "bigquery"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
// BigQuery configs
Name string `yaml:"name" validate:"required"`

View File

@@ -83,17 +83,17 @@ func TestFailParseFromYaml(t *testing.T) {
location: us
foo: bar
`,
err: "unable to parse as \"bigquery\": [1:1] unknown field \"foo\"\n> 1 | foo: bar\n ^\n 2 | kind: bigquery\n 3 | location: us\n 4 | project: my-project",
err: "unable to parse source \"my-instance\" as \"bigquery\": [1:1] unknown field \"foo\"\n> 1 | foo: bar\n ^\n 2 | kind: bigquery\n 3 | location: us\n 4 | project: my-project",
},
{
desc: "missing required field",
in: `
sources:
my-mysql-instance:
my-instance:
kind: bigquery
location: us
`,
err: "unable to parse as \"bigquery\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
err: "unable to parse source \"my-instance\" as \"bigquery\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -19,6 +19,7 @@ import (
"fmt"
"cloud.google.com/go/bigtable"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
@@ -30,6 +31,20 @@ const SourceKind string = "bigtable"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -84,7 +84,7 @@ func TestFailParseFromYaml(t *testing.T) {
instance: my-instance
foo: bar
`,
err: "unable to parse as \"bigtable\": [1:1] unknown field \"foo\"\n> 1 | foo: bar\n ^\n 2 | instance: my-instance\n 3 | kind: bigtable\n 4 | project: my-project",
err: "unable to parse source \"my-bigtable-instance\" as \"bigtable\": [1:1] unknown field \"foo\"\n> 1 | foo: bar\n ^\n 2 | instance: my-instance\n 3 | kind: bigtable\n 4 | project: my-project",
},
{
desc: "missing required field",
@@ -94,7 +94,7 @@ func TestFailParseFromYaml(t *testing.T) {
kind: bigtable
project: my-project
`,
err: "unable to parse as \"bigtable\": Key: 'Config.Instance' Error:Field validation for 'Instance' failed on the 'required' tag",
err: "unable to parse source \"my-bigtable-instance\" as \"bigtable\": Key: 'Config.Instance' Error:Field validation for 'Instance' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -21,6 +21,7 @@ import (
"slices"
"cloud.google.com/go/cloudsqlconn/sqlserver/mssql"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
@@ -31,6 +32,20 @@ const SourceKind string = "cloud-sql-mssql"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, IPType: "public"} // Default IPType
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
// Cloud SQL MSSQL configs
Name string `yaml:"name" validate:"required"`

View File

@@ -99,7 +99,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mssql\": ipType invalid: must be one of \"public\", or \"private\"",
err: "unable to parse source \"my-instance\" as \"cloud-sql-mssql\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
@@ -116,7 +116,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | ipAddress: localhost\n 5 | kind: cloud-sql-mssql\n 6 | ",
err: "unable to parse source \"my-instance\" as \"cloud-sql-mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | ipAddress: localhost\n 5 | kind: cloud-sql-mssql\n 6 | ",
},
{
desc: "missing required field",
@@ -131,7 +131,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mssql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
err: "unable to parse source \"my-instance\" as \"cloud-sql-mssql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -21,6 +21,7 @@ import (
"slices"
"cloud.google.com/go/cloudsqlconn/mysql/mysql"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
@@ -31,6 +32,20 @@ const SourceKind string = "cloud-sql-mysql"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, IPType: "public"} // Default IPType
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -152,7 +152,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mysql\": ipType invalid: must be one of \"public\", or \"private\"",
err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
@@ -168,7 +168,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-mysql\n 5 | password: my_pass\n 6 | ",
err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-mysql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
@@ -182,7 +182,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mysql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -20,6 +20,7 @@ import (
"net"
"cloud.google.com/go/cloudsqlconn"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"github.com/jackc/pgx/v5/pgxpool"
@@ -31,6 +32,20 @@ const SourceKind string = "cloud-sql-postgres"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, IPType: "public"} // Default IPType
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -152,7 +152,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
@@ -168,7 +168,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-postgres\n 5 | password: my_pass\n 6 | ",
err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-postgres\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
@@ -182,7 +182,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -17,10 +17,12 @@ package couchbase
import (
"context"
"crypto/tls"
"fmt"
"os"
"github.com/couchbase/gocb/v2"
tlsutil "github.com/couchbase/tools-common/http/tls"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"go.opentelemetry.io/otel/trace"
)
@@ -30,6 +32,20 @@ const SourceKind string = "couchbase"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -125,7 +125,7 @@ func TestFailParseFromYaml(t *testing.T) {
scope: inventory
foo: bar
`,
err: "unable to parse as \"couchbase\": [3:1] unknown field \"foo\"\n 1 | bucket: travel-sample\n 2 | connectionString: localhost\n> 3 | foo: bar\n ^\n 4 | kind: couchbase\n 5 | password: password\n 6 | scope: inventory\n 7 | ",
err: "unable to parse source \"my-couchbase-instance\" as \"couchbase\": [3:1] unknown field \"foo\"\n 1 | bucket: travel-sample\n 2 | connectionString: localhost\n> 3 | foo: bar\n ^\n 4 | kind: couchbase\n 5 | password: password\n 6 | scope: inventory\n 7 | ",
},
{
desc: "missing required field",
@@ -138,7 +138,7 @@ func TestFailParseFromYaml(t *testing.T) {
bucket: travel-sample
scope: inventory
`,
err: "unable to parse as \"couchbase\": Key: 'Config.ConnectionString' Error:Field validation for 'ConnectionString' failed on the 'required' tag",
err: "unable to parse source \"my-couchbase-instance\" as \"couchbase\": Key: 'Config.ConnectionString' Error:Field validation for 'ConnectionString' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -24,6 +24,7 @@ import (
"net/url"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"go.opentelemetry.io/otel/trace"
)
@@ -33,6 +34,20 @@ const SourceKind string = "dgraph"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
// HttpToken stores credentials for making HTTP request
type HttpToken struct {
UserId string

View File

@@ -106,7 +106,7 @@ func TestFailParseFromYaml(t *testing.T) {
dgraphUrl: https://localhost:8080
foo: bar
`,
err: "unable to parse as \"dgraph\": [2:1] unknown field \"foo\"\n 1 | dgraphUrl: https://localhost:8080\n> 2 | foo: bar\n ^\n 3 | kind: dgraph",
err: "unable to parse source \"my-dgraph-instance\" as \"dgraph\": [2:1] unknown field \"foo\"\n 1 | dgraphUrl: https://localhost:8080\n> 2 | foo: bar\n ^\n 3 | kind: dgraph",
},
{
desc: "missing required field",
@@ -115,7 +115,7 @@ func TestFailParseFromYaml(t *testing.T) {
my-dgraph-instance:
kind: dgraph
`,
err: "unable to parse as \"dgraph\": Key: 'Config.DgraphUrl' Error:Field validation for 'DgraphUrl' failed on the 'required' tag",
err: "unable to parse source \"my-dgraph-instance\" as \"dgraph\": Key: 'Config.DgraphUrl' Error:Field validation for 'DgraphUrl' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -20,6 +20,7 @@ import (
"net/url"
"time"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"go.opentelemetry.io/otel/trace"
)
@@ -29,6 +30,20 @@ const SourceKind string = "http"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, Timeout: "30s"} // Default timeout
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
@@ -42,11 +57,6 @@ func (r Config) SourceConfigKind() string {
return SourceKind
}
// DefaultConfig is a helper function that generates the default configuration for an HTTP Tool Config.
func DefaultConfig(name string) Config {
return Config{Name: name, Timeout: "30s"}
}
// Initialize initializes an HTTP Source instance.
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
duration, err := time.ParseDuration(r.Timeout)

View File

@@ -15,7 +15,6 @@
package http_test
import (
"strings"
"testing"
yaml "github.com/goccy/go-yaml"
@@ -96,7 +95,7 @@ func TestFailParseFromYaml(t *testing.T) {
api-key: test_api_key
project: test-project
`,
err: "unable to parse as \"http\"",
err: "unable to parse source \"my-http-instance\" as \"http\": [5:1] unknown field \"project\"\n 2 | headers:\n 3 | Authorization: test_header\n 4 | kind: http\n> 5 | project: test-project\n ^\n 6 | queryParams:\n 7 | api-key: test_api_key\n 8 | timeout: 10s",
},
{
desc: "missing required field",
@@ -105,7 +104,7 @@ func TestFailParseFromYaml(t *testing.T) {
my-http-instance:
baseUrl: http://test_server/
`,
err: "missing 'kind' field for \"my-http-instance\"",
err: "missing 'kind' field for source \"my-http-instance\"",
},
}
for _, tc := range tcs {
@@ -119,9 +118,8 @@ func TestFailParseFromYaml(t *testing.T) {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}

View File

@@ -19,6 +19,7 @@ import (
"database/sql"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
_ "github.com/microsoft/go-mssqldb"
"go.opentelemetry.io/otel/trace"
@@ -29,6 +30,20 @@ const SourceKind string = "mssql"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
// Cloud SQL MSSQL configs
Name string `yaml:"name" validate:"required"`

View File

@@ -70,7 +70,6 @@ func TestParseFromYamlMssql(t *testing.T) {
}
})
}
}
func TestFailParseFromYaml(t *testing.T) {
@@ -92,7 +91,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mssql\n 5 | password: my_pass\n 6 | ",
err: "unable to parse source \"my-mssql-instance\" as \"mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mssql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
@@ -105,7 +104,7 @@ func TestFailParseFromYaml(t *testing.T) {
database: my_db
user: my_user
`,
err: "unable to parse as \"mssql\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
err: "unable to parse source \"my-mssql-instance\" as \"mssql\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -20,6 +20,7 @@ import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"go.opentelemetry.io/otel/trace"
)
@@ -29,6 +30,20 @@ const SourceKind string = "mysql"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -92,7 +92,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mysql\n 5 | password: my_pass\n 6 | ",
err: "unable to parse source \"my-mysql-instance\" as \"mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mysql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
@@ -105,7 +105,7 @@ func TestFailParseFromYaml(t *testing.T) {
user: my_user
password: my_pass
`,
err: "unable to parse as \"mysql\": Key: 'Config.Host' Error:Field validation for 'Host' failed on the 'required' tag",
err: "unable to parse source \"my-mysql-instance\" as \"mysql\": Key: 'Config.Host' Error:Field validation for 'Host' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"go.opentelemetry.io/otel/trace"
@@ -28,6 +29,20 @@ const SourceKind string = "neo4j"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, Database: "neo4j"} // Default database
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -89,7 +89,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"neo4j\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | kind: neo4j\n 4 | password: my_pass\n 5 | uri: neo4j+s://my-host:7687\n 6 | ",
err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | kind: neo4j\n 4 | password: my_pass\n 5 | uri: neo4j+s://my-host:7687\n 6 | ",
},
{
desc: "missing required field",
@@ -101,7 +101,7 @@ func TestFailParseFromYaml(t *testing.T) {
database: my_db
user: my_user
`,
err: "unable to parse as \"neo4j\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/jackc/pgx/v5/pgxpool"
"go.opentelemetry.io/otel/trace"
@@ -28,6 +29,20 @@ const SourceKind string = "postgres"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -92,7 +92,7 @@ func TestFailParseFromYaml(t *testing.T) {
password: my_pass
foo: bar
`,
err: "unable to parse as \"postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: my-host\n 4 | kind: postgres\n 5 | password: my_pass\n 6 | ",
err: "unable to parse source \"my-pg-instance\" as \"postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: my-host\n 4 | kind: postgres\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
@@ -105,7 +105,7 @@ func TestFailParseFromYaml(t *testing.T) {
database: my_db
user: my_user
`,
err: "unable to parse as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
err: "unable to parse source \"my-pg-instance\" as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -0,0 +1,152 @@
// 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.
package redis
import (
"context"
"fmt"
"time"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/redis/go-redis/v9"
"go.opentelemetry.io/otel/trace"
)
const SourceKind string = "redis"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" 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"`
}
func (r Config) SourceConfigKind() string {
return SourceKind
}
// RedisClient is an interface for `redis.Client` and `redis.ClusterClient
type RedisClient interface {
Do(context.Context, ...any) *redis.Cmd
}
var _ RedisClient = (*redis.Client)(nil)
var _ RedisClient = (*redis.ClusterClient)(nil)
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
client, err := initRedisClient(ctx, r)
if err != nil {
return nil, fmt.Errorf("error initializing Redis client: %s", err)
}
s := &Source{
Name: r.Name,
Kind: SourceKind,
Client: client,
}
return s, nil
}
func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
var authFn func(ctx context.Context) (username string, password string, err error)
if r.UseGCPIAM {
// Pass in an access token getter fn for IAM auth
authFn = func(ctx context.Context) (username string, password string, err error) {
token, err := sources.GetIAMAccessToken(ctx)
if err != nil {
return "", "", err
}
return "default", token, nil
}
}
var client RedisClient
var err error
if r.ClusterEnabled {
// Create a new Redis Cluster client
clusterClient := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: r.Address,
// PoolSize applies per cluster node and not for the whole cluster.
PoolSize: 10,
ConnMaxIdleTime: 60 * time.Second,
MinIdleConns: 1,
CredentialsProviderContext: authFn,
Username: r.Username,
Password: r.Password,
})
err = clusterClient.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
return shard.Ping(ctx).Err()
})
if err != nil {
return nil, fmt.Errorf("unable to connect to redis cluster: %s", err)
}
client = clusterClient
return client, nil
}
// Create a new Redis client
standaloneClient := redis.NewClient(&redis.Options{
Addr: r.Address[0],
PoolSize: 10,
ConnMaxIdleTime: 60 * time.Second,
MinIdleConns: 1,
DB: r.Database,
CredentialsProviderContext: authFn,
Username: r.Username,
Password: r.Password,
})
_, err = standaloneClient.Ping(ctx).Result()
if err != nil {
return nil, fmt.Errorf("unable to connect to redis: %s", err)
}
client = standaloneClient
return client, nil
}
var _ sources.Source = &Source{}
type Source struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Client RedisClient
}
func (s *Source) SourceKind() string {
return SourceKind
}
func (s *Source) RedisClient() RedisClient {
return s.Client
}

View File

@@ -0,0 +1,157 @@
// 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.
package redis_test
import (
"strings"
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/sources/redis"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParseFromYamlRedis(t *testing.T) {
tcs := []struct {
desc string
in string
want server.SourceConfigs
}{
{
desc: "default setting",
in: `
sources:
my-redis-instance:
kind: redis
address:
- 127.0.0.1
`,
want: server.SourceConfigs{
"my-redis-instance": redis.Config{
Name: "my-redis-instance",
Kind: redis.SourceKind,
Address: []string{"127.0.0.1"},
ClusterEnabled: false,
UseGCPIAM: false,
},
},
},
{
desc: "advanced example",
in: `
sources:
my-redis-instance:
kind: redis
address:
- 127.0.0.1
password: my-pass
database: 1
useGCPIAM: true
clusterEnabled: true
`,
want: server.SourceConfigs{
"my-redis-instance": redis.Config{
Name: "my-redis-instance",
Kind: redis.SourceKind,
Address: []string{"127.0.0.1"},
Password: "my-pass",
Database: 1,
ClusterEnabled: true,
UseGCPIAM: true,
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if !cmp.Equal(tc.want, got.Sources) {
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
}
})
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid database",
in: `
sources:
my-redis-instance:
kind: redis
project: my-project
address:
- 127.0.0.1
password: my-pass
database: data
`,
err: "cannot unmarshal string into Go struct field .Sources of type int",
},
{
desc: "extra field",
in: `
sources:
my-redis-instance:
kind: redis
project: my-project
address:
- 127.0.0.1
password: my-pass
database: 1
`,
err: "unable to parse source \"my-redis-instance\" as \"redis\": [6:1] unknown field \"project\"",
},
{
desc: "missing required field",
in: `
sources:
my-redis-instance:
kind: redis
`,
err: "unable to parse source \"my-redis-instance\" as \"redis\": Key: 'Config.Address' Error:Field validation for 'Address' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -17,10 +17,42 @@ package sources
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// SourceConfigFactory defines the function signature for creating a SourceConfig.
type SourceConfigFactory func(ctx context.Context, name string, decoder *yaml.Decoder) (SourceConfig, error)
var sourceRegistry = make(map[string]SourceConfigFactory)
// Register registers a new source kind with its factory.
// It returns false if the kind is already registered.
func Register(kind string, factory SourceConfigFactory) bool {
if _, exists := sourceRegistry[kind]; exists {
// Source with this kind already exists, do not overwrite.
return false
}
sourceRegistry[kind] = factory
return true
}
// DecodeConfig decodes a source configuration using the registered factory for the given kind.
func DecodeConfig(ctx context.Context, kind string, name string, decoder *yaml.Decoder) (SourceConfig, error) {
factory, found := sourceRegistry[kind]
if !found {
return nil, fmt.Errorf("unknown source kind: %q", kind)
}
sourceConfig, err := factory(ctx, name, decoder)
if err != nil {
return nil, fmt.Errorf("unable to parse source %q as %q: %w", name, kind, err)
}
return sourceConfig, err
}
// SourceConfig is the interface for configuring a source.
type SourceConfig interface {
SourceConfigKind() string

View File

@@ -19,6 +19,7 @@ import (
"fmt"
"cloud.google.com/go/spanner"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
@@ -29,6 +30,20 @@ const SourceKind string = "spanner"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name, Dialect: "googlesql"} // Default dialect
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -132,7 +132,7 @@ func TestFailParseFromYaml(t *testing.T) {
dialect: fail
database: my_db
`,
err: "unable to parse as \"spanner\": dialect invalid: must be one of \"googlesql\", or \"postgresql\"",
err: "unable to parse source \"my-spanner-instance\" as \"spanner\": dialect invalid: must be one of \"googlesql\", or \"postgresql\"",
},
{
desc: "extra field",
@@ -145,7 +145,7 @@ func TestFailParseFromYaml(t *testing.T) {
database: my_db
foo: bar
`,
err: "unable to parse as \"spanner\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: spanner\n 5 | project: my-project",
err: "unable to parse source \"my-spanner-instance\" as \"spanner\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: spanner\n 5 | project: my-project",
},
{
desc: "missing required field",
@@ -156,7 +156,7 @@ func TestFailParseFromYaml(t *testing.T) {
project: my-project
instance: my-instance
`,
err: "unable to parse as \"spanner\": Key: 'Config.Database' Error:Field validation for 'Database' failed on the 'required' tag",
err: "unable to parse source \"my-spanner-instance\" as \"spanner\": Key: 'Config.Database' Error:Field validation for 'Database' failed on the 'required' tag",
},
}
for _, tc := range tcs {

View File

@@ -19,6 +19,7 @@ import (
"database/sql"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"go.opentelemetry.io/otel/trace"
_ "modernc.org/sqlite" // Pure Go SQLite driver
@@ -29,6 +30,20 @@ const SourceKind string = "sqlite"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`

View File

@@ -85,3 +85,20 @@ func GetIAMPrincipalEmailFromADC(ctx context.Context) (string, error) {
email := strings.TrimSuffix(emailValue.(string), ".gserviceaccount.com")
return email, nil
}
func GetIAMAccessToken(ctx context.Context) (string, error) {
creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return "", fmt.Errorf("failed to find default credentials (run 'gcloud auth application-default login'?): %w", err)
}
token, err := creds.TokenSource.Token() // This gets an oauth2.Token
if err != nil {
return "", fmt.Errorf("failed to get token from token source: %w", err)
}
if !token.Valid() {
return "", fmt.Errorf("retrieved token is invalid or expired")
}
return token.AccessToken, nil
}

View File

@@ -0,0 +1,125 @@
// 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.
package valkey
import (
"context"
"fmt"
"log"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/valkey-io/valkey-go"
"go.opentelemetry.io/otel/trace"
)
const SourceKind string = "valkey"
// validate interface
var _ sources.SourceConfig = Config{}
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Address []string `yaml:"address" validate:"required"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Database int `yaml:"database"`
UseGCPIAM bool `yaml:"useGCPIAM"`
DisableCache bool `yaml:"disableCache"`
}
func (r Config) SourceConfigKind() string {
return SourceKind
}
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
client, err := initValkeyClient(ctx, r)
if err != nil {
return nil, fmt.Errorf("error initializing Valkey client: %s", err)
}
s := &Source{
Name: r.Name,
Kind: SourceKind,
Client: client,
}
return s, nil
}
func initValkeyClient(ctx context.Context, r Config) (valkey.Client, error) {
var authFn func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error)
if r.UseGCPIAM {
// Pass in an access token getter fn for IAM auth
authFn = func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error) {
token, err := sources.GetIAMAccessToken(ctx)
creds := valkey.AuthCredentials{Username: "default", Password: token}
if err != nil {
return creds, err
}
return creds, nil
}
}
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: r.Address,
SelectDB: r.Database,
Username: r.Username,
Password: r.Password,
AuthCredentialsFn: authFn,
DisableCache: r.DisableCache,
})
if err != nil {
log.Fatalf("error creating Valkey client: %v", err)
}
// Ping the server to check connectivity
pingCmd := client.B().Ping().Build()
_, err = client.Do(ctx, pingCmd).ToString()
if err != nil {
log.Fatalf("Failed to execute PING command: %v", err)
}
return client, nil
}
var _ sources.Source = &Source{}
type Source struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Client valkey.Client
}
func (s *Source) SourceKind() string {
return SourceKind
}
func (s *Source) ValkeyClient() valkey.Client {
return s.Client
}

View File

@@ -0,0 +1,162 @@
// 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.
package valkey_test
import (
"strings"
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/sources/valkey"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParseFromYamlValkey(t *testing.T) {
tcs := []struct {
desc string
in string
want server.SourceConfigs
}{
{
desc: "default setting",
in: `
sources:
my-valkey-instance:
kind: valkey
address:
- 127.0.0.1
`,
want: map[string]sources.SourceConfig{
"my-valkey-instance": valkey.Config{
Name: "my-valkey-instance",
Kind: valkey.SourceKind,
Address: []string{"127.0.0.1"},
Username: "",
Password: "",
Database: 0,
UseGCPIAM: false,
DisableCache: false,
},
},
},
{
desc: "advanced example",
in: `
sources:
my-valkey-instance:
kind: valkey
address:
- 127.0.0.1
database: 1
username: user
password: pass
useGCPIAM: true
disableCache: true
`,
want: map[string]sources.SourceConfig{
"my-valkey-instance": valkey.Config{
Name: "my-valkey-instance",
Kind: valkey.SourceKind,
Address: []string{"127.0.0.1"},
Username: "user",
Password: "pass",
Database: 1,
UseGCPIAM: true,
DisableCache: true,
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if !cmp.Equal(tc.want, got.Sources) {
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
}
})
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid database",
in: `
sources:
my-valkey-instance:
kind: valkey
project: my-project
address:
- 127.0.0.1
database: my-db
useGCPIAM: false
`,
err: "cannot unmarshal string into Go struct field .Sources of type int",
},
{
desc: "extra field",
in: `
sources:
my-valkey-instance:
kind: valkey
address:
- 127.0.0.1
project: proj
database: 1
`,
err: "unable to parse source \"my-valkey-instance\" as \"valkey\": [5:1] unknown field \"project\"",
},
{
desc: "missing required field",
in: `
sources:
my-valkey-instance:
kind: valkey
`,
err: "unable to parse source \"my-valkey-instance\" as \"valkey\": Key: 'Config.Address' Error:Field validation for 'Address' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -218,16 +218,11 @@ func convertParamToJSON(param any) (string, error) {
// Helper function to generate the HTTP request body upon Tool invocation.
func getRequestBody(bodyParams tools.Parameters, requestBodyPayload string, paramsMap map[string]any) (string, error) {
// Create a map for request body parameters
bodyParamsMap := make(map[string]any)
for _, p := range bodyParams {
k := p.GetName()
v, ok := paramsMap[k]
if !ok {
return "", fmt.Errorf("missing request body parameter %s", k)
}
bodyParamsMap[k] = v
bodyParamValues, err := tools.GetParams(bodyParams, paramsMap)
if err != nil {
return "", err
}
bodyParamsMap := bodyParamValues.AsMap()
// Create a FuncMap to format array parameters
funcMap := template.FuncMap{

View File

@@ -54,13 +54,14 @@ var _ compatibleSource = &mssql.Source{}
var compatibleSources = [...]string{cloudsqlmssql.SourceKind, mssql.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// validate interface
@@ -83,22 +84,26 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Db: s.MSSQLDB(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
TemplateParameters: cfg.TemplateParameters,
AllParams: allParameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Db: s.MSSQLDB(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
@@ -107,10 +112,12 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
Db *sql.DB
Statement string
@@ -119,18 +126,29 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
namedArgs := make([]any, 0, len(params))
paramsMap := params.AsReversedMap()
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
namedArgs := make([]any, 0, len(newParams))
newParamsMap := newParams.AsReversedMap()
// To support both named args (e.g @id) and positional args (e.g @p1), check if arg name is contained in the statement.
for _, v := range params.AsSlice() {
paramName := paramsMap[v]
if strings.Contains(t.Statement, "@"+paramName) {
for _, v := range newParams.AsSlice() {
paramName := newParamsMap[v]
if strings.Contains(newStatement, "@"+paramName) {
namedArgs = append(namedArgs, sql.Named(paramName, v))
} else {
namedArgs = append(namedArgs, v)
}
}
rows, err := t.Db.QueryContext(ctx, t.Statement, namedArgs...)
rows, err := t.Db.QueryContext(ctx, newStatement, namedArgs...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
@@ -173,7 +191,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, erro
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
return tools.ParseParams(t.AllParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {

View File

@@ -90,5 +90,86 @@ func TestParseFromYamlMssql(t *testing.T) {
}
})
}
}
func TestParseFromYamlWithTemplateMssql(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: mssql-sql
source: my-instance
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
authRequired:
- my-google-auth-service
- other-auth-service
parameters:
- name: country
type: string
description: some description
authServices:
- name: my-google-auth-service
field: user_id
- name: other-auth-service
field: user_id
templateParameters:
- name: tableName
type: string
description: The table to select hotels from.
- name: fieldArray
type: array
description: The columns to return for the query.
items:
name: column
type: string
description: A column name that will be returned from the query.
`,
want: server.ToolConfigs{
"example_tool": mssqlsql.Config{
Name: "example_tool",
Kind: "mssql-sql",
Source: "my-instance",
Description: "some description",
Statement: "SELECT * FROM SQL_STATEMENT;\n",
AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
Parameters: []tools.Parameter{
tools.NewStringParameterWithAuth("country", "some description",
[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
{Name: "other-auth-service", Field: "user_id"}}),
},
TemplateParameters: []tools.Parameter{
tools.NewStringParameter("tableName", "The table to select hotels from."),
tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -53,13 +53,14 @@ var _ compatibleSource = &mysql.Source{}
var compatibleSources = [...]string{cloudsqlmysql.SourceKind, mysql.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// validate interface
@@ -82,22 +83,26 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Pool: s.MySQLPool(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
TemplateParameters: cfg.TemplateParameters,
AllParams: allParameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Pool: s.MySQLPool(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
@@ -106,10 +111,12 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
Pool *sql.DB
Statement string
@@ -118,9 +125,19 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
sliceParams := params.AsSlice()
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
results, err := t.Pool.QueryContext(ctx, t.Statement, sliceParams...)
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
sliceParams := newParams.AsSlice()
results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
@@ -179,7 +196,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, erro
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
return tools.ParseParams(t.AllParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {

View File

@@ -90,5 +90,86 @@ func TestParseFromYamlMySQL(t *testing.T) {
}
})
}
}
func TestParseFromYamlWithTemplateParamsMySQL(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: mysql-sql
source: my-mysql-instance
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
authRequired:
- my-google-auth-service
- other-auth-service
parameters:
- name: country
type: string
description: some description
authServices:
- name: my-google-auth-service
field: user_id
- name: other-auth-service
field: user_id
templateParameters:
- name: tableName
type: string
description: The table to select hotels from.
- name: fieldArray
type: array
description: The columns to return for the query.
items:
name: column
type: string
description: A column name that will be returned from the query.
`,
want: server.ToolConfigs{
"example_tool": mysqlsql.Config{
Name: "example_tool",
Kind: "mysql-sql",
Source: "my-mysql-instance",
Description: "some description",
Statement: "SELECT * FROM SQL_STATEMENT;\n",
AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
Parameters: []tools.Parameter{
tools.NewStringParameterWithAuth("country", "some description",
[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
{Name: "other-auth-service", Field: "user_id"}}),
},
TemplateParameters: []tools.Parameter{
tools.NewStringParameter("tableName", "The table to select hotels from."),
tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
"slices"
"strings"
"text/template"
@@ -204,6 +205,49 @@ func ResolveTemplateParams(templateParams Parameters, originalStatement string,
return modifiedStatement, nil
}
// ProcessParameters concatenate templateParameters and parameters from a tool.
// It returns a list of concatenated parameters, concatenated Toolbox manifest, and concatenated MCP Manifest.
func ProcessParameters(templateParams Parameters, params Parameters) (Parameters, []ParameterManifest, McpToolsSchema) {
allParameters := slices.Concat(params, templateParams)
paramManifest := slices.Concat(
params.Manifest(),
templateParams.Manifest(),
)
if paramManifest == nil {
paramManifest = make([]ParameterManifest, 0)
}
parametersMcpManifest := params.McpManifest()
templateParametersMcpManifest := templateParams.McpManifest()
// Concatenate parameters for MCP `required` field
concatRequiredManifest := slices.Concat(
parametersMcpManifest.Required,
templateParametersMcpManifest.Required,
)
if concatRequiredManifest == nil {
concatRequiredManifest = []string{}
}
// Concatenate parameters for MCP `properties` field
concatPropertiesManifest := make(map[string]ParameterMcpManifest)
for name, p := range parametersMcpManifest.Properties {
concatPropertiesManifest[name] = p
}
for name, p := range templateParametersMcpManifest.Properties {
concatPropertiesManifest[name] = p
}
// Create a new McpToolsSchema with all parameters
paramMcpManifest := McpToolsSchema{
Type: "object",
Properties: concatPropertiesManifest,
Required: concatRequiredManifest,
}
return allParameters, paramManifest, paramMcpManifest
}
type Parameter interface {
// Note: It's typically not idiomatic to include "Get" in the function name,
// but this is done to differentiate it from the fields in CommonParameter.

View File

@@ -17,7 +17,6 @@ package postgressql
import (
"context"
"fmt"
"slices"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
@@ -86,43 +85,7 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters := slices.Concat(cfg.Parameters, cfg.TemplateParameters)
paramManifest := slices.Concat(
cfg.Parameters.Manifest(),
cfg.TemplateParameters.Manifest(),
)
if paramManifest == nil {
paramManifest = make([]tools.ParameterManifest, 0)
}
parametersMcpManifest := cfg.Parameters.McpManifest()
templateParametersMcpManifest := cfg.TemplateParameters.McpManifest()
// Concatenate parameters for MCP `required` field
concatRequiredManifest := slices.Concat(
parametersMcpManifest.Required,
templateParametersMcpManifest.Required,
)
if concatRequiredManifest == nil {
concatRequiredManifest = []string{}
}
// Concatenate parameters for MCP `properties` field
concatPropertiesManifest := make(map[string]tools.ParameterMcpManifest)
for name, p := range parametersMcpManifest.Properties {
concatPropertiesManifest[name] = p
}
for name, p := range templateParametersMcpManifest.Properties {
concatPropertiesManifest[name] = p
}
// Create a new McpToolsSchema with all parameters
paramMcpManifest := tools.McpToolsSchema{
Type: "object",
Properties: concatPropertiesManifest,
Required: concatRequiredManifest,
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
mcpManifest := tools.McpManifest{
Name: cfg.Name,

View File

@@ -0,0 +1,210 @@
// 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.
package redis
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
redissrc "github.com/googleapis/genai-toolbox/internal/sources/redis"
"github.com/googleapis/genai-toolbox/internal/tools"
jsoniter "github.com/json-iterator/go"
"github.com/redis/go-redis/v9"
)
const kind string = "redis"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
RedisClient() redissrc.RedisClient
}
// validate compatible sources are still compatible
var _ compatibleSource = &redissrc.Source{}
var compatibleSources = [...]string{redissrc.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Commands [][]string `yaml:"commands" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigKind() string {
return kind
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[cfg.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
}
// verify the source is compatible
s, ok := rawS.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Commands: cfg.Commands,
AuthRequired: cfg.AuthRequired,
Client: s.RedisClient(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Client redissrc.RedisClient
Commands [][]string
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
cmds, err := replaceCommandsParams(t.Commands, t.Parameters, params)
if err != nil {
return nil, fmt.Errorf("error replacing commands' parameters: %s", err)
}
// Execute commands
responses := make([]*redis.Cmd, len(cmds))
for i, cmd := range cmds {
responses[i] = t.Client.Do(ctx, cmd...)
}
// Parse responses
out := make([]any, len(t.Commands))
for i, resp := range responses {
if err := resp.Err(); err != nil {
// Add error from each command to `errSum`
errString := fmt.Sprintf("error from executing command at index %d: %s", i, err)
out[i] = errString
continue
}
val, err := resp.Result()
if err != nil {
return nil, fmt.Errorf("error getting result: %s", err)
}
// If result is a map, convert map[any]any to map[string]any
// Because the Go's built-in json/encoding marshalling doesn't support
// map[any]any as an input
var strMap map[string]any
var json = jsoniter.ConfigCompatibleWithStandardLibrary
mapStr, err := json.Marshal(val)
if err != nil {
return nil, fmt.Errorf("error marshalling result: %s", err)
}
err = json.Unmarshal(mapStr, &strMap)
if err != nil {
// result is not a map
out[i] = val
continue
}
out[i] = strMap
}
return out, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
}
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}
// replaceCommandsParams is a helper function to replace parameters in the commands
func replaceCommandsParams(commands [][]string, params tools.Parameters, paramValues tools.ParamValues) ([][]any, error) {
paramMap := paramValues.AsMapWithDollarPrefix()
typeMap := make(map[string]string, len(params))
for _, p := range params {
placeholder := "$" + p.GetName()
typeMap[placeholder] = p.GetType()
}
newCommands := make([][]any, len(commands))
for i, cmd := range commands {
newCmd := make([]any, len(cmd))
for j, part := range cmd {
v, ok := paramMap[part]
if !ok {
// Command part is not a Parameter placeholder
newCmd[j] = part
continue
}
if typeMap[part] == "array" {
for _, item := range v.([]any) {
// Nested arrays will only be expanded once
// e.g., [A, [B, C]] --> ["A", "[B C]"]
newCmd = append(newCmd, fmt.Sprintf("%s", item))
}
continue
}
newCmd[j] = fmt.Sprintf("%s", v)
}
newCommands[i] = newCmd
}
return newCommands, nil
}

View File

@@ -0,0 +1,85 @@
// Copyright 2024 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 redis_test
import (
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/redis"
)
func TestParseFromYamlRedis(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
redis_tool:
kind: redis
source: my-redis-instance
description: some description
commands:
- [SET, greeting, "hello, {{.name}}"]
- [GET, id]
parameters:
- name: name
type: string
description: user name
`,
want: server.ToolConfigs{
"redis_tool": redis.Config{
Name: "redis_tool",
Kind: "redis",
Source: "my-redis-instance",
Description: "some description",
AuthRequired: []string{},
Commands: [][]string{{"SET", "greeting", "hello, {{.name}}"}, {"GET", "id"}},
Parameters: []tools.Parameter{
tools.NewStringParameter("name", "user name"),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -54,14 +54,15 @@ var _ compatibleSource = &spannerdb.Source{}
var compatibleSources = [...]string{spannerdb.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
ReadOnly bool `yaml:"readOnly"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
ReadOnly bool `yaml:"readOnly"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// validate interface
@@ -84,24 +85,28 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
ReadOnly: cfg.ReadOnly,
Client: s.SpannerClient(),
dialect: s.DatabaseDialect(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
TemplateParameters: cfg.TemplateParameters,
AllParams: allParameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
ReadOnly: cfg.ReadOnly,
Client: s.SpannerClient(),
dialect: s.DatabaseDialect(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
@@ -110,16 +115,18 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
ReadOnly bool `yaml:"readOnly"`
Client *spanner.Client
dialect string
Statement string
manifest tools.Manifest
mcpManifest tools.McpManifest
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
ReadOnly bool `yaml:"readOnly"`
Client *spanner.Client
dialect string
Statement string
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func getMapParams(params tools.ParamValues, dialect string) (map[string]interface{}, error) {
@@ -158,7 +165,17 @@ func processRows(iter *spanner.RowIterator) ([]any, error) {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
mapParams, err := getMapParams(params, t.dialect)
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
mapParams, err := getMapParams(newParams, t.dialect)
if err != nil {
return nil, fmt.Errorf("fail to get map params: %w", err)
}
@@ -166,7 +183,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, erro
var results []any
var opErr error
stmt := spanner.Statement{
SQL: t.Statement,
SQL: newStatement,
Params: mapParams,
}
@@ -192,7 +209,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, erro
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
return tools.ParseParams(t.AllParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {

View File

@@ -113,3 +113,107 @@ func TestParseFromYamlSpanner(t *testing.T) {
}
}
func TestParseFromYamlWithTemplateParamsSpanner(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: spanner-sql
source: my-pg-instance
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
parameters:
- name: country
type: string
description: some description
templateParameters:
- name: tableName
type: string
description: The table to select hotels from.
- name: fieldArray
type: array
description: The columns to return for the query.
items:
name: column
type: string
description: A column name that will be returned from the query.
`,
want: server.ToolConfigs{
"example_tool": spanner.Config{
Name: "example_tool",
Kind: "spanner-sql",
Source: "my-pg-instance",
Description: "some description",
Statement: "SELECT * FROM SQL_STATEMENT;\n",
AuthRequired: []string{},
Parameters: []tools.Parameter{
tools.NewStringParameter("country", "some description"),
},
TemplateParameters: []tools.Parameter{
tools.NewStringParameter("tableName", "The table to select hotels from."),
tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
},
},
},
},
{
desc: "read only set to true",
in: `
tools:
example_tool:
kind: spanner-sql
source: my-pg-instance
description: some description
readOnly: true
statement: |
SELECT * FROM SQL_STATEMENT;
parameters:
- name: country
type: string
description: some description
`,
want: server.ToolConfigs{
"example_tool": spanner.Config{
Name: "example_tool",
Kind: "spanner-sql",
Source: "my-pg-instance",
Description: "some description",
Statement: "SELECT * FROM SQL_STATEMENT;\n",
ReadOnly: true,
AuthRequired: []string{},
Parameters: []tools.Parameter{
tools.NewStringParameter("country", "some description"),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -51,13 +51,14 @@ var _ compatibleSource = &sqlite.Source{}
var compatibleSources = [...]string{sqlite.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// validate interface
@@ -80,22 +81,26 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Db: s.SQLiteDB(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
TemplateParameters: cfg.TemplateParameters,
AllParams: allParameters,
Statement: cfg.Statement,
AuthRequired: cfg.AuthRequired,
Db: s.SQLiteDB(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
@@ -104,10 +109,12 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
Db *sql.DB
Statement string `yaml:"statement"`
@@ -116,8 +123,19 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
// Execute the SQL query with parameters
rows, err := t.Db.QueryContext(ctx, t.Statement, params.AsSlice()...)
rows, err := t.Db.QueryContext(ctx, newStatement, newParams.AsSlice()...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
@@ -171,7 +189,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, erro
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
return tools.ParseParams(t.AllParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {

View File

@@ -92,3 +92,85 @@ func TestParseFromYamlSQLite(t *testing.T) {
}
}
func TestParseFromYamlWithTemplateSqlite(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: sqlite-sql
source: my-sqlite-db
description: some description
statement: |
SELECT * FROM SQL_STATEMENT;
authRequired:
- my-google-auth-service
- other-auth-service
parameters:
- name: country
type: string
description: some description
authServices:
- name: my-google-auth-service
field: user_id
- name: other-auth-service
field: user_id
templateParameters:
- name: tableName
type: string
description: The table to select hotels from.
- name: fieldArray
type: array
description: The columns to return for the query.
items:
name: column
type: string
description: A column name that will be returned from the query.
`,
want: server.ToolConfigs{
"example_tool": sqlitesql.Config{
Name: "example_tool",
Kind: "sqlite-sql",
Source: "my-sqlite-db",
Description: "some description",
Statement: "SELECT * FROM SQL_STATEMENT;\n",
AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
Parameters: []tools.Parameter{
tools.NewStringParameterWithAuth("country", "some description",
[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
{Name: "other-auth-service", Field: "user_id"}}),
},
TemplateParameters: []tools.Parameter{
tools.NewStringParameter("tableName", "The table to select hotels from."),
tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -0,0 +1,204 @@
// 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.
package valkey
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
valkeysrc "github.com/googleapis/genai-toolbox/internal/sources/valkey"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/valkey-io/valkey-go"
)
const kind string = "valkey"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
ValkeyClient() valkey.Client
}
// validate compatible sources are still compatible
var _ compatibleSource = &valkeysrc.Source{}
var compatibleSources = [...]string{valkeysrc.SourceKind, valkeysrc.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Commands [][]string `yaml:"commands" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigKind() string {
return kind
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[cfg.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
}
// verify the source is compatible
s, ok := rawS.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: cfg.Parameters.McpManifest(),
}
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: cfg.Parameters,
Commands: cfg.Commands,
AuthRequired: cfg.AuthRequired,
Client: s.ValkeyClient(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
Client valkey.Client
Commands [][]string
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
// Replace parameters
commands, err := replaceCommandsParams(t.Commands, t.Parameters, params)
if err != nil {
return nil, fmt.Errorf("error replacing commands' parameters: %s", err)
}
// Build commands
builtCmds := make(valkey.Commands, len(commands))
for i, cmd := range commands {
builtCmds[i] = t.Client.B().Arbitrary(cmd...).Build()
}
if len(builtCmds) == 0 {
return nil, fmt.Errorf("no valid commands were built to execute")
}
// Execute commands
responses := t.Client.DoMulti(ctx, builtCmds...)
// Parse responses
out := make([]any, len(t.Commands))
for i, resp := range responses {
if err := resp.Error(); err != nil {
// Add error from each command to `errSum`
out[i] = fmt.Sprintf("error from executing command at index %d: %s", i, err)
continue
}
val, err := resp.ToAny()
if err != nil {
out[i] = fmt.Sprintf("error parsing response: %s", err)
continue
}
out[i] = val
}
return out, nil
}
// Helper function to replace parameters in the commands
func replaceCommandsParams(commands [][]string, params tools.Parameters, paramValues tools.ParamValues) ([][]string, error) {
paramMap := paramValues.AsMapWithDollarPrefix()
typeMap := make(map[string]string, len(params))
for _, p := range params {
placeholder := "$" + p.GetName()
typeMap[placeholder] = p.GetType()
}
newCommands := make([][]string, len(commands))
for i, cmd := range commands {
newCmd := make([]string, len(cmd))
for j, part := range cmd {
v, ok := paramMap[part]
if !ok {
// Command part is not a Parameter placeholder
newCmd[j] = part
continue
}
if typeMap[part] == "array" {
for _, item := range v.([]any) {
// Nested arrays will only be expanded once
// e.g., [A, [B, C]] --> ["A", "[B C]"]
newCmd = append(newCmd, fmt.Sprintf("%s", item))
}
continue
}
newCmd[j] = fmt.Sprintf("%s", v)
}
newCommands[i] = newCmd
}
return newCommands, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
}
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -0,0 +1,85 @@
// Copyright 2024 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 valkey_test
import (
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/valkey"
)
func TestParseFromYamlvalkey(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
valkey_tool:
kind: valkey
source: my-valkey-instance
description: some description
commands:
- [SET, greeting, "hello, {{.name}}"]
- [GET, id]
parameters:
- name: name
type: string
description: user name
`,
want: server.ToolConfigs{
"valkey_tool": valkey.Config{
Name: "valkey_tool",
Kind: "valkey",
Source: "my-valkey-instance",
Description: "some description",
AuthRequired: []string{},
Commands: [][]string{{"SET", "greeting", "hello, {{.name}}"}, {"GET", "id"}},
Parameters: []tools.Parameter{
tools.NewStringParameter("name", "user name"),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -146,7 +146,9 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, ALLOYDB_POSTGRES_TOOL_KIND, tool_statement1, tool_statement2)
toolsFile = tests.AddPgExecuteSqlConfig(t, toolsFile)
toolsFile = tests.AddTemplateParamConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, ALLOYDB_POSTGRES_TOOL_KIND, tmplSelectCombined, tmplSelectFilterCombined)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
t.Fatalf("command initialization returned an error: %s", err)
@@ -164,11 +166,11 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetPostgresWants()
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, tmplSelectAllWant, tmplSelect1Want := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tmplSelectAllWant, tmplSelect1Want, false)
}
// Test connection with different IP type

View File

@@ -129,7 +129,7 @@ func TestBigQueryToolEndpoints(t *testing.T) {
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: googleapi: Error 400: Syntax error: Unexpected identifier \"SELEC\" at [1:1]`
datasetInfoWant := "\"Location\":\"US\",\"DefaultTableExpiration\":0,\"Labels\":null,\"Access\":"
tableInfoWant := "[{\"Name\":\"\",\"Location\":\"US\",\"Description\":\"\",\"Schema\":[{\"Name\":\"id\""
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, _, _ := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
runBigQueryExecuteSqlToolInvokeTest(t, select1Want, invokeParamWant, tableNameParam)

View File

@@ -106,7 +106,7 @@ func TestBigtableToolEndpoints(t *testing.T) {
// Actual test parameters are set in https://github.com/googleapis/genai-toolbox/blob/52b09a67cb40ac0c5f461598b4673136699a3089/tests/tool_test.go#L250
select1Want := "[{\"$col1\":1}]"
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to prepare statement: rpc error: code = InvalidArgument desc = Syntax error: Unexpected identifier \"SELEC\" [at 1:1]"}],"isError":true}}`
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, _, _ := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
}

View File

@@ -118,6 +118,7 @@ func TestCloudSQLMssqlToolEndpoints(t *testing.T) {
// create table name with UUID
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
create_statement1, insert_statement1, tool_statement1, params1 := tests.GetMssqlParamToolInfo(tableNameParam)
@@ -125,13 +126,15 @@ func TestCloudSQLMssqlToolEndpoints(t *testing.T) {
defer teardownTable1(t)
// set up data for auth tool
create_statement2, insert_statement2, tool_statement2, params2 := tests.GetMssqlLAuthToolInfo(tableNameAuth)
create_statement2, insert_statement2, tool_statement2, params2 := tests.GetMssqlAuthToolInfo(tableNameAuth)
teardownTable2 := tests.SetupMsSQLTable(t, ctx, db, create_statement2, insert_statement2, tableNameAuth, params2)
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CLOUD_SQL_MSSQL_TOOL_KIND, tool_statement1, tool_statement2)
toolsFile = tests.AddMssqlExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMssqlTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CLOUD_SQL_MSSQL_TOOL_KIND, tmplSelectCombined, tmplSelectFilterCombined)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
@@ -150,10 +153,11 @@ func TestCloudSQLMssqlToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMssqlWants()
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, tmplSelectAllWant, tmplSelect1Want := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tmplSelectAllWant, tmplSelect1Want, false)
}
// Test connection with different IP type

View File

@@ -112,6 +112,7 @@ func TestCloudSQLMysqlToolEndpoints(t *testing.T) {
// create table name with UUID
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
create_statement1, insert_statement1, tool_statement1, params1 := tests.GetMysqlParamToolInfo(tableNameParam)
@@ -119,13 +120,15 @@ func TestCloudSQLMysqlToolEndpoints(t *testing.T) {
defer teardownTable1(t)
// set up data for auth tool
create_statement2, insert_statement2, tool_statement2, params2 := tests.GetMysqlLAuthToolInfo(tableNameAuth)
create_statement2, insert_statement2, tool_statement2, params2 := tests.GetMysqlAuthToolInfo(tableNameAuth)
teardownTable2 := tests.SetupMySQLTable(t, ctx, pool, create_statement2, insert_statement2, tableNameAuth, params2)
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CLOUD_SQL_MYSQL_TOOL_KIND, tool_statement1, tool_statement2)
toolsFile = tests.AddMySqlExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMysqlTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CLOUD_SQL_MYSQL_TOOL_KIND, tmplSelectCombined, tmplSelectFilterCombined)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
@@ -144,10 +147,11 @@ func TestCloudSQLMysqlToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMysqlWants()
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, tmplSelectAllWant, tmplSelect1Want := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tmplSelectAllWant, tmplSelect1Want, false)
}
// Test connection with different IP type

View File

@@ -116,6 +116,7 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
// create table name with UUID
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
create_statement1, insert_statement1, tool_statement1, params1 := tests.GetPostgresSQLParamToolInfo(tableNameParam)
@@ -130,6 +131,8 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CLOUD_SQL_POSTGRES_TOOL_KIND, tool_statement1, tool_statement2)
toolsFile = tests.AddPgExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CLOUD_SQL_POSTGRES_TOOL_KIND, tmplSelectCombined, tmplSelectFilterCombined)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
@@ -148,10 +151,11 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetPostgresWants()
invokeParamWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, mcpInvokeParamWant, tmplSelectAllWant, tmplSelect1Want := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tmplSelectAllWant, tmplSelect1Want, false)
}
// Test connection with different IP type

Some files were not shown because too many files have changed in this diff Show More